aboutsummaryrefslogtreecommitdiffstats
path: root/code/qcommon/files.c
diff options
context:
space:
mode:
Diffstat (limited to 'code/qcommon/files.c')
-rwxr-xr-xcode/qcommon/files.c6838
1 files changed, 3419 insertions, 3419 deletions
diff --git a/code/qcommon/files.c b/code/qcommon/files.c
index f27d2f2..0e21165 100755
--- a/code/qcommon/files.c
+++ b/code/qcommon/files.c
@@ -1,3419 +1,3419 @@
-/*
-===========================================================================
-Copyright (C) 1999-2005 Id Software, Inc.
-
-This file is part of Quake III Arena source code.
-
-Quake III Arena source code is free software; you can redistribute it
-and/or modify it under the terms of the GNU General Public License as
-published by the Free Software Foundation; either version 2 of the License,
-or (at your option) any later version.
-
-Quake III Arena source code is distributed in the hope that it will be
-useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Foobar; if not, write to the Free Software
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-===========================================================================
-*/
-/*****************************************************************************
- * name: files.c
- *
- * desc: handle based filesystem for Quake III Arena
- *
- * $Archive: /MissionPack/code/qcommon/files.c $
- *
- *****************************************************************************/
-
-
-#include "../game/q_shared.h"
-#include "qcommon.h"
-#include "unzip.h"
-
-/*
-=============================================================================
-
-QUAKE3 FILESYSTEM
-
-All of Quake's data access is through a hierarchical file system, but the contents of
-the file system can be transparently merged from several sources.
-
-A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include
-a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
-references outside the quake directory system.
-
-The "base path" is the path to the directory holding all the game directories and usually
-the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
-command line to allow code debugging in a different directory. Basepath cannot
-be modified at all after startup. Any files that are created (demos, screenshots,
-etc) will be created reletive to the base path, so base path should usually be writable.
-
-The "cd path" is the path to an alternate hierarchy that will be searched if a file
-is not located in the base path. A user can do a partial install that copies some
-data to a base path created on their hard drive and leave the rest on the cd. Files
-are never writen to the cd path. It defaults to a value set by the installer, like
-"e:\quake3", but it can be overridden with "+set ds_cdpath g:\quake3".
-
-If a user runs the game directly from a CD, the base path would be on the CD. This
-should still function correctly, but all file writes will fail (harmlessly).
-
-The "home path" is the path used for all write access. On win32 systems we have "base path"
-== "home path", but on *nix systems the base installation is usually readonly, and
-"home path" points to ~/.q3a or similar
-
-The user can also install custom mods and content in "home path", so it should be searched
-along with "home path" and "cd path" for game content.
-
-
-The "base game" is the directory under the paths where data comes from by default, and
-can be either "baseq3" or "demoq3".
-
-The "current game" may be the same as the base game, or it may be the name of another
-directory under the paths that should be searched for files before looking in the base game.
-This is the basis for addons.
-
-Clients automatically set the game directory after receiving a gamestate from a server,
-so only servers need to worry about +set fs_game.
-
-No other directories outside of the base game and current game will ever be referenced by
-filesystem functions.
-
-To save disk space and speed loading, directory trees can be collapsed into zip files.
-The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
-otherwise the are simply normal uncompressed zip files. A game directory can have multiple
-zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order
-from the highest number to the lowest, and will always take precedence over the filesystem.
-This allows a pk3 distributed as a patch to override all existing data.
-
-Because we will have updated executables freely available online, there is no point to
-trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
-should be exactly the same executables as release versions, but with different data that
-automatically restricts where game media can come from to prevent add-ons from working.
-
-After the paths are initialized, quake will look for the product.txt file. If not
-found and verified, the game will run in restricted mode. In restricted mode, only
-files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is
-verified to not have been modified. A single exception is made for q3config.cfg. Files
-can still be written out in restricted mode, so screenshots and demos are allowed.
-Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
-if there is a valid product.txt under the basepath or cdpath.
-
-If not running in restricted mode, and a file is not found in any local filesystem,
-an attempt will be made to download it and save it under the base path.
-
-If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
-path, it will be copied over to the base path. This is a development aid to help build
-test releases and to copy working sets over slow network links.
-
-File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
-structure and stop on the first successful hit. fs_searchpaths is built with successive
-calls to FS_AddGameDirectory
-
-Additionaly, we search in several subdirectories:
-current game is the current mode
-base game is a variable to allow mods based on other mods
-(such as baseq3 + missionpack content combination in a mod for instance)
-BASEGAME is the hardcoded base game ("baseq3")
-
-e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
-
-home path + current game's zip files
-home path + current game's directory
-base path + current game's zip files
-base path + current game's directory
-cd path + current game's zip files
-cd path + current game's directory
-
-home path + base game's zip file
-home path + base game's directory
-base path + base game's zip file
-base path + base game's directory
-cd path + base game's zip file
-cd path + base game's directory
-
-home path + BASEGAME's zip file
-home path + BASEGAME's directory
-base path + BASEGAME's zip file
-base path + BASEGAME's directory
-cd path + BASEGAME's zip file
-cd path + BASEGAME's directory
-
-server download, to be written to home path + current game's directory
-
-
-The filesystem can be safely shutdown and reinitialized with different
-basedir / cddir / game combinations, but all other subsystems that rely on it
-(sound, video) must also be forced to restart.
-
-Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
-subsystems, a simple single-file caching scheme is used. The CM_ subsystems will
-load the file with a request to cache. Only one file will be kept cached at a time,
-so any models that are going to be referenced by both subsystems should alternate
-between the CM_ load function and the ref load function.
-
-TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
-game is currently active. This allows character models, skins, and sounds to be downloaded
-to a common directory no matter which game is active.
-
-How to prevent downloading zip files?
-Pass pk3 file names in systeminfo, and download before FS_Restart()?
-
-Aborting a download disconnects the client from the server.
-
-How to mark files as downloadable? Commercial add-ons won't be downloadable.
-
-Non-commercial downloads will want to download the entire zip file.
-the game would have to be reset to actually read the zip in
-
-Auto-update information
-
-Path separators
-
-Casing
-
- separate server gamedir and client gamedir, so if the user starts
- a local game after having connected to a network game, it won't stick
- with the network game.
-
- allow menu options for game selection?
-
-Read / write config to floppy option.
-
-Different version coexistance?
-
-When building a pak file, make sure a q3config.cfg isn't present in it,
-or configs will never get loaded from disk!
-
- todo:
-
- downloading (outside fs?)
- game directory passing and restarting
-
-=============================================================================
-
-*/
-
-#define DEMOGAME "demota"
-
-// every time a new demo pk3 file is built, this checksum must be updated.
-// the easiest way to get it is to just run the game and see what it spits out
-#define DEMO_PAK_CHECKSUM 437558517u
-
-// if this is defined, the executable positively won't work with any paks other
-// than the demo pak, even if productid is present. This is only used for our
-// last demo release to prevent the mac and linux users from using the demo
-// executable with the production windows pak before the mac/linux products
-// hit the shelves a little later
-// NOW defined in build files
-//#define PRE_RELEASE_TADEMO
-
-#define MAX_ZPATH 256
-#define MAX_SEARCH_PATHS 4096
-#define MAX_FILEHASH_SIZE 1024
-
-typedef struct fileInPack_s {
- char *name; // name of the file
- unsigned long pos; // file info position in zip
- struct fileInPack_s* next; // next file in the hash
-} fileInPack_t;
-
-typedef struct {
- char pakFilename[MAX_OSPATH]; // c:\quake3\baseq3\pak0.pk3
- char pakBasename[MAX_OSPATH]; // pak0
- char pakGamename[MAX_OSPATH]; // baseq3
- unzFile handle; // handle to zip file
- int checksum; // regular checksum
- int pure_checksum; // checksum for pure
- int numfiles; // number of files in pk3
- int referenced; // referenced file flags
- int hashSize; // hash table size (power of 2)
- fileInPack_t* *hashTable; // hash table
- fileInPack_t* buildBuffer; // buffer with the filenames etc.
-} pack_t;
-
-typedef struct {
- char path[MAX_OSPATH]; // c:\quake3
- char gamedir[MAX_OSPATH]; // baseq3
-} directory_t;
-
-typedef struct searchpath_s {
- struct searchpath_s *next;
-
- pack_t *pack; // only one of pack / dir will be non NULL
- directory_t *dir;
-} searchpath_t;
-
-static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
-static cvar_t *fs_debug;
-static cvar_t *fs_homepath;
-static cvar_t *fs_basepath;
-static cvar_t *fs_basegame;
-static cvar_t *fs_cdpath;
-static cvar_t *fs_copyfiles;
-static cvar_t *fs_gamedirvar;
-static cvar_t *fs_restrict;
-static searchpath_t *fs_searchpaths;
-static int fs_readCount; // total bytes read
-static int fs_loadCount; // total files read
-static int fs_loadStack; // total files in memory
-static int fs_packFiles; // total number of files in packs
-
-static int fs_fakeChkSum;
-static int fs_checksumFeed;
-
-typedef union qfile_gus {
- FILE* o;
- unzFile z;
-} qfile_gut;
-
-typedef struct qfile_us {
- qfile_gut file;
- qboolean unique;
-} qfile_ut;
-
-typedef struct {
- qfile_ut handleFiles;
- qboolean handleSync;
- int baseOffset;
- int fileSize;
- int zipFilePos;
- qboolean zipFile;
- qboolean streamed;
- char name[MAX_ZPATH];
-} fileHandleData_t;
-
-static fileHandleData_t fsh[MAX_FILE_HANDLES];
-
-// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
-// wether we did a reorder on the current search path when joining the server
-static qboolean fs_reordered;
-
-// never load anything from pk3 files that are not present at the server when pure
-static int fs_numServerPaks;
-static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums
-static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names
-
-// only used for autodownload, to make sure the client has at least
-// all the pk3 files that are referenced at the server side
-static int fs_numServerReferencedPaks;
-static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums
-static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names
-
-// last valid game folder used
-char lastValidBase[MAX_OSPATH];
-char lastValidGame[MAX_OSPATH];
-
-// productId: This file is copyright 1999 Id Software, and may not be duplicated except during a licensed installation of the full commercial version of Quake 3:Arena
-static byte fs_scrambledProductId[152] = {
-220, 129, 255, 108, 244, 163, 171, 55, 133, 65, 199, 36, 140, 222, 53, 99, 65, 171, 175, 232, 236, 193, 210, 250, 169, 104, 231, 231, 21, 201, 170, 208, 135, 175, 130, 136, 85, 215, 71, 23, 96, 32, 96, 83, 44, 240, 219, 138, 184, 215, 73, 27, 196, 247, 55, 139, 148, 68, 78, 203, 213, 238, 139, 23, 45, 205, 118, 186, 236, 230, 231, 107, 212, 1, 10, 98, 30, 20, 116, 180, 216, 248, 166, 35, 45, 22, 215, 229, 35, 116, 250, 167, 117, 3, 57, 55, 201, 229, 218, 222, 128, 12, 141, 149, 32, 110, 168, 215, 184, 53, 31, 147, 62, 12, 138, 67, 132, 54, 125, 6, 221, 148, 140, 4, 21, 44, 198, 3, 126, 12, 100, 236, 61, 42, 44, 251, 15, 135, 14, 134, 89, 92, 177, 246, 152, 106, 124, 78, 118, 80, 28, 42
-};
-
-#ifdef FS_MISSING
-FILE* missingFiles = NULL;
-#endif
-
-/*
-==============
-FS_Initialized
-==============
-*/
-
-qboolean FS_Initialized() {
- return (fs_searchpaths != NULL);
-}
-
-/*
-=================
-FS_PakIsPure
-=================
-*/
-qboolean FS_PakIsPure( pack_t *pack ) {
- int i;
-
- if ( fs_numServerPaks ) {
- for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
- // FIXME: also use hashed file names
- // NOTE TTimo: a pk3 with same checksum but different name would be validated too
- // I don't see this as allowing for any exploit, it would only happen if the client does manips of it's file names 'not a bug'
- if ( pack->checksum == fs_serverPaks[i] ) {
- return qtrue; // on the aproved list
- }
- }
- return qfalse; // not on the pure server pak list
- }
- return qtrue;
-}
-
-
-/*
-=================
-FS_LoadStack
-return load stack
-=================
-*/
-int FS_LoadStack()
-{
- return fs_loadStack;
-}
-
-/*
-================
-return a hash value for the filename
-================
-*/
-static long FS_HashFileName( const char *fname, int hashSize ) {
- int i;
- long hash;
- char letter;
-
- hash = 0;
- i = 0;
- while (fname[i] != '\0') {
- letter = tolower(fname[i]);
- if (letter =='.') break; // don't include extension
- if (letter =='\\') letter = '/'; // damn path names
- if (letter == PATH_SEP) letter = '/'; // damn path names
- hash+=(long)(letter)*(i+119);
- i++;
- }
- hash = (hash ^ (hash >> 10) ^ (hash >> 20));
- hash &= (hashSize-1);
- return hash;
-}
-
-static fileHandle_t FS_HandleForFile(void) {
- int i;
-
- for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
- if ( fsh[i].handleFiles.file.o == NULL ) {
- return i;
- }
- }
- Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
- return 0;
-}
-
-static FILE *FS_FileForHandle( fileHandle_t f ) {
- if ( f < 0 || f > MAX_FILE_HANDLES ) {
- Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" );
- }
- if (fsh[f].zipFile == qtrue) {
- Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
- }
- if ( ! fsh[f].handleFiles.file.o ) {
- Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
- }
-
- return fsh[f].handleFiles.file.o;
-}
-
-void FS_ForceFlush( fileHandle_t f ) {
- FILE *file;
-
- file = FS_FileForHandle(f);
- setvbuf( file, NULL, _IONBF, 0 );
-}
-
-/*
-================
-FS_filelength
-
-If this is called on a non-unique FILE (from a pak file),
-it will return the size of the pak file, not the expected
-size of the file.
-================
-*/
-int FS_filelength( fileHandle_t f ) {
- int pos;
- int end;
- FILE* h;
-
- h = FS_FileForHandle(f);
- pos = ftell (h);
- fseek (h, 0, SEEK_END);
- end = ftell (h);
- fseek (h, pos, SEEK_SET);
-
- return end;
-}
-
-/*
-====================
-FS_ReplaceSeparators
-
-Fix things up differently for win/unix/mac
-====================
-*/
-static void FS_ReplaceSeparators( char *path ) {
- char *s;
-
- for ( s = path ; *s ; s++ ) {
- if ( *s == '/' || *s == '\\' ) {
- *s = PATH_SEP;
- }
- }
-}
-
-/*
-===================
-FS_BuildOSPath
-
-Qpath may have either forward or backwards slashes
-===================
-*/
-char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
- char temp[MAX_OSPATH];
- static char ospath[2][MAX_OSPATH];
- static int toggle;
-
- toggle ^= 1; // flip-flop to allow two returns without clash
-
- if( !game || !game[0] ) {
- game = fs_gamedir;
- }
-
- Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath );
- FS_ReplaceSeparators( temp );
- Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
-
- return ospath[toggle];
-}
-
-
-/*
-============
-FS_CreatePath
-
-Creates any directories needed to store the given filename
-============
-*/
-static qboolean FS_CreatePath (char *OSPath) {
- char *ofs;
-
- // make absolutely sure that it can't back up the path
- // FIXME: is c: allowed???
- if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
- Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
- return qtrue;
- }
-
- for (ofs = OSPath+1 ; *ofs ; ofs++) {
- if (*ofs == PATH_SEP) {
- // create the directory
- *ofs = 0;
- Sys_Mkdir (OSPath);
- *ofs = PATH_SEP;
- }
- }
- return qfalse;
-}
-
-/*
-=================
-FS_CopyFile
-
-Copy a fully specified file from one place to another
-=================
-*/
-static void FS_CopyFile( char *fromOSPath, char *toOSPath ) {
- FILE *f;
- int len;
- byte *buf;
-
- Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath );
-
- if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) {
- Com_Printf( "Ignoring journal files\n");
- return;
- }
-
- f = fopen( fromOSPath, "rb" );
- if ( !f ) {
- return;
- }
- fseek (f, 0, SEEK_END);
- len = ftell (f);
- fseek (f, 0, SEEK_SET);
-
- // we are using direct malloc instead of Z_Malloc here, so it
- // probably won't work on a mac... Its only for developers anyway...
- buf = malloc( len );
- if (fread( buf, 1, len, f ) != len)
- Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" );
- fclose( f );
-
- if( FS_CreatePath( toOSPath ) ) {
- return;
- }
-
- f = fopen( toOSPath, "wb" );
- if ( !f ) {
- return;
- }
- if (fwrite( buf, 1, len, f ) != len)
- Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" );
- fclose( f );
- free( buf );
-}
-
-/*
-===========
-FS_Remove
-
-===========
-*/
-static void FS_Remove( const char *osPath ) {
- remove( osPath );
-}
-
-/*
-================
-FS_FileExists
-
-Tests if the file exists in the current gamedir, this DOES NOT
-search the paths. This is to determine if opening a file to write
-(which always goes into the current gamedir) will cause any overwrites.
-NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
-================
-*/
-qboolean FS_FileExists( const char *file )
-{
- FILE *f;
- char *testpath;
-
- testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file );
-
- f = fopen( testpath, "rb" );
- if (f) {
- fclose( f );
- return qtrue;
- }
- return qfalse;
-}
-
-/*
-================
-FS_SV_FileExists
-
-Tests if the file exists
-================
-*/
-qboolean FS_SV_FileExists( const char *file )
-{
- FILE *f;
- char *testpath;
-
- testpath = FS_BuildOSPath( fs_homepath->string, file, "");
- testpath[strlen(testpath)-1] = '\0';
-
- f = fopen( testpath, "rb" );
- if (f) {
- fclose( f );
- return qtrue;
- }
- return qfalse;
-}
-
-
-/*
-===========
-FS_SV_FOpenFileWrite
-
-===========
-*/
-fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) {
- char *ospath;
- fileHandle_t f;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
- ospath[strlen(ospath)-1] = '\0';
-
- f = FS_HandleForFile();
- fsh[f].zipFile = qfalse;
-
- if ( fs_debug->integer ) {
- Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath );
- }
-
- if( FS_CreatePath( ospath ) ) {
- return 0;
- }
-
- Com_DPrintf( "writing to: %s\n", ospath );
- fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
-
- Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
-
- fsh[f].handleSync = qfalse;
- if (!fsh[f].handleFiles.file.o) {
- f = 0;
- }
- return f;
-}
-
-/*
-===========
-FS_SV_FOpenFileRead
-search for a file somewhere below the home path, base path or cd path
-we search in that order, matching FS_SV_FOpenFileRead order
-===========
-*/
-int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
- char *ospath;
- fileHandle_t f = 0;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- f = FS_HandleForFile();
- fsh[f].zipFile = qfalse;
-
- Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
-
- // don't let sound stutter
- S_ClearSoundBuffer();
-
- // search homepath
- ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
- // remove trailing slash
- ospath[strlen(ospath)-1] = '\0';
-
- if ( fs_debug->integer ) {
- Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath );
- }
-
- fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
- fsh[f].handleSync = qfalse;
- if (!fsh[f].handleFiles.file.o)
- {
- // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid
- if (Q_stricmp(fs_homepath->string,fs_basepath->string))
- {
- // search basepath
- ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
- ospath[strlen(ospath)-1] = '\0';
-
- if ( fs_debug->integer )
- {
- Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath );
- }
-
- fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
- fsh[f].handleSync = qfalse;
-
- if ( !fsh[f].handleFiles.file.o )
- {
- f = 0;
- }
- }
- }
-
- if (!fsh[f].handleFiles.file.o) {
- // search cd path
- ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" );
- ospath[strlen(ospath)-1] = '\0';
-
- if (fs_debug->integer)
- {
- Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath );
- }
-
- fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
- fsh[f].handleSync = qfalse;
-
- if( !fsh[f].handleFiles.file.o ) {
- f = 0;
- }
- }
-
- *fp = f;
- if (f) {
- return FS_filelength(f);
- }
- return 0;
-}
-
-
-/*
-===========
-FS_SV_Rename
-
-===========
-*/
-void FS_SV_Rename( const char *from, const char *to ) {
- char *from_ospath, *to_ospath;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- // don't let sound stutter
- S_ClearSoundBuffer();
-
- from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" );
- to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" );
- from_ospath[strlen(from_ospath)-1] = '\0';
- to_ospath[strlen(to_ospath)-1] = '\0';
-
- if ( fs_debug->integer ) {
- Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath );
- }
-
- if (rename( from_ospath, to_ospath )) {
- // Failed, try copying it and deleting the original
- FS_CopyFile ( from_ospath, to_ospath );
- FS_Remove ( from_ospath );
- }
-}
-
-
-
-/*
-===========
-FS_Rename
-
-===========
-*/
-void FS_Rename( const char *from, const char *to ) {
- char *from_ospath, *to_ospath;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- // don't let sound stutter
- S_ClearSoundBuffer();
-
- from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from );
- to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to );
-
- if ( fs_debug->integer ) {
- Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath );
- }
-
- if (rename( from_ospath, to_ospath )) {
- // Failed, try copying it and deleting the original
- FS_CopyFile ( from_ospath, to_ospath );
- FS_Remove ( from_ospath );
- }
-}
-
-/*
-==============
-FS_FCloseFile
-
-If the FILE pointer is an open pak file, leave it open.
-
-For some reason, other dll's can't just cal fclose()
-on files returned by FS_FOpenFile...
-==============
-*/
-void FS_FCloseFile( fileHandle_t f ) {
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if (fsh[f].streamed) {
- Sys_EndStreamedFile(f);
- }
- if (fsh[f].zipFile == qtrue) {
- unzCloseCurrentFile( fsh[f].handleFiles.file.z );
- if ( fsh[f].handleFiles.unique ) {
- unzClose( fsh[f].handleFiles.file.z );
- }
- Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
- return;
- }
-
- // we didn't find it as a pak, so close it as a unique file
- if (fsh[f].handleFiles.file.o) {
- fclose (fsh[f].handleFiles.file.o);
- }
- Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
-}
-
-/*
-===========
-FS_FOpenFileWrite
-
-===========
-*/
-fileHandle_t FS_FOpenFileWrite( const char *filename ) {
- char *ospath;
- fileHandle_t f;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- f = FS_HandleForFile();
- fsh[f].zipFile = qfalse;
-
- ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
-
- if ( fs_debug->integer ) {
- Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
- }
-
- if( FS_CreatePath( ospath ) ) {
- return 0;
- }
-
- // enabling the following line causes a recursive function call loop
- // when running with +set logfile 1 +set developer 1
- //Com_DPrintf( "writing to: %s\n", ospath );
- fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
-
- Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
-
- fsh[f].handleSync = qfalse;
- if (!fsh[f].handleFiles.file.o) {
- f = 0;
- }
- return f;
-}
-
-/*
-===========
-FS_FOpenFileAppend
-
-===========
-*/
-fileHandle_t FS_FOpenFileAppend( const char *filename ) {
- char *ospath;
- fileHandle_t f;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- f = FS_HandleForFile();
- fsh[f].zipFile = qfalse;
-
- Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
-
- // don't let sound stutter
- S_ClearSoundBuffer();
-
- ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
-
- if ( fs_debug->integer ) {
- Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
- }
-
- if( FS_CreatePath( ospath ) ) {
- return 0;
- }
-
- fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
- fsh[f].handleSync = qfalse;
- if (!fsh[f].handleFiles.file.o) {
- f = 0;
- }
- return f;
-}
-
-/*
-===========
-FS_FilenameCompare
-
-Ignore case and seprator char distinctions
-===========
-*/
-qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
- int c1, c2;
-
- do {
- c1 = *s1++;
- c2 = *s2++;
-
- if (c1 >= 'a' && c1 <= 'z') {
- c1 -= ('a' - 'A');
- }
- if (c2 >= 'a' && c2 <= 'z') {
- c2 -= ('a' - 'A');
- }
-
- if ( c1 == '\\' || c1 == ':' ) {
- c1 = '/';
- }
- if ( c2 == '\\' || c2 == ':' ) {
- c2 = '/';
- }
-
- if (c1 != c2) {
- return -1; // strings not equal
- }
- } while (c1);
-
- return 0; // strings are equal
-}
-
-/*
-===========
-FS_ShiftedStrStr
-===========
-*/
-char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) {
- char buf[MAX_STRING_TOKENS];
- int i;
-
- for (i = 0; substring[i]; i++) {
- buf[i] = substring[i] + shift;
- }
- buf[i] = '\0';
- return strstr(string, buf);
-}
-
-/*
-===========
-FS_FOpenFileRead
-
-Finds the file in the search path.
-Returns filesize and an open FILE pointer.
-Used for streaming data out of either a
-separate file or a ZIP file.
-===========
-*/
-extern qboolean com_fullyInitialized;
-
-int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) {
- searchpath_t *search;
- char *netpath;
- pack_t *pak;
- fileInPack_t *pakFile;
- directory_t *dir;
- long hash;
- unz_s *zfi;
- FILE *temp;
- int l;
- char demoExt[16];
-
- hash = 0;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( file == NULL ) {
- // just wants to see if file is there
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- //
- if ( search->pack ) {
- hash = FS_HashFileName(filename, search->pack->hashSize);
- }
- // is the element a pak file?
- if ( search->pack && search->pack->hashTable[hash] ) {
- // look through all the pak file elements
- pak = search->pack;
- pakFile = pak->hashTable[hash];
- do {
- // case and separator insensitive comparisons
- if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
- // found it!
- return qtrue;
- }
- pakFile = pakFile->next;
- } while(pakFile != NULL);
- } else if ( search->dir ) {
- dir = search->dir;
-
- netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
- temp = fopen (netpath, "rb");
- if ( !temp ) {
- continue;
- }
- fclose(temp);
- return qtrue;
- }
- }
- return qfalse;
- }
-
- if ( !filename ) {
- Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
- }
-
- Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION );
- // qpaths are not supposed to have a leading slash
- if ( filename[0] == '/' || filename[0] == '\\' ) {
- filename++;
- }
-
- // make absolutely sure that it can't back up the path.
- // The searchpaths do guarantee that something will always
- // be prepended, so we don't need to worry about "c:" or "//limbo"
- if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
- *file = 0;
- return -1;
- }
-
- // make sure the q3key file is only readable by the quake3.exe at initialization
- // any other time the key should only be accessed in memory using the provided functions
- if( com_fullyInitialized && strstr( filename, "q3key" ) ) {
- *file = 0;
- return -1;
- }
-
- //
- // search through the path, one element at a time
- //
-
- *file = FS_HandleForFile();
- fsh[*file].handleFiles.unique = uniqueFILE;
-
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- //
- if ( search->pack ) {
- hash = FS_HashFileName(filename, search->pack->hashSize);
- }
- // is the element a pak file?
- if ( search->pack && search->pack->hashTable[hash] ) {
- // disregard if it doesn't match one of the allowed pure pak files
- if ( !FS_PakIsPure(search->pack) ) {
- continue;
- }
-
- // look through all the pak file elements
- pak = search->pack;
- pakFile = pak->hashTable[hash];
- do {
- // case and separator insensitive comparisons
- if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
- // found it!
-
- // mark the pak as having been referenced and mark specifics on cgame and ui
- // shaders, txt, arena files by themselves do not count as a reference as
- // these are loaded from all pk3s
- // from every pk3 file..
- l = strlen( filename );
- if ( !(pak->referenced & FS_GENERAL_REF)) {
- if ( Q_stricmp(filename + l - 7, ".shader") != 0 &&
- Q_stricmp(filename + l - 4, ".txt") != 0 &&
- Q_stricmp(filename + l - 4, ".cfg") != 0 &&
- Q_stricmp(filename + l - 7, ".config") != 0 &&
- strstr(filename, "levelshots") == NULL &&
- Q_stricmp(filename + l - 4, ".bot") != 0 &&
- Q_stricmp(filename + l - 6, ".arena") != 0 &&
- Q_stricmp(filename + l - 5, ".menu") != 0) {
- pak->referenced |= FS_GENERAL_REF;
- }
- }
-
- // qagame.qvm - 13
- // dTZT`X!di`
- if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "dTZT`X!di`", 13)) {
- pak->referenced |= FS_QAGAME_REF;
- }
- // cgame.qvm - 7
- // \`Zf^'jof
- if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) {
- pak->referenced |= FS_CGAME_REF;
- }
- // ui.qvm - 5
- // pd)lqh
- if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) {
- pak->referenced |= FS_UI_REF;
- }
-
- if ( uniqueFILE ) {
- // open a new file on the pakfile
- fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle);
- if (fsh[*file].handleFiles.file.z == NULL) {
- Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename);
- }
- } else {
- fsh[*file].handleFiles.file.z = pak->handle;
- }
- Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
- fsh[*file].zipFile = qtrue;
- zfi = (unz_s *)fsh[*file].handleFiles.file.z;
- // in case the file was new
- temp = zfi->file;
- // set the file position in the zip file (also sets the current file info)
- unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos);
- // copy the file info into the unzip structure
- Com_Memcpy( zfi, pak->handle, sizeof(unz_s) );
- // we copy this back into the structure
- zfi->file = temp;
- // open the file in the zip
- unzOpenCurrentFile( fsh[*file].handleFiles.file.z );
- fsh[*file].zipFilePos = pakFile->pos;
-
- if ( fs_debug->integer ) {
- Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n",
- filename, pak->pakFilename );
- }
- return zfi->cur_file_info.uncompressed_size;
- }
- pakFile = pakFile->next;
- } while(pakFile != NULL);
- } else if ( search->dir ) {
- // check a file in the directory tree
-
- // if we are running restricted, the only files we
- // will allow to come from the directory are .cfg files
- l = strlen( filename );
- // FIXME TTimo I'm not sure about the fs_numServerPaks test
- // if you are using FS_ReadFile to find out if a file exists,
- // this test can make the search fail although the file is in the directory
- // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
- // turned out I used FS_FileExists instead
- if ( fs_restrict->integer || fs_numServerPaks ) {
-
- if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files
- && Q_stricmp( filename + l - 5, ".menu" ) // menu files
- && Q_stricmp( filename + l - 5, ".game" ) // menu files
- && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
- && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files
- continue;
- }
- }
-
- dir = search->dir;
-
- netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
- fsh[*file].handleFiles.file.o = fopen (netpath, "rb");
- if ( !fsh[*file].handleFiles.file.o ) {
- continue;
- }
-
- if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files
- && Q_stricmp( filename + l - 5, ".menu" ) // menu files
- && Q_stricmp( filename + l - 5, ".game" ) // menu files
- && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
- && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files
- fs_fakeChkSum = random();
- }
-
- Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
- fsh[*file].zipFile = qfalse;
- if ( fs_debug->integer ) {
- Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename,
- dir->path, dir->gamedir );
- }
-
- // if we are getting it from the cdpath, optionally copy it
- // to the basepath
- if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) {
- char *copypath;
-
- copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename );
- FS_CopyFile( netpath, copypath );
- }
-
- return FS_filelength (*file);
- }
- }
-
- Com_DPrintf ("Can't find %s\n", filename);
-#ifdef FS_MISSING
- if (missingFiles) {
- fprintf(missingFiles, "%s\n", filename);
- }
-#endif
- *file = 0;
- return -1;
-}
-
-
-/*
-=================
-FS_Read
-
-Properly handles partial reads
-=================
-*/
-int FS_Read2( void *buffer, int len, fileHandle_t f ) {
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( !f ) {
- return 0;
- }
- if (fsh[f].streamed) {
- int r;
- fsh[f].streamed = qfalse;
- r = Sys_StreamedRead( buffer, len, 1, f);
- fsh[f].streamed = qtrue;
- return r;
- } else {
- return FS_Read( buffer, len, f);
- }
-}
-
-int FS_Read( void *buffer, int len, fileHandle_t f ) {
- int block, remaining;
- int read;
- byte *buf;
- int tries;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( !f ) {
- return 0;
- }
-
- buf = (byte *)buffer;
- fs_readCount += len;
-
- if (fsh[f].zipFile == qfalse) {
- remaining = len;
- tries = 0;
- while (remaining) {
- block = remaining;
- read = fread (buf, 1, block, fsh[f].handleFiles.file.o);
- if (read == 0) {
- // we might have been trying to read from a CD, which
- // sometimes returns a 0 read on windows
- if (!tries) {
- tries = 1;
- } else {
- return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
- }
- }
-
- if (read == -1) {
- Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
- }
-
- remaining -= read;
- buf += read;
- }
- return len;
- } else {
- return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len);
- }
-}
-
-/*
-=================
-FS_Write
-
-Properly handles partial writes
-=================
-*/
-int FS_Write( const void *buffer, int len, fileHandle_t h ) {
- int block, remaining;
- int written;
- byte *buf;
- int tries;
- FILE *f;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( !h ) {
- return 0;
- }
-
- f = FS_FileForHandle(h);
- buf = (byte *)buffer;
-
- remaining = len;
- tries = 0;
- while (remaining) {
- block = remaining;
- written = fwrite (buf, 1, block, f);
- if (written == 0) {
- if (!tries) {
- tries = 1;
- } else {
- Com_Printf( "FS_Write: 0 bytes written\n" );
- return 0;
- }
- }
-
- if (written == -1) {
- Com_Printf( "FS_Write: -1 bytes written\n" );
- return 0;
- }
-
- remaining -= written;
- buf += written;
- }
- if ( fsh[h].handleSync ) {
- fflush( f );
- }
- return len;
-}
-
-void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
- va_list argptr;
- char msg[MAXPRINTMSG];
-
- va_start (argptr,fmt);
- Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
- va_end (argptr);
-
- FS_Write(msg, strlen(msg), h);
-}
-
-/*
-=================
-FS_Seek
-
-=================
-*/
-int FS_Seek( fileHandle_t f, long offset, int origin ) {
- int _origin;
- char foo[65536];
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- return -1;
- }
-
- if (fsh[f].streamed) {
- fsh[f].streamed = qfalse;
- Sys_StreamSeek( f, offset, origin );
- fsh[f].streamed = qtrue;
- }
-
- if (fsh[f].zipFile == qtrue) {
- if (offset == 0 && origin == FS_SEEK_SET) {
- // set the file position in the zip file (also sets the current file info)
- unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
- return unzOpenCurrentFile(fsh[f].handleFiles.file.z);
- } else if (offset<65536) {
- // set the file position in the zip file (also sets the current file info)
- unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
- unzOpenCurrentFile(fsh[f].handleFiles.file.z);
- return FS_Read(foo, offset, f);
- } else {
- Com_Error( ERR_FATAL, "ZIP FILE FSEEK NOT YET IMPLEMENTED\n" );
- return -1;
- }
- } else {
- FILE *file;
- file = FS_FileForHandle(f);
- switch( origin ) {
- case FS_SEEK_CUR:
- _origin = SEEK_CUR;
- break;
- case FS_SEEK_END:
- _origin = SEEK_END;
- break;
- case FS_SEEK_SET:
- _origin = SEEK_SET;
- break;
- default:
- _origin = SEEK_CUR;
- Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
- break;
- }
-
- return fseek( file, offset, _origin );
- }
-}
-
-
-/*
-======================================================================================
-
-CONVENIENCE FUNCTIONS FOR ENTIRE FILES
-
-======================================================================================
-*/
-
-int FS_FileIsInPAK(const char *filename, int *pChecksum ) {
- searchpath_t *search;
- pack_t *pak;
- fileInPack_t *pakFile;
- long hash = 0;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( !filename ) {
- Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
- }
-
- // qpaths are not supposed to have a leading slash
- if ( filename[0] == '/' || filename[0] == '\\' ) {
- filename++;
- }
-
- // make absolutely sure that it can't back up the path.
- // The searchpaths do guarantee that something will always
- // be prepended, so we don't need to worry about "c:" or "//limbo"
- if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
- return -1;
- }
-
- //
- // search through the path, one element at a time
- //
-
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- //
- if (search->pack) {
- hash = FS_HashFileName(filename, search->pack->hashSize);
- }
- // is the element a pak file?
- if ( search->pack && search->pack->hashTable[hash] ) {
- // disregard if it doesn't match one of the allowed pure pak files
- if ( !FS_PakIsPure(search->pack) ) {
- continue;
- }
-
- // look through all the pak file elements
- pak = search->pack;
- pakFile = pak->hashTable[hash];
- do {
- // case and separator insensitive comparisons
- if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
- if (pChecksum) {
- *pChecksum = pak->pure_checksum;
- }
- return 1;
- }
- pakFile = pakFile->next;
- } while(pakFile != NULL);
- }
- }
- return -1;
-}
-
-/*
-============
-FS_ReadFile
-
-Filename are relative to the quake search path
-a null buffer will just return the file length without loading
-============
-*/
-int FS_ReadFile( const char *qpath, void **buffer ) {
- fileHandle_t h;
- byte* buf;
- qboolean isConfig;
- int len;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( !qpath || !qpath[0] ) {
- Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
- }
-
- buf = NULL; // quiet compiler warning
-
- // if this is a .cfg file and we are playing back a journal, read
- // it from the journal file
- if ( strstr( qpath, ".cfg" ) ) {
- isConfig = qtrue;
- if ( com_journal && com_journal->integer == 2 ) {
- int r;
-
- Com_DPrintf( "Loading %s from journal file.\n", qpath );
- r = FS_Read( &len, sizeof( len ), com_journalDataFile );
- if ( r != sizeof( len ) ) {
- if (buffer != NULL) *buffer = NULL;
- return -1;
- }
- // if the file didn't exist when the journal was created
- if (!len) {
- if (buffer == NULL) {
- return 1; // hack for old journal files
- }
- *buffer = NULL;
- return -1;
- }
- if (buffer == NULL) {
- return len;
- }
-
- buf = Hunk_AllocateTempMemory(len+1);
- *buffer = buf;
-
- r = FS_Read( buf, len, com_journalDataFile );
- if ( r != len ) {
- Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
- }
-
- fs_loadCount++;
- fs_loadStack++;
-
- // guarantee that it will have a trailing 0 for string operations
- buf[len] = 0;
-
- return len;
- }
- } else {
- isConfig = qfalse;
- }
-
- // look for it in the filesystem or pack files
- len = FS_FOpenFileRead( qpath, &h, qfalse );
- if ( h == 0 ) {
- if ( buffer ) {
- *buffer = NULL;
- }
- // if we are journalling and it is a config file, write a zero to the journal file
- if ( isConfig && com_journal && com_journal->integer == 1 ) {
- Com_DPrintf( "Writing zero for %s to journal file.\n", qpath );
- len = 0;
- FS_Write( &len, sizeof( len ), com_journalDataFile );
- FS_Flush( com_journalDataFile );
- }
- return -1;
- }
-
- if ( !buffer ) {
- if ( isConfig && com_journal && com_journal->integer == 1 ) {
- Com_DPrintf( "Writing len for %s to journal file.\n", qpath );
- FS_Write( &len, sizeof( len ), com_journalDataFile );
- FS_Flush( com_journalDataFile );
- }
- FS_FCloseFile( h);
- return len;
- }
-
- fs_loadCount++;
- fs_loadStack++;
-
- buf = Hunk_AllocateTempMemory(len+1);
- *buffer = buf;
-
- FS_Read (buf, len, h);
-
- // guarantee that it will have a trailing 0 for string operations
- buf[len] = 0;
- FS_FCloseFile( h );
-
- // if we are journalling and it is a config file, write it to the journal file
- if ( isConfig && com_journal && com_journal->integer == 1 ) {
- Com_DPrintf( "Writing %s to journal file.\n", qpath );
- FS_Write( &len, sizeof( len ), com_journalDataFile );
- FS_Write( buf, len, com_journalDataFile );
- FS_Flush( com_journalDataFile );
- }
- return len;
-}
-
-/*
-=============
-FS_FreeFile
-=============
-*/
-void FS_FreeFile( void *buffer ) {
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
- if ( !buffer ) {
- Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
- }
- fs_loadStack--;
-
- Hunk_FreeTempMemory( buffer );
-
- // if all of our temp files are free, clear all of our space
- if ( fs_loadStack == 0 ) {
- Hunk_ClearTempMemory();
- }
-}
-
-/*
-============
-FS_WriteFile
-
-Filename are reletive to the quake search path
-============
-*/
-void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
- fileHandle_t f;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( !qpath || !buffer ) {
- Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
- }
-
- f = FS_FOpenFileWrite( qpath );
- if ( !f ) {
- Com_Printf( "Failed to open %s\n", qpath );
- return;
- }
-
- FS_Write( buffer, size, f );
-
- FS_FCloseFile( f );
-}
-
-
-
-/*
-==========================================================================
-
-ZIP FILE LOADING
-
-==========================================================================
-*/
-
-/*
-=================
-FS_LoadZipFile
-
-Creates a new pak_t in the search chain for the contents
-of a zip file.
-=================
-*/
-static pack_t *FS_LoadZipFile( char *zipfile, const char *basename )
-{
- fileInPack_t *buildBuffer;
- pack_t *pack;
- unzFile uf;
- int err;
- unz_global_info gi;
- char filename_inzip[MAX_ZPATH];
- unz_file_info file_info;
- int i, len;
- long hash;
- int fs_numHeaderLongs;
- int *fs_headerLongs;
- char *namePtr;
-
- fs_numHeaderLongs = 0;
-
- uf = unzOpen(zipfile);
- err = unzGetGlobalInfo (uf,&gi);
-
- if (err != UNZ_OK)
- return NULL;
-
- fs_packFiles += gi.number_entry;
-
- len = 0;
- unzGoToFirstFile(uf);
- for (i = 0; i < gi.number_entry; i++)
- {
- err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
- if (err != UNZ_OK) {
- break;
- }
- len += strlen(filename_inzip) + 1;
- unzGoToNextFile(uf);
- }
-
- buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len );
- namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t );
- fs_headerLongs = Z_Malloc( gi.number_entry * sizeof(int) );
-
- // get the hash table size from the number of files in the zip
- // because lots of custom pk3 files have less than 32 or 64 files
- for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) {
- if (i > gi.number_entry) {
- break;
- }
- }
-
- pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) );
- pack->hashSize = i;
- pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t ));
- for(i = 0; i < pack->hashSize; i++) {
- pack->hashTable[i] = NULL;
- }
-
- Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) );
- Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) );
-
- // strip .pk3 if needed
- if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) {
- pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0;
- }
-
- pack->handle = uf;
- pack->numfiles = gi.number_entry;
- unzGoToFirstFile(uf);
-
- for (i = 0; i < gi.number_entry; i++)
- {
- err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
- if (err != UNZ_OK) {
- break;
- }
- if (file_info.uncompressed_size > 0) {
- fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
- }
- Q_strlwr( filename_inzip );
- hash = FS_HashFileName(filename_inzip, pack->hashSize);
- buildBuffer[i].name = namePtr;
- strcpy( buildBuffer[i].name, filename_inzip );
- namePtr += strlen(filename_inzip) + 1;
- // store the file position in the zip
- unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos);
- //
- buildBuffer[i].next = pack->hashTable[hash];
- pack->hashTable[hash] = &buildBuffer[i];
- unzGoToNextFile(uf);
- }
-
- pack->checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
- pack->pure_checksum = Com_BlockChecksumKey( fs_headerLongs, 4 * fs_numHeaderLongs, LittleLong(fs_checksumFeed) );
- pack->checksum = LittleLong( pack->checksum );
- pack->pure_checksum = LittleLong( pack->pure_checksum );
-
- Z_Free(fs_headerLongs);
-
- pack->buildBuffer = buildBuffer;
- return pack;
-}
-
-/*
-=================================================================================
-
-DIRECTORY SCANNING FUNCTIONS
-
-=================================================================================
-*/
-
-#define MAX_FOUND_FILES 0x1000
-
-static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) {
- int len, at, newdep;
-
- newdep = 0;
- zpath[0] = 0;
- len = 0;
- at = 0;
-
- while(zname[at] != 0)
- {
- if (zname[at]=='/' || zname[at]=='\\') {
- len = at;
- newdep++;
- }
- at++;
- }
- strcpy(zpath, zname);
- zpath[len] = 0;
- *depth = newdep;
-
- return len;
-}
-
-/*
-==================
-FS_AddFileToList
-==================
-*/
-static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
- int i;
-
- if ( nfiles == MAX_FOUND_FILES - 1 ) {
- return nfiles;
- }
- for ( i = 0 ; i < nfiles ; i++ ) {
- if ( !Q_stricmp( name, list[i] ) ) {
- return nfiles; // allready in list
- }
- }
- list[nfiles] = CopyString( name );
- nfiles++;
-
- return nfiles;
-}
-
-/*
-===============
-FS_ListFilteredFiles
-
-Returns a uniqued list of files that match the given criteria
-from all search paths
-===============
-*/
-char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) {
- int nfiles;
- char **listCopy;
- char *list[MAX_FOUND_FILES];
- searchpath_t *search;
- int i;
- int pathLength;
- int extensionLength;
- int length, pathDepth, temp;
- pack_t *pak;
- fileInPack_t *buildBuffer;
- char zpath[MAX_ZPATH];
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( !path ) {
- *numfiles = 0;
- return NULL;
- }
- if ( !extension ) {
- extension = "";
- }
-
- pathLength = strlen( path );
- if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) {
- pathLength--;
- }
- extensionLength = strlen( extension );
- nfiles = 0;
- FS_ReturnPath(path, zpath, &pathDepth);
-
- //
- // search through the path, one element at a time, adding to list
- //
- for (search = fs_searchpaths ; search ; search = search->next) {
- // is the element a pak file?
- if (search->pack) {
-
- //ZOID: If we are pure, don't search for files on paks that
- // aren't on the pure list
- if ( !FS_PakIsPure(search->pack) ) {
- continue;
- }
-
- // look through all the pak file elements
- pak = search->pack;
- buildBuffer = pak->buildBuffer;
- for (i = 0; i < pak->numfiles; i++) {
- char *name;
- int zpathLen, depth;
-
- // check for directory match
- name = buildBuffer[i].name;
- //
- if (filter) {
- // case insensitive
- if (!Com_FilterPath( filter, name, qfalse ))
- continue;
- // unique the match
- nfiles = FS_AddFileToList( name, list, nfiles );
- }
- else {
-
- zpathLen = FS_ReturnPath(name, zpath, &depth);
-
- if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) {
- continue;
- }
-
- // check for extension match
- length = strlen( name );
- if ( length < extensionLength ) {
- continue;
- }
-
- if ( Q_stricmp( name + length - extensionLength, extension ) ) {
- continue;
- }
- // unique the match
-
- temp = pathLength;
- if (pathLength) {
- temp++; // include the '/'
- }
- nfiles = FS_AddFileToList( name + temp, list, nfiles );
- }
- }
- } else if (search->dir) { // scan for files in the filesystem
- char *netpath;
- int numSysFiles;
- char **sysFiles;
- char *name;
-
- // don't scan directories for files if we are pure or restricted
- if ( fs_restrict->integer || fs_numServerPaks ) {
- continue;
- } else {
- netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path );
- sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse );
- for ( i = 0 ; i < numSysFiles ; i++ ) {
- // unique the match
- name = sysFiles[i];
- nfiles = FS_AddFileToList( name, list, nfiles );
- }
- Sys_FreeFileList( sysFiles );
- }
- }
- }
-
- // return a copy of the list
- *numfiles = nfiles;
-
- if ( !nfiles ) {
- return NULL;
- }
-
- listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
- for ( i = 0 ; i < nfiles ; i++ ) {
- listCopy[i] = list[i];
- }
- listCopy[i] = NULL;
-
- return listCopy;
-}
-
-/*
-=================
-FS_ListFiles
-=================
-*/
-char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
- return FS_ListFilteredFiles( path, extension, NULL, numfiles );
-}
-
-/*
-=================
-FS_FreeFileList
-=================
-*/
-void FS_FreeFileList( char **list ) {
- int i;
-
- if ( !fs_searchpaths ) {
- Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
- }
-
- if ( !list ) {
- return;
- }
-
- for ( i = 0 ; list[i] ; i++ ) {
- Z_Free( list[i] );
- }
-
- Z_Free( list );
-}
-
-
-/*
-================
-FS_GetFileList
-================
-*/
-int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) {
- int nFiles, i, nTotal, nLen;
- char **pFiles = NULL;
-
- *listbuf = 0;
- nFiles = 0;
- nTotal = 0;
-
- if (Q_stricmp(path, "$modlist") == 0) {
- return FS_GetModList(listbuf, bufsize);
- }
-
- pFiles = FS_ListFiles(path, extension, &nFiles);
-
- for (i =0; i < nFiles; i++) {
- nLen = strlen(pFiles[i]) + 1;
- if (nTotal + nLen + 1 < bufsize) {
- strcpy(listbuf, pFiles[i]);
- listbuf += nLen;
- nTotal += nLen;
- }
- else {
- nFiles = i;
- break;
- }
- }
-
- FS_FreeFileList(pFiles);
-
- return nFiles;
-}
-
-/*
-=======================
-Sys_ConcatenateFileLists
-
-mkv: Naive implementation. Concatenates three lists into a
- new list, and frees the old lists from the heap.
-bk001129 - from cvs1.17 (mkv)
-
-FIXME TTimo those two should move to common.c next to Sys_ListFiles
-=======================
- */
-static unsigned int Sys_CountFileList(char **list)
-{
- int i = 0;
-
- if (list)
- {
- while (*list)
- {
- list++;
- i++;
- }
- }
- return i;
-}
-
-static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 )
-{
- int totalLength = 0;
- char** cat = NULL, **dst, **src;
-
- totalLength += Sys_CountFileList(list0);
- totalLength += Sys_CountFileList(list1);
- totalLength += Sys_CountFileList(list2);
-
- /* Create new list. */
- dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) );
-
- /* Copy over lists. */
- if (list0)
- {
- for (src = list0; *src; src++, dst++)
- *dst = *src;
- }
- if (list1)
- {
- for (src = list1; *src; src++, dst++)
- *dst = *src;
- }
- if (list2)
- {
- for (src = list2; *src; src++, dst++)
- *dst = *src;
- }
-
- // Terminate the list
- *dst = NULL;
-
- // Free our old lists.
- // NOTE: not freeing their content, it's been merged in dst and still being used
- if (list0) Z_Free( list0 );
- if (list1) Z_Free( list1 );
- if (list2) Z_Free( list2 );
-
- return cat;
-}
-
-/*
-================
-FS_GetModList
-
-Returns a list of mod directory names
-A mod directory is a peer to baseq3 with a pk3 in it
-The directories are searched in base path, cd path and home path
-================
-*/
-int FS_GetModList( char *listbuf, int bufsize ) {
- int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen;
- char **pFiles = NULL;
- char **pPaks = NULL;
- char *name, *path;
- char descPath[MAX_OSPATH];
- fileHandle_t descHandle;
-
- int dummy;
- char **pFiles0 = NULL;
- char **pFiles1 = NULL;
- char **pFiles2 = NULL;
- qboolean bDrop = qfalse;
-
- *listbuf = 0;
- nMods = nPotential = nTotal = 0;
-
- pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue );
- pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue );
- pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue );
- // we searched for mods in the three paths
- // it is likely that we have duplicate names now, which we will cleanup below
- pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 );
- nPotential = Sys_CountFileList(pFiles);
-
- for ( i = 0 ; i < nPotential ; i++ ) {
- name = pFiles[i];
- // NOTE: cleaner would involve more changes
- // ignore duplicate mod directories
- if (i!=0) {
- bDrop = qfalse;
- for(j=0; j<i; j++)
- {
- if (Q_stricmp(pFiles[j],name)==0) {
- // this one can be dropped
- bDrop = qtrue;
- break;
- }
- }
- }
- if (bDrop) {
- continue;
- }
- // we drop "baseq3" "." and ".."
- if (Q_stricmp(name, "baseq3") && Q_stricmpn(name, ".", 1)) {
- // now we need to find some .pk3 files to validate the mod
- // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?)
- // we didn't keep the information when we merged the directory names, as to what OS Path it was found under
- // so it could be in base path, cd path or home path
- // we will try each three of them here (yes, it's a bit messy)
- path = FS_BuildOSPath( fs_basepath->string, name, "" );
- nPaks = 0;
- pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse);
- Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present
-
- /* Try on cd path */
- if( nPaks <= 0 ) {
- path = FS_BuildOSPath( fs_cdpath->string, name, "" );
- nPaks = 0;
- pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
- Sys_FreeFileList( pPaks );
- }
-
- /* try on home path */
- if ( nPaks <= 0 )
- {
- path = FS_BuildOSPath( fs_homepath->string, name, "" );
- nPaks = 0;
- pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
- Sys_FreeFileList( pPaks );
- }
-
- if (nPaks > 0) {
- nLen = strlen(name) + 1;
- // nLen is the length of the mod path
- // we need to see if there is a description available
- descPath[0] = '\0';
- strcpy(descPath, name);
- strcat(descPath, "/description.txt");
- nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle );
- if ( nDescLen > 0 && descHandle) {
- FILE *file;
- file = FS_FileForHandle(descHandle);
- Com_Memset( descPath, 0, sizeof( descPath ) );
- nDescLen = fread(descPath, 1, 48, file);
- if (nDescLen >= 0) {
- descPath[nDescLen] = '\0';
- }
- FS_FCloseFile(descHandle);
- } else {
- strcpy(descPath, name);
- }
- nDescLen = strlen(descPath) + 1;
-
- if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) {
- strcpy(listbuf, name);
- listbuf += nLen;
- strcpy(listbuf, descPath);
- listbuf += nDescLen;
- nTotal += nLen + nDescLen;
- nMods++;
- }
- else {
- break;
- }
- }
- }
- }
- Sys_FreeFileList( pFiles );
-
- return nMods;
-}
-
-
-
-
-//============================================================================
-
-/*
-================
-FS_Dir_f
-================
-*/
-void FS_Dir_f( void ) {
- char *path;
- char *extension;
- char **dirnames;
- int ndirs;
- int i;
-
- if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) {
- Com_Printf( "usage: dir <directory> [extension]\n" );
- return;
- }
-
- if ( Cmd_Argc() == 2 ) {
- path = Cmd_Argv( 1 );
- extension = "";
- } else {
- path = Cmd_Argv( 1 );
- extension = Cmd_Argv( 2 );
- }
-
- Com_Printf( "Directory of %s %s\n", path, extension );
- Com_Printf( "---------------\n" );
-
- dirnames = FS_ListFiles( path, extension, &ndirs );
-
- for ( i = 0; i < ndirs; i++ ) {
- Com_Printf( "%s\n", dirnames[i] );
- }
- FS_FreeFileList( dirnames );
-}
-
-/*
-===========
-FS_ConvertPath
-===========
-*/
-void FS_ConvertPath( char *s ) {
- while (*s) {
- if ( *s == '\\' || *s == ':' ) {
- *s = '/';
- }
- s++;
- }
-}
-
-/*
-===========
-FS_PathCmp
-
-Ignore case and seprator char distinctions
-===========
-*/
-int FS_PathCmp( const char *s1, const char *s2 ) {
- int c1, c2;
-
- do {
- c1 = *s1++;
- c2 = *s2++;
-
- if (c1 >= 'a' && c1 <= 'z') {
- c1 -= ('a' - 'A');
- }
- if (c2 >= 'a' && c2 <= 'z') {
- c2 -= ('a' - 'A');
- }
-
- if ( c1 == '\\' || c1 == ':' ) {
- c1 = '/';
- }
- if ( c2 == '\\' || c2 == ':' ) {
- c2 = '/';
- }
-
- if (c1 < c2) {
- return -1; // strings not equal
- }
- if (c1 > c2) {
- return 1;
- }
- } while (c1);
-
- return 0; // strings are equal
-}
-
-/*
-================
-FS_SortFileList
-================
-*/
-void FS_SortFileList(char **filelist, int numfiles) {
- int i, j, k, numsortedfiles;
- char **sortedlist;
-
- sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) );
- sortedlist[0] = NULL;
- numsortedfiles = 0;
- for (i = 0; i < numfiles; i++) {
- for (j = 0; j < numsortedfiles; j++) {
- if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) {
- break;
- }
- }
- for (k = numsortedfiles; k > j; k--) {
- sortedlist[k] = sortedlist[k-1];
- }
- sortedlist[j] = filelist[i];
- numsortedfiles++;
- }
- Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) );
- Z_Free(sortedlist);
-}
-
-/*
-================
-FS_NewDir_f
-================
-*/
-void FS_NewDir_f( void ) {
- char *filter;
- char **dirnames;
- int ndirs;
- int i;
-
- if ( Cmd_Argc() < 2 ) {
- Com_Printf( "usage: fdir <filter>\n" );
- Com_Printf( "example: fdir *q3dm*.bsp\n");
- return;
- }
-
- filter = Cmd_Argv( 1 );
-
- Com_Printf( "---------------\n" );
-
- dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs );
-
- FS_SortFileList(dirnames, ndirs);
-
- for ( i = 0; i < ndirs; i++ ) {
- FS_ConvertPath(dirnames[i]);
- Com_Printf( "%s\n", dirnames[i] );
- }
- Com_Printf( "%d files listed\n", ndirs );
- FS_FreeFileList( dirnames );
-}
-
-/*
-============
-FS_Path_f
-
-============
-*/
-void FS_Path_f( void ) {
- searchpath_t *s;
- int i;
-
- Com_Printf ("Current search path:\n");
- for (s = fs_searchpaths; s; s = s->next) {
- if (s->pack) {
- Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles);
- if ( fs_numServerPaks ) {
- if ( !FS_PakIsPure(s->pack) ) {
- Com_Printf( " not on the pure list\n" );
- } else {
- Com_Printf( " on the pure list\n" );
- }
- }
- } else {
- Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir );
- }
- }
-
-
- Com_Printf( "\n" );
- for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
- if ( fsh[i].handleFiles.file.o ) {
- Com_Printf( "handle %i: %s\n", i, fsh[i].name );
- }
- }
-}
-
-/*
-============
-FS_TouchFile_f
-
-The only purpose of this function is to allow game script files to copy
-arbitrary files furing an "fs_copyfiles 1" run.
-============
-*/
-void FS_TouchFile_f( void ) {
- fileHandle_t f;
-
- if ( Cmd_Argc() != 2 ) {
- Com_Printf( "Usage: touchFile <file>\n" );
- return;
- }
-
- FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse );
- if ( f ) {
- FS_FCloseFile( f );
- }
-}
-
-//===========================================================================
-
-
-static int QDECL paksort( const void *a, const void *b ) {
- char *aa, *bb;
-
- aa = *(char **)a;
- bb = *(char **)b;
-
- return FS_PathCmp( aa, bb );
-}
-
-/*
-================
-FS_AddGameDirectory
-
-Sets fs_gamedir, adds the directory to the head of the path,
-then loads the zip headers
-================
-*/
-#define MAX_PAKFILES 1024
-static void FS_AddGameDirectory( const char *path, const char *dir ) {
- searchpath_t *sp;
- int i;
- searchpath_t *search;
- pack_t *pak;
- char *pakfile;
- int numfiles;
- char **pakfiles;
- char *sorted[MAX_PAKFILES];
-
- // this fixes the case where fs_basepath is the same as fs_cdpath
- // which happens on full installs
- for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
- if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) {
- return; // we've already got this one
- }
- }
-
- Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) );
-
- //
- // add the directory to the search path
- //
- search = Z_Malloc (sizeof(searchpath_t));
- search->dir = Z_Malloc( sizeof( *search->dir ) );
-
- Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) );
- Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) );
- search->next = fs_searchpaths;
- fs_searchpaths = search;
-
- // find all pak files in this directory
- pakfile = FS_BuildOSPath( path, dir, "" );
- pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash
-
- pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse );
-
- // sort them so that later alphabetic matches override
- // earlier ones. This makes pak1.pk3 override pak0.pk3
- if ( numfiles > MAX_PAKFILES ) {
- numfiles = MAX_PAKFILES;
- }
- for ( i = 0 ; i < numfiles ; i++ ) {
- sorted[i] = pakfiles[i];
- }
-
- qsort( sorted, numfiles, 4, paksort );
-
- for ( i = 0 ; i < numfiles ; i++ ) {
- pakfile = FS_BuildOSPath( path, dir, sorted[i] );
- if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 )
- continue;
- // store the game name for downloading
- strcpy(pak->pakGamename, dir);
-
- search = Z_Malloc (sizeof(searchpath_t));
- search->pack = pak;
- search->next = fs_searchpaths;
- fs_searchpaths = search;
- }
-
- // done
- Sys_FreeFileList( pakfiles );
-}
-
-/*
-================
-FS_idPak
-================
-*/
-qboolean FS_idPak( char *pak, char *base ) {
- int i;
-
- for (i = 0; i < NUM_ID_PAKS; i++) {
- if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) {
- break;
- }
- }
- if (i < NUM_ID_PAKS) {
- return qtrue;
- }
- return qfalse;
-}
-
-/*
-================
-FS_ComparePaks
-
-----------------
-dlstring == qtrue
-
-Returns a list of pak files that we should download from the server. They all get stored
-in the current gamedir and an FS_Restart will be fired up after we download them all.
-
-The string is the format:
-
-@remotename@localname [repeat]
-
-static int fs_numServerReferencedPaks;
-static int fs_serverReferencedPaks[MAX_SEARCH_PATHS];
-static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS];
-
-----------------
-dlstring == qfalse
-
-we are not interested in a download string format, we want something human-readable
-(this is used for diagnostics while connecting to a pure server)
-
-================
-*/
-qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
- searchpath_t *sp;
- qboolean havepak, badchecksum;
- int i;
-
- if ( !fs_numServerReferencedPaks ) {
- return qfalse; // Server didn't send any pack information along
- }
-
- *neededpaks = 0;
-
- for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) {
- // Ok, see if we have this pak file
- badchecksum = qfalse;
- havepak = qfalse;
-
- // never autodownload any of the id paks
- if ( FS_idPak(fs_serverReferencedPakNames[i], "baseq3") || FS_idPak(fs_serverReferencedPakNames[i], "missionpack") ) {
- continue;
- }
-
- for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
- if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) {
- havepak = qtrue; // This is it!
- break;
- }
- }
-
- if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) {
- // Don't got it
-
- if (dlstring)
- {
- // Remote name
- Q_strcat( neededpaks, len, "@");
- Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
- Q_strcat( neededpaks, len, ".pk3" );
-
- // Local name
- Q_strcat( neededpaks, len, "@");
- // Do we have one with the same name?
- if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
- {
- char st[MAX_ZPATH];
- // We already have one called this, we need to download it to another name
- // Make something up with the checksum in it
- Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] );
- Q_strcat( neededpaks, len, st );
- } else
- {
- Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
- Q_strcat( neededpaks, len, ".pk3" );
- }
- }
- else
- {
- Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
- Q_strcat( neededpaks, len, ".pk3" );
- // Do we have one with the same name?
- if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
- {
- Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
- }
- Q_strcat( neededpaks, len, "\n");
- }
- }
- }
-
- if ( *neededpaks ) {
- return qtrue;
- }
-
- return qfalse; // We have them all
-}
-
-/*
-================
-FS_Shutdown
-
-Frees all resources and closes all files
-================
-*/
-void FS_Shutdown( qboolean closemfp ) {
- searchpath_t *p, *next;
- int i;
-
- for(i = 0; i < MAX_FILE_HANDLES; i++) {
- if (fsh[i].fileSize) {
- FS_FCloseFile(i);
- }
- }
-
- // free everything
- for ( p = fs_searchpaths ; p ; p = next ) {
- next = p->next;
-
- if ( p->pack ) {
- unzClose(p->pack->handle);
- Z_Free( p->pack->buildBuffer );
- Z_Free( p->pack );
- }
- if ( p->dir ) {
- Z_Free( p->dir );
- }
- Z_Free( p );
- }
-
- // any FS_ calls will now be an error until reinitialized
- fs_searchpaths = NULL;
-
- Cmd_RemoveCommand( "path" );
- Cmd_RemoveCommand( "dir" );
- Cmd_RemoveCommand( "fdir" );
- Cmd_RemoveCommand( "touchFile" );
-
-#ifdef FS_MISSING
- if (closemfp) {
- fclose(missingFiles);
- }
-#endif
-}
-
-void Com_AppendCDKey( const char *filename );
-void Com_ReadCDKey( const char *filename );
-
-/*
-================
-FS_ReorderPurePaks
-NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*)
- this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
-================
-*/
-static void FS_ReorderPurePaks()
-{
- searchpath_t *s;
- int i;
- searchpath_t **p_insert_index, // for linked list reordering
- **p_previous; // when doing the scan
-
- // only relevant when connected to pure server
- if ( !fs_numServerPaks )
- return;
-
- fs_reordered = qfalse;
-
- p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list
- for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
- p_previous = p_insert_index; // track the pointer-to-current-item
- for (s = *p_insert_index; s; s = s->next) {
- // the part of the list before p_insert_index has been sorted already
- if (s->pack && fs_serverPaks[i] == s->pack->checksum) {
- fs_reordered = qtrue;
- // move this element to the insert list
- *p_previous = s->next;
- s->next = *p_insert_index;
- *p_insert_index = s;
- // increment insert list
- p_insert_index = &s->next;
- break; // iterate to next server pack
- }
- p_previous = &s->next;
- }
- }
-}
-
-/*
-================
-FS_Startup
-================
-*/
-static void FS_Startup( const char *gameName ) {
- const char *homePath;
- cvar_t *fs;
-
- Com_Printf( "----- FS_Startup -----\n" );
-
- fs_debug = Cvar_Get( "fs_debug", "0", 0 );
- fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT );
- fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT );
- fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT );
- fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
- homePath = Sys_DefaultHomePath();
- if (!homePath || !homePath[0]) {
- homePath = fs_basepath->string;
- }
- fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT );
- fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
- fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT );
-
- // add search path elements in reverse priority order
- if (fs_cdpath->string[0]) {
- FS_AddGameDirectory( fs_cdpath->string, gameName );
- }
- if (fs_basepath->string[0]) {
- FS_AddGameDirectory( fs_basepath->string, gameName );
- }
- // fs_homepath is somewhat particular to *nix systems, only add if relevant
- // NOTE: same filtering below for mods and basegame
- if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
- FS_AddGameDirectory ( fs_homepath->string, gameName );
- }
-
- // check for additional base game so mods can be based upon other mods
- if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) {
- if (fs_cdpath->string[0]) {
- FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string);
- }
- if (fs_basepath->string[0]) {
- FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
- }
- if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
- FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
- }
- }
-
- // check for additional game folder for mods
- if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) {
- if (fs_cdpath->string[0]) {
- FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string);
- }
- if (fs_basepath->string[0]) {
- FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
- }
- if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
- FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
- }
- }
-
- Com_ReadCDKey( "baseq3" );
- fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
- if (fs && fs->string[0] != 0) {
- Com_AppendCDKey( fs->string );
- }
-
- // add our commands
- Cmd_AddCommand ("path", FS_Path_f);
- Cmd_AddCommand ("dir", FS_Dir_f );
- Cmd_AddCommand ("fdir", FS_NewDir_f );
- Cmd_AddCommand ("touchFile", FS_TouchFile_f );
-
- // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506
- // reorder the pure pk3 files according to server order
- FS_ReorderPurePaks();
-
- // print the current search paths
- FS_Path_f();
-
- fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
-
- Com_Printf( "----------------------\n" );
-
-#ifdef FS_MISSING
- if (missingFiles == NULL) {
- missingFiles = fopen( "\\missing.txt", "ab" );
- }
-#endif
- Com_Printf( "%d files in pk3 files\n", fs_packFiles );
-}
-
-
-/*
-===================
-FS_SetRestrictions
-
-Looks for product keys and restricts media add on ability
-if the full version is not found
-===================
-*/
-static void FS_SetRestrictions( void ) {
- searchpath_t *path;
-
-#ifndef PRE_RELEASE_DEMO
- char *productId;
-
- // if fs_restrict is set, don't even look for the id file,
- // which allows the demo release to be tested even if
- // the full game is present
- if ( !fs_restrict->integer ) {
- // look for the full game id
- FS_ReadFile( "productid.txt", (void **)&productId );
- if ( productId ) {
- // check against the hardcoded string
- int seed, i;
-
- seed = 5000;
- for ( i = 0 ; i < sizeof( fs_scrambledProductId ) ; i++ ) {
- if ( ( fs_scrambledProductId[i] ^ (seed&255) ) != productId[i] ) {
- break;
- }
- seed = (69069 * seed + 1);
- }
-
- FS_FreeFile( productId );
-
- if ( i == sizeof( fs_scrambledProductId ) ) {
- return; // no restrictions
- }
- Com_Error( ERR_FATAL, "Invalid product identification" );
- }
- }
-#endif
- Cvar_Set( "fs_restrict", "1" );
-
- Com_Printf( "\nRunning in restricted demo mode.\n\n" );
-
- // restart the filesystem with just the demo directory
- FS_Shutdown(qfalse);
- FS_Startup( DEMOGAME );
-
- // make sure that the pak file has the header checksum we expect
- for ( path = fs_searchpaths ; path ; path = path->next ) {
- if ( path->pack ) {
- // a tiny attempt to keep the checksum from being scannable from the exe
- if ( (path->pack->checksum ^ 0x02261994u) != (DEMO_PAK_CHECKSUM ^ 0x02261994u) ) {
- Com_Error( ERR_FATAL, "Corrupted pak0.pk3: %u", path->pack->checksum );
- }
- }
- }
-}
-
-/*
-=====================
-FS_GamePureChecksum
-
-Returns the checksum of the pk3 from which the server loaded the qagame.qvm
-=====================
-*/
-const char *FS_GamePureChecksum( void ) {
- static char info[MAX_STRING_TOKENS];
- searchpath_t *search;
-
- info[0] = 0;
-
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- // is the element a pak file?
- if ( search->pack ) {
- if (search->pack->referenced & FS_QAGAME_REF) {
- Com_sprintf(info, sizeof(info), "%d", search->pack->checksum);
- }
- }
- }
-
- return info;
-}
-
-/*
-=====================
-FS_LoadedPakChecksums
-
-Returns a space separated string containing the checksums of all loaded pk3 files.
-Servers with sv_pure set will get this string and pass it to clients.
-=====================
-*/
-const char *FS_LoadedPakChecksums( void ) {
- static char info[BIG_INFO_STRING];
- searchpath_t *search;
-
- info[0] = 0;
-
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- // is the element a pak file?
- if ( !search->pack ) {
- continue;
- }
-
- Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
- }
-
- return info;
-}
-
-/*
-=====================
-FS_LoadedPakNames
-
-Returns a space separated string containing the names of all loaded pk3 files.
-Servers with sv_pure set will get this string and pass it to clients.
-=====================
-*/
-const char *FS_LoadedPakNames( void ) {
- static char info[BIG_INFO_STRING];
- searchpath_t *search;
-
- info[0] = 0;
-
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- // is the element a pak file?
- if ( !search->pack ) {
- continue;
- }
-
- if (*info) {
- Q_strcat(info, sizeof( info ), " " );
- }
- Q_strcat( info, sizeof( info ), search->pack->pakBasename );
- }
-
- return info;
-}
-
-/*
-=====================
-FS_LoadedPakPureChecksums
-
-Returns a space separated string containing the pure checksums of all loaded pk3 files.
-Servers with sv_pure use these checksums to compare with the checksums the clients send
-back to the server.
-=====================
-*/
-const char *FS_LoadedPakPureChecksums( void ) {
- static char info[BIG_INFO_STRING];
- searchpath_t *search;
-
- info[0] = 0;
-
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- // is the element a pak file?
- if ( !search->pack ) {
- continue;
- }
-
- Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
- }
-
- return info;
-}
-
-/*
-=====================
-FS_ReferencedPakChecksums
-
-Returns a space separated string containing the checksums of all referenced pk3 files.
-The server will send this to the clients so they can check which files should be auto-downloaded.
-=====================
-*/
-const char *FS_ReferencedPakChecksums( void ) {
- static char info[BIG_INFO_STRING];
- searchpath_t *search;
-
- info[0] = 0;
-
-
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- // is the element a pak file?
- if ( search->pack ) {
- if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
- Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
- }
- }
- }
-
- return info;
-}
-
-/*
-=====================
-FS_ReferencedPakPureChecksums
-
-Returns a space separated string containing the pure checksums of all referenced pk3 files.
-Servers with sv_pure set will get this string back from clients for pure validation
-
-The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..."
-=====================
-*/
-const char *FS_ReferencedPakPureChecksums( void ) {
- static char info[BIG_INFO_STRING];
- searchpath_t *search;
- int nFlags, numPaks, checksum;
-
- info[0] = 0;
-
- checksum = fs_checksumFeed;
- numPaks = 0;
- for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) {
- if (nFlags & FS_GENERAL_REF) {
- // add a delimter between must haves and general refs
- //Q_strcat(info, sizeof(info), "@ ");
- info[strlen(info)+1] = '\0';
- info[strlen(info)+2] = '\0';
- info[strlen(info)] = '@';
- info[strlen(info)] = ' ';
- }
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- // is the element a pak file and has it been referenced based on flag?
- if ( search->pack && (search->pack->referenced & nFlags)) {
- Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
- if (nFlags & (FS_CGAME_REF | FS_UI_REF)) {
- break;
- }
- checksum ^= search->pack->pure_checksum;
- numPaks++;
- }
- }
- if (fs_fakeChkSum != 0) {
- // only added if a non-pure file is referenced
- Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) );
- }
- }
- // last checksum is the encoded number of referenced pk3s
- checksum ^= numPaks;
- Q_strcat( info, sizeof( info ), va("%i ", checksum ) );
-
- return info;
-}
-
-/*
-=====================
-FS_ReferencedPakNames
-
-Returns a space separated string containing the names of all referenced pk3 files.
-The server will send this to the clients so they can check which files should be auto-downloaded.
-=====================
-*/
-const char *FS_ReferencedPakNames( void ) {
- static char info[BIG_INFO_STRING];
- searchpath_t *search;
-
- info[0] = 0;
-
- // we want to return ALL pk3's from the fs_game path
- // and referenced one's from baseq3
- for ( search = fs_searchpaths ; search ; search = search->next ) {
- // is the element a pak file?
- if ( search->pack ) {
- if (*info) {
- Q_strcat(info, sizeof( info ), " " );
- }
- if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
- Q_strcat( info, sizeof( info ), search->pack->pakGamename );
- Q_strcat( info, sizeof( info ), "/" );
- Q_strcat( info, sizeof( info ), search->pack->pakBasename );
- }
- }
- }
-
- return info;
-}
-
-/*
-=====================
-FS_ClearPakReferences
-=====================
-*/
-void FS_ClearPakReferences( int flags ) {
- searchpath_t *search;
-
- if ( !flags ) {
- flags = -1;
- }
- for ( search = fs_searchpaths; search; search = search->next ) {
- // is the element a pak file and has it been referenced?
- if ( search->pack ) {
- search->pack->referenced &= ~flags;
- }
- }
-}
-
-
-/*
-=====================
-FS_PureServerSetLoadedPaks
-
-If the string is empty, all data sources will be allowed.
-If not empty, only pk3 files that match one of the space
-separated checksums will be checked for files, with the
-exception of .cfg and .dat files.
-=====================
-*/
-void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) {
- int i, c, d;
-
- Cmd_TokenizeString( pakSums );
-
- c = Cmd_Argc();
- if ( c > MAX_SEARCH_PATHS ) {
- c = MAX_SEARCH_PATHS;
- }
-
- fs_numServerPaks = c;
-
- for ( i = 0 ; i < c ; i++ ) {
- fs_serverPaks[i] = atoi( Cmd_Argv( i ) );
- }
-
- if (fs_numServerPaks) {
- Com_DPrintf( "Connected to a pure server.\n" );
- }
- else
- {
- if (fs_reordered)
- {
- // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
- // force a restart to make sure the search order will be correct
- Com_DPrintf( "FS search reorder is required\n" );
- FS_Restart(fs_checksumFeed);
- return;
- }
- }
-
- for ( i = 0 ; i < c ; i++ ) {
- if (fs_serverPakNames[i]) {
- Z_Free(fs_serverPakNames[i]);
- }
- fs_serverPakNames[i] = NULL;
- }
- if ( pakNames && *pakNames ) {
- Cmd_TokenizeString( pakNames );
-
- d = Cmd_Argc();
- if ( d > MAX_SEARCH_PATHS ) {
- d = MAX_SEARCH_PATHS;
- }
-
- for ( i = 0 ; i < d ; i++ ) {
- fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) );
- }
- }
-}
-
-/*
-=====================
-FS_PureServerSetReferencedPaks
-
-The checksums and names of the pk3 files referenced at the server
-are sent to the client and stored here. The client will use these
-checksums to see if any pk3 files need to be auto-downloaded.
-=====================
-*/
-void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
- int i, c, d;
-
- Cmd_TokenizeString( pakSums );
-
- c = Cmd_Argc();
- if ( c > MAX_SEARCH_PATHS ) {
- c = MAX_SEARCH_PATHS;
- }
-
- fs_numServerReferencedPaks = c;
-
- for ( i = 0 ; i < c ; i++ ) {
- fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
- }
-
- for ( i = 0 ; i < c ; i++ ) {
- if (fs_serverReferencedPakNames[i]) {
- Z_Free(fs_serverReferencedPakNames[i]);
- }
- fs_serverReferencedPakNames[i] = NULL;
- }
- if ( pakNames && *pakNames ) {
- Cmd_TokenizeString( pakNames );
-
- d = Cmd_Argc();
- if ( d > MAX_SEARCH_PATHS ) {
- d = MAX_SEARCH_PATHS;
- }
-
- for ( i = 0 ; i < d ; i++ ) {
- fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
- }
- }
-}
-
-/*
-================
-FS_InitFilesystem
-
-Called only at inital startup, not when the filesystem
-is resetting due to a game change
-================
-*/
-void FS_InitFilesystem( void ) {
- // allow command line parms to override our defaults
- // we have to specially handle this, because normal command
- // line variable sets don't happen until after the filesystem
- // has already been initialized
- Com_StartupVariable( "fs_cdpath" );
- Com_StartupVariable( "fs_basepath" );
- Com_StartupVariable( "fs_homepath" );
- Com_StartupVariable( "fs_game" );
- Com_StartupVariable( "fs_copyfiles" );
- Com_StartupVariable( "fs_restrict" );
-
- // try to start up normally
- FS_Startup( BASEGAME );
-
- // see if we are going to allow add-ons
- FS_SetRestrictions();
-
- // if we can't find default.cfg, assume that the paths are
- // busted and error out now, rather than getting an unreadable
- // graphics screen when the font fails to load
- if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
- Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
- // bk001208 - SafeMode see below, FIXME?
- }
-
- Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
- Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
-
- // bk001208 - SafeMode see below, FIXME?
-}
-
-
-/*
-================
-FS_Restart
-================
-*/
-void FS_Restart( int checksumFeed ) {
-
- // free anything we currently have loaded
- FS_Shutdown(qfalse);
-
- // set the checksum feed
- fs_checksumFeed = checksumFeed;
-
- // clear pak references
- FS_ClearPakReferences(0);
-
- // try to start up normally
- FS_Startup( BASEGAME );
-
- // see if we are going to allow add-ons
- FS_SetRestrictions();
-
- // if we can't find default.cfg, assume that the paths are
- // busted and error out now, rather than getting an unreadable
- // graphics screen when the font fails to load
- if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
- // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3
- // (for instance a TA demo server)
- if (lastValidBase[0]) {
- FS_PureServerSetLoadedPaks("", "");
- Cvar_Set("fs_basepath", lastValidBase);
- Cvar_Set("fs_gamedirvar", lastValidGame);
- lastValidBase[0] = '\0';
- lastValidGame[0] = '\0';
- Cvar_Set( "fs_restrict", "0" );
- FS_Restart(checksumFeed);
- Com_Error( ERR_DROP, "Invalid game folder\n" );
- return;
- }
- Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
- }
-
- // bk010116 - new check before safeMode
- if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) {
- // skip the q3config.cfg if "safe" is on the command line
- if ( !Com_SafeMode() ) {
- Cbuf_AddText ("exec q3config.cfg\n");
- }
- }
-
- Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
- Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
-
-}
-
-/*
-=================
-FS_ConditionalRestart
-restart if necessary
-=================
-*/
-qboolean FS_ConditionalRestart( int checksumFeed ) {
- if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) {
- FS_Restart( checksumFeed );
- return qtrue;
- }
- return qfalse;
-}
-
-/*
-========================================================================================
-
-Handle based file calls for virtual machines
-
-========================================================================================
-*/
-
-int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
- int r;
- qboolean sync;
-
- sync = qfalse;
-
- switch( mode ) {
- case FS_READ:
- r = FS_FOpenFileRead( qpath, f, qtrue );
- break;
- case FS_WRITE:
- *f = FS_FOpenFileWrite( qpath );
- r = 0;
- if (*f == 0) {
- r = -1;
- }
- break;
- case FS_APPEND_SYNC:
- sync = qtrue;
- case FS_APPEND:
- *f = FS_FOpenFileAppend( qpath );
- r = 0;
- if (*f == 0) {
- r = -1;
- }
- break;
- default:
- Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
- return -1;
- }
-
- if (!f) {
- return r;
- }
-
- if ( *f ) {
- if (fsh[*f].zipFile == qtrue) {
- fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z);
- } else {
- fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o);
- }
- fsh[*f].fileSize = r;
- fsh[*f].streamed = qfalse;
-
- if (mode == FS_READ) {
- Sys_BeginStreamedFile( *f, 0x4000 );
- fsh[*f].streamed = qtrue;
- }
- }
- fsh[*f].handleSync = sync;
-
- return r;
-}
-
-int FS_FTell( fileHandle_t f ) {
- int pos;
- if (fsh[f].zipFile == qtrue) {
- pos = unztell(fsh[f].handleFiles.file.z);
- } else {
- pos = ftell(fsh[f].handleFiles.file.o);
- }
- return pos;
-}
-
-void FS_Flush( fileHandle_t f ) {
- fflush(fsh[f].handleFiles.file.o);
-}
-
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Quake III Arena source code is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Foobar; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+/*****************************************************************************
+ * name: files.c
+ *
+ * desc: handle based filesystem for Quake III Arena
+ *
+ * $Archive: /MissionPack/code/qcommon/files.c $
+ *
+ *****************************************************************************/
+
+
+#include "../game/q_shared.h"
+#include "qcommon.h"
+#include "unzip.h"
+
+/*
+=============================================================================
+
+QUAKE3 FILESYSTEM
+
+All of Quake's data access is through a hierarchical file system, but the contents of
+the file system can be transparently merged from several sources.
+
+A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include
+a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
+references outside the quake directory system.
+
+The "base path" is the path to the directory holding all the game directories and usually
+the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
+command line to allow code debugging in a different directory. Basepath cannot
+be modified at all after startup. Any files that are created (demos, screenshots,
+etc) will be created reletive to the base path, so base path should usually be writable.
+
+The "cd path" is the path to an alternate hierarchy that will be searched if a file
+is not located in the base path. A user can do a partial install that copies some
+data to a base path created on their hard drive and leave the rest on the cd. Files
+are never writen to the cd path. It defaults to a value set by the installer, like
+"e:\quake3", but it can be overridden with "+set ds_cdpath g:\quake3".
+
+If a user runs the game directly from a CD, the base path would be on the CD. This
+should still function correctly, but all file writes will fail (harmlessly).
+
+The "home path" is the path used for all write access. On win32 systems we have "base path"
+== "home path", but on *nix systems the base installation is usually readonly, and
+"home path" points to ~/.q3a or similar
+
+The user can also install custom mods and content in "home path", so it should be searched
+along with "home path" and "cd path" for game content.
+
+
+The "base game" is the directory under the paths where data comes from by default, and
+can be either "baseq3" or "demoq3".
+
+The "current game" may be the same as the base game, or it may be the name of another
+directory under the paths that should be searched for files before looking in the base game.
+This is the basis for addons.
+
+Clients automatically set the game directory after receiving a gamestate from a server,
+so only servers need to worry about +set fs_game.
+
+No other directories outside of the base game and current game will ever be referenced by
+filesystem functions.
+
+To save disk space and speed loading, directory trees can be collapsed into zip files.
+The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
+otherwise the are simply normal uncompressed zip files. A game directory can have multiple
+zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order
+from the highest number to the lowest, and will always take precedence over the filesystem.
+This allows a pk3 distributed as a patch to override all existing data.
+
+Because we will have updated executables freely available online, there is no point to
+trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
+should be exactly the same executables as release versions, but with different data that
+automatically restricts where game media can come from to prevent add-ons from working.
+
+After the paths are initialized, quake will look for the product.txt file. If not
+found and verified, the game will run in restricted mode. In restricted mode, only
+files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is
+verified to not have been modified. A single exception is made for q3config.cfg. Files
+can still be written out in restricted mode, so screenshots and demos are allowed.
+Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
+if there is a valid product.txt under the basepath or cdpath.
+
+If not running in restricted mode, and a file is not found in any local filesystem,
+an attempt will be made to download it and save it under the base path.
+
+If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
+path, it will be copied over to the base path. This is a development aid to help build
+test releases and to copy working sets over slow network links.
+
+File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
+structure and stop on the first successful hit. fs_searchpaths is built with successive
+calls to FS_AddGameDirectory
+
+Additionaly, we search in several subdirectories:
+current game is the current mode
+base game is a variable to allow mods based on other mods
+(such as baseq3 + missionpack content combination in a mod for instance)
+BASEGAME is the hardcoded base game ("baseq3")
+
+e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
+
+home path + current game's zip files
+home path + current game's directory
+base path + current game's zip files
+base path + current game's directory
+cd path + current game's zip files
+cd path + current game's directory
+
+home path + base game's zip file
+home path + base game's directory
+base path + base game's zip file
+base path + base game's directory
+cd path + base game's zip file
+cd path + base game's directory
+
+home path + BASEGAME's zip file
+home path + BASEGAME's directory
+base path + BASEGAME's zip file
+base path + BASEGAME's directory
+cd path + BASEGAME's zip file
+cd path + BASEGAME's directory
+
+server download, to be written to home path + current game's directory
+
+
+The filesystem can be safely shutdown and reinitialized with different
+basedir / cddir / game combinations, but all other subsystems that rely on it
+(sound, video) must also be forced to restart.
+
+Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
+subsystems, a simple single-file caching scheme is used. The CM_ subsystems will
+load the file with a request to cache. Only one file will be kept cached at a time,
+so any models that are going to be referenced by both subsystems should alternate
+between the CM_ load function and the ref load function.
+
+TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
+game is currently active. This allows character models, skins, and sounds to be downloaded
+to a common directory no matter which game is active.
+
+How to prevent downloading zip files?
+Pass pk3 file names in systeminfo, and download before FS_Restart()?
+
+Aborting a download disconnects the client from the server.
+
+How to mark files as downloadable? Commercial add-ons won't be downloadable.
+
+Non-commercial downloads will want to download the entire zip file.
+the game would have to be reset to actually read the zip in
+
+Auto-update information
+
+Path separators
+
+Casing
+
+ separate server gamedir and client gamedir, so if the user starts
+ a local game after having connected to a network game, it won't stick
+ with the network game.
+
+ allow menu options for game selection?
+
+Read / write config to floppy option.
+
+Different version coexistance?
+
+When building a pak file, make sure a q3config.cfg isn't present in it,
+or configs will never get loaded from disk!
+
+ todo:
+
+ downloading (outside fs?)
+ game directory passing and restarting
+
+=============================================================================
+
+*/
+
+#define DEMOGAME "demota"
+
+// every time a new demo pk3 file is built, this checksum must be updated.
+// the easiest way to get it is to just run the game and see what it spits out
+#define DEMO_PAK_CHECKSUM 437558517u
+
+// if this is defined, the executable positively won't work with any paks other
+// than the demo pak, even if productid is present. This is only used for our
+// last demo release to prevent the mac and linux users from using the demo
+// executable with the production windows pak before the mac/linux products
+// hit the shelves a little later
+// NOW defined in build files
+//#define PRE_RELEASE_TADEMO
+
+#define MAX_ZPATH 256
+#define MAX_SEARCH_PATHS 4096
+#define MAX_FILEHASH_SIZE 1024
+
+typedef struct fileInPack_s {
+ char *name; // name of the file
+ unsigned long pos; // file info position in zip
+ struct fileInPack_s* next; // next file in the hash
+} fileInPack_t;
+
+typedef struct {
+ char pakFilename[MAX_OSPATH]; // c:\quake3\baseq3\pak0.pk3
+ char pakBasename[MAX_OSPATH]; // pak0
+ char pakGamename[MAX_OSPATH]; // baseq3
+ unzFile handle; // handle to zip file
+ int checksum; // regular checksum
+ int pure_checksum; // checksum for pure
+ int numfiles; // number of files in pk3
+ int referenced; // referenced file flags
+ int hashSize; // hash table size (power of 2)
+ fileInPack_t* *hashTable; // hash table
+ fileInPack_t* buildBuffer; // buffer with the filenames etc.
+} pack_t;
+
+typedef struct {
+ char path[MAX_OSPATH]; // c:\quake3
+ char gamedir[MAX_OSPATH]; // baseq3
+} directory_t;
+
+typedef struct searchpath_s {
+ struct searchpath_s *next;
+
+ pack_t *pack; // only one of pack / dir will be non NULL
+ directory_t *dir;
+} searchpath_t;
+
+static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
+static cvar_t *fs_debug;
+static cvar_t *fs_homepath;
+static cvar_t *fs_basepath;
+static cvar_t *fs_basegame;
+static cvar_t *fs_cdpath;
+static cvar_t *fs_copyfiles;
+static cvar_t *fs_gamedirvar;
+static cvar_t *fs_restrict;
+static searchpath_t *fs_searchpaths;
+static int fs_readCount; // total bytes read
+static int fs_loadCount; // total files read
+static int fs_loadStack; // total files in memory
+static int fs_packFiles; // total number of files in packs
+
+static int fs_fakeChkSum;
+static int fs_checksumFeed;
+
+typedef union qfile_gus {
+ FILE* o;
+ unzFile z;
+} qfile_gut;
+
+typedef struct qfile_us {
+ qfile_gut file;
+ qboolean unique;
+} qfile_ut;
+
+typedef struct {
+ qfile_ut handleFiles;
+ qboolean handleSync;
+ int baseOffset;
+ int fileSize;
+ int zipFilePos;
+ qboolean zipFile;
+ qboolean streamed;
+ char name[MAX_ZPATH];
+} fileHandleData_t;
+
+static fileHandleData_t fsh[MAX_FILE_HANDLES];
+
+// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
+// wether we did a reorder on the current search path when joining the server
+static qboolean fs_reordered;
+
+// never load anything from pk3 files that are not present at the server when pure
+static int fs_numServerPaks;
+static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums
+static char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names
+
+// only used for autodownload, to make sure the client has at least
+// all the pk3 files that are referenced at the server side
+static int fs_numServerReferencedPaks;
+static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums
+static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names
+
+// last valid game folder used
+char lastValidBase[MAX_OSPATH];
+char lastValidGame[MAX_OSPATH];
+
+// productId: This file is copyright 1999 Id Software, and may not be duplicated except during a licensed installation of the full commercial version of Quake 3:Arena
+static byte fs_scrambledProductId[152] = {
+220, 129, 255, 108, 244, 163, 171, 55, 133, 65, 199, 36, 140, 222, 53, 99, 65, 171, 175, 232, 236, 193, 210, 250, 169, 104, 231, 231, 21, 201, 170, 208, 135, 175, 130, 136, 85, 215, 71, 23, 96, 32, 96, 83, 44, 240, 219, 138, 184, 215, 73, 27, 196, 247, 55, 139, 148, 68, 78, 203, 213, 238, 139, 23, 45, 205, 118, 186, 236, 230, 231, 107, 212, 1, 10, 98, 30, 20, 116, 180, 216, 248, 166, 35, 45, 22, 215, 229, 35, 116, 250, 167, 117, 3, 57, 55, 201, 229, 218, 222, 128, 12, 141, 149, 32, 110, 168, 215, 184, 53, 31, 147, 62, 12, 138, 67, 132, 54, 125, 6, 221, 148, 140, 4, 21, 44, 198, 3, 126, 12, 100, 236, 61, 42, 44, 251, 15, 135, 14, 134, 89, 92, 177, 246, 152, 106, 124, 78, 118, 80, 28, 42
+};
+
+#ifdef FS_MISSING
+FILE* missingFiles = NULL;
+#endif
+
+/*
+==============
+FS_Initialized
+==============
+*/
+
+qboolean FS_Initialized() {
+ return (fs_searchpaths != NULL);
+}
+
+/*
+=================
+FS_PakIsPure
+=================
+*/
+qboolean FS_PakIsPure( pack_t *pack ) {
+ int i;
+
+ if ( fs_numServerPaks ) {
+ for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
+ // FIXME: also use hashed file names
+ // NOTE TTimo: a pk3 with same checksum but different name would be validated too
+ // I don't see this as allowing for any exploit, it would only happen if the client does manips of it's file names 'not a bug'
+ if ( pack->checksum == fs_serverPaks[i] ) {
+ return qtrue; // on the aproved list
+ }
+ }
+ return qfalse; // not on the pure server pak list
+ }
+ return qtrue;
+}
+
+
+/*
+=================
+FS_LoadStack
+return load stack
+=================
+*/
+int FS_LoadStack()
+{
+ return fs_loadStack;
+}
+
+/*
+================
+return a hash value for the filename
+================
+*/
+static long FS_HashFileName( const char *fname, int hashSize ) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (fname[i] != '\0') {
+ letter = tolower(fname[i]);
+ if (letter =='.') break; // don't include extension
+ if (letter =='\\') letter = '/'; // damn path names
+ if (letter == PATH_SEP) letter = '/'; // damn path names
+ hash+=(long)(letter)*(i+119);
+ i++;
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20));
+ hash &= (hashSize-1);
+ return hash;
+}
+
+static fileHandle_t FS_HandleForFile(void) {
+ int i;
+
+ for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
+ if ( fsh[i].handleFiles.file.o == NULL ) {
+ return i;
+ }
+ }
+ Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
+ return 0;
+}
+
+static FILE *FS_FileForHandle( fileHandle_t f ) {
+ if ( f < 0 || f > MAX_FILE_HANDLES ) {
+ Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" );
+ }
+ if (fsh[f].zipFile == qtrue) {
+ Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
+ }
+ if ( ! fsh[f].handleFiles.file.o ) {
+ Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
+ }
+
+ return fsh[f].handleFiles.file.o;
+}
+
+void FS_ForceFlush( fileHandle_t f ) {
+ FILE *file;
+
+ file = FS_FileForHandle(f);
+ setvbuf( file, NULL, _IONBF, 0 );
+}
+
+/*
+================
+FS_filelength
+
+If this is called on a non-unique FILE (from a pak file),
+it will return the size of the pak file, not the expected
+size of the file.
+================
+*/
+int FS_filelength( fileHandle_t f ) {
+ int pos;
+ int end;
+ FILE* h;
+
+ h = FS_FileForHandle(f);
+ pos = ftell (h);
+ fseek (h, 0, SEEK_END);
+ end = ftell (h);
+ fseek (h, pos, SEEK_SET);
+
+ return end;
+}
+
+/*
+====================
+FS_ReplaceSeparators
+
+Fix things up differently for win/unix/mac
+====================
+*/
+static void FS_ReplaceSeparators( char *path ) {
+ char *s;
+
+ for ( s = path ; *s ; s++ ) {
+ if ( *s == '/' || *s == '\\' ) {
+ *s = PATH_SEP;
+ }
+ }
+}
+
+/*
+===================
+FS_BuildOSPath
+
+Qpath may have either forward or backwards slashes
+===================
+*/
+char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
+ char temp[MAX_OSPATH];
+ static char ospath[2][MAX_OSPATH];
+ static int toggle;
+
+ toggle ^= 1; // flip-flop to allow two returns without clash
+
+ if( !game || !game[0] ) {
+ game = fs_gamedir;
+ }
+
+ Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath );
+ FS_ReplaceSeparators( temp );
+ Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
+
+ return ospath[toggle];
+}
+
+
+/*
+============
+FS_CreatePath
+
+Creates any directories needed to store the given filename
+============
+*/
+static qboolean FS_CreatePath (char *OSPath) {
+ char *ofs;
+
+ // make absolutely sure that it can't back up the path
+ // FIXME: is c: allowed???
+ if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
+ Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
+ return qtrue;
+ }
+
+ for (ofs = OSPath+1 ; *ofs ; ofs++) {
+ if (*ofs == PATH_SEP) {
+ // create the directory
+ *ofs = 0;
+ Sys_Mkdir (OSPath);
+ *ofs = PATH_SEP;
+ }
+ }
+ return qfalse;
+}
+
+/*
+=================
+FS_CopyFile
+
+Copy a fully specified file from one place to another
+=================
+*/
+static void FS_CopyFile( char *fromOSPath, char *toOSPath ) {
+ FILE *f;
+ int len;
+ byte *buf;
+
+ Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath );
+
+ if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) {
+ Com_Printf( "Ignoring journal files\n");
+ return;
+ }
+
+ f = fopen( fromOSPath, "rb" );
+ if ( !f ) {
+ return;
+ }
+ fseek (f, 0, SEEK_END);
+ len = ftell (f);
+ fseek (f, 0, SEEK_SET);
+
+ // we are using direct malloc instead of Z_Malloc here, so it
+ // probably won't work on a mac... Its only for developers anyway...
+ buf = malloc( len );
+ if (fread( buf, 1, len, f ) != len)
+ Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" );
+ fclose( f );
+
+ if( FS_CreatePath( toOSPath ) ) {
+ return;
+ }
+
+ f = fopen( toOSPath, "wb" );
+ if ( !f ) {
+ return;
+ }
+ if (fwrite( buf, 1, len, f ) != len)
+ Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" );
+ fclose( f );
+ free( buf );
+}
+
+/*
+===========
+FS_Remove
+
+===========
+*/
+static void FS_Remove( const char *osPath ) {
+ remove( osPath );
+}
+
+/*
+================
+FS_FileExists
+
+Tests if the file exists in the current gamedir, this DOES NOT
+search the paths. This is to determine if opening a file to write
+(which always goes into the current gamedir) will cause any overwrites.
+NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
+================
+*/
+qboolean FS_FileExists( const char *file )
+{
+ FILE *f;
+ char *testpath;
+
+ testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file );
+
+ f = fopen( testpath, "rb" );
+ if (f) {
+ fclose( f );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+================
+FS_SV_FileExists
+
+Tests if the file exists
+================
+*/
+qboolean FS_SV_FileExists( const char *file )
+{
+ FILE *f;
+ char *testpath;
+
+ testpath = FS_BuildOSPath( fs_homepath->string, file, "");
+ testpath[strlen(testpath)-1] = '\0';
+
+ f = fopen( testpath, "rb" );
+ if (f) {
+ fclose( f );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+/*
+===========
+FS_SV_FOpenFileWrite
+
+===========
+*/
+fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) {
+ char *ospath;
+ fileHandle_t f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
+ ospath[strlen(ospath)-1] = '\0';
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = qfalse;
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath );
+ }
+
+ if( FS_CreatePath( ospath ) ) {
+ return 0;
+ }
+
+ Com_DPrintf( "writing to: %s\n", ospath );
+ fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
+
+ Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
+
+ fsh[f].handleSync = qfalse;
+ if (!fsh[f].handleFiles.file.o) {
+ f = 0;
+ }
+ return f;
+}
+
+/*
+===========
+FS_SV_FOpenFileRead
+search for a file somewhere below the home path, base path or cd path
+we search in that order, matching FS_SV_FOpenFileRead order
+===========
+*/
+int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
+ char *ospath;
+ fileHandle_t f = 0;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = qfalse;
+
+ Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ // search homepath
+ ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
+ // remove trailing slash
+ ospath[strlen(ospath)-1] = '\0';
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath );
+ }
+
+ fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
+ fsh[f].handleSync = qfalse;
+ if (!fsh[f].handleFiles.file.o)
+ {
+ // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid
+ if (Q_stricmp(fs_homepath->string,fs_basepath->string))
+ {
+ // search basepath
+ ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
+ ospath[strlen(ospath)-1] = '\0';
+
+ if ( fs_debug->integer )
+ {
+ Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath );
+ }
+
+ fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
+ fsh[f].handleSync = qfalse;
+
+ if ( !fsh[f].handleFiles.file.o )
+ {
+ f = 0;
+ }
+ }
+ }
+
+ if (!fsh[f].handleFiles.file.o) {
+ // search cd path
+ ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" );
+ ospath[strlen(ospath)-1] = '\0';
+
+ if (fs_debug->integer)
+ {
+ Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath );
+ }
+
+ fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
+ fsh[f].handleSync = qfalse;
+
+ if( !fsh[f].handleFiles.file.o ) {
+ f = 0;
+ }
+ }
+
+ *fp = f;
+ if (f) {
+ return FS_filelength(f);
+ }
+ return 0;
+}
+
+
+/*
+===========
+FS_SV_Rename
+
+===========
+*/
+void FS_SV_Rename( const char *from, const char *to ) {
+ char *from_ospath, *to_ospath;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" );
+ to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" );
+ from_ospath[strlen(from_ospath)-1] = '\0';
+ to_ospath[strlen(to_ospath)-1] = '\0';
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath );
+ }
+
+ if (rename( from_ospath, to_ospath )) {
+ // Failed, try copying it and deleting the original
+ FS_CopyFile ( from_ospath, to_ospath );
+ FS_Remove ( from_ospath );
+ }
+}
+
+
+
+/*
+===========
+FS_Rename
+
+===========
+*/
+void FS_Rename( const char *from, const char *to ) {
+ char *from_ospath, *to_ospath;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from );
+ to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to );
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath );
+ }
+
+ if (rename( from_ospath, to_ospath )) {
+ // Failed, try copying it and deleting the original
+ FS_CopyFile ( from_ospath, to_ospath );
+ FS_Remove ( from_ospath );
+ }
+}
+
+/*
+==============
+FS_FCloseFile
+
+If the FILE pointer is an open pak file, leave it open.
+
+For some reason, other dll's can't just cal fclose()
+on files returned by FS_FOpenFile...
+==============
+*/
+void FS_FCloseFile( fileHandle_t f ) {
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if (fsh[f].streamed) {
+ Sys_EndStreamedFile(f);
+ }
+ if (fsh[f].zipFile == qtrue) {
+ unzCloseCurrentFile( fsh[f].handleFiles.file.z );
+ if ( fsh[f].handleFiles.unique ) {
+ unzClose( fsh[f].handleFiles.file.z );
+ }
+ Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
+ return;
+ }
+
+ // we didn't find it as a pak, so close it as a unique file
+ if (fsh[f].handleFiles.file.o) {
+ fclose (fsh[f].handleFiles.file.o);
+ }
+ Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
+}
+
+/*
+===========
+FS_FOpenFileWrite
+
+===========
+*/
+fileHandle_t FS_FOpenFileWrite( const char *filename ) {
+ char *ospath;
+ fileHandle_t f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = qfalse;
+
+ ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
+ }
+
+ if( FS_CreatePath( ospath ) ) {
+ return 0;
+ }
+
+ // enabling the following line causes a recursive function call loop
+ // when running with +set logfile 1 +set developer 1
+ //Com_DPrintf( "writing to: %s\n", ospath );
+ fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
+
+ Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
+
+ fsh[f].handleSync = qfalse;
+ if (!fsh[f].handleFiles.file.o) {
+ f = 0;
+ }
+ return f;
+}
+
+/*
+===========
+FS_FOpenFileAppend
+
+===========
+*/
+fileHandle_t FS_FOpenFileAppend( const char *filename ) {
+ char *ospath;
+ fileHandle_t f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ f = FS_HandleForFile();
+ fsh[f].zipFile = qfalse;
+
+ Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
+
+ // don't let sound stutter
+ S_ClearSoundBuffer();
+
+ ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
+ }
+
+ if( FS_CreatePath( ospath ) ) {
+ return 0;
+ }
+
+ fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
+ fsh[f].handleSync = qfalse;
+ if (!fsh[f].handleFiles.file.o) {
+ f = 0;
+ }
+ return f;
+}
+
+/*
+===========
+FS_FilenameCompare
+
+Ignore case and seprator char distinctions
+===========
+*/
+qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
+ int c1, c2;
+
+ do {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (c1 >= 'a' && c1 <= 'z') {
+ c1 -= ('a' - 'A');
+ }
+ if (c2 >= 'a' && c2 <= 'z') {
+ c2 -= ('a' - 'A');
+ }
+
+ if ( c1 == '\\' || c1 == ':' ) {
+ c1 = '/';
+ }
+ if ( c2 == '\\' || c2 == ':' ) {
+ c2 = '/';
+ }
+
+ if (c1 != c2) {
+ return -1; // strings not equal
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+/*
+===========
+FS_ShiftedStrStr
+===========
+*/
+char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) {
+ char buf[MAX_STRING_TOKENS];
+ int i;
+
+ for (i = 0; substring[i]; i++) {
+ buf[i] = substring[i] + shift;
+ }
+ buf[i] = '\0';
+ return strstr(string, buf);
+}
+
+/*
+===========
+FS_FOpenFileRead
+
+Finds the file in the search path.
+Returns filesize and an open FILE pointer.
+Used for streaming data out of either a
+separate file or a ZIP file.
+===========
+*/
+extern qboolean com_fullyInitialized;
+
+int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) {
+ searchpath_t *search;
+ char *netpath;
+ pack_t *pak;
+ fileInPack_t *pakFile;
+ directory_t *dir;
+ long hash;
+ unz_s *zfi;
+ FILE *temp;
+ int l;
+ char demoExt[16];
+
+ hash = 0;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( file == NULL ) {
+ // just wants to see if file is there
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ //
+ if ( search->pack ) {
+ hash = FS_HashFileName(filename, search->pack->hashSize);
+ }
+ // is the element a pak file?
+ if ( search->pack && search->pack->hashTable[hash] ) {
+ // look through all the pak file elements
+ pak = search->pack;
+ pakFile = pak->hashTable[hash];
+ do {
+ // case and separator insensitive comparisons
+ if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
+ // found it!
+ return qtrue;
+ }
+ pakFile = pakFile->next;
+ } while(pakFile != NULL);
+ } else if ( search->dir ) {
+ dir = search->dir;
+
+ netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
+ temp = fopen (netpath, "rb");
+ if ( !temp ) {
+ continue;
+ }
+ fclose(temp);
+ return qtrue;
+ }
+ }
+ return qfalse;
+ }
+
+ if ( !filename ) {
+ Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
+ }
+
+ Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION );
+ // qpaths are not supposed to have a leading slash
+ if ( filename[0] == '/' || filename[0] == '\\' ) {
+ filename++;
+ }
+
+ // make absolutely sure that it can't back up the path.
+ // The searchpaths do guarantee that something will always
+ // be prepended, so we don't need to worry about "c:" or "//limbo"
+ if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
+ *file = 0;
+ return -1;
+ }
+
+ // make sure the q3key file is only readable by the quake3.exe at initialization
+ // any other time the key should only be accessed in memory using the provided functions
+ if( com_fullyInitialized && strstr( filename, "q3key" ) ) {
+ *file = 0;
+ return -1;
+ }
+
+ //
+ // search through the path, one element at a time
+ //
+
+ *file = FS_HandleForFile();
+ fsh[*file].handleFiles.unique = uniqueFILE;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ //
+ if ( search->pack ) {
+ hash = FS_HashFileName(filename, search->pack->hashSize);
+ }
+ // is the element a pak file?
+ if ( search->pack && search->pack->hashTable[hash] ) {
+ // disregard if it doesn't match one of the allowed pure pak files
+ if ( !FS_PakIsPure(search->pack) ) {
+ continue;
+ }
+
+ // look through all the pak file elements
+ pak = search->pack;
+ pakFile = pak->hashTable[hash];
+ do {
+ // case and separator insensitive comparisons
+ if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
+ // found it!
+
+ // mark the pak as having been referenced and mark specifics on cgame and ui
+ // shaders, txt, arena files by themselves do not count as a reference as
+ // these are loaded from all pk3s
+ // from every pk3 file..
+ l = strlen( filename );
+ if ( !(pak->referenced & FS_GENERAL_REF)) {
+ if ( Q_stricmp(filename + l - 7, ".shader") != 0 &&
+ Q_stricmp(filename + l - 4, ".txt") != 0 &&
+ Q_stricmp(filename + l - 4, ".cfg") != 0 &&
+ Q_stricmp(filename + l - 7, ".config") != 0 &&
+ strstr(filename, "levelshots") == NULL &&
+ Q_stricmp(filename + l - 4, ".bot") != 0 &&
+ Q_stricmp(filename + l - 6, ".arena") != 0 &&
+ Q_stricmp(filename + l - 5, ".menu") != 0) {
+ pak->referenced |= FS_GENERAL_REF;
+ }
+ }
+
+ // qagame.qvm - 13
+ // dTZT`X!di`
+ if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "dTZT`X!di`", 13)) {
+ pak->referenced |= FS_QAGAME_REF;
+ }
+ // cgame.qvm - 7
+ // \`Zf^'jof
+ if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) {
+ pak->referenced |= FS_CGAME_REF;
+ }
+ // ui.qvm - 5
+ // pd)lqh
+ if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) {
+ pak->referenced |= FS_UI_REF;
+ }
+
+ if ( uniqueFILE ) {
+ // open a new file on the pakfile
+ fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle);
+ if (fsh[*file].handleFiles.file.z == NULL) {
+ Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename);
+ }
+ } else {
+ fsh[*file].handleFiles.file.z = pak->handle;
+ }
+ Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
+ fsh[*file].zipFile = qtrue;
+ zfi = (unz_s *)fsh[*file].handleFiles.file.z;
+ // in case the file was new
+ temp = zfi->file;
+ // set the file position in the zip file (also sets the current file info)
+ unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos);
+ // copy the file info into the unzip structure
+ Com_Memcpy( zfi, pak->handle, sizeof(unz_s) );
+ // we copy this back into the structure
+ zfi->file = temp;
+ // open the file in the zip
+ unzOpenCurrentFile( fsh[*file].handleFiles.file.z );
+ fsh[*file].zipFilePos = pakFile->pos;
+
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n",
+ filename, pak->pakFilename );
+ }
+ return zfi->cur_file_info.uncompressed_size;
+ }
+ pakFile = pakFile->next;
+ } while(pakFile != NULL);
+ } else if ( search->dir ) {
+ // check a file in the directory tree
+
+ // if we are running restricted, the only files we
+ // will allow to come from the directory are .cfg files
+ l = strlen( filename );
+ // FIXME TTimo I'm not sure about the fs_numServerPaks test
+ // if you are using FS_ReadFile to find out if a file exists,
+ // this test can make the search fail although the file is in the directory
+ // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
+ // turned out I used FS_FileExists instead
+ if ( fs_restrict->integer || fs_numServerPaks ) {
+
+ if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files
+ && Q_stricmp( filename + l - 5, ".menu" ) // menu files
+ && Q_stricmp( filename + l - 5, ".game" ) // menu files
+ && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
+ && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files
+ continue;
+ }
+ }
+
+ dir = search->dir;
+
+ netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
+ fsh[*file].handleFiles.file.o = fopen (netpath, "rb");
+ if ( !fsh[*file].handleFiles.file.o ) {
+ continue;
+ }
+
+ if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files
+ && Q_stricmp( filename + l - 5, ".menu" ) // menu files
+ && Q_stricmp( filename + l - 5, ".game" ) // menu files
+ && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
+ && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files
+ fs_fakeChkSum = random();
+ }
+
+ Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
+ fsh[*file].zipFile = qfalse;
+ if ( fs_debug->integer ) {
+ Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename,
+ dir->path, dir->gamedir );
+ }
+
+ // if we are getting it from the cdpath, optionally copy it
+ // to the basepath
+ if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) {
+ char *copypath;
+
+ copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename );
+ FS_CopyFile( netpath, copypath );
+ }
+
+ return FS_filelength (*file);
+ }
+ }
+
+ Com_DPrintf ("Can't find %s\n", filename);
+#ifdef FS_MISSING
+ if (missingFiles) {
+ fprintf(missingFiles, "%s\n", filename);
+ }
+#endif
+ *file = 0;
+ return -1;
+}
+
+
+/*
+=================
+FS_Read
+
+Properly handles partial reads
+=================
+*/
+int FS_Read2( void *buffer, int len, fileHandle_t f ) {
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !f ) {
+ return 0;
+ }
+ if (fsh[f].streamed) {
+ int r;
+ fsh[f].streamed = qfalse;
+ r = Sys_StreamedRead( buffer, len, 1, f);
+ fsh[f].streamed = qtrue;
+ return r;
+ } else {
+ return FS_Read( buffer, len, f);
+ }
+}
+
+int FS_Read( void *buffer, int len, fileHandle_t f ) {
+ int block, remaining;
+ int read;
+ byte *buf;
+ int tries;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !f ) {
+ return 0;
+ }
+
+ buf = (byte *)buffer;
+ fs_readCount += len;
+
+ if (fsh[f].zipFile == qfalse) {
+ remaining = len;
+ tries = 0;
+ while (remaining) {
+ block = remaining;
+ read = fread (buf, 1, block, fsh[f].handleFiles.file.o);
+ if (read == 0) {
+ // we might have been trying to read from a CD, which
+ // sometimes returns a 0 read on windows
+ if (!tries) {
+ tries = 1;
+ } else {
+ return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
+ }
+ }
+
+ if (read == -1) {
+ Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
+ }
+
+ remaining -= read;
+ buf += read;
+ }
+ return len;
+ } else {
+ return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len);
+ }
+}
+
+/*
+=================
+FS_Write
+
+Properly handles partial writes
+=================
+*/
+int FS_Write( const void *buffer, int len, fileHandle_t h ) {
+ int block, remaining;
+ int written;
+ byte *buf;
+ int tries;
+ FILE *f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !h ) {
+ return 0;
+ }
+
+ f = FS_FileForHandle(h);
+ buf = (byte *)buffer;
+
+ remaining = len;
+ tries = 0;
+ while (remaining) {
+ block = remaining;
+ written = fwrite (buf, 1, block, f);
+ if (written == 0) {
+ if (!tries) {
+ tries = 1;
+ } else {
+ Com_Printf( "FS_Write: 0 bytes written\n" );
+ return 0;
+ }
+ }
+
+ if (written == -1) {
+ Com_Printf( "FS_Write: -1 bytes written\n" );
+ return 0;
+ }
+
+ remaining -= written;
+ buf += written;
+ }
+ if ( fsh[h].handleSync ) {
+ fflush( f );
+ }
+ return len;
+}
+
+void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
+ va_list argptr;
+ char msg[MAXPRINTMSG];
+
+ va_start (argptr,fmt);
+ Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
+ va_end (argptr);
+
+ FS_Write(msg, strlen(msg), h);
+}
+
+/*
+=================
+FS_Seek
+
+=================
+*/
+int FS_Seek( fileHandle_t f, long offset, int origin ) {
+ int _origin;
+ char foo[65536];
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ return -1;
+ }
+
+ if (fsh[f].streamed) {
+ fsh[f].streamed = qfalse;
+ Sys_StreamSeek( f, offset, origin );
+ fsh[f].streamed = qtrue;
+ }
+
+ if (fsh[f].zipFile == qtrue) {
+ if (offset == 0 && origin == FS_SEEK_SET) {
+ // set the file position in the zip file (also sets the current file info)
+ unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
+ return unzOpenCurrentFile(fsh[f].handleFiles.file.z);
+ } else if (offset<65536) {
+ // set the file position in the zip file (also sets the current file info)
+ unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
+ unzOpenCurrentFile(fsh[f].handleFiles.file.z);
+ return FS_Read(foo, offset, f);
+ } else {
+ Com_Error( ERR_FATAL, "ZIP FILE FSEEK NOT YET IMPLEMENTED\n" );
+ return -1;
+ }
+ } else {
+ FILE *file;
+ file = FS_FileForHandle(f);
+ switch( origin ) {
+ case FS_SEEK_CUR:
+ _origin = SEEK_CUR;
+ break;
+ case FS_SEEK_END:
+ _origin = SEEK_END;
+ break;
+ case FS_SEEK_SET:
+ _origin = SEEK_SET;
+ break;
+ default:
+ _origin = SEEK_CUR;
+ Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
+ break;
+ }
+
+ return fseek( file, offset, _origin );
+ }
+}
+
+
+/*
+======================================================================================
+
+CONVENIENCE FUNCTIONS FOR ENTIRE FILES
+
+======================================================================================
+*/
+
+int FS_FileIsInPAK(const char *filename, int *pChecksum ) {
+ searchpath_t *search;
+ pack_t *pak;
+ fileInPack_t *pakFile;
+ long hash = 0;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !filename ) {
+ Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
+ }
+
+ // qpaths are not supposed to have a leading slash
+ if ( filename[0] == '/' || filename[0] == '\\' ) {
+ filename++;
+ }
+
+ // make absolutely sure that it can't back up the path.
+ // The searchpaths do guarantee that something will always
+ // be prepended, so we don't need to worry about "c:" or "//limbo"
+ if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
+ return -1;
+ }
+
+ //
+ // search through the path, one element at a time
+ //
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ //
+ if (search->pack) {
+ hash = FS_HashFileName(filename, search->pack->hashSize);
+ }
+ // is the element a pak file?
+ if ( search->pack && search->pack->hashTable[hash] ) {
+ // disregard if it doesn't match one of the allowed pure pak files
+ if ( !FS_PakIsPure(search->pack) ) {
+ continue;
+ }
+
+ // look through all the pak file elements
+ pak = search->pack;
+ pakFile = pak->hashTable[hash];
+ do {
+ // case and separator insensitive comparisons
+ if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
+ if (pChecksum) {
+ *pChecksum = pak->pure_checksum;
+ }
+ return 1;
+ }
+ pakFile = pakFile->next;
+ } while(pakFile != NULL);
+ }
+ }
+ return -1;
+}
+
+/*
+============
+FS_ReadFile
+
+Filename are relative to the quake search path
+a null buffer will just return the file length without loading
+============
+*/
+int FS_ReadFile( const char *qpath, void **buffer ) {
+ fileHandle_t h;
+ byte* buf;
+ qboolean isConfig;
+ int len;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !qpath || !qpath[0] ) {
+ Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
+ }
+
+ buf = NULL; // quiet compiler warning
+
+ // if this is a .cfg file and we are playing back a journal, read
+ // it from the journal file
+ if ( strstr( qpath, ".cfg" ) ) {
+ isConfig = qtrue;
+ if ( com_journal && com_journal->integer == 2 ) {
+ int r;
+
+ Com_DPrintf( "Loading %s from journal file.\n", qpath );
+ r = FS_Read( &len, sizeof( len ), com_journalDataFile );
+ if ( r != sizeof( len ) ) {
+ if (buffer != NULL) *buffer = NULL;
+ return -1;
+ }
+ // if the file didn't exist when the journal was created
+ if (!len) {
+ if (buffer == NULL) {
+ return 1; // hack for old journal files
+ }
+ *buffer = NULL;
+ return -1;
+ }
+ if (buffer == NULL) {
+ return len;
+ }
+
+ buf = Hunk_AllocateTempMemory(len+1);
+ *buffer = buf;
+
+ r = FS_Read( buf, len, com_journalDataFile );
+ if ( r != len ) {
+ Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
+ }
+
+ fs_loadCount++;
+ fs_loadStack++;
+
+ // guarantee that it will have a trailing 0 for string operations
+ buf[len] = 0;
+
+ return len;
+ }
+ } else {
+ isConfig = qfalse;
+ }
+
+ // look for it in the filesystem or pack files
+ len = FS_FOpenFileRead( qpath, &h, qfalse );
+ if ( h == 0 ) {
+ if ( buffer ) {
+ *buffer = NULL;
+ }
+ // if we are journalling and it is a config file, write a zero to the journal file
+ if ( isConfig && com_journal && com_journal->integer == 1 ) {
+ Com_DPrintf( "Writing zero for %s to journal file.\n", qpath );
+ len = 0;
+ FS_Write( &len, sizeof( len ), com_journalDataFile );
+ FS_Flush( com_journalDataFile );
+ }
+ return -1;
+ }
+
+ if ( !buffer ) {
+ if ( isConfig && com_journal && com_journal->integer == 1 ) {
+ Com_DPrintf( "Writing len for %s to journal file.\n", qpath );
+ FS_Write( &len, sizeof( len ), com_journalDataFile );
+ FS_Flush( com_journalDataFile );
+ }
+ FS_FCloseFile( h);
+ return len;
+ }
+
+ fs_loadCount++;
+ fs_loadStack++;
+
+ buf = Hunk_AllocateTempMemory(len+1);
+ *buffer = buf;
+
+ FS_Read (buf, len, h);
+
+ // guarantee that it will have a trailing 0 for string operations
+ buf[len] = 0;
+ FS_FCloseFile( h );
+
+ // if we are journalling and it is a config file, write it to the journal file
+ if ( isConfig && com_journal && com_journal->integer == 1 ) {
+ Com_DPrintf( "Writing %s to journal file.\n", qpath );
+ FS_Write( &len, sizeof( len ), com_journalDataFile );
+ FS_Write( buf, len, com_journalDataFile );
+ FS_Flush( com_journalDataFile );
+ }
+ return len;
+}
+
+/*
+=============
+FS_FreeFile
+=============
+*/
+void FS_FreeFile( void *buffer ) {
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+ if ( !buffer ) {
+ Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
+ }
+ fs_loadStack--;
+
+ Hunk_FreeTempMemory( buffer );
+
+ // if all of our temp files are free, clear all of our space
+ if ( fs_loadStack == 0 ) {
+ Hunk_ClearTempMemory();
+ }
+}
+
+/*
+============
+FS_WriteFile
+
+Filename are reletive to the quake search path
+============
+*/
+void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
+ fileHandle_t f;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !qpath || !buffer ) {
+ Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
+ }
+
+ f = FS_FOpenFileWrite( qpath );
+ if ( !f ) {
+ Com_Printf( "Failed to open %s\n", qpath );
+ return;
+ }
+
+ FS_Write( buffer, size, f );
+
+ FS_FCloseFile( f );
+}
+
+
+
+/*
+==========================================================================
+
+ZIP FILE LOADING
+
+==========================================================================
+*/
+
+/*
+=================
+FS_LoadZipFile
+
+Creates a new pak_t in the search chain for the contents
+of a zip file.
+=================
+*/
+static pack_t *FS_LoadZipFile( char *zipfile, const char *basename )
+{
+ fileInPack_t *buildBuffer;
+ pack_t *pack;
+ unzFile uf;
+ int err;
+ unz_global_info gi;
+ char filename_inzip[MAX_ZPATH];
+ unz_file_info file_info;
+ int i, len;
+ long hash;
+ int fs_numHeaderLongs;
+ int *fs_headerLongs;
+ char *namePtr;
+
+ fs_numHeaderLongs = 0;
+
+ uf = unzOpen(zipfile);
+ err = unzGetGlobalInfo (uf,&gi);
+
+ if (err != UNZ_OK)
+ return NULL;
+
+ fs_packFiles += gi.number_entry;
+
+ len = 0;
+ unzGoToFirstFile(uf);
+ for (i = 0; i < gi.number_entry; i++)
+ {
+ err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
+ if (err != UNZ_OK) {
+ break;
+ }
+ len += strlen(filename_inzip) + 1;
+ unzGoToNextFile(uf);
+ }
+
+ buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len );
+ namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t );
+ fs_headerLongs = Z_Malloc( gi.number_entry * sizeof(int) );
+
+ // get the hash table size from the number of files in the zip
+ // because lots of custom pk3 files have less than 32 or 64 files
+ for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) {
+ if (i > gi.number_entry) {
+ break;
+ }
+ }
+
+ pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) );
+ pack->hashSize = i;
+ pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t ));
+ for(i = 0; i < pack->hashSize; i++) {
+ pack->hashTable[i] = NULL;
+ }
+
+ Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) );
+ Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) );
+
+ // strip .pk3 if needed
+ if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) {
+ pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0;
+ }
+
+ pack->handle = uf;
+ pack->numfiles = gi.number_entry;
+ unzGoToFirstFile(uf);
+
+ for (i = 0; i < gi.number_entry; i++)
+ {
+ err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
+ if (err != UNZ_OK) {
+ break;
+ }
+ if (file_info.uncompressed_size > 0) {
+ fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
+ }
+ Q_strlwr( filename_inzip );
+ hash = FS_HashFileName(filename_inzip, pack->hashSize);
+ buildBuffer[i].name = namePtr;
+ strcpy( buildBuffer[i].name, filename_inzip );
+ namePtr += strlen(filename_inzip) + 1;
+ // store the file position in the zip
+ unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos);
+ //
+ buildBuffer[i].next = pack->hashTable[hash];
+ pack->hashTable[hash] = &buildBuffer[i];
+ unzGoToNextFile(uf);
+ }
+
+ pack->checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
+ pack->pure_checksum = Com_BlockChecksumKey( fs_headerLongs, 4 * fs_numHeaderLongs, LittleLong(fs_checksumFeed) );
+ pack->checksum = LittleLong( pack->checksum );
+ pack->pure_checksum = LittleLong( pack->pure_checksum );
+
+ Z_Free(fs_headerLongs);
+
+ pack->buildBuffer = buildBuffer;
+ return pack;
+}
+
+/*
+=================================================================================
+
+DIRECTORY SCANNING FUNCTIONS
+
+=================================================================================
+*/
+
+#define MAX_FOUND_FILES 0x1000
+
+static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) {
+ int len, at, newdep;
+
+ newdep = 0;
+ zpath[0] = 0;
+ len = 0;
+ at = 0;
+
+ while(zname[at] != 0)
+ {
+ if (zname[at]=='/' || zname[at]=='\\') {
+ len = at;
+ newdep++;
+ }
+ at++;
+ }
+ strcpy(zpath, zname);
+ zpath[len] = 0;
+ *depth = newdep;
+
+ return len;
+}
+
+/*
+==================
+FS_AddFileToList
+==================
+*/
+static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
+ int i;
+
+ if ( nfiles == MAX_FOUND_FILES - 1 ) {
+ return nfiles;
+ }
+ for ( i = 0 ; i < nfiles ; i++ ) {
+ if ( !Q_stricmp( name, list[i] ) ) {
+ return nfiles; // allready in list
+ }
+ }
+ list[nfiles] = CopyString( name );
+ nfiles++;
+
+ return nfiles;
+}
+
+/*
+===============
+FS_ListFilteredFiles
+
+Returns a uniqued list of files that match the given criteria
+from all search paths
+===============
+*/
+char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) {
+ int nfiles;
+ char **listCopy;
+ char *list[MAX_FOUND_FILES];
+ searchpath_t *search;
+ int i;
+ int pathLength;
+ int extensionLength;
+ int length, pathDepth, temp;
+ pack_t *pak;
+ fileInPack_t *buildBuffer;
+ char zpath[MAX_ZPATH];
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !path ) {
+ *numfiles = 0;
+ return NULL;
+ }
+ if ( !extension ) {
+ extension = "";
+ }
+
+ pathLength = strlen( path );
+ if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) {
+ pathLength--;
+ }
+ extensionLength = strlen( extension );
+ nfiles = 0;
+ FS_ReturnPath(path, zpath, &pathDepth);
+
+ //
+ // search through the path, one element at a time, adding to list
+ //
+ for (search = fs_searchpaths ; search ; search = search->next) {
+ // is the element a pak file?
+ if (search->pack) {
+
+ //ZOID: If we are pure, don't search for files on paks that
+ // aren't on the pure list
+ if ( !FS_PakIsPure(search->pack) ) {
+ continue;
+ }
+
+ // look through all the pak file elements
+ pak = search->pack;
+ buildBuffer = pak->buildBuffer;
+ for (i = 0; i < pak->numfiles; i++) {
+ char *name;
+ int zpathLen, depth;
+
+ // check for directory match
+ name = buildBuffer[i].name;
+ //
+ if (filter) {
+ // case insensitive
+ if (!Com_FilterPath( filter, name, qfalse ))
+ continue;
+ // unique the match
+ nfiles = FS_AddFileToList( name, list, nfiles );
+ }
+ else {
+
+ zpathLen = FS_ReturnPath(name, zpath, &depth);
+
+ if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) {
+ continue;
+ }
+
+ // check for extension match
+ length = strlen( name );
+ if ( length < extensionLength ) {
+ continue;
+ }
+
+ if ( Q_stricmp( name + length - extensionLength, extension ) ) {
+ continue;
+ }
+ // unique the match
+
+ temp = pathLength;
+ if (pathLength) {
+ temp++; // include the '/'
+ }
+ nfiles = FS_AddFileToList( name + temp, list, nfiles );
+ }
+ }
+ } else if (search->dir) { // scan for files in the filesystem
+ char *netpath;
+ int numSysFiles;
+ char **sysFiles;
+ char *name;
+
+ // don't scan directories for files if we are pure or restricted
+ if ( fs_restrict->integer || fs_numServerPaks ) {
+ continue;
+ } else {
+ netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path );
+ sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse );
+ for ( i = 0 ; i < numSysFiles ; i++ ) {
+ // unique the match
+ name = sysFiles[i];
+ nfiles = FS_AddFileToList( name, list, nfiles );
+ }
+ Sys_FreeFileList( sysFiles );
+ }
+ }
+ }
+
+ // return a copy of the list
+ *numfiles = nfiles;
+
+ if ( !nfiles ) {
+ return NULL;
+ }
+
+ listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
+ for ( i = 0 ; i < nfiles ; i++ ) {
+ listCopy[i] = list[i];
+ }
+ listCopy[i] = NULL;
+
+ return listCopy;
+}
+
+/*
+=================
+FS_ListFiles
+=================
+*/
+char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
+ return FS_ListFilteredFiles( path, extension, NULL, numfiles );
+}
+
+/*
+=================
+FS_FreeFileList
+=================
+*/
+void FS_FreeFileList( char **list ) {
+ int i;
+
+ if ( !fs_searchpaths ) {
+ Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
+ }
+
+ if ( !list ) {
+ return;
+ }
+
+ for ( i = 0 ; list[i] ; i++ ) {
+ Z_Free( list[i] );
+ }
+
+ Z_Free( list );
+}
+
+
+/*
+================
+FS_GetFileList
+================
+*/
+int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) {
+ int nFiles, i, nTotal, nLen;
+ char **pFiles = NULL;
+
+ *listbuf = 0;
+ nFiles = 0;
+ nTotal = 0;
+
+ if (Q_stricmp(path, "$modlist") == 0) {
+ return FS_GetModList(listbuf, bufsize);
+ }
+
+ pFiles = FS_ListFiles(path, extension, &nFiles);
+
+ for (i =0; i < nFiles; i++) {
+ nLen = strlen(pFiles[i]) + 1;
+ if (nTotal + nLen + 1 < bufsize) {
+ strcpy(listbuf, pFiles[i]);
+ listbuf += nLen;
+ nTotal += nLen;
+ }
+ else {
+ nFiles = i;
+ break;
+ }
+ }
+
+ FS_FreeFileList(pFiles);
+
+ return nFiles;
+}
+
+/*
+=======================
+Sys_ConcatenateFileLists
+
+mkv: Naive implementation. Concatenates three lists into a
+ new list, and frees the old lists from the heap.
+bk001129 - from cvs1.17 (mkv)
+
+FIXME TTimo those two should move to common.c next to Sys_ListFiles
+=======================
+ */
+static unsigned int Sys_CountFileList(char **list)
+{
+ int i = 0;
+
+ if (list)
+ {
+ while (*list)
+ {
+ list++;
+ i++;
+ }
+ }
+ return i;
+}
+
+static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 )
+{
+ int totalLength = 0;
+ char** cat = NULL, **dst, **src;
+
+ totalLength += Sys_CountFileList(list0);
+ totalLength += Sys_CountFileList(list1);
+ totalLength += Sys_CountFileList(list2);
+
+ /* Create new list. */
+ dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) );
+
+ /* Copy over lists. */
+ if (list0)
+ {
+ for (src = list0; *src; src++, dst++)
+ *dst = *src;
+ }
+ if (list1)
+ {
+ for (src = list1; *src; src++, dst++)
+ *dst = *src;
+ }
+ if (list2)
+ {
+ for (src = list2; *src; src++, dst++)
+ *dst = *src;
+ }
+
+ // Terminate the list
+ *dst = NULL;
+
+ // Free our old lists.
+ // NOTE: not freeing their content, it's been merged in dst and still being used
+ if (list0) Z_Free( list0 );
+ if (list1) Z_Free( list1 );
+ if (list2) Z_Free( list2 );
+
+ return cat;
+}
+
+/*
+================
+FS_GetModList
+
+Returns a list of mod directory names
+A mod directory is a peer to baseq3 with a pk3 in it
+The directories are searched in base path, cd path and home path
+================
+*/
+int FS_GetModList( char *listbuf, int bufsize ) {
+ int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen;
+ char **pFiles = NULL;
+ char **pPaks = NULL;
+ char *name, *path;
+ char descPath[MAX_OSPATH];
+ fileHandle_t descHandle;
+
+ int dummy;
+ char **pFiles0 = NULL;
+ char **pFiles1 = NULL;
+ char **pFiles2 = NULL;
+ qboolean bDrop = qfalse;
+
+ *listbuf = 0;
+ nMods = nPotential = nTotal = 0;
+
+ pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue );
+ pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue );
+ pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue );
+ // we searched for mods in the three paths
+ // it is likely that we have duplicate names now, which we will cleanup below
+ pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 );
+ nPotential = Sys_CountFileList(pFiles);
+
+ for ( i = 0 ; i < nPotential ; i++ ) {
+ name = pFiles[i];
+ // NOTE: cleaner would involve more changes
+ // ignore duplicate mod directories
+ if (i!=0) {
+ bDrop = qfalse;
+ for(j=0; j<i; j++)
+ {
+ if (Q_stricmp(pFiles[j],name)==0) {
+ // this one can be dropped
+ bDrop = qtrue;
+ break;
+ }
+ }
+ }
+ if (bDrop) {
+ continue;
+ }
+ // we drop "baseq3" "." and ".."
+ if (Q_stricmp(name, "baseq3") && Q_stricmpn(name, ".", 1)) {
+ // now we need to find some .pk3 files to validate the mod
+ // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?)
+ // we didn't keep the information when we merged the directory names, as to what OS Path it was found under
+ // so it could be in base path, cd path or home path
+ // we will try each three of them here (yes, it's a bit messy)
+ path = FS_BuildOSPath( fs_basepath->string, name, "" );
+ nPaks = 0;
+ pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse);
+ Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present
+
+ /* Try on cd path */
+ if( nPaks <= 0 ) {
+ path = FS_BuildOSPath( fs_cdpath->string, name, "" );
+ nPaks = 0;
+ pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
+ Sys_FreeFileList( pPaks );
+ }
+
+ /* try on home path */
+ if ( nPaks <= 0 )
+ {
+ path = FS_BuildOSPath( fs_homepath->string, name, "" );
+ nPaks = 0;
+ pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
+ Sys_FreeFileList( pPaks );
+ }
+
+ if (nPaks > 0) {
+ nLen = strlen(name) + 1;
+ // nLen is the length of the mod path
+ // we need to see if there is a description available
+ descPath[0] = '\0';
+ strcpy(descPath, name);
+ strcat(descPath, "/description.txt");
+ nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle );
+ if ( nDescLen > 0 && descHandle) {
+ FILE *file;
+ file = FS_FileForHandle(descHandle);
+ Com_Memset( descPath, 0, sizeof( descPath ) );
+ nDescLen = fread(descPath, 1, 48, file);
+ if (nDescLen >= 0) {
+ descPath[nDescLen] = '\0';
+ }
+ FS_FCloseFile(descHandle);
+ } else {
+ strcpy(descPath, name);
+ }
+ nDescLen = strlen(descPath) + 1;
+
+ if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) {
+ strcpy(listbuf, name);
+ listbuf += nLen;
+ strcpy(listbuf, descPath);
+ listbuf += nDescLen;
+ nTotal += nLen + nDescLen;
+ nMods++;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+ Sys_FreeFileList( pFiles );
+
+ return nMods;
+}
+
+
+
+
+//============================================================================
+
+/*
+================
+FS_Dir_f
+================
+*/
+void FS_Dir_f( void ) {
+ char *path;
+ char *extension;
+ char **dirnames;
+ int ndirs;
+ int i;
+
+ if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) {
+ Com_Printf( "usage: dir <directory> [extension]\n" );
+ return;
+ }
+
+ if ( Cmd_Argc() == 2 ) {
+ path = Cmd_Argv( 1 );
+ extension = "";
+ } else {
+ path = Cmd_Argv( 1 );
+ extension = Cmd_Argv( 2 );
+ }
+
+ Com_Printf( "Directory of %s %s\n", path, extension );
+ Com_Printf( "---------------\n" );
+
+ dirnames = FS_ListFiles( path, extension, &ndirs );
+
+ for ( i = 0; i < ndirs; i++ ) {
+ Com_Printf( "%s\n", dirnames[i] );
+ }
+ FS_FreeFileList( dirnames );
+}
+
+/*
+===========
+FS_ConvertPath
+===========
+*/
+void FS_ConvertPath( char *s ) {
+ while (*s) {
+ if ( *s == '\\' || *s == ':' ) {
+ *s = '/';
+ }
+ s++;
+ }
+}
+
+/*
+===========
+FS_PathCmp
+
+Ignore case and seprator char distinctions
+===========
+*/
+int FS_PathCmp( const char *s1, const char *s2 ) {
+ int c1, c2;
+
+ do {
+ c1 = *s1++;
+ c2 = *s2++;
+
+ if (c1 >= 'a' && c1 <= 'z') {
+ c1 -= ('a' - 'A');
+ }
+ if (c2 >= 'a' && c2 <= 'z') {
+ c2 -= ('a' - 'A');
+ }
+
+ if ( c1 == '\\' || c1 == ':' ) {
+ c1 = '/';
+ }
+ if ( c2 == '\\' || c2 == ':' ) {
+ c2 = '/';
+ }
+
+ if (c1 < c2) {
+ return -1; // strings not equal
+ }
+ if (c1 > c2) {
+ return 1;
+ }
+ } while (c1);
+
+ return 0; // strings are equal
+}
+
+/*
+================
+FS_SortFileList
+================
+*/
+void FS_SortFileList(char **filelist, int numfiles) {
+ int i, j, k, numsortedfiles;
+ char **sortedlist;
+
+ sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) );
+ sortedlist[0] = NULL;
+ numsortedfiles = 0;
+ for (i = 0; i < numfiles; i++) {
+ for (j = 0; j < numsortedfiles; j++) {
+ if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) {
+ break;
+ }
+ }
+ for (k = numsortedfiles; k > j; k--) {
+ sortedlist[k] = sortedlist[k-1];
+ }
+ sortedlist[j] = filelist[i];
+ numsortedfiles++;
+ }
+ Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) );
+ Z_Free(sortedlist);
+}
+
+/*
+================
+FS_NewDir_f
+================
+*/
+void FS_NewDir_f( void ) {
+ char *filter;
+ char **dirnames;
+ int ndirs;
+ int i;
+
+ if ( Cmd_Argc() < 2 ) {
+ Com_Printf( "usage: fdir <filter>\n" );
+ Com_Printf( "example: fdir *q3dm*.bsp\n");
+ return;
+ }
+
+ filter = Cmd_Argv( 1 );
+
+ Com_Printf( "---------------\n" );
+
+ dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs );
+
+ FS_SortFileList(dirnames, ndirs);
+
+ for ( i = 0; i < ndirs; i++ ) {
+ FS_ConvertPath(dirnames[i]);
+ Com_Printf( "%s\n", dirnames[i] );
+ }
+ Com_Printf( "%d files listed\n", ndirs );
+ FS_FreeFileList( dirnames );
+}
+
+/*
+============
+FS_Path_f
+
+============
+*/
+void FS_Path_f( void ) {
+ searchpath_t *s;
+ int i;
+
+ Com_Printf ("Current search path:\n");
+ for (s = fs_searchpaths; s; s = s->next) {
+ if (s->pack) {
+ Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles);
+ if ( fs_numServerPaks ) {
+ if ( !FS_PakIsPure(s->pack) ) {
+ Com_Printf( " not on the pure list\n" );
+ } else {
+ Com_Printf( " on the pure list\n" );
+ }
+ }
+ } else {
+ Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir );
+ }
+ }
+
+
+ Com_Printf( "\n" );
+ for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
+ if ( fsh[i].handleFiles.file.o ) {
+ Com_Printf( "handle %i: %s\n", i, fsh[i].name );
+ }
+ }
+}
+
+/*
+============
+FS_TouchFile_f
+
+The only purpose of this function is to allow game script files to copy
+arbitrary files furing an "fs_copyfiles 1" run.
+============
+*/
+void FS_TouchFile_f( void ) {
+ fileHandle_t f;
+
+ if ( Cmd_Argc() != 2 ) {
+ Com_Printf( "Usage: touchFile <file>\n" );
+ return;
+ }
+
+ FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse );
+ if ( f ) {
+ FS_FCloseFile( f );
+ }
+}
+
+//===========================================================================
+
+
+static int QDECL paksort( const void *a, const void *b ) {
+ char *aa, *bb;
+
+ aa = *(char **)a;
+ bb = *(char **)b;
+
+ return FS_PathCmp( aa, bb );
+}
+
+/*
+================
+FS_AddGameDirectory
+
+Sets fs_gamedir, adds the directory to the head of the path,
+then loads the zip headers
+================
+*/
+#define MAX_PAKFILES 1024
+static void FS_AddGameDirectory( const char *path, const char *dir ) {
+ searchpath_t *sp;
+ int i;
+ searchpath_t *search;
+ pack_t *pak;
+ char *pakfile;
+ int numfiles;
+ char **pakfiles;
+ char *sorted[MAX_PAKFILES];
+
+ // this fixes the case where fs_basepath is the same as fs_cdpath
+ // which happens on full installs
+ for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
+ if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) {
+ return; // we've already got this one
+ }
+ }
+
+ Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) );
+
+ //
+ // add the directory to the search path
+ //
+ search = Z_Malloc (sizeof(searchpath_t));
+ search->dir = Z_Malloc( sizeof( *search->dir ) );
+
+ Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) );
+ Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) );
+ search->next = fs_searchpaths;
+ fs_searchpaths = search;
+
+ // find all pak files in this directory
+ pakfile = FS_BuildOSPath( path, dir, "" );
+ pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash
+
+ pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse );
+
+ // sort them so that later alphabetic matches override
+ // earlier ones. This makes pak1.pk3 override pak0.pk3
+ if ( numfiles > MAX_PAKFILES ) {
+ numfiles = MAX_PAKFILES;
+ }
+ for ( i = 0 ; i < numfiles ; i++ ) {
+ sorted[i] = pakfiles[i];
+ }
+
+ qsort( sorted, numfiles, 4, paksort );
+
+ for ( i = 0 ; i < numfiles ; i++ ) {
+ pakfile = FS_BuildOSPath( path, dir, sorted[i] );
+ if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 )
+ continue;
+ // store the game name for downloading
+ strcpy(pak->pakGamename, dir);
+
+ search = Z_Malloc (sizeof(searchpath_t));
+ search->pack = pak;
+ search->next = fs_searchpaths;
+ fs_searchpaths = search;
+ }
+
+ // done
+ Sys_FreeFileList( pakfiles );
+}
+
+/*
+================
+FS_idPak
+================
+*/
+qboolean FS_idPak( char *pak, char *base ) {
+ int i;
+
+ for (i = 0; i < NUM_ID_PAKS; i++) {
+ if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) {
+ break;
+ }
+ }
+ if (i < NUM_ID_PAKS) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+================
+FS_ComparePaks
+
+----------------
+dlstring == qtrue
+
+Returns a list of pak files that we should download from the server. They all get stored
+in the current gamedir and an FS_Restart will be fired up after we download them all.
+
+The string is the format:
+
+@remotename@localname [repeat]
+
+static int fs_numServerReferencedPaks;
+static int fs_serverReferencedPaks[MAX_SEARCH_PATHS];
+static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS];
+
+----------------
+dlstring == qfalse
+
+we are not interested in a download string format, we want something human-readable
+(this is used for diagnostics while connecting to a pure server)
+
+================
+*/
+qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
+ searchpath_t *sp;
+ qboolean havepak, badchecksum;
+ int i;
+
+ if ( !fs_numServerReferencedPaks ) {
+ return qfalse; // Server didn't send any pack information along
+ }
+
+ *neededpaks = 0;
+
+ for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) {
+ // Ok, see if we have this pak file
+ badchecksum = qfalse;
+ havepak = qfalse;
+
+ // never autodownload any of the id paks
+ if ( FS_idPak(fs_serverReferencedPakNames[i], "baseq3") || FS_idPak(fs_serverReferencedPakNames[i], "missionpack") ) {
+ continue;
+ }
+
+ for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
+ if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) {
+ havepak = qtrue; // This is it!
+ break;
+ }
+ }
+
+ if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) {
+ // Don't got it
+
+ if (dlstring)
+ {
+ // Remote name
+ Q_strcat( neededpaks, len, "@");
+ Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
+ Q_strcat( neededpaks, len, ".pk3" );
+
+ // Local name
+ Q_strcat( neededpaks, len, "@");
+ // Do we have one with the same name?
+ if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
+ {
+ char st[MAX_ZPATH];
+ // We already have one called this, we need to download it to another name
+ // Make something up with the checksum in it
+ Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] );
+ Q_strcat( neededpaks, len, st );
+ } else
+ {
+ Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
+ Q_strcat( neededpaks, len, ".pk3" );
+ }
+ }
+ else
+ {
+ Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
+ Q_strcat( neededpaks, len, ".pk3" );
+ // Do we have one with the same name?
+ if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
+ {
+ Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
+ }
+ Q_strcat( neededpaks, len, "\n");
+ }
+ }
+ }
+
+ if ( *neededpaks ) {
+ return qtrue;
+ }
+
+ return qfalse; // We have them all
+}
+
+/*
+================
+FS_Shutdown
+
+Frees all resources and closes all files
+================
+*/
+void FS_Shutdown( qboolean closemfp ) {
+ searchpath_t *p, *next;
+ int i;
+
+ for(i = 0; i < MAX_FILE_HANDLES; i++) {
+ if (fsh[i].fileSize) {
+ FS_FCloseFile(i);
+ }
+ }
+
+ // free everything
+ for ( p = fs_searchpaths ; p ; p = next ) {
+ next = p->next;
+
+ if ( p->pack ) {
+ unzClose(p->pack->handle);
+ Z_Free( p->pack->buildBuffer );
+ Z_Free( p->pack );
+ }
+ if ( p->dir ) {
+ Z_Free( p->dir );
+ }
+ Z_Free( p );
+ }
+
+ // any FS_ calls will now be an error until reinitialized
+ fs_searchpaths = NULL;
+
+ Cmd_RemoveCommand( "path" );
+ Cmd_RemoveCommand( "dir" );
+ Cmd_RemoveCommand( "fdir" );
+ Cmd_RemoveCommand( "touchFile" );
+
+#ifdef FS_MISSING
+ if (closemfp) {
+ fclose(missingFiles);
+ }
+#endif
+}
+
+void Com_AppendCDKey( const char *filename );
+void Com_ReadCDKey( const char *filename );
+
+/*
+================
+FS_ReorderPurePaks
+NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*)
+ this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
+================
+*/
+static void FS_ReorderPurePaks()
+{
+ searchpath_t *s;
+ int i;
+ searchpath_t **p_insert_index, // for linked list reordering
+ **p_previous; // when doing the scan
+
+ // only relevant when connected to pure server
+ if ( !fs_numServerPaks )
+ return;
+
+ fs_reordered = qfalse;
+
+ p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list
+ for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
+ p_previous = p_insert_index; // track the pointer-to-current-item
+ for (s = *p_insert_index; s; s = s->next) {
+ // the part of the list before p_insert_index has been sorted already
+ if (s->pack && fs_serverPaks[i] == s->pack->checksum) {
+ fs_reordered = qtrue;
+ // move this element to the insert list
+ *p_previous = s->next;
+ s->next = *p_insert_index;
+ *p_insert_index = s;
+ // increment insert list
+ p_insert_index = &s->next;
+ break; // iterate to next server pack
+ }
+ p_previous = &s->next;
+ }
+ }
+}
+
+/*
+================
+FS_Startup
+================
+*/
+static void FS_Startup( const char *gameName ) {
+ const char *homePath;
+ cvar_t *fs;
+
+ Com_Printf( "----- FS_Startup -----\n" );
+
+ fs_debug = Cvar_Get( "fs_debug", "0", 0 );
+ fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT );
+ fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT );
+ fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT );
+ fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
+ homePath = Sys_DefaultHomePath();
+ if (!homePath || !homePath[0]) {
+ homePath = fs_basepath->string;
+ }
+ fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT );
+ fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
+ fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT );
+
+ // add search path elements in reverse priority order
+ if (fs_cdpath->string[0]) {
+ FS_AddGameDirectory( fs_cdpath->string, gameName );
+ }
+ if (fs_basepath->string[0]) {
+ FS_AddGameDirectory( fs_basepath->string, gameName );
+ }
+ // fs_homepath is somewhat particular to *nix systems, only add if relevant
+ // NOTE: same filtering below for mods and basegame
+ if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
+ FS_AddGameDirectory ( fs_homepath->string, gameName );
+ }
+
+ // check for additional base game so mods can be based upon other mods
+ if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) {
+ if (fs_cdpath->string[0]) {
+ FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string);
+ }
+ if (fs_basepath->string[0]) {
+ FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
+ }
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
+ FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
+ }
+ }
+
+ // check for additional game folder for mods
+ if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) {
+ if (fs_cdpath->string[0]) {
+ FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string);
+ }
+ if (fs_basepath->string[0]) {
+ FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
+ }
+ if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
+ FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
+ }
+ }
+
+ Com_ReadCDKey( "baseq3" );
+ fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
+ if (fs && fs->string[0] != 0) {
+ Com_AppendCDKey( fs->string );
+ }
+
+ // add our commands
+ Cmd_AddCommand ("path", FS_Path_f);
+ Cmd_AddCommand ("dir", FS_Dir_f );
+ Cmd_AddCommand ("fdir", FS_NewDir_f );
+ Cmd_AddCommand ("touchFile", FS_TouchFile_f );
+
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506
+ // reorder the pure pk3 files according to server order
+ FS_ReorderPurePaks();
+
+ // print the current search paths
+ FS_Path_f();
+
+ fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
+
+ Com_Printf( "----------------------\n" );
+
+#ifdef FS_MISSING
+ if (missingFiles == NULL) {
+ missingFiles = fopen( "\\missing.txt", "ab" );
+ }
+#endif
+ Com_Printf( "%d files in pk3 files\n", fs_packFiles );
+}
+
+
+/*
+===================
+FS_SetRestrictions
+
+Looks for product keys and restricts media add on ability
+if the full version is not found
+===================
+*/
+static void FS_SetRestrictions( void ) {
+ searchpath_t *path;
+
+#ifndef PRE_RELEASE_DEMO
+ char *productId;
+
+ // if fs_restrict is set, don't even look for the id file,
+ // which allows the demo release to be tested even if
+ // the full game is present
+ if ( !fs_restrict->integer ) {
+ // look for the full game id
+ FS_ReadFile( "productid.txt", (void **)&productId );
+ if ( productId ) {
+ // check against the hardcoded string
+ int seed, i;
+
+ seed = 5000;
+ for ( i = 0 ; i < sizeof( fs_scrambledProductId ) ; i++ ) {
+ if ( ( fs_scrambledProductId[i] ^ (seed&255) ) != productId[i] ) {
+ break;
+ }
+ seed = (69069 * seed + 1);
+ }
+
+ FS_FreeFile( productId );
+
+ if ( i == sizeof( fs_scrambledProductId ) ) {
+ return; // no restrictions
+ }
+ Com_Error( ERR_FATAL, "Invalid product identification" );
+ }
+ }
+#endif
+ Cvar_Set( "fs_restrict", "1" );
+
+ Com_Printf( "\nRunning in restricted demo mode.\n\n" );
+
+ // restart the filesystem with just the demo directory
+ FS_Shutdown(qfalse);
+ FS_Startup( DEMOGAME );
+
+ // make sure that the pak file has the header checksum we expect
+ for ( path = fs_searchpaths ; path ; path = path->next ) {
+ if ( path->pack ) {
+ // a tiny attempt to keep the checksum from being scannable from the exe
+ if ( (path->pack->checksum ^ 0x02261994u) != (DEMO_PAK_CHECKSUM ^ 0x02261994u) ) {
+ Com_Error( ERR_FATAL, "Corrupted pak0.pk3: %u", path->pack->checksum );
+ }
+ }
+ }
+}
+
+/*
+=====================
+FS_GamePureChecksum
+
+Returns the checksum of the pk3 from which the server loaded the qagame.qvm
+=====================
+*/
+const char *FS_GamePureChecksum( void ) {
+ static char info[MAX_STRING_TOKENS];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( search->pack ) {
+ if (search->pack->referenced & FS_QAGAME_REF) {
+ Com_sprintf(info, sizeof(info), "%d", search->pack->checksum);
+ }
+ }
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_LoadedPakChecksums
+
+Returns a space separated string containing the checksums of all loaded pk3 files.
+Servers with sv_pure set will get this string and pass it to clients.
+=====================
+*/
+const char *FS_LoadedPakChecksums( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( !search->pack ) {
+ continue;
+ }
+
+ Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_LoadedPakNames
+
+Returns a space separated string containing the names of all loaded pk3 files.
+Servers with sv_pure set will get this string and pass it to clients.
+=====================
+*/
+const char *FS_LoadedPakNames( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( !search->pack ) {
+ continue;
+ }
+
+ if (*info) {
+ Q_strcat(info, sizeof( info ), " " );
+ }
+ Q_strcat( info, sizeof( info ), search->pack->pakBasename );
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_LoadedPakPureChecksums
+
+Returns a space separated string containing the pure checksums of all loaded pk3 files.
+Servers with sv_pure use these checksums to compare with the checksums the clients send
+back to the server.
+=====================
+*/
+const char *FS_LoadedPakPureChecksums( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( !search->pack ) {
+ continue;
+ }
+
+ Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_ReferencedPakChecksums
+
+Returns a space separated string containing the checksums of all referenced pk3 files.
+The server will send this to the clients so they can check which files should be auto-downloaded.
+=====================
+*/
+const char *FS_ReferencedPakChecksums( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( search->pack ) {
+ if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
+ Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
+ }
+ }
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_ReferencedPakPureChecksums
+
+Returns a space separated string containing the pure checksums of all referenced pk3 files.
+Servers with sv_pure set will get this string back from clients for pure validation
+
+The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..."
+=====================
+*/
+const char *FS_ReferencedPakPureChecksums( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+ int nFlags, numPaks, checksum;
+
+ info[0] = 0;
+
+ checksum = fs_checksumFeed;
+ numPaks = 0;
+ for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) {
+ if (nFlags & FS_GENERAL_REF) {
+ // add a delimter between must haves and general refs
+ //Q_strcat(info, sizeof(info), "@ ");
+ info[strlen(info)+1] = '\0';
+ info[strlen(info)+2] = '\0';
+ info[strlen(info)] = '@';
+ info[strlen(info)] = ' ';
+ }
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file and has it been referenced based on flag?
+ if ( search->pack && (search->pack->referenced & nFlags)) {
+ Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
+ if (nFlags & (FS_CGAME_REF | FS_UI_REF)) {
+ break;
+ }
+ checksum ^= search->pack->pure_checksum;
+ numPaks++;
+ }
+ }
+ if (fs_fakeChkSum != 0) {
+ // only added if a non-pure file is referenced
+ Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) );
+ }
+ }
+ // last checksum is the encoded number of referenced pk3s
+ checksum ^= numPaks;
+ Q_strcat( info, sizeof( info ), va("%i ", checksum ) );
+
+ return info;
+}
+
+/*
+=====================
+FS_ReferencedPakNames
+
+Returns a space separated string containing the names of all referenced pk3 files.
+The server will send this to the clients so they can check which files should be auto-downloaded.
+=====================
+*/
+const char *FS_ReferencedPakNames( void ) {
+ static char info[BIG_INFO_STRING];
+ searchpath_t *search;
+
+ info[0] = 0;
+
+ // we want to return ALL pk3's from the fs_game path
+ // and referenced one's from baseq3
+ for ( search = fs_searchpaths ; search ; search = search->next ) {
+ // is the element a pak file?
+ if ( search->pack ) {
+ if (*info) {
+ Q_strcat(info, sizeof( info ), " " );
+ }
+ if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
+ Q_strcat( info, sizeof( info ), search->pack->pakGamename );
+ Q_strcat( info, sizeof( info ), "/" );
+ Q_strcat( info, sizeof( info ), search->pack->pakBasename );
+ }
+ }
+ }
+
+ return info;
+}
+
+/*
+=====================
+FS_ClearPakReferences
+=====================
+*/
+void FS_ClearPakReferences( int flags ) {
+ searchpath_t *search;
+
+ if ( !flags ) {
+ flags = -1;
+ }
+ for ( search = fs_searchpaths; search; search = search->next ) {
+ // is the element a pak file and has it been referenced?
+ if ( search->pack ) {
+ search->pack->referenced &= ~flags;
+ }
+ }
+}
+
+
+/*
+=====================
+FS_PureServerSetLoadedPaks
+
+If the string is empty, all data sources will be allowed.
+If not empty, only pk3 files that match one of the space
+separated checksums will be checked for files, with the
+exception of .cfg and .dat files.
+=====================
+*/
+void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) {
+ int i, c, d;
+
+ Cmd_TokenizeString( pakSums );
+
+ c = Cmd_Argc();
+ if ( c > MAX_SEARCH_PATHS ) {
+ c = MAX_SEARCH_PATHS;
+ }
+
+ fs_numServerPaks = c;
+
+ for ( i = 0 ; i < c ; i++ ) {
+ fs_serverPaks[i] = atoi( Cmd_Argv( i ) );
+ }
+
+ if (fs_numServerPaks) {
+ Com_DPrintf( "Connected to a pure server.\n" );
+ }
+ else
+ {
+ if (fs_reordered)
+ {
+ // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
+ // force a restart to make sure the search order will be correct
+ Com_DPrintf( "FS search reorder is required\n" );
+ FS_Restart(fs_checksumFeed);
+ return;
+ }
+ }
+
+ for ( i = 0 ; i < c ; i++ ) {
+ if (fs_serverPakNames[i]) {
+ Z_Free(fs_serverPakNames[i]);
+ }
+ fs_serverPakNames[i] = NULL;
+ }
+ if ( pakNames && *pakNames ) {
+ Cmd_TokenizeString( pakNames );
+
+ d = Cmd_Argc();
+ if ( d > MAX_SEARCH_PATHS ) {
+ d = MAX_SEARCH_PATHS;
+ }
+
+ for ( i = 0 ; i < d ; i++ ) {
+ fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) );
+ }
+ }
+}
+
+/*
+=====================
+FS_PureServerSetReferencedPaks
+
+The checksums and names of the pk3 files referenced at the server
+are sent to the client and stored here. The client will use these
+checksums to see if any pk3 files need to be auto-downloaded.
+=====================
+*/
+void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
+ int i, c, d;
+
+ Cmd_TokenizeString( pakSums );
+
+ c = Cmd_Argc();
+ if ( c > MAX_SEARCH_PATHS ) {
+ c = MAX_SEARCH_PATHS;
+ }
+
+ fs_numServerReferencedPaks = c;
+
+ for ( i = 0 ; i < c ; i++ ) {
+ fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
+ }
+
+ for ( i = 0 ; i < c ; i++ ) {
+ if (fs_serverReferencedPakNames[i]) {
+ Z_Free(fs_serverReferencedPakNames[i]);
+ }
+ fs_serverReferencedPakNames[i] = NULL;
+ }
+ if ( pakNames && *pakNames ) {
+ Cmd_TokenizeString( pakNames );
+
+ d = Cmd_Argc();
+ if ( d > MAX_SEARCH_PATHS ) {
+ d = MAX_SEARCH_PATHS;
+ }
+
+ for ( i = 0 ; i < d ; i++ ) {
+ fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
+ }
+ }
+}
+
+/*
+================
+FS_InitFilesystem
+
+Called only at inital startup, not when the filesystem
+is resetting due to a game change
+================
+*/
+void FS_InitFilesystem( void ) {
+ // allow command line parms to override our defaults
+ // we have to specially handle this, because normal command
+ // line variable sets don't happen until after the filesystem
+ // has already been initialized
+ Com_StartupVariable( "fs_cdpath" );
+ Com_StartupVariable( "fs_basepath" );
+ Com_StartupVariable( "fs_homepath" );
+ Com_StartupVariable( "fs_game" );
+ Com_StartupVariable( "fs_copyfiles" );
+ Com_StartupVariable( "fs_restrict" );
+
+ // try to start up normally
+ FS_Startup( BASEGAME );
+
+ // see if we are going to allow add-ons
+ FS_SetRestrictions();
+
+ // if we can't find default.cfg, assume that the paths are
+ // busted and error out now, rather than getting an unreadable
+ // graphics screen when the font fails to load
+ if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
+ Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
+ // bk001208 - SafeMode see below, FIXME?
+ }
+
+ Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
+ Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
+
+ // bk001208 - SafeMode see below, FIXME?
+}
+
+
+/*
+================
+FS_Restart
+================
+*/
+void FS_Restart( int checksumFeed ) {
+
+ // free anything we currently have loaded
+ FS_Shutdown(qfalse);
+
+ // set the checksum feed
+ fs_checksumFeed = checksumFeed;
+
+ // clear pak references
+ FS_ClearPakReferences(0);
+
+ // try to start up normally
+ FS_Startup( BASEGAME );
+
+ // see if we are going to allow add-ons
+ FS_SetRestrictions();
+
+ // if we can't find default.cfg, assume that the paths are
+ // busted and error out now, rather than getting an unreadable
+ // graphics screen when the font fails to load
+ if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
+ // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3
+ // (for instance a TA demo server)
+ if (lastValidBase[0]) {
+ FS_PureServerSetLoadedPaks("", "");
+ Cvar_Set("fs_basepath", lastValidBase);
+ Cvar_Set("fs_gamedirvar", lastValidGame);
+ lastValidBase[0] = '\0';
+ lastValidGame[0] = '\0';
+ Cvar_Set( "fs_restrict", "0" );
+ FS_Restart(checksumFeed);
+ Com_Error( ERR_DROP, "Invalid game folder\n" );
+ return;
+ }
+ Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
+ }
+
+ // bk010116 - new check before safeMode
+ if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) {
+ // skip the q3config.cfg if "safe" is on the command line
+ if ( !Com_SafeMode() ) {
+ Cbuf_AddText ("exec q3config.cfg\n");
+ }
+ }
+
+ Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
+ Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
+
+}
+
+/*
+=================
+FS_ConditionalRestart
+restart if necessary
+=================
+*/
+qboolean FS_ConditionalRestart( int checksumFeed ) {
+ if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) {
+ FS_Restart( checksumFeed );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+========================================================================================
+
+Handle based file calls for virtual machines
+
+========================================================================================
+*/
+
+int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
+ int r;
+ qboolean sync;
+
+ sync = qfalse;
+
+ switch( mode ) {
+ case FS_READ:
+ r = FS_FOpenFileRead( qpath, f, qtrue );
+ break;
+ case FS_WRITE:
+ *f = FS_FOpenFileWrite( qpath );
+ r = 0;
+ if (*f == 0) {
+ r = -1;
+ }
+ break;
+ case FS_APPEND_SYNC:
+ sync = qtrue;
+ case FS_APPEND:
+ *f = FS_FOpenFileAppend( qpath );
+ r = 0;
+ if (*f == 0) {
+ r = -1;
+ }
+ break;
+ default:
+ Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
+ return -1;
+ }
+
+ if (!f) {
+ return r;
+ }
+
+ if ( *f ) {
+ if (fsh[*f].zipFile == qtrue) {
+ fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z);
+ } else {
+ fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o);
+ }
+ fsh[*f].fileSize = r;
+ fsh[*f].streamed = qfalse;
+
+ if (mode == FS_READ) {
+ Sys_BeginStreamedFile( *f, 0x4000 );
+ fsh[*f].streamed = qtrue;
+ }
+ }
+ fsh[*f].handleSync = sync;
+
+ return r;
+}
+
+int FS_FTell( fileHandle_t f ) {
+ int pos;
+ if (fsh[f].zipFile == qtrue) {
+ pos = unztell(fsh[f].handleFiles.file.z);
+ } else {
+ pos = ftell(fsh[f].handleFiles.file.o);
+ }
+ return pos;
+}
+
+void FS_Flush( fileHandle_t f ) {
+ fflush(fsh[f].handleFiles.file.o);
+}
+