diff --git a/include/QF/quakefs.h b/include/QF/quakefs.h index 2579d78e0..dad69fc23 100644 --- a/include/QF/quakefs.h +++ b/include/QF/quakefs.h @@ -67,6 +67,17 @@ typedef struct gamedir_s { } dir; } gamedir_t; +typedef struct vpath_s vpath_t; + +typedef struct findfile_s { + const vpath_t *vpath; ///< vpath in which file was found + qboolean in_pak; ///< if true, path refers to a pak file rather + ///< than a directory + const char *realname; ///< the name of the file as found (may have + ///< .gz appended, or .ogg substituded for + ///< .wav) does not include the path +} findfile_t; + /** Cached information about the current game directory. \see \ref dirconf. */ extern gamedir_t *qfs_gamedir; @@ -121,6 +132,30 @@ void QFS_Init (const char *game); */ void QFS_Gamedir (const char *gamedir); +/** Search for a file in the quake file-system. + + The search will begin in the \a start vpath and end in the \a end vpath. + If \a start is null, the search will begin in the vpath specified by + qfs_vpaths (ie, the first directory in the \c Path attribute + (\ref dirconf). If \a end is null, the search will continue to the end + of the list of vpaths. If \a start and \a end are the same (and non-null), + then only the one vpath will be searched. + + \note All search paths in a vpath will be searched. This keeps \QF's + directory system transparent. + + \note It is a fatal error for \a end to be non-null but not in the list + of vpaths. + + \param fname The name of the file to be searched for. + \param start The first vpath (gamedir) to search. + \param start The last vpath (gamedir) to search. + \return Pointer to the findfile_t record indicating the location + of the file, or null if the file was not found. +*/ +findfile_t *QFS_FindFile (const char *fname, const vpath_t *start, + const vpath_t *end); + /** Open a file from within the user directory. If \a path attempts to leave the user directory, this function sets diff --git a/libs/util/quakefs.c b/libs/util/quakefs.c index a3f956195..6168abf4c 100644 --- a/libs/util/quakefs.c +++ b/libs/util/quakefs.c @@ -146,12 +146,20 @@ typedef struct searchpath_s { The purpose is to enable searches to be limited to a single gamedir or to not search past a certain gamedir. */ -typedef struct vpath_s { +struct vpath_s { // typedef to vpath_t is in quakefs.h char *name; // usually the gamedir name searchpath_t *user; searchpath_t *share; struct vpath_s *next; -} vpath_t; +}; + +typedef struct int_findfile_s { + findfile_t ff; + struct pack_s *pack; + dpackfile_t *packfile; + const char *path; + int fname_index; +} int_findfile_t; static searchpath_t *free_searchpaths; static vpath_t *free_vpaths; @@ -792,6 +800,129 @@ QFS_WriteFile (const char *filename, const void *data, int len) Qclose (f); } +static int_findfile_t * +qfs_findfile_search (const vpath_t *vpath, const searchpath_t *sp, + const char **fnames) +{ + static int_findfile_t found; + const char **fn; + + found.ff.vpath = 0; + found.ff.in_pak = false; + found.pack = 0; + found.packfile = 0; + found.fname_index = 0; + if (found.ff.realname) { + free ((char *) found.ff.realname); + found.ff.realname = 0; + } + if (found.path) { + free ((char *) found.path); + found.path = 0; + } + // is the element a pak file? + if (sp->pack) { + dpackfile_t *packfile; + + for (fn = fnames; *fn; fn++) { + packfile = pack_find_file (sp->pack, *fn); + if (packfile) { + break; + } + } + if (packfile) { + Sys_MaskPrintf (SYS_FS_F, "PackFile: %s : %s\n", + sp->pack->filename, packfile->name); + found.ff.vpath = vpath; + found.ff.in_pak = true; + found.ff.realname = strdup (*fn); + found.pack = sp->pack; + found.packfile = packfile; + found.fname_index = fn - fnames; + found.path = strdup (sp->pack->filename); + return &found; + } + } else { + // check a file in the directory tree + dstring_t *path = dstring_new (); + + for (fn = fnames; *fn; fn++) { + if (qfs_expand_path (path, sp->filename, *fn, 1) == 0) { + if (Sys_FileTime (path->str) == -1) { + dstring_delete (path); + return 0; + } + + Sys_MaskPrintf (SYS_FS_F, "FindFile: %s\n", path->str); + + found.ff.vpath = vpath; + found.ff.in_pak = false; + found.ff.realname = strdup (*fn); + found.path = strdup (path->str); + found.fname_index = fn - fnames; + return &found; + } + } + dstring_delete (path); + } + return 0; +} + +static int_findfile_t * +qfs_findfile (const char **fnames, const vpath_t *start, const vpath_t *end) +{ + const vpath_t *vp; + searchpath_t *sp; + int_findfile_t *found; + + if (!start) { + start = qfs_vpaths; + } + if (end) { + for (vp = start; vp; vp = vp->next) { + if (vp == end) { + break; + } + } + if (!vp) { + Sys_Error ("QFS_FindFile: end vpath not in search list"); + } + end = end->next; + } + for (vp = start; vp && vp != end; vp = vp->next) { + for (sp = vp->user; sp; sp = sp->next) { + found = qfs_findfile_search (vp, sp, fnames); + if (found) { + return found; + } + } + for (sp = vp->share; sp; sp = sp->next) { + found = qfs_findfile_search (vp, sp, fnames); + if (found) { + return found; + } + } + } + // file not found + return 0; +} + +VISIBLE findfile_t * +QFS_FindFile (const char *fname, const vpath_t *start, const vpath_t *end) +{ + int_findfile_t *found; + const char *fname_list[2]; + + fname_list[0] = fname; + fname_list[1] = 0; + + found = qfs_findfile (fname_list, start, end); + if (found) { + return &found->ff; + } + return 0; +} + static QFile * qfs_openread (const char *path, int offs, int len, int zip) {