From 01d78fcaf73a926c1d0aa3ade0b3604c6949fe85 Mon Sep 17 00:00:00 2001 From: Knightmare66 Date: Tue, 8 Oct 2019 21:55:44 -0400 Subject: [PATCH] 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. --- game/game.h | 1 + game/q_shared.c | 262 +++++++++++++++++++++++++++++++++++++++++ game/q_shared.h | 12 +- qcommon/files.c | 206 ++++++++++++++++++++++++++++++++ qcommon/qcommon.h | 2 +- server/sv_game.c | 1 + ui/ui_mp_startserver.c | 61 +++++++++- 7 files changed, 542 insertions(+), 3 deletions(-) diff --git a/game/game.h b/game/game.h index c51a1fc..c16f986 100644 --- a/game/game.h +++ b/game/game.h @@ -206,6 +206,7 @@ typedef struct char *(*GameDir) (void); char *(*SaveGameDir) (void); void (*CreatePath) (char *path); + char **(*GetFileList) (const char *path, const char *extension, int *num); #endif } game_import_t; diff --git a/game/q_shared.c b/game/q_shared.c index 328069d..1b87f45 100644 --- a/game/q_shared.c +++ b/game/q_shared.c @@ -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 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 c1, c2; @@ -1625,6 +1882,11 @@ int Q_strncasecmp (char *s1, char *s2, int n) } +/* +================= +Q_strcasecmp +================= +*/ int Q_strcasecmp (char *s1, char *s2) { return Q_strncasecmp (s1, s2, 99999); diff --git a/game/q_shared.h b/game/q_shared.h index 659801c..e334330 100644 --- a/game/q_shared.h +++ b/game/q_shared.h @@ -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_strcasecmp (char *s1, char *s2); int Q_strncasecmp (char *s1, char *s2, int n); diff --git a/qcommon/files.c b/qcommon/files.c index db02f8e..dc5899f 100644 --- a/qcommon/files.c +++ b/qcommon/files.c @@ -1201,6 +1201,212 @@ char **FS_ListPak (char *find, int *num) 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 ; inumFiles ; 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; inext) + { + if (search->pack) // search inside a pak/pk3 file + { + pak = search->pack; + for (i=0 ; inumFiles ; 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