Added new FS_GetFileList() function to retrieve lists of files (by extension or filter) from both paks and game dirs.

Changed UI_LoadArenas() to use FS_GetFileList().
Exported FS_GetFileList() to game DLL.
This commit is contained in:
Knightmare66 2019-10-08 21:55:44 -04:00
parent a57b260b7b
commit 01d78fcaf7
7 changed files with 542 additions and 3 deletions

View file

@ -206,6 +206,7 @@ typedef struct
char *(*GameDir) (void); char *(*GameDir) (void);
char *(*SaveGameDir) (void); char *(*SaveGameDir) (void);
void (*CreatePath) (char *path); void (*CreatePath) (char *path);
char **(*GetFileList) (const char *path, const char *extension, int *num);
#endif #endif
} game_import_t; } game_import_t;

View file

@ -1587,6 +1587,195 @@ void Com_PageInMemory (byte *buffer, int size)
============================================================================ ============================================================================
*/ */
/*
=================
Q_GlobMatchAfterStar
From Q2E
Like Q_GlobMatch, but match pattern against any final segment of text
=================
*/
static qboolean Q_GlobMatchAfterStar (const char *pattern, const char *text, qboolean caseSensitive){
const char *p = pattern;
const char *t = text;
char c1, c2;
while ((c1 = *p++) == '?' || c1 == '*'){
if (c1 == '?' && *t++ == '\0')
return false;
}
if (c1 == '\0')
return true;
if (c1 == '\\')
c2 = *p;
else
c2 = c1;
while (1){
if (caseSensitive){
if (c1 == '[' || *t == c2){
if (Q_GlobMatch(p - 1, t, caseSensitive))
return true;
}
}
else {
if (c1 == '[' || tolower(*t) == tolower(c2)){
if (Q_GlobMatch(p - 1, t, caseSensitive))
return true;
}
}
if (*t++ == '\0')
return false;
}
}
/*
=================
Q_GlobMatch
From Q2E
Matches the pattern against text.
Returns true if matches, false otherwise.
A match means the entire text is used up in matching.
In the pattern string, '*' matches any sequence of characters, '?'
matches any character, '[SET]' matches any character in the specified
set, '[!SET]' matches any character not in the specified set.
A set is composed of characters or ranges. A range looks like character
hyphen character (as in 0-9 or A-Z).
[0-9a-zA-Z_] is the set of characters allowed in C identifiers.
Any other character in the pattern must be matched exactly.
To suppress the special syntactic significance of any of '[]*?!-\', and
match the character exactly, precede it with a '\'.
=================
*/
qboolean Q_GlobMatch (const char *pattern, const char *text, qboolean caseSensitive){
const char *p = pattern;
const char *t = text;
char c1, c2, start, end;
qboolean invert;
while ((c1 = *p++) != '\0'){
switch (c1){
case '?':
if (*t == '\0')
return false;
else
++t;
break;
case '\\':
if (caseSensitive){
if (*p++ != *t++)
return false;
}
else {
if (tolower(*p++) != tolower(*t++))
return false;
}
break;
case '*':
return Q_GlobMatchAfterStar(p, t, caseSensitive);
case '[':
c2 = *t++;
if (!c2)
return false;
invert = (*p == '!');
if (invert)
p++;
c1 = *p++;
while (1){
start = c1;
end = c1;
if (c1 == '\\'){
start = *p++;
end = start;
}
if (c1 == '\0')
return false;
c1 = *p++;
if (c1 == '-' && *p != ']'){
end = *p++;
if (end == '\\')
end = *p++;
if (end == '\0')
return false;
c1 = *p++;
}
if (caseSensitive){
if (c2 >= start && c2 <= end)
goto match;
}
else {
if (tolower(c2) >= tolower(start) && tolower(c2) <= tolower(end))
goto match;
}
if (c1 == ']')
break;
}
if (!invert)
return false;
break;
match:
while (c1 != ']'){
if (c1 == '\0')
return false;
c1 = *p++;
if (c1 == '\0')
return false;
else if (c1 == '\\')
++p;
}
if (invert)
return false;
break;
default:
if (caseSensitive){
if (c1 != *t++)
return false;
}
else {
if (tolower(c1) != tolower(*t++))
return false;
}
break;
}
}
return (*t == '\0');
}
/*
=================
Q_stricmp
=================
*/
// FIXME: replace all Q_stricmp with Q_strcasecmp // FIXME: replace all Q_stricmp with Q_strcasecmp
int Q_stricmp (char *s1, char *s2) int Q_stricmp (char *s1, char *s2)
{ {
@ -1598,6 +1787,74 @@ int Q_stricmp (char *s1, char *s2)
} }
/*
=================
Q_strncmp
From Q2E
=================
*/
int Q_strncmp (const char *string1, const char *string2, int n)
{
int c1, c2;
if (string1 == NULL)
{
if (string2 == NULL)
return 0;
else
return -1;
}
else if (string2 == NULL)
return 1;
do
{
c1 = *string1++;
c2 = *string2++;
if (!n--)
return 0; // Strings are equal until end point
if (c1 != c2)
return c1 < c2 ? -1 : 1;
} while (c1);
return 0; // Strings are equal
}
/*
=================
Q_strcmp
From Q2E
=================
*/
int Q_strcmp (const char *string1, const char *string2)
{
return Q_strncmp(string1, string2, 99999);
}
/*
=================
Q_SortStrcmp
From Q2E
=================
*/
int Q_SortStrcmp (const char **arg1, const char **arg2)
{
return Q_strcmp(*arg1, *arg2);
}
/*
=================
Q_strncasecmp
=================
*/
int Q_strncasecmp (char *s1, char *s2, int n) int Q_strncasecmp (char *s1, char *s2, int n)
{ {
int c1, c2; int c1, c2;
@ -1625,6 +1882,11 @@ int Q_strncasecmp (char *s1, char *s2, int n)
} }
/*
=================
Q_strcasecmp
=================
*/
int Q_strcasecmp (char *s1, char *s2) int Q_strcasecmp (char *s1, char *s2)
{ {
return Q_strncasecmp (s1, s2, 99999); return Q_strncasecmp (s1, s2, 99999);

View file

@ -445,7 +445,17 @@ void Com_PageInMemory (byte *buffer, int size);
//============================================= //=============================================
// portable case insensitive compare // Matches the pattern against text
qboolean Q_GlobMatch (const char *pattern, const char *text, qboolean caseSensitive);
// portable string compare
int Q_strncmp (const char *string1, const char *string2, int n);
int Q_strcmp (const char *string1, const char *string2);
// string compare for qsort calls
int Q_SortStrcmp (const char **arg1, const char **arg2);
// portable case insensitive string compare
int Q_stricmp (char *s1, char *s2); int Q_stricmp (char *s1, char *s2);
int Q_strcasecmp (char *s1, char *s2); int Q_strcasecmp (char *s1, char *s2);
int Q_strncasecmp (char *s1, char *s2, int n); int Q_strncasecmp (char *s1, char *s2, int n);

View file

@ -1201,6 +1201,212 @@ char **FS_ListPak (char *find, int *num)
return list; return list;
} }
/*
=================
FS_FindFiles
Generates a listing of files in the given path with an optional extension.
Lists all files if extension is NULL.
=================
*/
char **FS_FindFiles (const char *path, const char *extension, int *num)
{
fsSearchPath_t *search;
fsPack_t *pak;
char dir[MAX_OSPATH], ext[16], findName[1024];
char *name, **itemFiles, *tmpList[MAX_FIND_FILES], **outList = NULL;
int nFound = 0;
int i, nItems; // len, extLen;
memset (tmpList, 0, sizeof(tmpList));
for (search = fs_searchPaths; search; search = search->next)
{
if (search->pack) // search inside a pak/pk3 file
{
pak = search->pack;
for (i=0 ; i<pak->numFiles ; i++)
{
// check path
COM_FilePath (pak->files[i].name, dir, sizeof(dir));
if ( Q_stricmp((char *)path, dir) )
continue;
// check extension
if ( (extension != NULL) && (strlen(extension) > 0) ) {
Com_FileExtension(pak->files[i].name, ext, sizeof(ext));
if ( Q_stricmp((char *)extension, ext) )
continue;
}
// found something
name = pak->files[i].name;
if (nFound < (MAX_FIND_FILES-1))
{
if (!FS_ItemInList(name, nFound, tmpList)) // check if already in list
{
tmpList[nFound] = strdup(name);
nFound++;
}
}
}
}
else // search in a directory tree
{
if ( (extension != NULL) && (strlen(extension) > 0) )
Com_sprintf (findName, sizeof(findName), "%s/%s/*.%s", search->path, path, extension);
else
Com_sprintf (findName, sizeof(findName), "%s/%s/*.*", search->path, path);
itemFiles = FS_ListFiles(findName, &nItems, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM);
for (i=0; i < nItems; i++)
{
if (!itemFiles || !itemFiles[i])
continue;
// check extension
/* if ( (extension != NULL) && (strlen(extension) > 0) ) {
len = (int)strlen(itemFiles[i]);
extLen = (int)strlen(extension);
if ( strcmp(itemFiles[i]+max(len-(extLen+1),0), va(".%s", extension)) )
continue;
}*/
// found something
name = itemFiles[i] + strlen(search->path) + 1; // skip over search path and /
if (nFound < (MAX_FIND_FILES-1))
{
if (!FS_ItemInList(name, nFound, tmpList)) // check if already in list
{
tmpList[nFound] = strdup(name);
nFound++;
}
}
}
if (nItems)
FS_FreeFileList (itemFiles, nItems);
}
}
// sort the list
qsort(tmpList, nFound, sizeof(char *), Q_SortStrcmp);
// alloc and copy output list
outList = malloc(sizeof(char *) * (nFound+1));
memset(outList, 0, sizeof(char *) * (nFound+1));
for (i=0; i<nFound; i++) {
outList[i] = tmpList[i];
}
if (num)
*num = nFound;
return outList;
}
/*
=================
FS_FilteredFindFiles
Generates a listing of files that matches the given filter/widlcards.
=================
*/
char **FS_FilteredFindFiles (const char *pattern, int *num)
{
fsSearchPath_t *search;
fsPack_t *pak;
char findName[1024];
char *name, **itemFiles, *tmpList[MAX_FIND_FILES], **outList = NULL;
int nFound = 0;
int i, nItems;
memset (tmpList, 0, sizeof(tmpList));
for (search = fs_searchPaths; search; search = search->next)
{
if (search->pack) // search inside a pak/pk3 file
{
pak = search->pack;
for (i=0 ; i<pak->numFiles ; i++)
{
// match pattern
if ( !Q_GlobMatch(pattern, pak->files[i].name, false) )
continue;
// found something
name = pak->files[i].name;
if (nFound < (MAX_FIND_FILES-1))
{
if (!FS_ItemInList(name, nFound, tmpList)) // check if already in list
{
tmpList[nFound] = strdup(name);
nFound++;
}
}
}
}
else // search in a directory tree
{
Com_sprintf (findName, sizeof(findName), "%s/%s", search->path, pattern);
itemFiles = FS_ListFiles(findName, &nItems, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM);
for (i=0; i < nItems; i++)
{
if (!itemFiles || !itemFiles[i])
continue;
// match pattern
if ( !Q_GlobMatch(pattern, itemFiles[i] + strlen(search->path) + 1, false) )
continue;
// found something
name = itemFiles[i] + strlen(search->path) + 1; // skip over search path and /
if (nFound < (MAX_FIND_FILES-1))
{
if (!FS_ItemInList(name, nFound, tmpList)) // check if already in list
{
tmpList[nFound] = strdup(name);
nFound++;
}
}
}
if (nItems)
FS_FreeFileList (itemFiles, nItems);
}
}
// sort the list
qsort(tmpList, nFound, sizeof(char *), Q_SortStrcmp);
// alloc and copy output list
outList = malloc(sizeof(char *) * (nFound+1));
memset(outList, 0, sizeof(char *) * (nFound+1));
for (i=0; i<nFound; i++) {
outList[i] = tmpList[i];
}
if (num)
*num = nFound;
return outList;
}
/*
=================
FS_GetFileList
Generates a listing of files in the given path with the specified optional extension
If extension is NULL, retuns all files in the path. Also does filtered search based on wildcards.
=================
*/
char **FS_GetFileList (const char *path, const char *extension, int *num)
{
// If wildcards are in path, use filtered search instead of extension
if ( strchr(path, '*') || strchr(path, '?') || strchr(path, '[') || strchr(path, ']') )
return FS_FilteredFindFiles (path, num);
else
return FS_FindFiles (path, extension, num);
}
/* /*
================= =================
FS_Seek FS_Seek

View file

@ -834,12 +834,12 @@ void FS_InsertInList (char **list, char *insert, int len, int start);
void FS_Dir_f (void); void FS_Dir_f (void);
void FS_ExecAutoexec (void); void FS_ExecAutoexec (void);
int FS_GetFileList (const char *path, const char *extension, char *buffer, int size, fsSearchType_t searchType);
int FS_LoadFile (char *path, void **buffer); int FS_LoadFile (char *path, void **buffer);
void FS_AddPAKFile (const char *packPath); // add pak file function void FS_AddPAKFile (const char *packPath); // add pak file function
void FS_AddPK3File (const char *packPath); // add pk3 file function void FS_AddPK3File (const char *packPath); // add pk3 file function
char **FS_ListPak (char *find, int *num); // pak list function char **FS_ListPak (char *find, int *num); // pak list function
char **FS_GetFileList (const char *path, const char *extension, int *num);
void FS_SetGamedir (char *dir); void FS_SetGamedir (char *dir);
char *FS_Gamedir (void); char *FS_Gamedir (void);
void FS_FreeFile (void *buffer); void FS_FreeFile (void *buffer);

View file

@ -413,6 +413,7 @@ void SV_InitGameProgs (void)
import.GameDir = FS_GameDir; import.GameDir = FS_GameDir;
import.SaveGameDir = FS_GameDir; import.SaveGameDir = FS_GameDir;
import.CreatePath = FS_CreatePath; import.CreatePath = FS_CreatePath;
import.GetFileList = FS_GetFileList;
import.SetAreaPortalState = CM_SetAreaPortalState; import.SetAreaPortalState = CM_SetAreaPortalState;
import.AreasConnected = CM_AreasConnected; import.AreasConnected = CM_AreasConnected;

View file

@ -218,7 +218,7 @@ void UI_LoadArenas (void)
char **arenafiles; char **arenafiles;
char **tmplist = 0; char **tmplist = 0;
char *path = NULL; char *path = NULL;
char findName[1024]; // char findName[1024];
char shortname[MAX_TOKEN_CHARS]; char shortname[MAX_TOKEN_CHARS];
char longname[MAX_TOKEN_CHARS]; char longname[MAX_TOKEN_CHARS];
char gametypes[MAX_TOKEN_CHARS]; char gametypes[MAX_TOKEN_CHARS];
@ -241,6 +241,63 @@ void UI_LoadArenas (void)
tmplist = malloc( sizeof( char * ) * MAX_ARENAS ); tmplist = malloc( sizeof( char * ) * MAX_ARENAS );
memset( tmplist, 0, sizeof( char * ) * MAX_ARENAS ); memset( tmplist, 0, sizeof( char * ) * MAX_ARENAS );
#if 1
arenafiles = FS_GetFileList ("scripts", "arena", &narenas);
// arenafiles = FS_GetFileList ("scripts/*.arena", NULL, &narenas);
for (i = 0; i < narenas && narenanames < MAX_ARENAS; i++)
{
if (!arenafiles || !arenafiles[i])
continue;
len = (int)strlen(arenafiles[i]);
if ( strcmp(arenafiles[i]+max(len-6,0), ".arena") )
continue;
p = arenafiles[i];
if (!FS_ItemInList(p, narenanames, tmplist)) // check if already in list
{
if (UI_ParseArenaFromFile (p, shortname, longname, gametypes, MAX_TOKEN_CHARS))
{
// Com_sprintf(scratch, sizeof(scratch), MAPLIST_FORMAT, longname, shortname);
Com_sprintf(scratch, sizeof(scratch), "%s\n%s", longname, shortname);
for (j=0; j<NUM_MAPTYPES; j++)
type_supported[j] = false;
s = gametypes;
tok = strdup(COM_Parse (&s));
while (s != NULL)
{
for (j=0; j<NUM_MAPTYPES; j++)
{
s2 = gametype_names[j].tokens;
tok2 = COM_Parse (&s2);
while (s2 != NULL) {
if ( !Q_strcasecmp(tok, tok2) )
type_supported[j] = true;
tok2 = COM_Parse (&s2);
}
}
if (tok) free (tok);
tok = strdup(COM_Parse(&s));
}
if (tok) free (tok);
for (j=0; j<NUM_MAPTYPES; j++)
if (type_supported[j]) {
ui_svr_arena_mapnames[j][ui_svr_arena_nummaps[j]] = malloc(strlen(scratch) + 1);
// strncpy(ui_svr_arena_mapnames[j][ui_svr_arena_nummaps[j]], scratch);
Q_strncpyz(ui_svr_arena_mapnames[j][ui_svr_arena_nummaps[j]], scratch, strlen(scratch) + 1);
ui_svr_arena_nummaps[j]++;
}
// Com_Printf ("UI_LoadArenas: successfully loaded arena file %s: mapname: %s levelname: %s gametypes: %s\n", p, shortname, longname, gametypes);
narenanames++;
FS_InsertInList(tmplist, p, narenanames, 0); // add to list
}
}
}
#else
// //
// search in searchpaths for .arena files // search in searchpaths for .arena files
// //
@ -366,6 +423,8 @@ void UI_LoadArenas (void)
} }
} }
} }
#endif
if (narenas) if (narenas)
FS_FreeFileList (arenafiles, narenas); FS_FreeFileList (arenafiles, narenas);