Main Page | Class Hierarchy | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

files.c

Go to the documentation of this file.
00001 /*
00002 ===========================================================================
00003 Copyright (C) 1999-2005 Id Software, Inc.
00004 
00005 This file is part of Quake III Arena source code.
00006 
00007 Quake III Arena source code is free software; you can redistribute it
00008 and/or modify it under the terms of the GNU General Public License as
00009 published by the Free Software Foundation; either version 2 of the License,
00010 or (at your option) any later version.
00011 
00012 Quake III Arena source code is distributed in the hope that it will be
00013 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Foobar; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 ===========================================================================
00021 */
00022 /*****************************************************************************
00023  * name:        files.c
00024  *
00025  * desc:        handle based filesystem for Quake III Arena 
00026  *
00027  * $Archive: /MissionPack/code/qcommon/files.c $
00028  *
00029  *****************************************************************************/
00030 
00031 
00032 #include "../game/q_shared.h"
00033 #include "qcommon.h"
00034 #include "unzip.h"
00035 
00036 /*
00037 =============================================================================
00038 
00039 QUAKE3 FILESYSTEM
00040 
00041 All of Quake's data access is through a hierarchical file system, but the contents of 
00042 the file system can be transparently merged from several sources.
00043 
00044 A "qpath" is a reference to game file data.  MAX_ZPATH is 256 characters, which must include
00045 a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
00046 references outside the quake directory system.
00047 
00048 The "base path" is the path to the directory holding all the game directories and usually
00049 the executable.  It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
00050 command line to allow code debugging in a different directory.  Basepath cannot
00051 be modified at all after startup.  Any files that are created (demos, screenshots,
00052 etc) will be created reletive to the base path, so base path should usually be writable.
00053 
00054 The "cd path" is the path to an alternate hierarchy that will be searched if a file
00055 is not located in the base path.  A user can do a partial install that copies some
00056 data to a base path created on their hard drive and leave the rest on the cd.  Files
00057 are never writen to the cd path.  It defaults to a value set by the installer, like
00058 "e:\quake3", but it can be overridden with "+set ds_cdpath g:\quake3".
00059 
00060 If a user runs the game directly from a CD, the base path would be on the CD.  This
00061 should still function correctly, but all file writes will fail (harmlessly).
00062 
00063 The "home path" is the path used for all write access. On win32 systems we have "base path"
00064 == "home path", but on *nix systems the base installation is usually readonly, and
00065 "home path" points to ~/.q3a or similar
00066 
00067 The user can also install custom mods and content in "home path", so it should be searched
00068 along with "home path" and "cd path" for game content.
00069 
00070 
00071 The "base game" is the directory under the paths where data comes from by default, and
00072 can be either "baseq3" or "demoq3".
00073 
00074 The "current game" may be the same as the base game, or it may be the name of another
00075 directory under the paths that should be searched for files before looking in the base game.
00076 This is the basis for addons.
00077 
00078 Clients automatically set the game directory after receiving a gamestate from a server,
00079 so only servers need to worry about +set fs_game.
00080 
00081 No other directories outside of the base game and current game will ever be referenced by
00082 filesystem functions.
00083 
00084 To save disk space and speed loading, directory trees can be collapsed into zip files.
00085 The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
00086 otherwise the are simply normal uncompressed zip files.  A game directory can have multiple
00087 zip files of the form "pak0.pk3", "pak1.pk3", etc.  Zip files are searched in decending order
00088 from the highest number to the lowest, and will always take precedence over the filesystem.
00089 This allows a pk3 distributed as a patch to override all existing data.
00090 
00091 Because we will have updated executables freely available online, there is no point to
00092 trying to restrict demo / oem versions of the game with code changes.  Demo / oem versions
00093 should be exactly the same executables as release versions, but with different data that
00094 automatically restricts where game media can come from to prevent add-ons from working.
00095 
00096 After the paths are initialized, quake will look for the product.txt file.  If not
00097 found and verified, the game will run in restricted mode.  In restricted mode, only 
00098 files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is
00099 verified to not have been modified.  A single exception is made for q3config.cfg.  Files
00100 can still be written out in restricted mode, so screenshots and demos are allowed.
00101 Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
00102 if there is a valid product.txt under the basepath or cdpath.
00103 
00104 If not running in restricted mode, and a file is not found in any local filesystem,
00105 an attempt will be made to download it and save it under the base path.
00106 
00107 If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
00108 path, it will be copied over to the base path.  This is a development aid to help build
00109 test releases and to copy working sets over slow network links.
00110 
00111 File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
00112 structure and stop on the first successful hit. fs_searchpaths is built with successive
00113 calls to FS_AddGameDirectory
00114 
00115 Additionaly, we search in several subdirectories:
00116 current game is the current mode
00117 base game is a variable to allow mods based on other mods
00118 (such as baseq3 + missionpack content combination in a mod for instance)
00119 BASEGAME is the hardcoded base game ("baseq3")
00120 
00121 e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
00122 
00123 home path + current game's zip files
00124 home path + current game's directory
00125 base path + current game's zip files
00126 base path + current game's directory
00127 cd path + current game's zip files
00128 cd path + current game's directory
00129 
00130 home path + base game's zip file
00131 home path + base game's directory
00132 base path + base game's zip file
00133 base path + base game's directory
00134 cd path + base game's zip file
00135 cd path + base game's directory
00136 
00137 home path + BASEGAME's zip file
00138 home path + BASEGAME's directory
00139 base path + BASEGAME's zip file
00140 base path + BASEGAME's directory
00141 cd path + BASEGAME's zip file
00142 cd path + BASEGAME's directory
00143 
00144 server download, to be written to home path + current game's directory
00145 
00146 
00147 The filesystem can be safely shutdown and reinitialized with different
00148 basedir / cddir / game combinations, but all other subsystems that rely on it
00149 (sound, video) must also be forced to restart.
00150 
00151 Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
00152 subsystems, a simple single-file caching scheme is used.  The CM_ subsystems will
00153 load the file with a request to cache.  Only one file will be kept cached at a time,
00154 so any models that are going to be referenced by both subsystems should alternate
00155 between the CM_ load function and the ref load function.
00156 
00157 TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
00158 game is currently active.  This allows character models, skins, and sounds to be downloaded
00159 to a common directory no matter which game is active.
00160 
00161 How to prevent downloading zip files?
00162 Pass pk3 file names in systeminfo, and download before FS_Restart()?
00163 
00164 Aborting a download disconnects the client from the server.
00165 
00166 How to mark files as downloadable?  Commercial add-ons won't be downloadable.
00167 
00168 Non-commercial downloads will want to download the entire zip file.
00169 the game would have to be reset to actually read the zip in
00170 
00171 Auto-update information
00172 
00173 Path separators
00174 
00175 Casing
00176 
00177   separate server gamedir and client gamedir, so if the user starts
00178   a local game after having connected to a network game, it won't stick
00179   with the network game.
00180 
00181   allow menu options for game selection?
00182 
00183 Read / write config to floppy option.
00184 
00185 Different version coexistance?
00186 
00187 When building a pak file, make sure a q3config.cfg isn't present in it,
00188 or configs will never get loaded from disk!
00189 
00190   todo:
00191 
00192   downloading (outside fs?)
00193   game directory passing and restarting
00194 
00195 =============================================================================
00196 
00197 */
00198 
00199 #define DEMOGAME            "demota"
00200 
00201 // every time a new demo pk3 file is built, this checksum must be updated.
00202 // the easiest way to get it is to just run the game and see what it spits out
00203 #define DEMO_PAK_CHECKSUM   437558517u
00204 
00205 // if this is defined, the executable positively won't work with any paks other
00206 // than the demo pak, even if productid is present.  This is only used for our
00207 // last demo release to prevent the mac and linux users from using the demo
00208 // executable with the production windows pak before the mac/linux products
00209 // hit the shelves a little later
00210 // NOW defined in build files
00211 //#define PRE_RELEASE_TADEMO
00212 
00213 #define MAX_ZPATH           256
00214 #define MAX_SEARCH_PATHS    4096
00215 #define MAX_FILEHASH_SIZE   1024
00216 
00217 typedef struct fileInPack_s {
00218     char                    *name;      // name of the file
00219     unsigned long           pos;        // file info position in zip
00220     struct  fileInPack_s*   next;       // next file in the hash
00221 } fileInPack_t;
00222 
00223 typedef struct {
00224     char            pakFilename[MAX_OSPATH];    // c:\quake3\baseq3\pak0.pk3
00225     char            pakBasename[MAX_OSPATH];    // pak0
00226     char            pakGamename[MAX_OSPATH];    // baseq3
00227     unzFile         handle;                     // handle to zip file
00228     int             checksum;                   // regular checksum
00229     int             pure_checksum;              // checksum for pure
00230     int             numfiles;                   // number of files in pk3
00231     int             referenced;                 // referenced file flags
00232     int             hashSize;                   // hash table size (power of 2)
00233     fileInPack_t*   *hashTable;                 // hash table
00234     fileInPack_t*   buildBuffer;                // buffer with the filenames etc.
00235 } pack_t;
00236 
00237 typedef struct {
00238     char        path[MAX_OSPATH];       // c:\quake3
00239     char        gamedir[MAX_OSPATH];    // baseq3
00240 } directory_t;
00241 
00242 typedef struct searchpath_s {
00243     struct searchpath_s *next;
00244 
00245     pack_t      *pack;      // only one of pack / dir will be non NULL
00246     directory_t *dir;
00247 } searchpath_t;
00248 
00249 static  char        fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators
00250 static  cvar_t      *fs_debug;
00251 static  cvar_t      *fs_homepath;
00252 static  cvar_t      *fs_basepath;
00253 static  cvar_t      *fs_basegame;
00254 static  cvar_t      *fs_cdpath;
00255 static  cvar_t      *fs_copyfiles;
00256 static  cvar_t      *fs_gamedirvar;
00257 static  cvar_t      *fs_restrict;
00258 static  searchpath_t    *fs_searchpaths;
00259 static  int         fs_readCount;           // total bytes read
00260 static  int         fs_loadCount;           // total files read
00261 static  int         fs_loadStack;           // total files in memory
00262 static  int         fs_packFiles;           // total number of files in packs
00263 
00264 static int fs_fakeChkSum;
00265 static int fs_checksumFeed;
00266 
00267 typedef union qfile_gus {
00268     FILE*       o;
00269     unzFile     z;
00270 } qfile_gut;
00271 
00272 typedef struct qfile_us {
00273     qfile_gut   file;
00274     qboolean    unique;
00275 } qfile_ut;
00276 
00277 typedef struct {
00278     qfile_ut    handleFiles;
00279     qboolean    handleSync;
00280     int         baseOffset;
00281     int         fileSize;
00282     int         zipFilePos;
00283     qboolean    zipFile;
00284     qboolean    streamed;
00285     char        name[MAX_ZPATH];
00286 } fileHandleData_t;
00287 
00288 static fileHandleData_t fsh[MAX_FILE_HANDLES];
00289 
00290 // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
00291 // wether we did a reorder on the current search path when joining the server
00292 static qboolean fs_reordered;
00293 
00294 // never load anything from pk3 files that are not present at the server when pure
00295 static int      fs_numServerPaks;
00296 static int      fs_serverPaks[MAX_SEARCH_PATHS];                // checksums
00297 static char     *fs_serverPakNames[MAX_SEARCH_PATHS];           // pk3 names
00298 
00299 // only used for autodownload, to make sure the client has at least
00300 // all the pk3 files that are referenced at the server side
00301 static int      fs_numServerReferencedPaks;
00302 static int      fs_serverReferencedPaks[MAX_SEARCH_PATHS];          // checksums
00303 static char     *fs_serverReferencedPakNames[MAX_SEARCH_PATHS];     // pk3 names
00304 
00305 // last valid game folder used
00306 char lastValidBase[MAX_OSPATH];
00307 char lastValidGame[MAX_OSPATH];
00308 
00309 // 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
00310 static byte fs_scrambledProductId[152] = {
00311 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
00312 };
00313 
00314 #ifdef FS_MISSING
00315 FILE*       missingFiles = NULL;
00316 #endif
00317 
00318 /*
00319 ==============
00320 FS_Initialized
00321 ==============
00322 */
00323 
00324 qboolean FS_Initialized() {
00325     return (fs_searchpaths != NULL);
00326 }
00327 
00328 /*
00329 =================
00330 FS_PakIsPure
00331 =================
00332 */
00333 qboolean FS_PakIsPure( pack_t *pack ) {
00334     int i;
00335 
00336     if ( fs_numServerPaks ) {
00337         for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
00338             // FIXME: also use hashed file names
00339             // NOTE TTimo: a pk3 with same checksum but different name would be validated too
00340             //   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'
00341             if ( pack->checksum == fs_serverPaks[i] ) {
00342                 return qtrue;       // on the aproved list
00343             }
00344         }
00345         return qfalse;  // not on the pure server pak list
00346     }
00347     return qtrue;
00348 }
00349 
00350 
00351 /*
00352 =================
00353 FS_LoadStack
00354 return load stack
00355 =================
00356 */
00357 int FS_LoadStack()
00358 {
00359     return fs_loadStack;
00360 }
00361                       
00362 /*
00363 ================
00364 return a hash value for the filename
00365 ================
00366 */
00367 static long FS_HashFileName( const char *fname, int hashSize ) {
00368     int     i;
00369     long    hash;
00370     char    letter;
00371 
00372     hash = 0;
00373     i = 0;
00374     while (fname[i] != '\0') {
00375         letter = tolower(fname[i]);
00376         if (letter =='.') break;                // don't include extension
00377         if (letter =='\\') letter = '/';        // damn path names
00378         if (letter == PATH_SEP) letter = '/';       // damn path names
00379         hash+=(long)(letter)*(i+119);
00380         i++;
00381     }
00382     hash = (hash ^ (hash >> 10) ^ (hash >> 20));
00383     hash &= (hashSize-1);
00384     return hash;
00385 }
00386 
00387 static fileHandle_t FS_HandleForFile(void) {
00388     int     i;
00389 
00390     for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
00391         if ( fsh[i].handleFiles.file.o == NULL ) {
00392             return i;
00393         }
00394     }
00395     Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
00396     return 0;
00397 }
00398 
00399 static FILE *FS_FileForHandle( fileHandle_t f ) {
00400     if ( f < 0 || f > MAX_FILE_HANDLES ) {
00401         Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" );
00402     }
00403     if (fsh[f].zipFile == qtrue) {
00404         Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
00405     }
00406     if ( ! fsh[f].handleFiles.file.o ) {
00407         Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
00408     }
00409     
00410     return fsh[f].handleFiles.file.o;
00411 }
00412 
00413 void    FS_ForceFlush( fileHandle_t f ) {
00414     FILE *file;
00415 
00416     file = FS_FileForHandle(f);
00417     setvbuf( file, NULL, _IONBF, 0 );
00418 }
00419 
00420 /*
00421 ================
00422 FS_filelength
00423 
00424 If this is called on a non-unique FILE (from a pak file),
00425 it will return the size of the pak file, not the expected
00426 size of the file.
00427 ================
00428 */
00429 int FS_filelength( fileHandle_t f ) {
00430     int     pos;
00431     int     end;
00432     FILE*   h;
00433 
00434     h = FS_FileForHandle(f);
00435     pos = ftell (h);
00436     fseek (h, 0, SEEK_END);
00437     end = ftell (h);
00438     fseek (h, pos, SEEK_SET);
00439 
00440     return end;
00441 }
00442 
00443 /*
00444 ====================
00445 FS_ReplaceSeparators
00446 
00447 Fix things up differently for win/unix/mac
00448 ====================
00449 */
00450 static void FS_ReplaceSeparators( char *path ) {
00451     char    *s;
00452 
00453     for ( s = path ; *s ; s++ ) {
00454         if ( *s == '/' || *s == '\\' ) {
00455             *s = PATH_SEP;
00456         }
00457     }
00458 }
00459 
00460 /*
00461 ===================
00462 FS_BuildOSPath
00463 
00464 Qpath may have either forward or backwards slashes
00465 ===================
00466 */
00467 char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
00468     char    temp[MAX_OSPATH];
00469     static char ospath[2][MAX_OSPATH];
00470     static int toggle;
00471     
00472     toggle ^= 1;        // flip-flop to allow two returns without clash
00473 
00474     if( !game || !game[0] ) {
00475         game = fs_gamedir;
00476     }
00477 
00478     Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath );
00479     FS_ReplaceSeparators( temp );   
00480     Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
00481     
00482     return ospath[toggle];
00483 }
00484 
00485 
00486 /*
00487 ============
00488 FS_CreatePath
00489 
00490 Creates any directories needed to store the given filename
00491 ============
00492 */
00493 static qboolean FS_CreatePath (char *OSPath) {
00494     char    *ofs;
00495     
00496     // make absolutely sure that it can't back up the path
00497     // FIXME: is c: allowed???
00498     if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
00499         Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
00500         return qtrue;
00501     }
00502 
00503     for (ofs = OSPath+1 ; *ofs ; ofs++) {
00504         if (*ofs == PATH_SEP) { 
00505             // create the directory
00506             *ofs = 0;
00507             Sys_Mkdir (OSPath);
00508             *ofs = PATH_SEP;
00509         }
00510     }
00511     return qfalse;
00512 }
00513 
00514 /*
00515 =================
00516 FS_CopyFile
00517 
00518 Copy a fully specified file from one place to another
00519 =================
00520 */
00521 static void FS_CopyFile( char *fromOSPath, char *toOSPath ) {
00522     FILE    *f;
00523     int     len;
00524     byte    *buf;
00525 
00526     Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath );
00527 
00528     if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) {
00529         Com_Printf( "Ignoring journal files\n");
00530         return;
00531     }
00532 
00533     f = fopen( fromOSPath, "rb" );
00534     if ( !f ) {
00535         return;
00536     }
00537     fseek (f, 0, SEEK_END);
00538     len = ftell (f);
00539     fseek (f, 0, SEEK_SET);
00540 
00541     // we are using direct malloc instead of Z_Malloc here, so it
00542     // probably won't work on a mac... Its only for developers anyway...
00543     buf = malloc( len );
00544     if (fread( buf, 1, len, f ) != len)
00545         Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" );
00546     fclose( f );
00547 
00548     if( FS_CreatePath( toOSPath ) ) {
00549         return;
00550     }
00551 
00552     f = fopen( toOSPath, "wb" );
00553     if ( !f ) {
00554         return;
00555     }
00556     if (fwrite( buf, 1, len, f ) != len)
00557         Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" );
00558     fclose( f );
00559     free( buf );
00560 }
00561 
00562 /*
00563 ===========
00564 FS_Remove
00565 
00566 ===========
00567 */
00568 static void FS_Remove( const char *osPath ) {
00569     remove( osPath );
00570 }
00571 
00572 /*
00573 ================
00574 FS_FileExists
00575 
00576 Tests if the file exists in the current gamedir, this DOES NOT
00577 search the paths.  This is to determine if opening a file to write
00578 (which always goes into the current gamedir) will cause any overwrites.
00579 NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
00580 ================
00581 */
00582 qboolean FS_FileExists( const char *file )
00583 {
00584     FILE *f;
00585     char *testpath;
00586 
00587     testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file );
00588 
00589     f = fopen( testpath, "rb" );
00590     if (f) {
00591         fclose( f );
00592         return qtrue;
00593     }
00594     return qfalse;
00595 }
00596 
00597 /*
00598 ================
00599 FS_SV_FileExists
00600 
00601 Tests if the file exists 
00602 ================
00603 */
00604 qboolean FS_SV_FileExists( const char *file )
00605 {
00606     FILE *f;
00607     char *testpath;
00608 
00609     testpath = FS_BuildOSPath( fs_homepath->string, file, "");
00610     testpath[strlen(testpath)-1] = '\0';
00611 
00612     f = fopen( testpath, "rb" );
00613     if (f) {
00614         fclose( f );
00615         return qtrue;
00616     }
00617     return qfalse;
00618 }
00619 
00620 
00621 /*
00622 ===========
00623 FS_SV_FOpenFileWrite
00624 
00625 ===========
00626 */
00627 fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) {
00628     char *ospath;
00629     fileHandle_t    f;
00630 
00631     if ( !fs_searchpaths ) {
00632         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
00633     }
00634 
00635     ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
00636     ospath[strlen(ospath)-1] = '\0';
00637 
00638     f = FS_HandleForFile();
00639     fsh[f].zipFile = qfalse;
00640 
00641     if ( fs_debug->integer ) {
00642         Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath );
00643     }
00644 
00645     if( FS_CreatePath( ospath ) ) {
00646         return 0;
00647     }
00648 
00649     Com_DPrintf( "writing to: %s\n", ospath );
00650     fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
00651 
00652     Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
00653 
00654     fsh[f].handleSync = qfalse;
00655     if (!fsh[f].handleFiles.file.o) {
00656         f = 0;
00657     }
00658     return f;
00659 }
00660 
00661 /*
00662 ===========
00663 FS_SV_FOpenFileRead
00664 search for a file somewhere below the home path, base path or cd path
00665 we search in that order, matching FS_SV_FOpenFileRead order
00666 ===========
00667 */
00668 int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
00669     char *ospath;
00670     fileHandle_t    f = 0;
00671 
00672     if ( !fs_searchpaths ) {
00673         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
00674     }
00675 
00676     f = FS_HandleForFile();
00677     fsh[f].zipFile = qfalse;
00678 
00679     Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
00680 
00681     // don't let sound stutter
00682     S_ClearSoundBuffer();
00683 
00684   // search homepath
00685     ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
00686     // remove trailing slash
00687     ospath[strlen(ospath)-1] = '\0';
00688 
00689     if ( fs_debug->integer ) {
00690         Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath );
00691     }
00692 
00693     fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
00694     fsh[f].handleSync = qfalse;
00695   if (!fsh[f].handleFiles.file.o)
00696   {
00697     // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid
00698     if (Q_stricmp(fs_homepath->string,fs_basepath->string))
00699     {
00700       // search basepath
00701       ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
00702       ospath[strlen(ospath)-1] = '\0';
00703 
00704       if ( fs_debug->integer )
00705       {
00706         Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath );
00707       }
00708 
00709       fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
00710       fsh[f].handleSync = qfalse;
00711 
00712       if ( !fsh[f].handleFiles.file.o )
00713       {
00714         f = 0;
00715       }
00716     }
00717   }
00718 
00719     if (!fsh[f].handleFiles.file.o) {
00720     // search cd path
00721     ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" );
00722     ospath[strlen(ospath)-1] = '\0';
00723 
00724     if (fs_debug->integer)
00725     {
00726       Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath );
00727     }
00728 
00729       fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
00730       fsh[f].handleSync = qfalse;
00731 
00732       if( !fsh[f].handleFiles.file.o ) {
00733         f = 0;
00734       }
00735   }
00736   
00737     *fp = f;
00738     if (f) {
00739         return FS_filelength(f);
00740     }
00741     return 0;
00742 }
00743 
00744 
00745 /*
00746 ===========
00747 FS_SV_Rename
00748 
00749 ===========
00750 */
00751 void FS_SV_Rename( const char *from, const char *to ) {
00752     char            *from_ospath, *to_ospath;
00753 
00754     if ( !fs_searchpaths ) {
00755         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
00756     }
00757 
00758     // don't let sound stutter
00759     S_ClearSoundBuffer();
00760 
00761     from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" );
00762     to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" );
00763     from_ospath[strlen(from_ospath)-1] = '\0';
00764     to_ospath[strlen(to_ospath)-1] = '\0';
00765 
00766     if ( fs_debug->integer ) {
00767         Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath );
00768     }
00769 
00770     if (rename( from_ospath, to_ospath )) {
00771         // Failed, try copying it and deleting the original
00772         FS_CopyFile ( from_ospath, to_ospath );
00773         FS_Remove ( from_ospath );
00774     }
00775 }
00776 
00777 
00778 
00779 /*
00780 ===========
00781 FS_Rename
00782 
00783 ===========
00784 */
00785 void FS_Rename( const char *from, const char *to ) {
00786     char            *from_ospath, *to_ospath;
00787 
00788     if ( !fs_searchpaths ) {
00789         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
00790     }
00791 
00792     // don't let sound stutter
00793     S_ClearSoundBuffer();
00794 
00795     from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from );
00796     to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to );
00797 
00798     if ( fs_debug->integer ) {
00799         Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath );
00800     }
00801 
00802     if (rename( from_ospath, to_ospath )) {
00803         // Failed, try copying it and deleting the original
00804         FS_CopyFile ( from_ospath, to_ospath );
00805         FS_Remove ( from_ospath );
00806     }
00807 }
00808 
00809 /*
00810 ==============
00811 FS_FCloseFile
00812 
00813 If the FILE pointer is an open pak file, leave it open.
00814 
00815 For some reason, other dll's can't just cal fclose()
00816 on files returned by FS_FOpenFile...
00817 ==============
00818 */
00819 void FS_FCloseFile( fileHandle_t f ) {
00820     if ( !fs_searchpaths ) {
00821         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
00822     }
00823 
00824     if (fsh[f].streamed) {
00825         Sys_EndStreamedFile(f);
00826     }
00827     if (fsh[f].zipFile == qtrue) {
00828         unzCloseCurrentFile( fsh[f].handleFiles.file.z );
00829         if ( fsh[f].handleFiles.unique ) {
00830             unzClose( fsh[f].handleFiles.file.z );
00831         }
00832         Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
00833         return;
00834     }
00835 
00836     // we didn't find it as a pak, so close it as a unique file
00837     if (fsh[f].handleFiles.file.o) {
00838         fclose (fsh[f].handleFiles.file.o);
00839     }
00840     Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
00841 }
00842 
00843 /*
00844 ===========
00845 FS_FOpenFileWrite
00846 
00847 ===========
00848 */
00849 fileHandle_t FS_FOpenFileWrite( const char *filename ) {
00850     char            *ospath;
00851     fileHandle_t    f;
00852 
00853     if ( !fs_searchpaths ) {
00854         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
00855     }
00856 
00857     f = FS_HandleForFile();
00858     fsh[f].zipFile = qfalse;
00859 
00860     ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
00861 
00862     if ( fs_debug->integer ) {
00863         Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
00864     }
00865 
00866     if( FS_CreatePath( ospath ) ) {
00867         return 0;
00868     }
00869 
00870     // enabling the following line causes a recursive function call loop
00871     // when running with +set logfile 1 +set developer 1
00872     //Com_DPrintf( "writing to: %s\n", ospath );
00873     fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
00874 
00875     Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
00876 
00877     fsh[f].handleSync = qfalse;
00878     if (!fsh[f].handleFiles.file.o) {
00879         f = 0;
00880     }
00881     return f;
00882 }
00883 
00884 /*
00885 ===========
00886 FS_FOpenFileAppend
00887 
00888 ===========
00889 */
00890 fileHandle_t FS_FOpenFileAppend( const char *filename ) {
00891     char            *ospath;
00892     fileHandle_t    f;
00893 
00894     if ( !fs_searchpaths ) {
00895         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
00896     }
00897 
00898     f = FS_HandleForFile();
00899     fsh[f].zipFile = qfalse;
00900 
00901     Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
00902 
00903     // don't let sound stutter
00904     S_ClearSoundBuffer();
00905 
00906     ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
00907 
00908     if ( fs_debug->integer ) {
00909         Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
00910     }
00911 
00912     if( FS_CreatePath( ospath ) ) {
00913         return 0;
00914     }
00915 
00916     fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
00917     fsh[f].handleSync = qfalse;
00918     if (!fsh[f].handleFiles.file.o) {
00919         f = 0;
00920     }
00921     return f;
00922 }
00923 
00924 /*
00925 ===========
00926 FS_FilenameCompare
00927 
00928 Ignore case and seprator char distinctions
00929 ===========
00930 */
00931 qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
00932     int     c1, c2;
00933     
00934     do {
00935         c1 = *s1++;
00936         c2 = *s2++;
00937 
00938         if (c1 >= 'a' && c1 <= 'z') {
00939             c1 -= ('a' - 'A');
00940         }
00941         if (c2 >= 'a' && c2 <= 'z') {
00942             c2 -= ('a' - 'A');
00943         }
00944 
00945         if ( c1 == '\\' || c1 == ':' ) {
00946             c1 = '/';
00947         }
00948         if ( c2 == '\\' || c2 == ':' ) {
00949             c2 = '/';
00950         }
00951         
00952         if (c1 != c2) {
00953             return -1;      // strings not equal
00954         }
00955     } while (c1);
00956     
00957     return 0;       // strings are equal
00958 }
00959 
00960 /*
00961 ===========
00962 FS_ShiftedStrStr
00963 ===========
00964 */
00965 char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) {
00966     char buf[MAX_STRING_TOKENS];
00967     int i;
00968 
00969     for (i = 0; substring[i]; i++) {
00970         buf[i] = substring[i] + shift;
00971     }
00972     buf[i] = '\0';
00973     return strstr(string, buf);
00974 }
00975 
00976 /*
00977 ===========
00978 FS_FOpenFileRead
00979 
00980 Finds the file in the search path.
00981 Returns filesize and an open FILE pointer.
00982 Used for streaming data out of either a
00983 separate file or a ZIP file.
00984 ===========
00985 */
00986 extern qboolean     com_fullyInitialized;
00987 
00988 int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) {
00989     searchpath_t    *search;
00990     char            *netpath;
00991     pack_t          *pak;
00992     fileInPack_t    *pakFile;
00993     directory_t     *dir;
00994     long            hash;
00995     unz_s           *zfi;
00996     FILE            *temp;
00997     int             l;
00998     char demoExt[16];
00999 
01000     hash = 0;
01001 
01002     if ( !fs_searchpaths ) {
01003         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01004     }
01005 
01006     if ( file == NULL ) {
01007         // just wants to see if file is there
01008         for ( search = fs_searchpaths ; search ; search = search->next ) {
01009             //
01010             if ( search->pack ) {
01011                 hash = FS_HashFileName(filename, search->pack->hashSize);
01012             }
01013             // is the element a pak file?
01014             if ( search->pack && search->pack->hashTable[hash] ) {
01015                 // look through all the pak file elements
01016                 pak = search->pack;
01017                 pakFile = pak->hashTable[hash];
01018                 do {
01019                     // case and separator insensitive comparisons
01020                     if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
01021                         // found it!
01022                         return qtrue;
01023                     }
01024                     pakFile = pakFile->next;
01025                 } while(pakFile != NULL);
01026             } else if ( search->dir ) {
01027                 dir = search->dir;
01028             
01029                 netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
01030                 temp = fopen (netpath, "rb");
01031                 if ( !temp ) {
01032                     continue;
01033                 }
01034                 fclose(temp);
01035                 return qtrue;
01036             }
01037         }
01038         return qfalse;
01039     }
01040 
01041     if ( !filename ) {
01042         Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
01043     }
01044 
01045     Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION );
01046     // qpaths are not supposed to have a leading slash
01047     if ( filename[0] == '/' || filename[0] == '\\' ) {
01048         filename++;
01049     }
01050 
01051     // make absolutely sure that it can't back up the path.
01052     // The searchpaths do guarantee that something will always
01053     // be prepended, so we don't need to worry about "c:" or "//limbo" 
01054     if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
01055         *file = 0;
01056         return -1;
01057     }
01058 
01059     // make sure the q3key file is only readable by the quake3.exe at initialization
01060     // any other time the key should only be accessed in memory using the provided functions
01061     if( com_fullyInitialized && strstr( filename, "q3key" ) ) {
01062         *file = 0;
01063         return -1;
01064     }
01065 
01066     //
01067     // search through the path, one element at a time
01068     //
01069 
01070     *file = FS_HandleForFile();
01071     fsh[*file].handleFiles.unique = uniqueFILE;
01072 
01073     for ( search = fs_searchpaths ; search ; search = search->next ) {
01074         //
01075         if ( search->pack ) {
01076             hash = FS_HashFileName(filename, search->pack->hashSize);
01077         }
01078         // is the element a pak file?
01079         if ( search->pack && search->pack->hashTable[hash] ) {
01080             // disregard if it doesn't match one of the allowed pure pak files
01081             if ( !FS_PakIsPure(search->pack) ) {
01082                 continue;
01083             }
01084 
01085             // look through all the pak file elements
01086             pak = search->pack;
01087             pakFile = pak->hashTable[hash];
01088             do {
01089                 // case and separator insensitive comparisons
01090                 if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
01091                     // found it!
01092 
01093                     // mark the pak as having been referenced and mark specifics on cgame and ui
01094                     // shaders, txt, arena files  by themselves do not count as a reference as 
01095                     // these are loaded from all pk3s 
01096                     // from every pk3 file.. 
01097                     l = strlen( filename );
01098                     if ( !(pak->referenced & FS_GENERAL_REF)) {
01099                         if ( Q_stricmp(filename + l - 7, ".shader") != 0 &&
01100                             Q_stricmp(filename + l - 4, ".txt") != 0 &&
01101                             Q_stricmp(filename + l - 4, ".cfg") != 0 &&
01102                             Q_stricmp(filename + l - 7, ".config") != 0 &&
01103                             strstr(filename, "levelshots") == NULL &&
01104                             Q_stricmp(filename + l - 4, ".bot") != 0 &&
01105                             Q_stricmp(filename + l - 6, ".arena") != 0 &&
01106                             Q_stricmp(filename + l - 5, ".menu") != 0) {
01107                             pak->referenced |= FS_GENERAL_REF;
01108                         }
01109                     }
01110 
01111                     // qagame.qvm   - 13
01112                     // dTZT`X!di`
01113                     if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "dTZT`X!di`", 13)) {
01114                         pak->referenced |= FS_QAGAME_REF;
01115                     }
01116                     // cgame.qvm    - 7
01117                     // \`Zf^'jof
01118                     if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) {
01119                         pak->referenced |= FS_CGAME_REF;
01120                     }
01121                     // ui.qvm       - 5
01122                     // pd)lqh
01123                     if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) {
01124                         pak->referenced |= FS_UI_REF;
01125                     }
01126 
01127                     if ( uniqueFILE ) {
01128                         // open a new file on the pakfile
01129                         fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle);
01130                         if (fsh[*file].handleFiles.file.z == NULL) {
01131                             Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename);
01132                         }
01133                     } else {
01134                         fsh[*file].handleFiles.file.z = pak->handle;
01135                     }
01136                     Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
01137                     fsh[*file].zipFile = qtrue;
01138                     zfi = (unz_s *)fsh[*file].handleFiles.file.z;
01139                     // in case the file was new
01140                     temp = zfi->file;
01141                     // set the file position in the zip file (also sets the current file info)
01142                     unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos);
01143                     // copy the file info into the unzip structure
01144                     Com_Memcpy( zfi, pak->handle, sizeof(unz_s) );
01145                     // we copy this back into the structure
01146                     zfi->file = temp;
01147                     // open the file in the zip
01148                     unzOpenCurrentFile( fsh[*file].handleFiles.file.z );
01149                     fsh[*file].zipFilePos = pakFile->pos;
01150 
01151                     if ( fs_debug->integer ) {
01152                         Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", 
01153                             filename, pak->pakFilename );
01154                     }
01155                     return zfi->cur_file_info.uncompressed_size;
01156                 }
01157                 pakFile = pakFile->next;
01158             } while(pakFile != NULL);
01159         } else if ( search->dir ) {
01160             // check a file in the directory tree
01161 
01162             // if we are running restricted, the only files we
01163             // will allow to come from the directory are .cfg files
01164             l = strlen( filename );
01165       // FIXME TTimo I'm not sure about the fs_numServerPaks test
01166       // if you are using FS_ReadFile to find out if a file exists,
01167       //   this test can make the search fail although the file is in the directory
01168       // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
01169       // turned out I used FS_FileExists instead
01170             if ( fs_restrict->integer || fs_numServerPaks ) {
01171 
01172                 if ( Q_stricmp( filename + l - 4, ".cfg" )      // for config files
01173                     && Q_stricmp( filename + l - 5, ".menu" )   // menu files
01174                     && Q_stricmp( filename + l - 5, ".game" )   // menu files
01175                     && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
01176                     && Q_stricmp( filename + l - 4, ".dat" ) ) {    // for journal files
01177                     continue;
01178                 }
01179             }
01180 
01181             dir = search->dir;
01182             
01183             netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
01184             fsh[*file].handleFiles.file.o = fopen (netpath, "rb");
01185             if ( !fsh[*file].handleFiles.file.o ) {
01186                 continue;
01187             }
01188 
01189             if ( Q_stricmp( filename + l - 4, ".cfg" )      // for config files
01190                 && Q_stricmp( filename + l - 5, ".menu" )   // menu files
01191                 && Q_stricmp( filename + l - 5, ".game" )   // menu files
01192                 && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files
01193                 && Q_stricmp( filename + l - 4, ".dat" ) ) {    // for journal files
01194                 fs_fakeChkSum = random();
01195             }
01196       
01197             Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
01198             fsh[*file].zipFile = qfalse;
01199             if ( fs_debug->integer ) {
01200                 Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename,
01201                     dir->path, dir->gamedir );
01202             }
01203 
01204             // if we are getting it from the cdpath, optionally copy it
01205             //  to the basepath
01206             if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) {
01207                 char    *copypath;
01208 
01209                 copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename );
01210                 FS_CopyFile( netpath, copypath );
01211             }
01212 
01213             return FS_filelength (*file);
01214         }       
01215     }
01216     
01217     Com_DPrintf ("Can't find %s\n", filename);
01218 #ifdef FS_MISSING
01219     if (missingFiles) {
01220         fprintf(missingFiles, "%s\n", filename);
01221     }
01222 #endif
01223     *file = 0;
01224     return -1;
01225 }
01226 
01227 
01228 /*
01229 =================
01230 FS_Read
01231 
01232 Properly handles partial reads
01233 =================
01234 */
01235 int FS_Read2( void *buffer, int len, fileHandle_t f ) {
01236     if ( !fs_searchpaths ) {
01237         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01238     }
01239 
01240     if ( !f ) {
01241         return 0;
01242     }
01243     if (fsh[f].streamed) {
01244         int r;
01245         fsh[f].streamed = qfalse;
01246         r = Sys_StreamedRead( buffer, len, 1, f);
01247         fsh[f].streamed = qtrue;
01248         return r;
01249     } else {
01250         return FS_Read( buffer, len, f);
01251     }
01252 }
01253 
01254 int FS_Read( void *buffer, int len, fileHandle_t f ) {
01255     int     block, remaining;
01256     int     read;
01257     byte    *buf;
01258     int     tries;
01259 
01260     if ( !fs_searchpaths ) {
01261         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01262     }
01263 
01264     if ( !f ) {
01265         return 0;
01266     }
01267 
01268     buf = (byte *)buffer;
01269     fs_readCount += len;
01270 
01271     if (fsh[f].zipFile == qfalse) {
01272         remaining = len;
01273         tries = 0;
01274         while (remaining) {
01275             block = remaining;
01276             read = fread (buf, 1, block, fsh[f].handleFiles.file.o);
01277             if (read == 0) {
01278                 // we might have been trying to read from a CD, which
01279                 // sometimes returns a 0 read on windows
01280                 if (!tries) {
01281                     tries = 1;
01282                 } else {
01283                     return len-remaining;   //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
01284                 }
01285             }
01286 
01287             if (read == -1) {
01288                 Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
01289             }
01290 
01291             remaining -= read;
01292             buf += read;
01293         }
01294         return len;
01295     } else {
01296         return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len);
01297     }
01298 }
01299 
01300 /*
01301 =================
01302 FS_Write
01303 
01304 Properly handles partial writes
01305 =================
01306 */
01307 int FS_Write( const void *buffer, int len, fileHandle_t h ) {
01308     int     block, remaining;
01309     int     written;
01310     byte    *buf;
01311     int     tries;
01312     FILE    *f;
01313 
01314     if ( !fs_searchpaths ) {
01315         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01316     }
01317 
01318     if ( !h ) {
01319         return 0;
01320     }
01321 
01322     f = FS_FileForHandle(h);
01323     buf = (byte *)buffer;
01324 
01325     remaining = len;
01326     tries = 0;
01327     while (remaining) {
01328         block = remaining;
01329         written = fwrite (buf, 1, block, f);
01330         if (written == 0) {
01331             if (!tries) {
01332                 tries = 1;
01333             } else {
01334                 Com_Printf( "FS_Write: 0 bytes written\n" );
01335                 return 0;
01336             }
01337         }
01338 
01339         if (written == -1) {
01340             Com_Printf( "FS_Write: -1 bytes written\n" );
01341             return 0;
01342         }
01343 
01344         remaining -= written;
01345         buf += written;
01346     }
01347     if ( fsh[h].handleSync ) {
01348         fflush( f );
01349     }
01350     return len;
01351 }
01352 
01353 void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
01354     va_list     argptr;
01355     char        msg[MAXPRINTMSG];
01356 
01357     va_start (argptr,fmt);
01358     Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
01359     va_end (argptr);
01360 
01361     FS_Write(msg, strlen(msg), h);
01362 }
01363 
01364 /*
01365 =================
01366 FS_Seek
01367 
01368 =================
01369 */
01370 int FS_Seek( fileHandle_t f, long offset, int origin ) {
01371     int     _origin;
01372     char    foo[65536];
01373 
01374     if ( !fs_searchpaths ) {
01375         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01376         return -1;
01377     }
01378 
01379     if (fsh[f].streamed) {
01380         fsh[f].streamed = qfalse;
01381         Sys_StreamSeek( f, offset, origin );
01382         fsh[f].streamed = qtrue;
01383     }
01384 
01385     if (fsh[f].zipFile == qtrue) {
01386         if (offset == 0 && origin == FS_SEEK_SET) {
01387             // set the file position in the zip file (also sets the current file info)
01388             unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
01389             return unzOpenCurrentFile(fsh[f].handleFiles.file.z);
01390         } else if (offset<65536) {
01391             // set the file position in the zip file (also sets the current file info)
01392             unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
01393             unzOpenCurrentFile(fsh[f].handleFiles.file.z);
01394             return FS_Read(foo, offset, f);
01395         } else {
01396             Com_Error( ERR_FATAL, "ZIP FILE FSEEK NOT YET IMPLEMENTED\n" );
01397             return -1;
01398         }
01399     } else {
01400         FILE *file;
01401         file = FS_FileForHandle(f);
01402         switch( origin ) {
01403         case FS_SEEK_CUR:
01404             _origin = SEEK_CUR;
01405             break;
01406         case FS_SEEK_END:
01407             _origin = SEEK_END;
01408             break;
01409         case FS_SEEK_SET:
01410             _origin = SEEK_SET;
01411             break;
01412         default:
01413             _origin = SEEK_CUR;
01414             Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
01415             break;
01416         }
01417 
01418         return fseek( file, offset, _origin );
01419     }
01420 }
01421 
01422 
01423 /*
01424 ======================================================================================
01425 
01426 CONVENIENCE FUNCTIONS FOR ENTIRE FILES
01427 
01428 ======================================================================================
01429 */
01430 
01431 int FS_FileIsInPAK(const char *filename, int *pChecksum ) {
01432     searchpath_t    *search;
01433     pack_t          *pak;
01434     fileInPack_t    *pakFile;
01435     long            hash = 0;
01436 
01437     if ( !fs_searchpaths ) {
01438         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01439     }
01440 
01441     if ( !filename ) {
01442         Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
01443     }
01444 
01445     // qpaths are not supposed to have a leading slash
01446     if ( filename[0] == '/' || filename[0] == '\\' ) {
01447         filename++;
01448     }
01449 
01450     // make absolutely sure that it can't back up the path.
01451     // The searchpaths do guarantee that something will always
01452     // be prepended, so we don't need to worry about "c:" or "//limbo" 
01453     if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
01454         return -1;
01455     }
01456 
01457     //
01458     // search through the path, one element at a time
01459     //
01460 
01461     for ( search = fs_searchpaths ; search ; search = search->next ) {
01462         //
01463         if (search->pack) {
01464             hash = FS_HashFileName(filename, search->pack->hashSize);
01465         }
01466         // is the element a pak file?
01467         if ( search->pack && search->pack->hashTable[hash] ) {
01468             // disregard if it doesn't match one of the allowed pure pak files
01469             if ( !FS_PakIsPure(search->pack) ) {
01470                 continue;
01471             }
01472 
01473             // look through all the pak file elements
01474             pak = search->pack;
01475             pakFile = pak->hashTable[hash];
01476             do {
01477                 // case and separator insensitive comparisons
01478                 if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
01479                     if (pChecksum) {
01480                         *pChecksum = pak->pure_checksum;
01481                     }
01482                     return 1;
01483                 }
01484                 pakFile = pakFile->next;
01485             } while(pakFile != NULL);
01486         }
01487     }
01488     return -1;
01489 }
01490 
01491 /*
01492 ============
01493 FS_ReadFile
01494 
01495 Filename are relative to the quake search path
01496 a null buffer will just return the file length without loading
01497 ============
01498 */
01499 int FS_ReadFile( const char *qpath, void **buffer ) {
01500     fileHandle_t    h;
01501     byte*           buf;
01502     qboolean        isConfig;
01503     int             len;
01504 
01505     if ( !fs_searchpaths ) {
01506         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01507     }
01508 
01509     if ( !qpath || !qpath[0] ) {
01510         Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
01511     }
01512 
01513     buf = NULL; // quiet compiler warning
01514 
01515     // if this is a .cfg file and we are playing back a journal, read
01516     // it from the journal file
01517     if ( strstr( qpath, ".cfg" ) ) {
01518         isConfig = qtrue;
01519         if ( com_journal && com_journal->integer == 2 ) {
01520             int     r;
01521 
01522             Com_DPrintf( "Loading %s from journal file.\n", qpath );
01523             r = FS_Read( &len, sizeof( len ), com_journalDataFile );
01524             if ( r != sizeof( len ) ) {
01525                 if (buffer != NULL) *buffer = NULL;
01526                 return -1;
01527             }
01528             // if the file didn't exist when the journal was created
01529             if (!len) {
01530                 if (buffer == NULL) {
01531                     return 1;           // hack for old journal files
01532                 }
01533                 *buffer = NULL;
01534                 return -1;
01535             }
01536             if (buffer == NULL) {
01537                 return len;
01538             }
01539 
01540             buf = Hunk_AllocateTempMemory(len+1);
01541             *buffer = buf;
01542 
01543             r = FS_Read( buf, len, com_journalDataFile );
01544             if ( r != len ) {
01545                 Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
01546             }
01547 
01548             fs_loadCount++;
01549             fs_loadStack++;
01550 
01551             // guarantee that it will have a trailing 0 for string operations
01552             buf[len] = 0;
01553 
01554             return len;
01555         }
01556     } else {
01557         isConfig = qfalse;
01558     }
01559 
01560     // look for it in the filesystem or pack files
01561     len = FS_FOpenFileRead( qpath, &h, qfalse );
01562     if ( h == 0 ) {
01563         if ( buffer ) {
01564             *buffer = NULL;
01565         }
01566         // if we are journalling and it is a config file, write a zero to the journal file
01567         if ( isConfig && com_journal && com_journal->integer == 1 ) {
01568             Com_DPrintf( "Writing zero for %s to journal file.\n", qpath );
01569             len = 0;
01570             FS_Write( &len, sizeof( len ), com_journalDataFile );
01571             FS_Flush( com_journalDataFile );
01572         }
01573         return -1;
01574     }
01575     
01576     if ( !buffer ) {
01577         if ( isConfig && com_journal && com_journal->integer == 1 ) {
01578             Com_DPrintf( "Writing len for %s to journal file.\n", qpath );
01579             FS_Write( &len, sizeof( len ), com_journalDataFile );
01580             FS_Flush( com_journalDataFile );
01581         }
01582         FS_FCloseFile( h);
01583         return len;
01584     }
01585 
01586     fs_loadCount++;
01587     fs_loadStack++;
01588 
01589     buf = Hunk_AllocateTempMemory(len+1);
01590     *buffer = buf;
01591 
01592     FS_Read (buf, len, h);
01593 
01594     // guarantee that it will have a trailing 0 for string operations
01595     buf[len] = 0;
01596     FS_FCloseFile( h );
01597 
01598     // if we are journalling and it is a config file, write it to the journal file
01599     if ( isConfig && com_journal && com_journal->integer == 1 ) {
01600         Com_DPrintf( "Writing %s to journal file.\n", qpath );
01601         FS_Write( &len, sizeof( len ), com_journalDataFile );
01602         FS_Write( buf, len, com_journalDataFile );
01603         FS_Flush( com_journalDataFile );
01604     }
01605     return len;
01606 }
01607 
01608 /*
01609 =============
01610 FS_FreeFile
01611 =============
01612 */
01613 void FS_FreeFile( void *buffer ) {
01614     if ( !fs_searchpaths ) {
01615         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01616     }
01617     if ( !buffer ) {
01618         Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
01619     }
01620     fs_loadStack--;
01621 
01622     Hunk_FreeTempMemory( buffer );
01623 
01624     // if all of our temp files are free, clear all of our space
01625     if ( fs_loadStack == 0 ) {
01626         Hunk_ClearTempMemory();
01627     }
01628 }
01629 
01630 /*
01631 ============
01632 FS_WriteFile
01633 
01634 Filename are reletive to the quake search path
01635 ============
01636 */
01637 void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
01638     fileHandle_t f;
01639 
01640     if ( !fs_searchpaths ) {
01641         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01642     }
01643 
01644     if ( !qpath || !buffer ) {
01645         Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
01646     }
01647 
01648     f = FS_FOpenFileWrite( qpath );
01649     if ( !f ) {
01650         Com_Printf( "Failed to open %s\n", qpath );
01651         return;
01652     }
01653 
01654     FS_Write( buffer, size, f );
01655 
01656     FS_FCloseFile( f );
01657 }
01658 
01659 
01660 
01661 /*
01662 ==========================================================================
01663 
01664 ZIP FILE LOADING
01665 
01666 ==========================================================================
01667 */
01668 
01669 /*
01670 =================
01671 FS_LoadZipFile
01672 
01673 Creates a new pak_t in the search chain for the contents
01674 of a zip file.
01675 =================
01676 */
01677 static pack_t *FS_LoadZipFile( char *zipfile, const char *basename )
01678 {
01679     fileInPack_t    *buildBuffer;
01680     pack_t          *pack;
01681     unzFile         uf;
01682     int             err;
01683     unz_global_info gi;
01684     char            filename_inzip[MAX_ZPATH];
01685     unz_file_info   file_info;
01686     int             i, len;
01687     long            hash;
01688     int             fs_numHeaderLongs;
01689     int             *fs_headerLongs;
01690     char            *namePtr;
01691 
01692     fs_numHeaderLongs = 0;
01693 
01694     uf = unzOpen(zipfile);
01695     err = unzGetGlobalInfo (uf,&gi);
01696 
01697     if (err != UNZ_OK)
01698         return NULL;
01699 
01700     fs_packFiles += gi.number_entry;
01701 
01702     len = 0;
01703     unzGoToFirstFile(uf);
01704     for (i = 0; i < gi.number_entry; i++)
01705     {
01706         err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
01707         if (err != UNZ_OK) {
01708             break;
01709         }
01710         len += strlen(filename_inzip) + 1;
01711         unzGoToNextFile(uf);
01712     }
01713 
01714     buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len );
01715     namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t );
01716     fs_headerLongs = Z_Malloc( gi.number_entry * sizeof(int) );
01717 
01718     // get the hash table size from the number of files in the zip
01719     // because lots of custom pk3 files have less than 32 or 64 files
01720     for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) {
01721         if (i > gi.number_entry) {
01722             break;
01723         }
01724     }
01725 
01726     pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) );
01727     pack->hashSize = i;
01728     pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t ));
01729     for(i = 0; i < pack->hashSize; i++) {
01730         pack->hashTable[i] = NULL;
01731     }
01732 
01733     Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) );
01734     Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) );
01735 
01736     // strip .pk3 if needed
01737     if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) {
01738         pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0;
01739     }
01740 
01741     pack->handle = uf;
01742     pack->numfiles = gi.number_entry;
01743     unzGoToFirstFile(uf);
01744 
01745     for (i = 0; i < gi.number_entry; i++)
01746     {
01747         err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
01748         if (err != UNZ_OK) {
01749             break;
01750         }
01751         if (file_info.uncompressed_size > 0) {
01752             fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
01753         }
01754         Q_strlwr( filename_inzip );
01755         hash = FS_HashFileName(filename_inzip, pack->hashSize);
01756         buildBuffer[i].name = namePtr;
01757         strcpy( buildBuffer[i].name, filename_inzip );
01758         namePtr += strlen(filename_inzip) + 1;
01759         // store the file position in the zip
01760         unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos);
01761         //
01762         buildBuffer[i].next = pack->hashTable[hash];
01763         pack->hashTable[hash] = &buildBuffer[i];
01764         unzGoToNextFile(uf);
01765     }
01766 
01767     pack->checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
01768     pack->pure_checksum = Com_BlockChecksumKey( fs_headerLongs, 4 * fs_numHeaderLongs, LittleLong(fs_checksumFeed) );
01769     pack->checksum = LittleLong( pack->checksum );
01770     pack->pure_checksum = LittleLong( pack->pure_checksum );
01771 
01772     Z_Free(fs_headerLongs);
01773 
01774     pack->buildBuffer = buildBuffer;
01775     return pack;
01776 }
01777 
01778 /*
01779 =================================================================================
01780 
01781 DIRECTORY SCANNING FUNCTIONS
01782 
01783 =================================================================================
01784 */
01785 
01786 #define MAX_FOUND_FILES 0x1000
01787 
01788 static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) {
01789     int len, at, newdep;
01790 
01791     newdep = 0;
01792     zpath[0] = 0;
01793     len = 0;
01794     at = 0;
01795 
01796     while(zname[at] != 0)
01797     {
01798         if (zname[at]=='/' || zname[at]=='\\') {
01799             len = at;
01800             newdep++;
01801         }
01802         at++;
01803     }
01804     strcpy(zpath, zname);
01805     zpath[len] = 0;
01806     *depth = newdep;
01807 
01808     return len;
01809 }
01810 
01811 /*
01812 ==================
01813 FS_AddFileToList
01814 ==================
01815 */
01816 static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
01817     int     i;
01818 
01819     if ( nfiles == MAX_FOUND_FILES - 1 ) {
01820         return nfiles;
01821     }
01822     for ( i = 0 ; i < nfiles ; i++ ) {
01823         if ( !Q_stricmp( name, list[i] ) ) {
01824             return nfiles;      // allready in list
01825         }
01826     }
01827     list[nfiles] = CopyString( name );
01828     nfiles++;
01829 
01830     return nfiles;
01831 }
01832 
01833 /*
01834 ===============
01835 FS_ListFilteredFiles
01836 
01837 Returns a uniqued list of files that match the given criteria
01838 from all search paths
01839 ===============
01840 */
01841 char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) {
01842     int             nfiles;
01843     char            **listCopy;
01844     char            *list[MAX_FOUND_FILES];
01845     searchpath_t    *search;
01846     int             i;
01847     int             pathLength;
01848     int             extensionLength;
01849     int             length, pathDepth, temp;
01850     pack_t          *pak;
01851     fileInPack_t    *buildBuffer;
01852     char            zpath[MAX_ZPATH];
01853 
01854     if ( !fs_searchpaths ) {
01855         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01856     }
01857 
01858     if ( !path ) {
01859         *numfiles = 0;
01860         return NULL;
01861     }
01862     if ( !extension ) {
01863         extension = "";
01864     }
01865 
01866     pathLength = strlen( path );
01867     if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) {
01868         pathLength--;
01869     }
01870     extensionLength = strlen( extension );
01871     nfiles = 0;
01872     FS_ReturnPath(path, zpath, &pathDepth);
01873 
01874     //
01875     // search through the path, one element at a time, adding to list
01876     //
01877     for (search = fs_searchpaths ; search ; search = search->next) {
01878         // is the element a pak file?
01879         if (search->pack) {
01880 
01881             //ZOID:  If we are pure, don't search for files on paks that
01882             // aren't on the pure list
01883             if ( !FS_PakIsPure(search->pack) ) {
01884                 continue;
01885             }
01886 
01887             // look through all the pak file elements
01888             pak = search->pack;
01889             buildBuffer = pak->buildBuffer;
01890             for (i = 0; i < pak->numfiles; i++) {
01891                 char    *name;
01892                 int     zpathLen, depth;
01893 
01894                 // check for directory match
01895                 name = buildBuffer[i].name;
01896                 //
01897                 if (filter) {
01898                     // case insensitive
01899                     if (!Com_FilterPath( filter, name, qfalse ))
01900                         continue;
01901                     // unique the match
01902                     nfiles = FS_AddFileToList( name, list, nfiles );
01903                 }
01904                 else {
01905 
01906                     zpathLen = FS_ReturnPath(name, zpath, &depth);
01907 
01908                     if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) {
01909                         continue;
01910                     }
01911 
01912                     // check for extension match
01913                     length = strlen( name );
01914                     if ( length < extensionLength ) {
01915                         continue;
01916                     }
01917 
01918                     if ( Q_stricmp( name + length - extensionLength, extension ) ) {
01919                         continue;
01920                     }
01921                     // unique the match
01922 
01923                     temp = pathLength;
01924                     if (pathLength) {
01925                         temp++;     // include the '/'
01926                     }
01927                     nfiles = FS_AddFileToList( name + temp, list, nfiles );
01928                 }
01929             }
01930         } else if (search->dir) { // scan for files in the filesystem
01931             char    *netpath;
01932             int     numSysFiles;
01933             char    **sysFiles;
01934             char    *name;
01935 
01936             // don't scan directories for files if we are pure or restricted
01937             if ( fs_restrict->integer || fs_numServerPaks ) {
01938                 continue;
01939             } else {
01940                 netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path );
01941                 sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse );
01942                 for ( i = 0 ; i < numSysFiles ; i++ ) {
01943                     // unique the match
01944                     name = sysFiles[i];
01945                     nfiles = FS_AddFileToList( name, list, nfiles );
01946                 }
01947                 Sys_FreeFileList( sysFiles );
01948             }
01949         }       
01950     }
01951 
01952     // return a copy of the list
01953     *numfiles = nfiles;
01954 
01955     if ( !nfiles ) {
01956         return NULL;
01957     }
01958 
01959     listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
01960     for ( i = 0 ; i < nfiles ; i++ ) {
01961         listCopy[i] = list[i];
01962     }
01963     listCopy[i] = NULL;
01964 
01965     return listCopy;
01966 }
01967 
01968 /*
01969 =================
01970 FS_ListFiles
01971 =================
01972 */
01973 char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
01974     return FS_ListFilteredFiles( path, extension, NULL, numfiles );
01975 }
01976 
01977 /*
01978 =================
01979 FS_FreeFileList
01980 =================
01981 */
01982 void FS_FreeFileList( char **list ) {
01983     int     i;
01984 
01985     if ( !fs_searchpaths ) {
01986         Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
01987     }
01988 
01989     if ( !list ) {
01990         return;
01991     }
01992 
01993     for ( i = 0 ; list[i] ; i++ ) {
01994         Z_Free( list[i] );
01995     }
01996 
01997     Z_Free( list );
01998 }
01999 
02000 
02001 /*
02002 ================
02003 FS_GetFileList
02004 ================
02005 */
02006 int FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize ) {
02007     int     nFiles, i, nTotal, nLen;
02008     char **pFiles = NULL;
02009 
02010     *listbuf = 0;
02011     nFiles = 0;
02012     nTotal = 0;
02013 
02014     if (Q_stricmp(path, "$modlist") == 0) {
02015         return FS_GetModList(listbuf, bufsize);
02016     }
02017 
02018     pFiles = FS_ListFiles(path, extension, &nFiles);
02019 
02020     for (i =0; i < nFiles; i++) {
02021         nLen = strlen(pFiles[i]) + 1;
02022         if (nTotal + nLen + 1 < bufsize) {
02023             strcpy(listbuf, pFiles[i]);
02024             listbuf += nLen;
02025             nTotal += nLen;
02026         }
02027         else {
02028             nFiles = i;
02029             break;
02030         }
02031     }
02032 
02033     FS_FreeFileList(pFiles);
02034 
02035     return nFiles;
02036 }
02037 
02038 /*
02039 =======================
02040 Sys_ConcatenateFileLists
02041 
02042 mkv: Naive implementation. Concatenates three lists into a
02043      new list, and frees the old lists from the heap.
02044 bk001129 - from cvs1.17 (mkv)
02045 
02046 FIXME TTimo those two should move to common.c next to Sys_ListFiles
02047 =======================
02048  */
02049 static unsigned int Sys_CountFileList(char **list)
02050 {
02051   int i = 0;
02052 
02053   if (list)
02054   {
02055     while (*list)
02056     {
02057       list++;
02058       i++;
02059     }
02060   }
02061   return i;
02062 }
02063 
02064 static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 )
02065 {
02066   int totalLength = 0;
02067   char** cat = NULL, **dst, **src;
02068 
02069   totalLength += Sys_CountFileList(list0);
02070   totalLength += Sys_CountFileList(list1);
02071   totalLength += Sys_CountFileList(list2);
02072 
02073   /* Create new list. */
02074   dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) );
02075 
02076   /* Copy over lists. */
02077   if (list0)
02078   {
02079     for (src = list0; *src; src++, dst++)
02080       *dst = *src;
02081   }
02082   if (list1)
02083   {
02084     for (src = list1; *src; src++, dst++)
02085       *dst = *src;
02086   }
02087   if (list2)
02088   {
02089     for (src = list2; *src; src++, dst++)
02090       *dst = *src;
02091   }
02092 
02093   // Terminate the list
02094   *dst = NULL;
02095 
02096   // Free our old lists.
02097   // NOTE: not freeing their content, it's been merged in dst and still being used
02098   if (list0) Z_Free( list0 );
02099   if (list1) Z_Free( list1 );
02100   if (list2) Z_Free( list2 );
02101 
02102   return cat;
02103 }
02104 
02105 /*
02106 ================
02107 FS_GetModList
02108 
02109 Returns a list of mod directory names
02110 A mod directory is a peer to baseq3 with a pk3 in it
02111 The directories are searched in base path, cd path and home path
02112 ================
02113 */
02114 int FS_GetModList( char *listbuf, int bufsize ) {
02115   int       nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen;
02116   char **pFiles = NULL;
02117   char **pPaks = NULL;
02118   char *name, *path;
02119   char descPath[MAX_OSPATH];
02120   fileHandle_t descHandle;
02121 
02122   int dummy;
02123   char **pFiles0 = NULL;
02124   char **pFiles1 = NULL;
02125   char **pFiles2 = NULL;
02126   qboolean bDrop = qfalse;
02127 
02128   *listbuf = 0;
02129   nMods = nPotential = nTotal = 0;
02130 
02131   pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue );
02132   pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue );
02133   pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue );
02134   // we searched for mods in the three paths
02135   // it is likely that we have duplicate names now, which we will cleanup below
02136   pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 );
02137   nPotential = Sys_CountFileList(pFiles);
02138 
02139   for ( i = 0 ; i < nPotential ; i++ ) {
02140     name = pFiles[i];
02141     // NOTE: cleaner would involve more changes
02142     // ignore duplicate mod directories
02143     if (i!=0) {
02144       bDrop = qfalse;
02145       for(j=0; j<i; j++)
02146       {
02147         if (Q_stricmp(pFiles[j],name)==0) {
02148           // this one can be dropped
02149           bDrop = qtrue;
02150           break;
02151         }
02152       }
02153     }
02154     if (bDrop) {
02155       continue;
02156     }
02157     // we drop "baseq3" "." and ".."
02158     if (Q_stricmp(name, "baseq3") && Q_stricmpn(name, ".", 1)) {
02159       // now we need to find some .pk3 files to validate the mod
02160       // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?)
02161       // we didn't keep the information when we merged the directory names, as to what OS Path it was found under
02162       //   so it could be in base path, cd path or home path
02163       //   we will try each three of them here (yes, it's a bit messy)
02164       path = FS_BuildOSPath( fs_basepath->string, name, "" );
02165       nPaks = 0;
02166       pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); 
02167       Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present
02168 
02169       /* Try on cd path */
02170       if( nPaks <= 0 ) {
02171         path = FS_BuildOSPath( fs_cdpath->string, name, "" );
02172         nPaks = 0;
02173         pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
02174         Sys_FreeFileList( pPaks );
02175       }
02176 
02177       /* try on home path */
02178       if ( nPaks <= 0 )
02179       {
02180         path = FS_BuildOSPath( fs_homepath->string, name, "" );
02181         nPaks = 0;
02182         pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
02183         Sys_FreeFileList( pPaks );
02184       }
02185 
02186       if (nPaks > 0) {
02187         nLen = strlen(name) + 1;
02188         // nLen is the length of the mod path
02189         // we need to see if there is a description available
02190         descPath[0] = '\0';
02191         strcpy(descPath, name);
02192         strcat(descPath, "/description.txt");
02193         nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle );
02194         if ( nDescLen > 0 && descHandle) {
02195           FILE *file;
02196           file = FS_FileForHandle(descHandle);
02197           Com_Memset( descPath, 0, sizeof( descPath ) );
02198           nDescLen = fread(descPath, 1, 48, file);
02199           if (nDescLen >= 0) {
02200             descPath[nDescLen] = '\0';
02201           }
02202           FS_FCloseFile(descHandle);
02203         } else {
02204           strcpy(descPath, name);
02205         }
02206         nDescLen = strlen(descPath) + 1;
02207 
02208         if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) {
02209           strcpy(listbuf, name);
02210           listbuf += nLen;
02211           strcpy(listbuf, descPath);
02212           listbuf += nDescLen;
02213           nTotal += nLen + nDescLen;
02214           nMods++;
02215         }
02216         else {
02217           break;
02218         }
02219       }
02220     }
02221   }
02222   Sys_FreeFileList( pFiles );
02223 
02224   return nMods;
02225 }
02226 
02227 
02228 
02229 
02230 //============================================================================
02231 
02232 /*
02233 ================
02234 FS_Dir_f
02235 ================
02236 */
02237 void FS_Dir_f( void ) {
02238     char    *path;
02239     char    *extension;
02240     char    **dirnames;
02241     int     ndirs;
02242     int     i;
02243 
02244     if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) {
02245         Com_Printf( "usage: dir <directory> [extension]\n" );
02246         return;
02247     }
02248 
02249     if ( Cmd_Argc() == 2 ) {
02250         path = Cmd_Argv( 1 );
02251         extension = "";
02252     } else {
02253         path = Cmd_Argv( 1 );
02254         extension = Cmd_Argv( 2 );
02255     }
02256 
02257     Com_Printf( "Directory of %s %s\n", path, extension );
02258     Com_Printf( "---------------\n" );
02259 
02260     dirnames = FS_ListFiles( path, extension, &ndirs );
02261 
02262     for ( i = 0; i < ndirs; i++ ) {
02263         Com_Printf( "%s\n", dirnames[i] );
02264     }
02265     FS_FreeFileList( dirnames );
02266 }
02267 
02268 /*
02269 ===========
02270 FS_ConvertPath
02271 ===========
02272 */
02273 void FS_ConvertPath( char *s ) {
02274     while (*s) {
02275         if ( *s == '\\' || *s == ':' ) {
02276             *s = '/';
02277         }
02278         s++;
02279     }
02280 }
02281 
02282 /*
02283 ===========
02284 FS_PathCmp
02285 
02286 Ignore case and seprator char distinctions
02287 ===========
02288 */
02289 int FS_PathCmp( const char *s1, const char *s2 ) {
02290     int     c1, c2;
02291     
02292     do {
02293         c1 = *s1++;
02294         c2 = *s2++;
02295 
02296         if (c1 >= 'a' && c1 <= 'z') {
02297             c1 -= ('a' - 'A');
02298         }
02299         if (c2 >= 'a' && c2 <= 'z') {
02300             c2 -= ('a' - 'A');
02301         }
02302 
02303         if ( c1 == '\\' || c1 == ':' ) {
02304             c1 = '/';
02305         }
02306         if ( c2 == '\\' || c2 == ':' ) {
02307             c2 = '/';
02308         }
02309         
02310         if (c1 < c2) {
02311             return -1;      // strings not equal
02312         }
02313         if (c1 > c2) {
02314             return 1;
02315         }
02316     } while (c1);
02317     
02318     return 0;       // strings are equal
02319 }
02320 
02321 /*
02322 ================
02323 FS_SortFileList
02324 ================
02325 */
02326 void FS_SortFileList(char **filelist, int numfiles) {
02327     int i, j, k, numsortedfiles;
02328     char **sortedlist;
02329 
02330     sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) );
02331     sortedlist[0] = NULL;
02332     numsortedfiles = 0;
02333     for (i = 0; i < numfiles; i++) {
02334         for (j = 0; j < numsortedfiles; j++) {
02335             if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) {
02336                 break;
02337             }
02338         }
02339         for (k = numsortedfiles; k > j; k--) {
02340             sortedlist[k] = sortedlist[k-1];
02341         }
02342         sortedlist[j] = filelist[i];
02343         numsortedfiles++;
02344     }
02345     Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) );
02346     Z_Free(sortedlist);
02347 }
02348 
02349 /*
02350 ================
02351 FS_NewDir_f
02352 ================
02353 */
02354 void FS_NewDir_f( void ) {
02355     char    *filter;
02356     char    **dirnames;
02357     int     ndirs;
02358     int     i;
02359 
02360     if ( Cmd_Argc() < 2 ) {
02361         Com_Printf( "usage: fdir <filter>\n" );
02362         Com_Printf( "example: fdir *q3dm*.bsp\n");
02363         return;
02364     }
02365 
02366     filter = Cmd_Argv( 1 );
02367 
02368     Com_Printf( "---------------\n" );
02369 
02370     dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs );
02371 
02372     FS_SortFileList(dirnames, ndirs);
02373 
02374     for ( i = 0; i < ndirs; i++ ) {
02375         FS_ConvertPath(dirnames[i]);
02376         Com_Printf( "%s\n", dirnames[i] );
02377     }
02378     Com_Printf( "%d files listed\n", ndirs );
02379     FS_FreeFileList( dirnames );
02380 }
02381 
02382 /*
02383 ============
02384 FS_Path_f
02385 
02386 ============
02387 */
02388 void FS_Path_f( void ) {
02389     searchpath_t    *s;
02390     int             i;
02391 
02392     Com_Printf ("Current search path:\n");
02393     for (s = fs_searchpaths; s; s = s->next) {
02394         if (s->pack) {
02395             Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles);
02396             if ( fs_numServerPaks ) {
02397                 if ( !FS_PakIsPure(s->pack) ) {
02398                     Com_Printf( "    not on the pure list\n" );
02399                 } else {
02400                     Com_Printf( "    on the pure list\n" );
02401                 }
02402             }
02403         } else {
02404             Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir );
02405         }
02406     }
02407 
02408 
02409     Com_Printf( "\n" );
02410     for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
02411         if ( fsh[i].handleFiles.file.o ) {
02412             Com_Printf( "handle %i: %s\n", i, fsh[i].name );
02413         }
02414     }
02415 }
02416 
02417 /*
02418 ============
02419 FS_TouchFile_f
02420 
02421 The only purpose of this function is to allow game script files to copy
02422 arbitrary files furing an "fs_copyfiles 1" run.
02423 ============
02424 */
02425 void FS_TouchFile_f( void ) {
02426     fileHandle_t    f;
02427 
02428     if ( Cmd_Argc() != 2 ) {
02429         Com_Printf( "Usage: touchFile <file>\n" );
02430         return;
02431     }
02432 
02433     FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse );
02434     if ( f ) {
02435         FS_FCloseFile( f );
02436     }
02437 }
02438 
02439 //===========================================================================
02440 
02441 
02442 static int QDECL paksort( const void *a, const void *b ) {
02443     char    *aa, *bb;
02444 
02445     aa = *(char **)a;
02446     bb = *(char **)b;
02447 
02448     return FS_PathCmp( aa, bb );
02449 }
02450 
02451 /*
02452 ================
02453 FS_AddGameDirectory
02454 
02455 Sets fs_gamedir, adds the directory to the head of the path,
02456 then loads the zip headers
02457 ================
02458 */
02459 #define MAX_PAKFILES    1024
02460 static void FS_AddGameDirectory( const char *path, const char *dir ) {
02461     searchpath_t    *sp;
02462     int             i;
02463     searchpath_t    *search;
02464     pack_t          *pak;
02465     char            *pakfile;
02466     int             numfiles;
02467     char            **pakfiles;
02468     char            *sorted[MAX_PAKFILES];
02469 
02470     // this fixes the case where fs_basepath is the same as fs_cdpath
02471     // which happens on full installs
02472     for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
02473         if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) {
02474             return;         // we've already got this one
02475         }
02476     }
02477     
02478     Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) );
02479 
02480     //
02481     // add the directory to the search path
02482     //
02483     search = Z_Malloc (sizeof(searchpath_t));
02484     search->dir = Z_Malloc( sizeof( *search->dir ) );
02485 
02486     Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) );
02487     Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) );
02488     search->next = fs_searchpaths;
02489     fs_searchpaths = search;
02490 
02491     // find all pak files in this directory
02492     pakfile = FS_BuildOSPath( path, dir, "" );
02493     pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash
02494 
02495     pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse );
02496 
02497     // sort them so that later alphabetic matches override
02498     // earlier ones.  This makes pak1.pk3 override pak0.pk3
02499     if ( numfiles > MAX_PAKFILES ) {
02500         numfiles = MAX_PAKFILES;
02501     }
02502     for ( i = 0 ; i < numfiles ; i++ ) {
02503         sorted[i] = pakfiles[i];
02504     }
02505 
02506     qsort( sorted, numfiles, 4, paksort );
02507 
02508     for ( i = 0 ; i < numfiles ; i++ ) {
02509         pakfile = FS_BuildOSPath( path, dir, sorted[i] );
02510         if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 )
02511             continue;
02512         // store the game name for downloading
02513         strcpy(pak->pakGamename, dir);
02514 
02515         search = Z_Malloc (sizeof(searchpath_t));
02516         search->pack = pak;
02517         search->next = fs_searchpaths;
02518         fs_searchpaths = search;
02519     }
02520 
02521     // done
02522     Sys_FreeFileList( pakfiles );
02523 }
02524 
02525 /*
02526 ================
02527 FS_idPak
02528 ================
02529 */
02530 qboolean FS_idPak( char *pak, char *base ) {
02531     int i;
02532 
02533     for (i = 0; i < NUM_ID_PAKS; i++) {
02534         if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) {
02535             break;
02536         }
02537     }
02538     if (i < NUM_ID_PAKS) {
02539         return qtrue;
02540     }
02541     return qfalse;
02542 }
02543 
02544 /*
02545 ================
02546 FS_ComparePaks
02547 
02548 ----------------
02549 dlstring == qtrue
02550 
02551 Returns a list of pak files that we should download from the server. They all get stored
02552 in the current gamedir and an FS_Restart will be fired up after we download them all.
02553 
02554 The string is the format:
02555 
02556 @remotename@localname [repeat]
02557 
02558 static int      fs_numServerReferencedPaks;
02559 static int      fs_serverReferencedPaks[MAX_SEARCH_PATHS];
02560 static char     *fs_serverReferencedPakNames[MAX_SEARCH_PATHS];
02561 
02562 ----------------
02563 dlstring == qfalse
02564 
02565 we are not interested in a download string format, we want something human-readable
02566 (this is used for diagnostics while connecting to a pure server)
02567 
02568 ================
02569 */
02570 qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
02571     searchpath_t    *sp;
02572     qboolean havepak, badchecksum;
02573     int i;
02574 
02575     if ( !fs_numServerReferencedPaks ) {
02576         return qfalse; // Server didn't send any pack information along
02577     }
02578 
02579     *neededpaks = 0;
02580 
02581     for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) {
02582         // Ok, see if we have this pak file
02583         badchecksum = qfalse;
02584         havepak = qfalse;
02585 
02586         // never autodownload any of the id paks
02587         if ( FS_idPak(fs_serverReferencedPakNames[i], "baseq3") || FS_idPak(fs_serverReferencedPakNames[i], "missionpack") ) {
02588             continue;
02589         }
02590 
02591         for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
02592             if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) {
02593                 havepak = qtrue; // This is it!
02594                 break;
02595             }
02596         }
02597 
02598         if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { 
02599             // Don't got it
02600 
02601       if (dlstring)
02602       {
02603         // Remote name
02604         Q_strcat( neededpaks, len, "@");
02605         Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
02606         Q_strcat( neededpaks, len, ".pk3" );
02607 
02608         // Local name
02609         Q_strcat( neededpaks, len, "@");
02610         // Do we have one with the same name?
02611         if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
02612         {
02613           char st[MAX_ZPATH];
02614           // We already have one called this, we need to download it to another name
02615           // Make something up with the checksum in it
02616           Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] );
02617           Q_strcat( neededpaks, len, st );
02618         } else
02619         {
02620           Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
02621           Q_strcat( neededpaks, len, ".pk3" );
02622         }
02623       }
02624       else
02625       {
02626         Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
02627               Q_strcat( neededpaks, len, ".pk3" );
02628         // Do we have one with the same name?
02629         if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
02630         {
02631           Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
02632         }
02633         Q_strcat( neededpaks, len, "\n");
02634       }
02635         }
02636     }
02637 
02638     if ( *neededpaks ) {
02639         return qtrue;
02640     }
02641 
02642     return qfalse; // We have them all
02643 }
02644 
02645 /*
02646 ================
02647 FS_Shutdown
02648 
02649 Frees all resources and closes all files
02650 ================
02651 */
02652 void FS_Shutdown( qboolean closemfp ) {
02653     searchpath_t    *p, *next;
02654     int i;
02655 
02656     for(i = 0; i < MAX_FILE_HANDLES; i++) {
02657         if (fsh[i].fileSize) {
02658             FS_FCloseFile(i);
02659         }
02660     }
02661 
02662     // free everything
02663     for ( p = fs_searchpaths ; p ; p = next ) {
02664         next = p->next;
02665 
02666         if ( p->pack ) {
02667             unzClose(p->pack->handle);
02668             Z_Free( p->pack->buildBuffer );
02669             Z_Free( p->pack );
02670         }
02671         if ( p->dir ) {
02672             Z_Free( p->dir );
02673         }
02674         Z_Free( p );
02675     }
02676 
02677     // any FS_ calls will now be an error until reinitialized
02678     fs_searchpaths = NULL;
02679 
02680     Cmd_RemoveCommand( "path" );
02681     Cmd_RemoveCommand( "dir" );
02682     Cmd_RemoveCommand( "fdir" );
02683     Cmd_RemoveCommand( "touchFile" );
02684 
02685 #ifdef FS_MISSING
02686     if (closemfp) {
02687         fclose(missingFiles);
02688     }
02689 #endif
02690 }
02691 
02692 void Com_AppendCDKey( const char *filename );
02693 void Com_ReadCDKey( const char *filename );
02694  
02695 /*
02696 ================
02697 FS_ReorderPurePaks
02698 NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*)
02699   this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
02700 ================
02701 */
02702 static void FS_ReorderPurePaks()
02703 {
02704     searchpath_t *s;
02705     int i;
02706     searchpath_t **p_insert_index, // for linked list reordering
02707         **p_previous; // when doing the scan
02708     
02709     // only relevant when connected to pure server
02710     if ( !fs_numServerPaks )
02711         return;
02712     
02713     fs_reordered = qfalse;
02714     
02715     p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list 
02716     for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
02717         p_previous = p_insert_index; // track the pointer-to-current-item
02718         for (s = *p_insert_index; s; s = s->next) {
02719             // the part of the list before p_insert_index has been sorted already
02720             if (s->pack && fs_serverPaks[i] == s->pack->checksum) {
02721                 fs_reordered = qtrue;
02722                 // move this element to the insert list
02723                 *p_previous = s->next;
02724                 s->next = *p_insert_index;
02725                 *p_insert_index = s;
02726                 // increment insert list
02727                 p_insert_index = &s->next;
02728                 break; // iterate to next server pack
02729             }
02730             p_previous = &s->next; 
02731         }
02732     }
02733 }
02734 
02735 /*
02736 ================
02737 FS_Startup
02738 ================
02739 */
02740 static void FS_Startup( const char *gameName ) {
02741         const char *homePath;
02742     cvar_t  *fs;
02743 
02744     Com_Printf( "----- FS_Startup -----\n" );
02745 
02746     fs_debug = Cvar_Get( "fs_debug", "0", 0 );
02747     fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT );
02748     fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT );
02749     fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT );
02750     fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
02751   homePath = Sys_DefaultHomePath();
02752   if (!homePath || !homePath[0]) {
02753         homePath = fs_basepath->string;
02754     }
02755     fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT );
02756     fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
02757     fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT );
02758 
02759     // add search path elements in reverse priority order
02760     if (fs_cdpath->string[0]) {
02761         FS_AddGameDirectory( fs_cdpath->string, gameName );
02762     }
02763     if (fs_basepath->string[0]) {
02764         FS_AddGameDirectory( fs_basepath->string, gameName );
02765     }
02766   // fs_homepath is somewhat particular to *nix systems, only add if relevant
02767   // NOTE: same filtering below for mods and basegame
02768     if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
02769         FS_AddGameDirectory ( fs_homepath->string, gameName );
02770     }
02771         
02772     // check for additional base game so mods can be based upon other mods
02773     if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) {
02774         if (fs_cdpath->string[0]) {
02775             FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string);
02776         }
02777         if (fs_basepath->string[0]) {
02778             FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
02779         }
02780         if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
02781             FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
02782         }
02783     }
02784 
02785     // check for additional game folder for mods
02786     if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) {
02787         if (fs_cdpath->string[0]) {
02788             FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string);
02789         }
02790         if (fs_basepath->string[0]) {
02791             FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
02792         }
02793         if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
02794             FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
02795         }
02796     }
02797 
02798     Com_ReadCDKey( "baseq3" );
02799     fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
02800     if (fs && fs->string[0] != 0) {
02801         Com_AppendCDKey( fs->string );
02802     }
02803 
02804     // add our commands
02805     Cmd_AddCommand ("path", FS_Path_f);
02806     Cmd_AddCommand ("dir", FS_Dir_f );
02807     Cmd_AddCommand ("fdir", FS_NewDir_f );
02808     Cmd_AddCommand ("touchFile", FS_TouchFile_f );
02809 
02810     // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506
02811     // reorder the pure pk3 files according to server order
02812     FS_ReorderPurePaks();
02813     
02814     // print the current search paths
02815     FS_Path_f();
02816 
02817     fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
02818 
02819     Com_Printf( "----------------------\n" );
02820 
02821 #ifdef FS_MISSING
02822     if (missingFiles == NULL) {
02823         missingFiles = fopen( "\\missing.txt", "ab" );
02824     }
02825 #endif
02826     Com_Printf( "%d files in pk3 files\n", fs_packFiles );
02827 }
02828 
02829 
02830 /*
02831 ===================
02832 FS_SetRestrictions
02833 
02834 Looks for product keys and restricts media add on ability
02835 if the full version is not found
02836 ===================
02837 */
02838 static void FS_SetRestrictions( void ) {
02839     searchpath_t    *path;
02840 
02841 #ifndef PRE_RELEASE_DEMO
02842     char    *productId;
02843 
02844     // if fs_restrict is set, don't even look for the id file,
02845     // which allows the demo release to be tested even if
02846     // the full game is present
02847     if ( !fs_restrict->integer ) {
02848         // look for the full game id
02849         FS_ReadFile( "productid.txt", (void **)&productId );
02850         if ( productId ) {
02851             // check against the hardcoded string
02852             int     seed, i;
02853 
02854             seed = 5000;
02855             for ( i = 0 ; i < sizeof( fs_scrambledProductId ) ; i++ ) {
02856                 if ( ( fs_scrambledProductId[i] ^ (seed&255) ) != productId[i] ) {
02857                     break;
02858                 }
02859                 seed = (69069 * seed + 1);
02860             }
02861 
02862             FS_FreeFile( productId );
02863 
02864             if ( i == sizeof( fs_scrambledProductId ) ) {
02865                 return; // no restrictions
02866             }
02867             Com_Error( ERR_FATAL, "Invalid product identification" );
02868         }
02869     }
02870 #endif
02871     Cvar_Set( "fs_restrict", "1" );
02872 
02873     Com_Printf( "\nRunning in restricted demo mode.\n\n" );
02874 
02875     // restart the filesystem with just the demo directory
02876     FS_Shutdown(qfalse);
02877     FS_Startup( DEMOGAME );
02878 
02879     // make sure that the pak file has the header checksum we expect
02880     for ( path = fs_searchpaths ; path ; path = path->next ) {
02881         if ( path->pack ) {
02882             // a tiny attempt to keep the checksum from being scannable from the exe
02883             if ( (path->pack->checksum ^ 0x02261994u) != (DEMO_PAK_CHECKSUM ^ 0x02261994u) ) {
02884                 Com_Error( ERR_FATAL, "Corrupted pak0.pk3: %u", path->pack->checksum );
02885             }
02886         }
02887     }
02888 }
02889 
02890 /*
02891 =====================
02892 FS_GamePureChecksum
02893 
02894 Returns the checksum of the pk3 from which the server loaded the qagame.qvm
02895 =====================
02896 */
02897 const char *FS_GamePureChecksum( void ) {
02898     static char info[MAX_STRING_TOKENS];
02899     searchpath_t *search;
02900 
02901     info[0] = 0;
02902 
02903     for ( search = fs_searchpaths ; search ; search = search->next ) {
02904         // is the element a pak file?
02905         if ( search->pack ) {
02906             if (search->pack->referenced & FS_QAGAME_REF) {
02907                 Com_sprintf(info, sizeof(info), "%d", search->pack->checksum);
02908             }
02909         }
02910     }
02911 
02912     return info;
02913 }
02914 
02915 /*
02916 =====================
02917 FS_LoadedPakChecksums
02918 
02919 Returns a space separated string containing the checksums of all loaded pk3 files.
02920 Servers with sv_pure set will get this string and pass it to clients.
02921 =====================
02922 */
02923 const char *FS_LoadedPakChecksums( void ) {
02924     static char info[BIG_INFO_STRING];
02925     searchpath_t    *search;
02926 
02927     info[0] = 0;
02928 
02929     for ( search = fs_searchpaths ; search ; search = search->next ) {
02930         // is the element a pak file? 
02931         if ( !search->pack ) {
02932             continue;
02933         }
02934 
02935         Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
02936     }
02937 
02938     return info;
02939 }
02940 
02941 /*
02942 =====================
02943 FS_LoadedPakNames
02944 
02945 Returns a space separated string containing the names of all loaded pk3 files.
02946 Servers with sv_pure set will get this string and pass it to clients.
02947 =====================
02948 */
02949 const char *FS_LoadedPakNames( void ) {
02950     static char info[BIG_INFO_STRING];
02951     searchpath_t    *search;
02952 
02953     info[0] = 0;
02954 
02955     for ( search = fs_searchpaths ; search ; search = search->next ) {
02956         // is the element a pak file?
02957         if ( !search->pack ) {
02958             continue;
02959         }
02960 
02961         if (*info) {
02962             Q_strcat(info, sizeof( info ), " " );
02963         }
02964         Q_strcat( info, sizeof( info ), search->pack->pakBasename );
02965     }
02966 
02967     return info;
02968 }
02969 
02970 /*
02971 =====================
02972 FS_LoadedPakPureChecksums
02973 
02974 Returns a space separated string containing the pure checksums of all loaded pk3 files.
02975 Servers with sv_pure use these checksums to compare with the checksums the clients send
02976 back to the server.
02977 =====================
02978 */
02979 const char *FS_LoadedPakPureChecksums( void ) {
02980     static char info[BIG_INFO_STRING];
02981     searchpath_t    *search;
02982 
02983     info[0] = 0;
02984 
02985     for ( search = fs_searchpaths ; search ; search = search->next ) {
02986         // is the element a pak file? 
02987         if ( !search->pack ) {
02988             continue;
02989         }
02990 
02991         Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
02992     }
02993 
02994     return info;
02995 }
02996 
02997 /*
02998 =====================
02999 FS_ReferencedPakChecksums
03000 
03001 Returns a space separated string containing the checksums of all referenced pk3 files.
03002 The server will send this to the clients so they can check which files should be auto-downloaded. 
03003 =====================
03004 */
03005 const char *FS_ReferencedPakChecksums( void ) {
03006     static char info[BIG_INFO_STRING];
03007     searchpath_t *search;
03008 
03009     info[0] = 0;
03010 
03011 
03012     for ( search = fs_searchpaths ; search ; search = search->next ) {
03013         // is the element a pak file?
03014         if ( search->pack ) {
03015             if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
03016                 Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
03017             }
03018         }
03019     }
03020 
03021     return info;
03022 }
03023 
03024 /*
03025 =====================
03026 FS_ReferencedPakPureChecksums
03027 
03028 Returns a space separated string containing the pure checksums of all referenced pk3 files.
03029 Servers with sv_pure set will get this string back from clients for pure validation 
03030 
03031 The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..."
03032 =====================
03033 */
03034 const char *FS_ReferencedPakPureChecksums( void ) {
03035     static char info[BIG_INFO_STRING];
03036     searchpath_t    *search;
03037     int nFlags, numPaks, checksum;
03038 
03039     info[0] = 0;
03040 
03041     checksum = fs_checksumFeed;
03042     numPaks = 0;
03043     for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) {
03044         if (nFlags & FS_GENERAL_REF) {
03045             // add a delimter between must haves and general refs
03046             //Q_strcat(info, sizeof(info), "@ ");
03047             info[strlen(info)+1] = '\0';
03048             info[strlen(info)+2] = '\0';
03049             info[strlen(info)] = '@';
03050             info[strlen(info)] = ' ';
03051         }
03052         for ( search = fs_searchpaths ; search ; search = search->next ) {
03053             // is the element a pak file and has it been referenced based on flag?
03054             if ( search->pack && (search->pack->referenced & nFlags)) {
03055                 Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
03056                 if (nFlags & (FS_CGAME_REF | FS_UI_REF)) {
03057                     break;
03058                 }
03059                 checksum ^= search->pack->pure_checksum;
03060                 numPaks++;
03061             }
03062         }
03063         if (fs_fakeChkSum != 0) {
03064             // only added if a non-pure file is referenced
03065             Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) );
03066         }
03067     }
03068     // last checksum is the encoded number of referenced pk3s
03069     checksum ^= numPaks;
03070     Q_strcat( info, sizeof( info ), va("%i ", checksum ) );
03071 
03072     return info;
03073 }
03074 
03075 /*
03076 =====================
03077 FS_ReferencedPakNames
03078 
03079 Returns a space separated string containing the names of all referenced pk3 files.
03080 The server will send this to the clients so they can check which files should be auto-downloaded. 
03081 =====================
03082 */
03083 const char *FS_ReferencedPakNames( void ) {
03084     static char info[BIG_INFO_STRING];
03085     searchpath_t    *search;
03086 
03087     info[0] = 0;
03088 
03089     // we want to return ALL pk3's from the fs_game path
03090     // and referenced one's from baseq3
03091     for ( search = fs_searchpaths ; search ; search = search->next ) {
03092         // is the element a pak file?
03093         if ( search->pack ) {
03094             if (*info) {
03095                 Q_strcat(info, sizeof( info ), " " );
03096             }
03097             if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
03098                 Q_strcat( info, sizeof( info ), search->pack->pakGamename );
03099                 Q_strcat( info, sizeof( info ), "/" );
03100                 Q_strcat( info, sizeof( info ), search->pack->pakBasename );
03101             }
03102         }
03103     }
03104 
03105     return info;
03106 }
03107 
03108 /*
03109 =====================
03110 FS_ClearPakReferences
03111 =====================
03112 */
03113 void FS_ClearPakReferences( int flags ) {
03114     searchpath_t *search;
03115 
03116     if ( !flags ) {
03117         flags = -1;
03118     }
03119     for ( search = fs_searchpaths; search; search = search->next ) {
03120         // is the element a pak file and has it been referenced?
03121         if ( search->pack ) {
03122             search->pack->referenced &= ~flags;
03123         }
03124     }
03125 }
03126 
03127 
03128 /*
03129 =====================
03130 FS_PureServerSetLoadedPaks
03131 
03132 If the string is empty, all data sources will be allowed.
03133 If not empty, only pk3 files that match one of the space
03134 separated checksums will be checked for files, with the
03135 exception of .cfg and .dat files.
03136 =====================
03137 */
03138 void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) {
03139     int     i, c, d;
03140 
03141     Cmd_TokenizeString( pakSums );
03142 
03143     c = Cmd_Argc();
03144     if ( c > MAX_SEARCH_PATHS ) {
03145         c = MAX_SEARCH_PATHS;
03146     }
03147 
03148     fs_numServerPaks = c;
03149 
03150     for ( i = 0 ; i < c ; i++ ) {
03151         fs_serverPaks[i] = atoi( Cmd_Argv( i ) );
03152     }
03153 
03154     if (fs_numServerPaks) {
03155         Com_DPrintf( "Connected to a pure server.\n" );
03156     }
03157     else
03158     {
03159         if (fs_reordered)
03160         {
03161             // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
03162             // force a restart to make sure the search order will be correct
03163             Com_DPrintf( "FS search reorder is required\n" );
03164             FS_Restart(fs_checksumFeed);
03165             return;
03166         }
03167     }
03168 
03169     for ( i = 0 ; i < c ; i++ ) {
03170         if (fs_serverPakNames[i]) {
03171             Z_Free(fs_serverPakNames[i]);
03172         }
03173         fs_serverPakNames[i] = NULL;
03174     }
03175     if ( pakNames && *pakNames ) {
03176         Cmd_TokenizeString( pakNames );
03177 
03178         d = Cmd_Argc();
03179         if ( d > MAX_SEARCH_PATHS ) {
03180             d = MAX_SEARCH_PATHS;
03181         }
03182 
03183         for ( i = 0 ; i < d ; i++ ) {
03184             fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) );
03185         }
03186     }
03187 }
03188 
03189 /*
03190 =====================
03191 FS_PureServerSetReferencedPaks
03192 
03193 The checksums and names of the pk3 files referenced at the server
03194 are sent to the client and stored here. The client will use these
03195 checksums to see if any pk3 files need to be auto-downloaded. 
03196 =====================
03197 */
03198 void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
03199     int     i, c, d;
03200 
03201     Cmd_TokenizeString( pakSums );
03202 
03203     c = Cmd_Argc();
03204     if ( c > MAX_SEARCH_PATHS ) {
03205         c = MAX_SEARCH_PATHS;
03206     }
03207 
03208     fs_numServerReferencedPaks = c;
03209 
03210     for ( i = 0 ; i < c ; i++ ) {
03211         fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
03212     }
03213 
03214     for ( i = 0 ; i < c ; i++ ) {
03215         if (fs_serverReferencedPakNames[i]) {
03216             Z_Free(fs_serverReferencedPakNames[i]);
03217         }
03218         fs_serverReferencedPakNames[i] = NULL;
03219     }
03220     if ( pakNames && *pakNames ) {
03221         Cmd_TokenizeString( pakNames );
03222 
03223         d = Cmd_Argc();
03224         if ( d > MAX_SEARCH_PATHS ) {
03225             d = MAX_SEARCH_PATHS;
03226         }
03227 
03228         for ( i = 0 ; i < d ; i++ ) {
03229             fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
03230         }
03231     }
03232 }
03233 
03234 /*
03235 ================
03236 FS_InitFilesystem
03237 
03238 Called only at inital startup, not when the filesystem
03239 is resetting due to a game change
03240 ================
03241 */
03242 void FS_InitFilesystem( void ) {
03243     // allow command line parms to override our defaults
03244     // we have to specially handle this, because normal command
03245     // line variable sets don't happen until after the filesystem
03246     // has already been initialized
03247     Com_StartupVariable( "fs_cdpath" );
03248     Com_StartupVariable( "fs_basepath" );
03249     Com_StartupVariable( "fs_homepath" );
03250     Com_StartupVariable( "fs_game" );
03251     Com_StartupVariable( "fs_copyfiles" );
03252     Com_StartupVariable( "fs_restrict" );
03253 
03254     // try to start up normally
03255     FS_Startup( BASEGAME );
03256 
03257     // see if we are going to allow add-ons
03258     FS_SetRestrictions();
03259 
03260     // if we can't find default.cfg, assume that the paths are
03261     // busted and error out now, rather than getting an unreadable
03262     // graphics screen when the font fails to load
03263     if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
03264         Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
03265         // bk001208 - SafeMode see below, FIXME?
03266     }
03267 
03268     Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
03269     Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
03270 
03271   // bk001208 - SafeMode see below, FIXME?
03272 }
03273 
03274 
03275 /*
03276 ================
03277 FS_Restart
03278 ================
03279 */
03280 void FS_Restart( int checksumFeed ) {
03281 
03282     // free anything we currently have loaded
03283     FS_Shutdown(qfalse);
03284 
03285     // set the checksum feed
03286     fs_checksumFeed = checksumFeed;
03287 
03288     // clear pak references
03289     FS_ClearPakReferences(0);
03290 
03291     // try to start up normally
03292     FS_Startup( BASEGAME );
03293 
03294     // see if we are going to allow add-ons
03295     FS_SetRestrictions();
03296 
03297     // if we can't find default.cfg, assume that the paths are
03298     // busted and error out now, rather than getting an unreadable
03299     // graphics screen when the font fails to load
03300     if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
03301         // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3
03302         // (for instance a TA demo server)
03303         if (lastValidBase[0]) {
03304             FS_PureServerSetLoadedPaks("", "");
03305             Cvar_Set("fs_basepath", lastValidBase);
03306             Cvar_Set("fs_gamedirvar", lastValidGame);
03307             lastValidBase[0] = '\0';
03308             lastValidGame[0] = '\0';
03309             Cvar_Set( "fs_restrict", "0" );
03310             FS_Restart(checksumFeed);
03311             Com_Error( ERR_DROP, "Invalid game folder\n" );
03312             return;
03313         }
03314         Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
03315     }
03316 
03317     // bk010116 - new check before safeMode
03318     if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) {
03319         // skip the q3config.cfg if "safe" is on the command line
03320         if ( !Com_SafeMode() ) {
03321             Cbuf_AddText ("exec q3config.cfg\n");
03322         }
03323     }
03324 
03325     Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
03326     Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
03327 
03328 }
03329 
03330 /*
03331 =================
03332 FS_ConditionalRestart
03333 restart if necessary
03334 =================
03335 */
03336 qboolean FS_ConditionalRestart( int checksumFeed ) {
03337     if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) {
03338         FS_Restart( checksumFeed );
03339         return qtrue;
03340     }
03341     return qfalse;
03342 }
03343 
03344 /*
03345 ========================================================================================
03346 
03347 Handle based file calls for virtual machines
03348 
03349 ========================================================================================
03350 */
03351 
03352 int     FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
03353     int     r;
03354     qboolean    sync;
03355 
03356     sync = qfalse;
03357 
03358     switch( mode ) {
03359     case FS_READ:
03360         r = FS_FOpenFileRead( qpath, f, qtrue );
03361         break;
03362     case FS_WRITE:
03363         *f = FS_FOpenFileWrite( qpath );
03364         r = 0;
03365         if (*f == 0) {
03366             r = -1;
03367         }
03368         break;
03369     case FS_APPEND_SYNC:
03370         sync = qtrue;
03371     case FS_APPEND:
03372         *f = FS_FOpenFileAppend( qpath );
03373         r = 0;
03374         if (*f == 0) {
03375             r = -1;
03376         }
03377         break;
03378     default:
03379         Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
03380         return -1;
03381     }
03382 
03383     if (!f) {
03384         return r;
03385     }
03386 
03387     if ( *f ) {
03388         if (fsh[*f].zipFile == qtrue) {
03389             fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z);
03390         } else {
03391             fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o);
03392         }
03393         fsh[*f].fileSize = r;
03394         fsh[*f].streamed = qfalse;
03395 
03396         if (mode == FS_READ) {
03397             Sys_BeginStreamedFile( *f, 0x4000 );
03398             fsh[*f].streamed = qtrue;
03399         }
03400     }
03401     fsh[*f].handleSync = sync;
03402 
03403     return r;
03404 }
03405 
03406 int     FS_FTell( fileHandle_t f ) {
03407     int pos;
03408     if (fsh[f].zipFile == qtrue) {
03409         pos = unztell(fsh[f].handleFiles.file.z);
03410     } else {
03411         pos = ftell(fsh[f].handleFiles.file.o);
03412     }
03413     return pos;
03414 }
03415 
03416 void    FS_Flush( fileHandle_t f ) {
03417     fflush(fsh[f].handleFiles.file.o);
03418 }
03419 

Generated on Thu Aug 25 12:37:47 2005 for Quake III Arena by  doxygen 1.3.9.1