blankart/src/filesrch.c
Alug 5d1f03b77f delet all the Z_Free checks
Z_Free already checks its input internally!
2026-02-08 09:51:17 -05:00

636 lines
15 KiB
C

/// \file
/// \brief Support to find files
///
///
/// filesearch:
///
/// ATTENTION : make sure there is enouth space in filename to put a full path (255 or 512)
/// if wantedhash == 0 there is no hash check
/// if completepath then filename will be change with the full path and name
/// maxsearchdepth == 0 only search given directory, no subdirs
/// return FS_NOTFOUND
/// FS_BADHASH;
/// FS_FOUND
#include <stdio.h>
#ifdef __GNUC__
#include <dirent.h>
#endif
#if defined (_WIN32) && !defined (_XBOX)
//#define WIN32_LEAN_AND_MEAN
#define RPC_NO_WINDOWS_H
#include <windows.h>
#endif
#include <sys/stat.h>
#include <string.h>
#include "filesrch.h"
#include "d_netfil.h"
#include "m_misc.h"
#include "z_zone.h"
#include "m_menu.h" // Addons_option_Onchange
static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"}, {1, "HOME"}, {2, "SRB2KART"}, {3, "CUSTOM"}, {0, NULL}};
consvar_t cv_addons_option = CVAR_INIT ("addons_option", "Default", CV_SAVE|CV_CALL|CV_NOINIT, addons_cons_t, Addons_option_Onchange);
consvar_t cv_addons_folder = CVAR_INIT ("addons_folder", "", CV_SAVE, NULL, NULL);
static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}};
consvar_t cv_addons_md5 = CVAR_INIT ("addons_md5", "Name", CV_SAVE, addons_md5_cons_t, NULL);
consvar_t cv_addons_showall = CVAR_INIT ("addons_showall", "No", CV_SAVE, CV_YesNo, NULL);
consvar_t cv_addons_search_case = CVAR_INIT ("addons_search_case", "No", CV_SAVE, CV_YesNo, NULL);
static CV_PossibleValue_t addons_search_type_cons_t[] = {{0, "Start"}, {1, "Anywhere"}, {0, NULL}};
consvar_t cv_addons_search_type = CVAR_INIT ("addons_search_type", "Anywhere", CV_SAVE, addons_search_type_cons_t, NULL);
char menupath[MAXFILEPATH];
size_t menupathindex[menudepth];
size_t menudepthleft = menudepth;
char menusearchbuf[MAXSTRINGLENGTH+1];
textinput_t menusearch = {};
char **dirmenu, **coredirmenu; // core only local for this file
size_t sizedirmenu, sizecoredirmenu; // ditto
size_t dir_on[menudepth];
UINT8 refreshdirmenu = 0;
char *refreshdirname = NULL;
// skip those folders, they will not have any addons
static const char *exclude_paths[] = {
"logs",
"models",
"media",
NULL
};
filestatus_t filesearch(char *filename, const char *startpath, UINT64 wantedhash, boolean completepath, int maxsearchdepth)
{
filestatus_t retval = FS_NOTFOUND;
DIR **dirhandle;
struct dirent *dent;
#ifndef _WIN32
struct stat fsstat;
#endif
int found = 0;
char *searchname;
int depthleft = maxsearchdepth;
char searchpath[MAXFILEPATH];
size_t *searchpathindex;
dirhandle = (DIR**)malloc(maxsearchdepth * sizeof(DIR*));
searchpathindex = (size_t *)malloc(maxsearchdepth * sizeof(size_t));
strcpy(searchpath, startpath);
searchpathindex[--depthleft] = strlen(searchpath) + 1;
dirhandle[depthleft] = opendir(searchpath);
if (dirhandle[depthleft] == NULL)
{
free(dirhandle);
free(searchpathindex);
return FS_NOTFOUND;
}
searchname = strdup(filename);
if (searchpath[searchpathindex[depthleft]-2] != PATHSEP[0])
{
searchpath[searchpathindex[depthleft]-1] = PATHSEP[0];
searchpath[searchpathindex[depthleft]] = 0;
}
else
searchpathindex[depthleft]--;
while ((!found) && (depthleft < maxsearchdepth))
{
searchpath[searchpathindex[depthleft]]=0;
dent = readdir(dirhandle[depthleft]);
if (!dent)
{
closedir(dirhandle[depthleft++]);
continue;
}
if (dent->d_name[0]=='.' &&
(dent->d_name[1]=='\0' ||
(dent->d_name[1]=='.' &&
dent->d_name[2]=='\0')))
{
// we don't want to scan uptree
continue;
}
// okay, now we actually want searchpath to incorporate d_name
strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name);
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__)
if (dent->d_type == DT_UNKNOWN || dent->d_type == DT_LNK)
if (stat(searchpath, &fsstat) == 0 && S_ISDIR(fsstat.st_mode))
dent->d_type = DT_DIR;
// Linux and FreeBSD has a special field for file type on dirent, so use that to speed up lookups.
if (dent->d_type == DT_DIR)
#elif defined (_WIN32)
// if we wanna follow symlinks we can check with FILE_ATTRIBUTE_REPARSE_POINT
DWORD fileattr = GetFileAttributes(searchpath);
if (fileattr == INVALID_FILE_ATTRIBUTES)
continue; // was the file (re)moved? can't stat it
if (fileattr & FILE_ATTRIBUTE_DIRECTORY)
#else
if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat
continue; // was the file (re)moved? can't stat it
if (S_ISDIR(fsstat.st_mode))
#endif
{
// I am a folder!
if (!depthleft)
continue; // No additional folder delving permitted...
const char **path = exclude_paths;
if (depthleft == maxsearchdepth-1)
{
// When we're at the root of the search, we exclude certain folders.
boolean skipfolder = false;
for (; *path != NULL; path++)
{
if (strcasecmp(*path, dent->d_name) == 0)
{
skipfolder = true;
break;
}
}
// This folder is excluded
if (skipfolder)
{
continue;
}
}
if (strcasecmp(".git", dent->d_name) // sanity if you're weird like me
&& (dirhandle[depthleft-1] = opendir(searchpath)) != NULL)
{
// Got read permissions!
searchpathindex[--depthleft] = strlen(searchpath) + 1;
searchpath[searchpathindex[depthleft]-1] = PATHSEP[0];
searchpath[searchpathindex[depthleft]] = 0;
}
continue;
}
// I am a file!
if (strcasecmp(searchname, dent->d_name))
continue; // Not what we're looking for!
switch (checkfilehash(searchpath, wantedhash))
{
case FS_FOUND:
if (completepath)
strcpy(filename, searchpath);
else
strcpy(filename, dent->d_name);
retval = FS_FOUND;
found = 1;
break;
case FS_BADHASH:
retval = FS_BADHASH;
break;
default: // prevent some compiler warnings
break;
}
}
for (; depthleft < maxsearchdepth; closedir(dirhandle[depthleft++]));
free(searchname);
free(searchpathindex);
free(dirhandle);
return retval;
}
char exttable[NUM_EXT_TABLE][7] = { // maximum extension length (currently 4) plus 3 (null terminator, stop, and length including previous two)
"\5.txt", "\5.cfg", // exec
"\5.wad",
#ifdef USE_KART
"\6.kart",
#endif
"\5.pk3", "\5.soc", "\5.lua"}; // addfile
char filenamebuf[MAX_WADFILES][MAX_WADPATH];
static boolean filemenucmp(char *haystack, char *needle)
{
static char localhaystack[128];
strlcpy(localhaystack, haystack, 128);
if (!cv_addons_search_case.value)
strupr(localhaystack);
if (cv_addons_search_type.value)
return (strstr(localhaystack, needle) != NULL);
return (!strncmp(localhaystack, needle, menusearch.length));
}
void closefilemenu(boolean validsize)
{
// search
if (dirmenu)
{
if (dirmenu != coredirmenu)
{
if (dirmenu[0] && ((UINT8)(dirmenu[0][DIR_TYPE]) == EXT_NORESULTS))
{
Z_Free(dirmenu[0]);
dirmenu[0] = NULL;
}
Z_Free(dirmenu);
}
dirmenu = NULL;
sizedirmenu = 0;
}
if (coredirmenu)
{
// core
if (validsize)
{
for (; sizecoredirmenu > 0; sizecoredirmenu--)
{
Z_Free(coredirmenu[sizecoredirmenu-1]);
coredirmenu[sizecoredirmenu-1] = NULL;
}
}
else
sizecoredirmenu = 0;
Z_Free(coredirmenu);
coredirmenu = NULL;
}
Z_Free(refreshdirname);
refreshdirname = NULL;
}
void searchfilemenu(char *tempname)
{
size_t i, first;
char localmenusearch[MAXSTRINGLENGTH] = "";
if (dirmenu)
{
if (dirmenu != coredirmenu)
{
if (dirmenu[0] && ((UINT8)(dirmenu[0][DIR_TYPE]) == EXT_NORESULTS))
{
Z_Free(dirmenu[0]);
dirmenu[0] = NULL;
}
//Z_Free(dirmenu); -- Z_Realloc later tho...
}
else
dirmenu = NULL;
}
first = (((UINT8)(coredirmenu[0][DIR_TYPE]) == EXT_UP) ? 1 : 0); // skip UP...
if (!menusearch.length)
{
Z_Free(dirmenu);
dirmenu = coredirmenu;
sizedirmenu = sizecoredirmenu;
if (tempname)
{
for (i = first; i < sizedirmenu; i++)
{
if (fastcmp(dirmenu[i]+DIR_STRING, tempname))
{
dir_on[menudepthleft] = i;
break;
}
}
if (i == sizedirmenu)
dir_on[menudepthleft] = first;
Z_Free(tempname);
}
return;
}
strcpy(localmenusearch, menusearch.buffer);
if (!cv_addons_search_case.value)
strupr(localmenusearch);
sizedirmenu = 0;
for (i = first; i < sizecoredirmenu; i++)
{
if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch))
sizedirmenu++;
}
if (!sizedirmenu) // no results...
{
if ((!(dirmenu = Z_Realloc(dirmenu, sizeof(char *), PU_STATIC, NULL)))
|| !(dirmenu[0] = Z_StrDup(va("%c\13No results...", EXT_NORESULTS))))
I_Error("searchfilemenu(): could not create \"No results...\".");
sizedirmenu = 1;
dir_on[menudepthleft] = 0;
Z_Free(tempname);
return;
}
if (!(dirmenu = Z_Realloc(dirmenu, sizedirmenu*sizeof(char *), PU_STATIC, NULL)))
I_Error("searchfilemenu(): could not reallocate dirmenu.");
sizedirmenu = 0;
for (i = first; i < sizecoredirmenu; i++)
{
if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch))
{
if (tempname && fastcmp(coredirmenu[i]+DIR_STRING, tempname))
{
dir_on[menudepthleft] = sizedirmenu;
Z_Free(tempname);
tempname = NULL;
}
dirmenu[sizedirmenu++] = coredirmenu[i]; // pointer reuse
}
}
if (tempname)
{
dir_on[menudepthleft] = 0; //first; -- can't be first, causes problems
Z_Free(tempname);
}
}
boolean preparefilemenu(boolean samedepth, boolean replayhut)
{
DIR *dirhandle;
struct dirent *dent;
#ifndef _WIN32
struct stat fsstat;
#endif
size_t pos = 0, folderpos = 0, numfolders = 0;
char *tempname = NULL;
if (samedepth)
{
if (dirmenu && dirmenu[dir_on[menudepthleft]])
tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL
}
else
M_TextInputInit(&menusearch, menusearchbuf, MAXSTRINGLENGTH+1);
if (!(dirhandle = opendir(menupath))) // get directory
{
closefilemenu(true);
return false;
}
if (dirmenu != coredirmenu)
Z_Free(dirmenu);
dirmenu = NULL;
if (coredirmenu != NULL)
{
for (; sizecoredirmenu > 0; sizecoredirmenu--) // clear out existing items
{
Z_Free(coredirmenu[sizecoredirmenu-1]);
coredirmenu[sizecoredirmenu-1] = NULL;
}
}
while (true)
{
menupath[menupathindex[menudepthleft]] = 0;
dent = readdir(dirhandle);
if (!dent)
break;
else if (dent->d_name[0]=='.' &&
(dent->d_name[1]=='\0' ||
(dent->d_name[1]=='.' &&
dent->d_name[2]=='\0')))
continue; // we don't want to scan uptree
strcpy(&menupath[menupathindex[menudepthleft]],dent->d_name);
#ifndef _WIN32
if (stat(menupath, &fsstat) < 0)
#else
// if we wanna follow symlinks we can check with FILE_ATTRIBUTE_REPARSE_POINT
DWORD fileattr = GetFileAttributes(menupath);
if (fileattr == INVALID_FILE_ATTRIBUTES)
#endif
; // was the file (re)moved? can't stat it
else // is a file or directory
{
#ifndef _WIN32
if (!S_ISDIR(fsstat.st_mode)) // file
#else
if (!(fileattr & FILE_ATTRIBUTE_DIRECTORY))
#endif
{
size_t len = strlen(dent->d_name)+1;
if (replayhut)
{
if (strcasecmp(".lmp", dent->d_name+len-5))
continue; // Not a replay
}
else if (!cv_addons_showall.value)
{
UINT8 ext;
for (ext = 0; ext < NUM_EXT_TABLE; ext++)
if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0])))
break; // extension comparison
if (ext == NUM_EXT_TABLE)
continue; // not an addfile-able (or exec-able) file
}
}
else // directory
numfolders++;
sizecoredirmenu++;
}
}
if (!sizecoredirmenu)
{
closedir(dirhandle);
closefilemenu(false);
Z_Free(tempname);
return false;
}
if (menudepthleft != menudepth-1) // Make room for UP...
{
sizecoredirmenu++;
numfolders++;
folderpos++;
}
if (dirmenu && dirmenu == coredirmenu)
dirmenu = NULL;
if (!(coredirmenu = Z_Realloc(coredirmenu, sizecoredirmenu*sizeof(char *), PU_STATIC, NULL)))
{
closedir(dirhandle); // just in case
I_Error("preparefilemenu(): could not reallocate coredirmenu.");
}
rewinddir(dirhandle);
while ((pos+folderpos) < sizecoredirmenu)
{
menupath[menupathindex[menudepthleft]] = 0;
dent = readdir(dirhandle);
if (!dent)
break;
else if (dent->d_name[0]=='.' &&
(dent->d_name[1]=='\0' ||
(dent->d_name[1]=='.' &&
dent->d_name[2]=='\0')))
continue; // we don't want to scan uptree
strcpy(&menupath[menupathindex[menudepthleft]],dent->d_name);
#ifndef _WIN32
if (stat(menupath, &fsstat) < 0)
#else
// if we wanna follow symlinks we can check with FILE_ATTRIBUTE_REPARSE_POINT
DWORD fileattr = GetFileAttributes(menupath);
if (fileattr == INVALID_FILE_ATTRIBUTES)
#endif
; // was the file (re)moved? can't stat it
else // is a file or directory
{
char *temp;
size_t len = strlen(dent->d_name)+1;
UINT8 ext = EXT_FOLDER;
UINT8 folder;
#ifndef _WIN32
if (!S_ISDIR(fsstat.st_mode)) // file
#else
if (!(fileattr & FILE_ATTRIBUTE_DIRECTORY))
#endif
{
if (!((numfolders+pos) < sizecoredirmenu))
continue; // crash prevention
if (replayhut)
{
if (strcasecmp(".lmp", dent->d_name+len-5))
continue; // Not a replay
ext = EXT_TXT; // This isn't used anywhere but better safe than sorry for messing with this...
}
else
{
for (; ext < NUM_EXT_TABLE; ext++)
if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0])))
break; // extension comparison
if (ext == NUM_EXT_TABLE && !cv_addons_showall.value)
continue; // not an addfile-able (or exec-able) file
ext += EXT_START; // moving to be appropriate position
if (ext >= EXT_LOADSTART)
{
size_t i;
for (i = 0; i < numwadfiles; i++)
{
if (!filenamebuf[i][0])
{
strncpy(filenamebuf[i], wadfiles[i]->filename, MAX_WADPATH);
filenamebuf[i][MAX_WADPATH - 1] = '\0';
nameonly(filenamebuf[i]);
}
if (!fastcmp(dent->d_name, filenamebuf[i]))
continue;
if (cv_addons_md5.value && !checkfilehash(menupath, wadfiles[i]->hash))
continue;
ext |= EXT_LOADED;
}
}
else if (ext == EXT_TXT)
{
if (fastcmp(dent->d_name, "log.txt") || fastcmp(dent->d_name, "errorlog.txt"))
ext |= EXT_LOADED;
}
if (fastcmp(dent->d_name, configfile))
ext |= EXT_LOADED;
}
folder = 0;
}
else // directory
len += (folder = 1);
if (len > 255)
len = 255;
if (!(temp = Z_Malloc((len+DIR_STRING+folder) * sizeof (char), PU_STATIC, NULL)))
I_Error("preparefilemenu(): could not create file entry.");
temp[DIR_TYPE] = ext;
temp[DIR_LEN] = (UINT8)(len);
strlcpy(temp+DIR_STRING, dent->d_name, len);
if (folder)
{
strcpy(temp+len, PATHSEP);
coredirmenu[folderpos++] = temp;
}
else if (replayhut) // Reverse-alphabetical on just the files; acts as a fake "most recent first" with the current filename format
coredirmenu[sizecoredirmenu - 1 - pos++] = temp;
else
coredirmenu[numfolders + pos++] = temp;
}
}
closedir(dirhandle);
if ((menudepthleft != menudepth-1) // now for UP... entry
&& !(coredirmenu[0] = Z_StrDup(va("%c\5UP...", EXT_UP))))
I_Error("preparefilemenu(): could not create \"UP...\".");
menupath[menupathindex[menudepthleft]] = 0;
sizecoredirmenu = (numfolders+pos); // just in case things shrink between opening and rewind
if (!sizecoredirmenu)
{
dir_on[menudepthleft] = 0;
closefilemenu(false);
return false;
}
searchfilemenu(tempname);
return true;
}