diff options
Diffstat (limited to 'ioq3-r437/src/qcommon/files.c')
-rw-r--r-- | ioq3-r437/src/qcommon/files.c | 3440 |
1 files changed, 0 insertions, 3440 deletions
diff --git a/ioq3-r437/src/qcommon/files.c b/ioq3-r437/src/qcommon/files.c deleted file mode 100644 index ebf19b16..00000000 --- a/ioq3-r437/src/qcommon/files.c +++ /dev/null @@ -1,3440 +0,0 @@ -/* -=========================================================================== -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 Quake III Arena source code; 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 "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( void ) { - 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( void ) -{ - 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 qtrue; // strings not equal - } - } while (c1); - - return qfalse; // 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); -} - -#define PK3_SEEK_BUFFER_SIZE 65536 - -/* -================= -FS_Seek - -================= -*/ -int FS_Seek( fileHandle_t f, long offset, int origin ) { - int _origin; - - 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) { - //FIXME: this is incomplete and really, really - //crappy (but better than what was here before) - byte buffer[PK3_SEEK_BUFFER_SIZE]; - int remainder = offset; - - if( offset < 0 || origin == FS_SEEK_END ) { - Com_Error( ERR_FATAL, "Negative offsets and FS_SEEK_END not implemented " - "for FS_Seek on pk3 file contents\n" ); - return -1; - } - - switch( origin ) { - case FS_SEEK_SET: - unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); - unzOpenCurrentFile(fsh[f].handleFiles.file.z); - //fallthrough - - case FS_SEEK_CUR: - while( remainder > PK3_SEEK_BUFFER_SIZE ) { - FS_Read( buffer, PK3_SEEK_BUFFER_SIZE, f ); - remainder -= PK3_SEEK_BUFFER_SIZE; - } - FS_Read( buffer, remainder, f ); - return offset; - break; - - default: - Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); - return -1; - break; - } - } 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, sizeof(char*), 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( void ) -{ - 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; - char *productId; - - return; - -#ifndef PRE_RELEASE_DEMO - - // 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); -} - |