Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

files.c (87296B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 /*****************************************************************************
     23  * name:		files.c
     24  *
     25  * desc:		handle based filesystem for Quake III Arena 
     26  *
     27  * $Archive: /MissionPack/code/qcommon/files.c $
     28  *
     29  *****************************************************************************/
     30 
     31 
     32 #include "../game/q_shared.h"
     33 #include "qcommon.h"
     34 #include "unzip.h"
     35 
     36 /*
     37 =============================================================================
     38 
     39 QUAKE3 FILESYSTEM
     40 
     41 All of Quake's data access is through a hierarchical file system, but the contents of 
     42 the file system can be transparently merged from several sources.
     43 
     44 A "qpath" is a reference to game file data.  MAX_ZPATH is 256 characters, which must include
     45 a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
     46 references outside the quake directory system.
     47 
     48 The "base path" is the path to the directory holding all the game directories and usually
     49 the executable.  It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
     50 command line to allow code debugging in a different directory.  Basepath cannot
     51 be modified at all after startup.  Any files that are created (demos, screenshots,
     52 etc) will be created reletive to the base path, so base path should usually be writable.
     53 
     54 The "cd path" is the path to an alternate hierarchy that will be searched if a file
     55 is not located in the base path.  A user can do a partial install that copies some
     56 data to a base path created on their hard drive and leave the rest on the cd.  Files
     57 are never writen to the cd path.  It defaults to a value set by the installer, like
     58 "e:\quake3", but it can be overridden with "+set ds_cdpath g:\quake3".
     59 
     60 If a user runs the game directly from a CD, the base path would be on the CD.  This
     61 should still function correctly, but all file writes will fail (harmlessly).
     62 
     63 The "home path" is the path used for all write access. On win32 systems we have "base path"
     64 == "home path", but on *nix systems the base installation is usually readonly, and
     65 "home path" points to ~/.q3a or similar
     66 
     67 The user can also install custom mods and content in "home path", so it should be searched
     68 along with "home path" and "cd path" for game content.
     69 
     70 
     71 The "base game" is the directory under the paths where data comes from by default, and
     72 can be either "baseq3" or "demoq3".
     73 
     74 The "current game" may be the same as the base game, or it may be the name of another
     75 directory under the paths that should be searched for files before looking in the base game.
     76 This is the basis for addons.
     77 
     78 Clients automatically set the game directory after receiving a gamestate from a server,
     79 so only servers need to worry about +set fs_game.
     80 
     81 No other directories outside of the base game and current game will ever be referenced by
     82 filesystem functions.
     83 
     84 To save disk space and speed loading, directory trees can be collapsed into zip files.
     85 The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
     86 otherwise the are simply normal uncompressed zip files.  A game directory can have multiple
     87 zip files of the form "pak0.pk3", "pak1.pk3", etc.  Zip files are searched in decending order
     88 from the highest number to the lowest, and will always take precedence over the filesystem.
     89 This allows a pk3 distributed as a patch to override all existing data.
     90 
     91 Because we will have updated executables freely available online, there is no point to
     92 trying to restrict demo / oem versions of the game with code changes.  Demo / oem versions
     93 should be exactly the same executables as release versions, but with different data that
     94 automatically restricts where game media can come from to prevent add-ons from working.
     95 
     96 After the paths are initialized, quake will look for the product.txt file.  If not
     97 found and verified, the game will run in restricted mode.  In restricted mode, only 
     98 files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is
     99 verified to not have been modified.  A single exception is made for q3config.cfg.  Files
    100 can still be written out in restricted mode, so screenshots and demos are allowed.
    101 Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
    102 if there is a valid product.txt under the basepath or cdpath.
    103 
    104 If not running in restricted mode, and a file is not found in any local filesystem,
    105 an attempt will be made to download it and save it under the base path.
    106 
    107 If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
    108 path, it will be copied over to the base path.  This is a development aid to help build
    109 test releases and to copy working sets over slow network links.
    110 
    111 File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
    112 structure and stop on the first successful hit. fs_searchpaths is built with successive
    113 calls to FS_AddGameDirectory
    114 
    115 Additionaly, we search in several subdirectories:
    116 current game is the current mode
    117 base game is a variable to allow mods based on other mods
    118 (such as baseq3 + missionpack content combination in a mod for instance)
    119 BASEGAME is the hardcoded base game ("baseq3")
    120 
    121 e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
    122 
    123 home path + current game's zip files
    124 home path + current game's directory
    125 base path + current game's zip files
    126 base path + current game's directory
    127 cd path + current game's zip files
    128 cd path + current game's directory
    129 
    130 home path + base game's zip file
    131 home path + base game's directory
    132 base path + base game's zip file
    133 base path + base game's directory
    134 cd path + base game's zip file
    135 cd path + base game's directory
    136 
    137 home path + BASEGAME's zip file
    138 home path + BASEGAME's directory
    139 base path + BASEGAME's zip file
    140 base path + BASEGAME's directory
    141 cd path + BASEGAME's zip file
    142 cd path + BASEGAME's directory
    143 
    144 server download, to be written to home path + current game's directory
    145 
    146 
    147 The filesystem can be safely shutdown and reinitialized with different
    148 basedir / cddir / game combinations, but all other subsystems that rely on it
    149 (sound, video) must also be forced to restart.
    150 
    151 Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
    152 subsystems, a simple single-file caching scheme is used.  The CM_ subsystems will
    153 load the file with a request to cache.  Only one file will be kept cached at a time,
    154 so any models that are going to be referenced by both subsystems should alternate
    155 between the CM_ load function and the ref load function.
    156 
    157 TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
    158 game is currently active.  This allows character models, skins, and sounds to be downloaded
    159 to a common directory no matter which game is active.
    160 
    161 How to prevent downloading zip files?
    162 Pass pk3 file names in systeminfo, and download before FS_Restart()?
    163 
    164 Aborting a download disconnects the client from the server.
    165 
    166 How to mark files as downloadable?  Commercial add-ons won't be downloadable.
    167 
    168 Non-commercial downloads will want to download the entire zip file.
    169 the game would have to be reset to actually read the zip in
    170 
    171 Auto-update information
    172 
    173 Path separators
    174 
    175 Casing
    176 
    177   separate server gamedir and client gamedir, so if the user starts
    178   a local game after having connected to a network game, it won't stick
    179   with the network game.
    180 
    181   allow menu options for game selection?
    182 
    183 Read / write config to floppy option.
    184 
    185 Different version coexistance?
    186 
    187 When building a pak file, make sure a q3config.cfg isn't present in it,
    188 or configs will never get loaded from disk!
    189 
    190   todo:
    191 
    192   downloading (outside fs?)
    193   game directory passing and restarting
    194 
    195 =============================================================================
    196 
    197 */
    198 
    199 #define	DEMOGAME			"demota"
    200 
    201 // every time a new demo pk3 file is built, this checksum must be updated.
    202 // the easiest way to get it is to just run the game and see what it spits out
    203 #define	DEMO_PAK_CHECKSUM	437558517u
    204 
    205 // if this is defined, the executable positively won't work with any paks other
    206 // than the demo pak, even if productid is present.  This is only used for our
    207 // last demo release to prevent the mac and linux users from using the demo
    208 // executable with the production windows pak before the mac/linux products
    209 // hit the shelves a little later
    210 // NOW defined in build files
    211 //#define PRE_RELEASE_TADEMO
    212 
    213 #define MAX_ZPATH			256
    214 #define	MAX_SEARCH_PATHS	4096
    215 #define MAX_FILEHASH_SIZE	1024
    216 
    217 typedef struct fileInPack_s {
    218 	char					*name;		// name of the file
    219 	unsigned long			pos;		// file info position in zip
    220 	struct	fileInPack_s*	next;		// next file in the hash
    221 } fileInPack_t;
    222 
    223 typedef struct {
    224 	char			pakFilename[MAX_OSPATH];	// c:\quake3\baseq3\pak0.pk3
    225 	char			pakBasename[MAX_OSPATH];	// pak0
    226 	char			pakGamename[MAX_OSPATH];	// baseq3
    227 	unzFile			handle;						// handle to zip file
    228 	int				checksum;					// regular checksum
    229 	int				pure_checksum;				// checksum for pure
    230 	int				numfiles;					// number of files in pk3
    231 	int				referenced;					// referenced file flags
    232 	int				hashSize;					// hash table size (power of 2)
    233 	fileInPack_t*	*hashTable;					// hash table
    234 	fileInPack_t*	buildBuffer;				// buffer with the filenames etc.
    235 } pack_t;
    236 
    237 typedef struct {
    238 	char		path[MAX_OSPATH];		// c:\quake3
    239 	char		gamedir[MAX_OSPATH];	// baseq3
    240 } directory_t;
    241 
    242 typedef struct searchpath_s {
    243 	struct searchpath_s *next;
    244 
    245 	pack_t		*pack;		// only one of pack / dir will be non NULL
    246 	directory_t	*dir;
    247 } searchpath_t;
    248 
    249 static	char		fs_gamedir[MAX_OSPATH];	// this will be a single file name with no separators
    250 static	cvar_t		*fs_debug;
    251 static	cvar_t		*fs_homepath;
    252 static	cvar_t		*fs_basepath;
    253 static	cvar_t		*fs_basegame;
    254 static	cvar_t		*fs_cdpath;
    255 static	cvar_t		*fs_copyfiles;
    256 static	cvar_t		*fs_gamedirvar;
    257 static	cvar_t		*fs_restrict;
    258 static	searchpath_t	*fs_searchpaths;
    259 static	int			fs_readCount;			// total bytes read
    260 static	int			fs_loadCount;			// total files read
    261 static	int			fs_loadStack;			// total files in memory
    262 static	int			fs_packFiles;			// total number of files in packs
    263 
    264 static int fs_fakeChkSum;
    265 static int fs_checksumFeed;
    266 
    267 typedef union qfile_gus {
    268 	FILE*		o;
    269 	unzFile		z;
    270 } qfile_gut;
    271 
    272 typedef struct qfile_us {
    273 	qfile_gut	file;
    274 	qboolean	unique;
    275 } qfile_ut;
    276 
    277 typedef struct {
    278 	qfile_ut	handleFiles;
    279 	qboolean	handleSync;
    280 	int			baseOffset;
    281 	int			fileSize;
    282 	int			zipFilePos;
    283 	qboolean	zipFile;
    284 	qboolean	streamed;
    285 	char		name[MAX_ZPATH];
    286 } fileHandleData_t;
    287 
    288 static fileHandleData_t	fsh[MAX_FILE_HANDLES];
    289 
    290 // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
    291 // wether we did a reorder on the current search path when joining the server
    292 static qboolean fs_reordered;
    293 
    294 // never load anything from pk3 files that are not present at the server when pure
    295 static int		fs_numServerPaks;
    296 static int		fs_serverPaks[MAX_SEARCH_PATHS];				// checksums
    297 static char		*fs_serverPakNames[MAX_SEARCH_PATHS];			// pk3 names
    298 
    299 // only used for autodownload, to make sure the client has at least
    300 // all the pk3 files that are referenced at the server side
    301 static int		fs_numServerReferencedPaks;
    302 static int		fs_serverReferencedPaks[MAX_SEARCH_PATHS];			// checksums
    303 static char		*fs_serverReferencedPakNames[MAX_SEARCH_PATHS];		// pk3 names
    304 
    305 // last valid game folder used
    306 char lastValidBase[MAX_OSPATH];
    307 char lastValidGame[MAX_OSPATH];
    308 
    309 // 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
    310 static byte fs_scrambledProductId[152] = {
    311 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
    312 };
    313 
    314 #ifdef FS_MISSING
    315 FILE*		missingFiles = NULL;
    316 #endif
    317 
    318 /*
    319 ==============
    320 FS_Initialized
    321 ==============
    322 */
    323 
    324 qboolean FS_Initialized() {
    325 	return (fs_searchpaths != NULL);
    326 }
    327 
    328 /*
    329 =================
    330 FS_PakIsPure
    331 =================
    332 */
    333 qboolean FS_PakIsPure( pack_t *pack ) {
    334 	int i;
    335 
    336 	if ( fs_numServerPaks ) {
    337 		for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
    338 			// FIXME: also use hashed file names
    339 			// NOTE TTimo: a pk3 with same checksum but different name would be validated too
    340 			//   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'
    341 			if ( pack->checksum == fs_serverPaks[i] ) {
    342 				return qtrue;		// on the aproved list
    343 			}
    344 		}
    345 		return qfalse;	// not on the pure server pak list
    346 	}
    347 	return qtrue;
    348 }
    349 
    350 
    351 /*
    352 =================
    353 FS_LoadStack
    354 return load stack
    355 =================
    356 */
    357 int FS_LoadStack()
    358 {
    359 	return fs_loadStack;
    360 }
    361                       
    362 /*
    363 ================
    364 return a hash value for the filename
    365 ================
    366 */
    367 static long FS_HashFileName( const char *fname, int hashSize ) {
    368 	int		i;
    369 	long	hash;
    370 	char	letter;
    371 
    372 	hash = 0;
    373 	i = 0;
    374 	while (fname[i] != '\0') {
    375 		letter = tolower(fname[i]);
    376 		if (letter =='.') break;				// don't include extension
    377 		if (letter =='\\') letter = '/';		// damn path names
    378 		if (letter == PATH_SEP) letter = '/';		// damn path names
    379 		hash+=(long)(letter)*(i+119);
    380 		i++;
    381 	}
    382 	hash = (hash ^ (hash >> 10) ^ (hash >> 20));
    383 	hash &= (hashSize-1);
    384 	return hash;
    385 }
    386 
    387 static fileHandle_t	FS_HandleForFile(void) {
    388 	int		i;
    389 
    390 	for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
    391 		if ( fsh[i].handleFiles.file.o == NULL ) {
    392 			return i;
    393 		}
    394 	}
    395 	Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
    396 	return 0;
    397 }
    398 
    399 static FILE	*FS_FileForHandle( fileHandle_t f ) {
    400 	if ( f < 0 || f > MAX_FILE_HANDLES ) {
    401 		Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" );
    402 	}
    403 	if (fsh[f].zipFile == qtrue) {
    404 		Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
    405 	}
    406 	if ( ! fsh[f].handleFiles.file.o ) {
    407 		Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
    408 	}
    409 	
    410 	return fsh[f].handleFiles.file.o;
    411 }
    412 
    413 void	FS_ForceFlush( fileHandle_t f ) {
    414 	FILE *file;
    415 
    416 	file = FS_FileForHandle(f);
    417 	setvbuf( file, NULL, _IONBF, 0 );
    418 }
    419 
    420 /*
    421 ================
    422 FS_filelength
    423 
    424 If this is called on a non-unique FILE (from a pak file),
    425 it will return the size of the pak file, not the expected
    426 size of the file.
    427 ================
    428 */
    429 int FS_filelength( fileHandle_t f ) {
    430 	int		pos;
    431 	int		end;
    432 	FILE*	h;
    433 
    434 	h = FS_FileForHandle(f);
    435 	pos = ftell (h);
    436 	fseek (h, 0, SEEK_END);
    437 	end = ftell (h);
    438 	fseek (h, pos, SEEK_SET);
    439 
    440 	return end;
    441 }
    442 
    443 /*
    444 ====================
    445 FS_ReplaceSeparators
    446 
    447 Fix things up differently for win/unix/mac
    448 ====================
    449 */
    450 static void FS_ReplaceSeparators( char *path ) {
    451 	char	*s;
    452 
    453 	for ( s = path ; *s ; s++ ) {
    454 		if ( *s == '/' || *s == '\\' ) {
    455 			*s = PATH_SEP;
    456 		}
    457 	}
    458 }
    459 
    460 /*
    461 ===================
    462 FS_BuildOSPath
    463 
    464 Qpath may have either forward or backwards slashes
    465 ===================
    466 */
    467 char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
    468 	char	temp[MAX_OSPATH];
    469 	static char ospath[2][MAX_OSPATH];
    470 	static int toggle;
    471 	
    472 	toggle ^= 1;		// flip-flop to allow two returns without clash
    473 
    474 	if( !game || !game[0] ) {
    475 		game = fs_gamedir;
    476 	}
    477 
    478 	Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath );
    479 	FS_ReplaceSeparators( temp );	
    480 	Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
    481 	
    482 	return ospath[toggle];
    483 }
    484 
    485 
    486 /*
    487 ============
    488 FS_CreatePath
    489 
    490 Creates any directories needed to store the given filename
    491 ============
    492 */
    493 static qboolean FS_CreatePath (char *OSPath) {
    494 	char	*ofs;
    495 	
    496 	// make absolutely sure that it can't back up the path
    497 	// FIXME: is c: allowed???
    498 	if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
    499 		Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
    500 		return qtrue;
    501 	}
    502 
    503 	for (ofs = OSPath+1 ; *ofs ; ofs++) {
    504 		if (*ofs == PATH_SEP) {	
    505 			// create the directory
    506 			*ofs = 0;
    507 			Sys_Mkdir (OSPath);
    508 			*ofs = PATH_SEP;
    509 		}
    510 	}
    511 	return qfalse;
    512 }
    513 
    514 /*
    515 =================
    516 FS_CopyFile
    517 
    518 Copy a fully specified file from one place to another
    519 =================
    520 */
    521 static void FS_CopyFile( char *fromOSPath, char *toOSPath ) {
    522 	FILE	*f;
    523 	int		len;
    524 	byte	*buf;
    525 
    526 	Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath );
    527 
    528 	if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) {
    529 		Com_Printf( "Ignoring journal files\n");
    530 		return;
    531 	}
    532 
    533 	f = fopen( fromOSPath, "rb" );
    534 	if ( !f ) {
    535 		return;
    536 	}
    537 	fseek (f, 0, SEEK_END);
    538 	len = ftell (f);
    539 	fseek (f, 0, SEEK_SET);
    540 
    541 	// we are using direct malloc instead of Z_Malloc here, so it
    542 	// probably won't work on a mac... Its only for developers anyway...
    543 	buf = malloc( len );
    544 	if (fread( buf, 1, len, f ) != len)
    545 		Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" );
    546 	fclose( f );
    547 
    548 	if( FS_CreatePath( toOSPath ) ) {
    549 		return;
    550 	}
    551 
    552 	f = fopen( toOSPath, "wb" );
    553 	if ( !f ) {
    554 		return;
    555 	}
    556 	if (fwrite( buf, 1, len, f ) != len)
    557 		Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" );
    558 	fclose( f );
    559 	free( buf );
    560 }
    561 
    562 /*
    563 ===========
    564 FS_Remove
    565 
    566 ===========
    567 */
    568 static void FS_Remove( const char *osPath ) {
    569 	remove( osPath );
    570 }
    571 
    572 /*
    573 ================
    574 FS_FileExists
    575 
    576 Tests if the file exists in the current gamedir, this DOES NOT
    577 search the paths.  This is to determine if opening a file to write
    578 (which always goes into the current gamedir) will cause any overwrites.
    579 NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
    580 ================
    581 */
    582 qboolean FS_FileExists( const char *file )
    583 {
    584 	FILE *f;
    585 	char *testpath;
    586 
    587 	testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file );
    588 
    589 	f = fopen( testpath, "rb" );
    590 	if (f) {
    591 		fclose( f );
    592 		return qtrue;
    593 	}
    594 	return qfalse;
    595 }
    596 
    597 /*
    598 ================
    599 FS_SV_FileExists
    600 
    601 Tests if the file exists 
    602 ================
    603 */
    604 qboolean FS_SV_FileExists( const char *file )
    605 {
    606 	FILE *f;
    607 	char *testpath;
    608 
    609 	testpath = FS_BuildOSPath( fs_homepath->string, file, "");
    610 	testpath[strlen(testpath)-1] = '\0';
    611 
    612 	f = fopen( testpath, "rb" );
    613 	if (f) {
    614 		fclose( f );
    615 		return qtrue;
    616 	}
    617 	return qfalse;
    618 }
    619 
    620 
    621 /*
    622 ===========
    623 FS_SV_FOpenFileWrite
    624 
    625 ===========
    626 */
    627 fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) {
    628 	char *ospath;
    629 	fileHandle_t	f;
    630 
    631 	if ( !fs_searchpaths ) {
    632 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
    633 	}
    634 
    635 	ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
    636 	ospath[strlen(ospath)-1] = '\0';
    637 
    638 	f = FS_HandleForFile();
    639 	fsh[f].zipFile = qfalse;
    640 
    641 	if ( fs_debug->integer ) {
    642 		Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath );
    643 	}
    644 
    645 	if( FS_CreatePath( ospath ) ) {
    646 		return 0;
    647 	}
    648 
    649 	Com_DPrintf( "writing to: %s\n", ospath );
    650 	fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
    651 
    652 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
    653 
    654 	fsh[f].handleSync = qfalse;
    655 	if (!fsh[f].handleFiles.file.o) {
    656 		f = 0;
    657 	}
    658 	return f;
    659 }
    660 
    661 /*
    662 ===========
    663 FS_SV_FOpenFileRead
    664 search for a file somewhere below the home path, base path or cd path
    665 we search in that order, matching FS_SV_FOpenFileRead order
    666 ===========
    667 */
    668 int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
    669 	char *ospath;
    670 	fileHandle_t	f = 0;
    671 
    672 	if ( !fs_searchpaths ) {
    673 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
    674 	}
    675 
    676 	f = FS_HandleForFile();
    677 	fsh[f].zipFile = qfalse;
    678 
    679 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
    680 
    681 	// don't let sound stutter
    682 	S_ClearSoundBuffer();
    683 
    684   // search homepath
    685 	ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
    686 	// remove trailing slash
    687 	ospath[strlen(ospath)-1] = '\0';
    688 
    689 	if ( fs_debug->integer ) {
    690 		Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath );
    691 	}
    692 
    693 	fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
    694 	fsh[f].handleSync = qfalse;
    695   if (!fsh[f].handleFiles.file.o)
    696   {
    697     // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid
    698     if (Q_stricmp(fs_homepath->string,fs_basepath->string))
    699     {
    700       // search basepath
    701       ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
    702       ospath[strlen(ospath)-1] = '\0';
    703 
    704       if ( fs_debug->integer )
    705       {
    706         Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath );
    707       }
    708 
    709       fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
    710       fsh[f].handleSync = qfalse;
    711 
    712       if ( !fsh[f].handleFiles.file.o )
    713       {
    714         f = 0;
    715       }
    716     }
    717   }
    718 
    719 	if (!fsh[f].handleFiles.file.o) {
    720     // search cd path
    721     ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" );
    722     ospath[strlen(ospath)-1] = '\0';
    723 
    724     if (fs_debug->integer)
    725     {
    726       Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath );
    727     }
    728 
    729 	  fsh[f].handleFiles.file.o = fopen( ospath, "rb" );
    730 	  fsh[f].handleSync = qfalse;
    731 
    732 	  if( !fsh[f].handleFiles.file.o ) {
    733 	    f = 0;
    734 	  }
    735   }
    736   
    737 	*fp = f;
    738 	if (f) {
    739 		return FS_filelength(f);
    740 	}
    741 	return 0;
    742 }
    743 
    744 
    745 /*
    746 ===========
    747 FS_SV_Rename
    748 
    749 ===========
    750 */
    751 void FS_SV_Rename( const char *from, const char *to ) {
    752 	char			*from_ospath, *to_ospath;
    753 
    754 	if ( !fs_searchpaths ) {
    755 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
    756 	}
    757 
    758 	// don't let sound stutter
    759 	S_ClearSoundBuffer();
    760 
    761 	from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" );
    762 	to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" );
    763 	from_ospath[strlen(from_ospath)-1] = '\0';
    764 	to_ospath[strlen(to_ospath)-1] = '\0';
    765 
    766 	if ( fs_debug->integer ) {
    767 		Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath );
    768 	}
    769 
    770 	if (rename( from_ospath, to_ospath )) {
    771 		// Failed, try copying it and deleting the original
    772 		FS_CopyFile ( from_ospath, to_ospath );
    773 		FS_Remove ( from_ospath );
    774 	}
    775 }
    776 
    777 
    778 
    779 /*
    780 ===========
    781 FS_Rename
    782 
    783 ===========
    784 */
    785 void FS_Rename( const char *from, const char *to ) {
    786 	char			*from_ospath, *to_ospath;
    787 
    788 	if ( !fs_searchpaths ) {
    789 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
    790 	}
    791 
    792 	// don't let sound stutter
    793 	S_ClearSoundBuffer();
    794 
    795 	from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from );
    796 	to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to );
    797 
    798 	if ( fs_debug->integer ) {
    799 		Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath );
    800 	}
    801 
    802 	if (rename( from_ospath, to_ospath )) {
    803 		// Failed, try copying it and deleting the original
    804 		FS_CopyFile ( from_ospath, to_ospath );
    805 		FS_Remove ( from_ospath );
    806 	}
    807 }
    808 
    809 /*
    810 ==============
    811 FS_FCloseFile
    812 
    813 If the FILE pointer is an open pak file, leave it open.
    814 
    815 For some reason, other dll's can't just cal fclose()
    816 on files returned by FS_FOpenFile...
    817 ==============
    818 */
    819 void FS_FCloseFile( fileHandle_t f ) {
    820 	if ( !fs_searchpaths ) {
    821 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
    822 	}
    823 
    824 	if (fsh[f].streamed) {
    825 		Sys_EndStreamedFile(f);
    826 	}
    827 	if (fsh[f].zipFile == qtrue) {
    828 		unzCloseCurrentFile( fsh[f].handleFiles.file.z );
    829 		if ( fsh[f].handleFiles.unique ) {
    830 			unzClose( fsh[f].handleFiles.file.z );
    831 		}
    832 		Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
    833 		return;
    834 	}
    835 
    836 	// we didn't find it as a pak, so close it as a unique file
    837 	if (fsh[f].handleFiles.file.o) {
    838 		fclose (fsh[f].handleFiles.file.o);
    839 	}
    840 	Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
    841 }
    842 
    843 /*
    844 ===========
    845 FS_FOpenFileWrite
    846 
    847 ===========
    848 */
    849 fileHandle_t FS_FOpenFileWrite( const char *filename ) {
    850 	char			*ospath;
    851 	fileHandle_t	f;
    852 
    853 	if ( !fs_searchpaths ) {
    854 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
    855 	}
    856 
    857 	f = FS_HandleForFile();
    858 	fsh[f].zipFile = qfalse;
    859 
    860 	ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
    861 
    862 	if ( fs_debug->integer ) {
    863 		Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
    864 	}
    865 
    866 	if( FS_CreatePath( ospath ) ) {
    867 		return 0;
    868 	}
    869 
    870 	// enabling the following line causes a recursive function call loop
    871 	// when running with +set logfile 1 +set developer 1
    872 	//Com_DPrintf( "writing to: %s\n", ospath );
    873 	fsh[f].handleFiles.file.o = fopen( ospath, "wb" );
    874 
    875 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
    876 
    877 	fsh[f].handleSync = qfalse;
    878 	if (!fsh[f].handleFiles.file.o) {
    879 		f = 0;
    880 	}
    881 	return f;
    882 }
    883 
    884 /*
    885 ===========
    886 FS_FOpenFileAppend
    887 
    888 ===========
    889 */
    890 fileHandle_t FS_FOpenFileAppend( const char *filename ) {
    891 	char			*ospath;
    892 	fileHandle_t	f;
    893 
    894 	if ( !fs_searchpaths ) {
    895 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
    896 	}
    897 
    898 	f = FS_HandleForFile();
    899 	fsh[f].zipFile = qfalse;
    900 
    901 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
    902 
    903 	// don't let sound stutter
    904 	S_ClearSoundBuffer();
    905 
    906 	ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
    907 
    908 	if ( fs_debug->integer ) {
    909 		Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
    910 	}
    911 
    912 	if( FS_CreatePath( ospath ) ) {
    913 		return 0;
    914 	}
    915 
    916 	fsh[f].handleFiles.file.o = fopen( ospath, "ab" );
    917 	fsh[f].handleSync = qfalse;
    918 	if (!fsh[f].handleFiles.file.o) {
    919 		f = 0;
    920 	}
    921 	return f;
    922 }
    923 
    924 /*
    925 ===========
    926 FS_FilenameCompare
    927 
    928 Ignore case and seprator char distinctions
    929 ===========
    930 */
    931 qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
    932 	int		c1, c2;
    933 	
    934 	do {
    935 		c1 = *s1++;
    936 		c2 = *s2++;
    937 
    938 		if (c1 >= 'a' && c1 <= 'z') {
    939 			c1 -= ('a' - 'A');
    940 		}
    941 		if (c2 >= 'a' && c2 <= 'z') {
    942 			c2 -= ('a' - 'A');
    943 		}
    944 
    945 		if ( c1 == '\\' || c1 == ':' ) {
    946 			c1 = '/';
    947 		}
    948 		if ( c2 == '\\' || c2 == ':' ) {
    949 			c2 = '/';
    950 		}
    951 		
    952 		if (c1 != c2) {
    953 			return -1;		// strings not equal
    954 		}
    955 	} while (c1);
    956 	
    957 	return 0;		// strings are equal
    958 }
    959 
    960 /*
    961 ===========
    962 FS_ShiftedStrStr
    963 ===========
    964 */
    965 char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) {
    966 	char buf[MAX_STRING_TOKENS];
    967 	int i;
    968 
    969 	for (i = 0; substring[i]; i++) {
    970 		buf[i] = substring[i] + shift;
    971 	}
    972 	buf[i] = '\0';
    973 	return strstr(string, buf);
    974 }
    975 
    976 /*
    977 ===========
    978 FS_FOpenFileRead
    979 
    980 Finds the file in the search path.
    981 Returns filesize and an open FILE pointer.
    982 Used for streaming data out of either a
    983 separate file or a ZIP file.
    984 ===========
    985 */
    986 extern qboolean		com_fullyInitialized;
    987 
    988 int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) {
    989 	searchpath_t	*search;
    990 	char			*netpath;
    991 	pack_t			*pak;
    992 	fileInPack_t	*pakFile;
    993 	directory_t		*dir;
    994 	long			hash;
    995 	unz_s			*zfi;
    996 	FILE			*temp;
    997 	int				l;
    998 	char demoExt[16];
    999 
   1000 	hash = 0;
   1001 
   1002 	if ( !fs_searchpaths ) {
   1003 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1004 	}
   1005 
   1006 	if ( file == NULL ) {
   1007 		// just wants to see if file is there
   1008 		for ( search = fs_searchpaths ; search ; search = search->next ) {
   1009 			//
   1010 			if ( search->pack ) {
   1011 				hash = FS_HashFileName(filename, search->pack->hashSize);
   1012 			}
   1013 			// is the element a pak file?
   1014 			if ( search->pack && search->pack->hashTable[hash] ) {
   1015 				// look through all the pak file elements
   1016 				pak = search->pack;
   1017 				pakFile = pak->hashTable[hash];
   1018 				do {
   1019 					// case and separator insensitive comparisons
   1020 					if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
   1021 						// found it!
   1022 						return qtrue;
   1023 					}
   1024 					pakFile = pakFile->next;
   1025 				} while(pakFile != NULL);
   1026 			} else if ( search->dir ) {
   1027 				dir = search->dir;
   1028 			
   1029 				netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
   1030 				temp = fopen (netpath, "rb");
   1031 				if ( !temp ) {
   1032 					continue;
   1033 				}
   1034 				fclose(temp);
   1035 				return qtrue;
   1036 			}
   1037 		}
   1038 		return qfalse;
   1039 	}
   1040 
   1041 	if ( !filename ) {
   1042 		Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
   1043 	}
   1044 
   1045 	Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION );
   1046 	// qpaths are not supposed to have a leading slash
   1047 	if ( filename[0] == '/' || filename[0] == '\\' ) {
   1048 		filename++;
   1049 	}
   1050 
   1051 	// make absolutely sure that it can't back up the path.
   1052 	// The searchpaths do guarantee that something will always
   1053 	// be prepended, so we don't need to worry about "c:" or "//limbo" 
   1054 	if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
   1055 		*file = 0;
   1056 		return -1;
   1057 	}
   1058 
   1059 	// make sure the q3key file is only readable by the quake3.exe at initialization
   1060 	// any other time the key should only be accessed in memory using the provided functions
   1061 	if( com_fullyInitialized && strstr( filename, "q3key" ) ) {
   1062 		*file = 0;
   1063 		return -1;
   1064 	}
   1065 
   1066 	//
   1067 	// search through the path, one element at a time
   1068 	//
   1069 
   1070 	*file = FS_HandleForFile();
   1071 	fsh[*file].handleFiles.unique = uniqueFILE;
   1072 
   1073 	for ( search = fs_searchpaths ; search ; search = search->next ) {
   1074 		//
   1075 		if ( search->pack ) {
   1076 			hash = FS_HashFileName(filename, search->pack->hashSize);
   1077 		}
   1078 		// is the element a pak file?
   1079 		if ( search->pack && search->pack->hashTable[hash] ) {
   1080 			// disregard if it doesn't match one of the allowed pure pak files
   1081 			if ( !FS_PakIsPure(search->pack) ) {
   1082 				continue;
   1083 			}
   1084 
   1085 			// look through all the pak file elements
   1086 			pak = search->pack;
   1087 			pakFile = pak->hashTable[hash];
   1088 			do {
   1089 				// case and separator insensitive comparisons
   1090 				if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
   1091 					// found it!
   1092 
   1093 					// mark the pak as having been referenced and mark specifics on cgame and ui
   1094 					// shaders, txt, arena files  by themselves do not count as a reference as 
   1095 					// these are loaded from all pk3s 
   1096 					// from every pk3 file.. 
   1097 					l = strlen( filename );
   1098 					if ( !(pak->referenced & FS_GENERAL_REF)) {
   1099 						if ( Q_stricmp(filename + l - 7, ".shader") != 0 &&
   1100 							Q_stricmp(filename + l - 4, ".txt") != 0 &&
   1101 							Q_stricmp(filename + l - 4, ".cfg") != 0 &&
   1102 							Q_stricmp(filename + l - 7, ".config") != 0 &&
   1103 							strstr(filename, "levelshots") == NULL &&
   1104 							Q_stricmp(filename + l - 4, ".bot") != 0 &&
   1105 							Q_stricmp(filename + l - 6, ".arena") != 0 &&
   1106 							Q_stricmp(filename + l - 5, ".menu") != 0) {
   1107 							pak->referenced |= FS_GENERAL_REF;
   1108 						}
   1109 					}
   1110 
   1111 					// qagame.qvm	- 13
   1112 					// dTZT`X!di`
   1113 					if (!(pak->referenced & FS_QAGAME_REF) && FS_ShiftedStrStr(filename, "dTZT`X!di`", 13)) {
   1114 						pak->referenced |= FS_QAGAME_REF;
   1115 					}
   1116 					// cgame.qvm	- 7
   1117 					// \`Zf^'jof
   1118 					if (!(pak->referenced & FS_CGAME_REF) && FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7)) {
   1119 						pak->referenced |= FS_CGAME_REF;
   1120 					}
   1121 					// ui.qvm		- 5
   1122 					// pd)lqh
   1123 					if (!(pak->referenced & FS_UI_REF) && FS_ShiftedStrStr(filename , "pd)lqh", 5)) {
   1124 						pak->referenced |= FS_UI_REF;
   1125 					}
   1126 
   1127 					if ( uniqueFILE ) {
   1128 						// open a new file on the pakfile
   1129 						fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle);
   1130 						if (fsh[*file].handleFiles.file.z == NULL) {
   1131 							Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename);
   1132 						}
   1133 					} else {
   1134 						fsh[*file].handleFiles.file.z = pak->handle;
   1135 					}
   1136 					Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
   1137 					fsh[*file].zipFile = qtrue;
   1138 					zfi = (unz_s *)fsh[*file].handleFiles.file.z;
   1139 					// in case the file was new
   1140 					temp = zfi->file;
   1141 					// set the file position in the zip file (also sets the current file info)
   1142 					unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos);
   1143 					// copy the file info into the unzip structure
   1144 					Com_Memcpy( zfi, pak->handle, sizeof(unz_s) );
   1145 					// we copy this back into the structure
   1146 					zfi->file = temp;
   1147 					// open the file in the zip
   1148 					unzOpenCurrentFile( fsh[*file].handleFiles.file.z );
   1149 					fsh[*file].zipFilePos = pakFile->pos;
   1150 
   1151 					if ( fs_debug->integer ) {
   1152 						Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", 
   1153 							filename, pak->pakFilename );
   1154 					}
   1155 					return zfi->cur_file_info.uncompressed_size;
   1156 				}
   1157 				pakFile = pakFile->next;
   1158 			} while(pakFile != NULL);
   1159 		} else if ( search->dir ) {
   1160 			// check a file in the directory tree
   1161 
   1162 			// if we are running restricted, the only files we
   1163 			// will allow to come from the directory are .cfg files
   1164 			l = strlen( filename );
   1165       // FIXME TTimo I'm not sure about the fs_numServerPaks test
   1166       // if you are using FS_ReadFile to find out if a file exists,
   1167       //   this test can make the search fail although the file is in the directory
   1168       // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
   1169       // turned out I used FS_FileExists instead
   1170 			if ( fs_restrict->integer || fs_numServerPaks ) {
   1171 
   1172 				if ( Q_stricmp( filename + l - 4, ".cfg" )		// for config files
   1173 					&& Q_stricmp( filename + l - 5, ".menu" )	// menu files
   1174 					&& Q_stricmp( filename + l - 5, ".game" )	// menu files
   1175 					&& Q_stricmp( filename + l - strlen(demoExt), demoExt )	// menu files
   1176 					&& Q_stricmp( filename + l - 4, ".dat" ) ) {	// for journal files
   1177 					continue;
   1178 				}
   1179 			}
   1180 
   1181 			dir = search->dir;
   1182 			
   1183 			netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename );
   1184 			fsh[*file].handleFiles.file.o = fopen (netpath, "rb");
   1185 			if ( !fsh[*file].handleFiles.file.o ) {
   1186 				continue;
   1187 			}
   1188 
   1189 			if ( Q_stricmp( filename + l - 4, ".cfg" )		// for config files
   1190 				&& Q_stricmp( filename + l - 5, ".menu" )	// menu files
   1191 				&& Q_stricmp( filename + l - 5, ".game" )	// menu files
   1192 				&& Q_stricmp( filename + l - strlen(demoExt), demoExt )	// menu files
   1193 				&& Q_stricmp( filename + l - 4, ".dat" ) ) {	// for journal files
   1194 				fs_fakeChkSum = random();
   1195 			}
   1196       
   1197 			Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) );
   1198 			fsh[*file].zipFile = qfalse;
   1199 			if ( fs_debug->integer ) {
   1200 				Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename,
   1201 					dir->path, dir->gamedir );
   1202 			}
   1203 
   1204 			// if we are getting it from the cdpath, optionally copy it
   1205 			//  to the basepath
   1206 			if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) {
   1207 				char	*copypath;
   1208 
   1209 				copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename );
   1210 				FS_CopyFile( netpath, copypath );
   1211 			}
   1212 
   1213 			return FS_filelength (*file);
   1214 		}		
   1215 	}
   1216 	
   1217 	Com_DPrintf ("Can't find %s\n", filename);
   1218 #ifdef FS_MISSING
   1219 	if (missingFiles) {
   1220 		fprintf(missingFiles, "%s\n", filename);
   1221 	}
   1222 #endif
   1223 	*file = 0;
   1224 	return -1;
   1225 }
   1226 
   1227 
   1228 /*
   1229 =================
   1230 FS_Read
   1231 
   1232 Properly handles partial reads
   1233 =================
   1234 */
   1235 int FS_Read2( void *buffer, int len, fileHandle_t f ) {
   1236 	if ( !fs_searchpaths ) {
   1237 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1238 	}
   1239 
   1240 	if ( !f ) {
   1241 		return 0;
   1242 	}
   1243 	if (fsh[f].streamed) {
   1244 		int r;
   1245 		fsh[f].streamed = qfalse;
   1246 		r = Sys_StreamedRead( buffer, len, 1, f);
   1247 		fsh[f].streamed = qtrue;
   1248 		return r;
   1249 	} else {
   1250 		return FS_Read( buffer, len, f);
   1251 	}
   1252 }
   1253 
   1254 int FS_Read( void *buffer, int len, fileHandle_t f ) {
   1255 	int		block, remaining;
   1256 	int		read;
   1257 	byte	*buf;
   1258 	int		tries;
   1259 
   1260 	if ( !fs_searchpaths ) {
   1261 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1262 	}
   1263 
   1264 	if ( !f ) {
   1265 		return 0;
   1266 	}
   1267 
   1268 	buf = (byte *)buffer;
   1269 	fs_readCount += len;
   1270 
   1271 	if (fsh[f].zipFile == qfalse) {
   1272 		remaining = len;
   1273 		tries = 0;
   1274 		while (remaining) {
   1275 			block = remaining;
   1276 			read = fread (buf, 1, block, fsh[f].handleFiles.file.o);
   1277 			if (read == 0) {
   1278 				// we might have been trying to read from a CD, which
   1279 				// sometimes returns a 0 read on windows
   1280 				if (!tries) {
   1281 					tries = 1;
   1282 				} else {
   1283 					return len-remaining;	//Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
   1284 				}
   1285 			}
   1286 
   1287 			if (read == -1) {
   1288 				Com_Error (ERR_FATAL, "FS_Read: -1 bytes read");
   1289 			}
   1290 
   1291 			remaining -= read;
   1292 			buf += read;
   1293 		}
   1294 		return len;
   1295 	} else {
   1296 		return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len);
   1297 	}
   1298 }
   1299 
   1300 /*
   1301 =================
   1302 FS_Write
   1303 
   1304 Properly handles partial writes
   1305 =================
   1306 */
   1307 int FS_Write( const void *buffer, int len, fileHandle_t h ) {
   1308 	int		block, remaining;
   1309 	int		written;
   1310 	byte	*buf;
   1311 	int		tries;
   1312 	FILE	*f;
   1313 
   1314 	if ( !fs_searchpaths ) {
   1315 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1316 	}
   1317 
   1318 	if ( !h ) {
   1319 		return 0;
   1320 	}
   1321 
   1322 	f = FS_FileForHandle(h);
   1323 	buf = (byte *)buffer;
   1324 
   1325 	remaining = len;
   1326 	tries = 0;
   1327 	while (remaining) {
   1328 		block = remaining;
   1329 		written = fwrite (buf, 1, block, f);
   1330 		if (written == 0) {
   1331 			if (!tries) {
   1332 				tries = 1;
   1333 			} else {
   1334 				Com_Printf( "FS_Write: 0 bytes written\n" );
   1335 				return 0;
   1336 			}
   1337 		}
   1338 
   1339 		if (written == -1) {
   1340 			Com_Printf( "FS_Write: -1 bytes written\n" );
   1341 			return 0;
   1342 		}
   1343 
   1344 		remaining -= written;
   1345 		buf += written;
   1346 	}
   1347 	if ( fsh[h].handleSync ) {
   1348 		fflush( f );
   1349 	}
   1350 	return len;
   1351 }
   1352 
   1353 void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
   1354 	va_list		argptr;
   1355 	char		msg[MAXPRINTMSG];
   1356 
   1357 	va_start (argptr,fmt);
   1358 	Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
   1359 	va_end (argptr);
   1360 
   1361 	FS_Write(msg, strlen(msg), h);
   1362 }
   1363 
   1364 /*
   1365 =================
   1366 FS_Seek
   1367 
   1368 =================
   1369 */
   1370 int FS_Seek( fileHandle_t f, long offset, int origin ) {
   1371 	int		_origin;
   1372 	char	foo[65536];
   1373 
   1374 	if ( !fs_searchpaths ) {
   1375 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1376 		return -1;
   1377 	}
   1378 
   1379 	if (fsh[f].streamed) {
   1380 		fsh[f].streamed = qfalse;
   1381 		Sys_StreamSeek( f, offset, origin );
   1382 		fsh[f].streamed = qtrue;
   1383 	}
   1384 
   1385 	if (fsh[f].zipFile == qtrue) {
   1386 		if (offset == 0 && origin == FS_SEEK_SET) {
   1387 			// set the file position in the zip file (also sets the current file info)
   1388 			unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
   1389 			return unzOpenCurrentFile(fsh[f].handleFiles.file.z);
   1390 		} else if (offset<65536) {
   1391 			// set the file position in the zip file (also sets the current file info)
   1392 			unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
   1393 			unzOpenCurrentFile(fsh[f].handleFiles.file.z);
   1394 			return FS_Read(foo, offset, f);
   1395 		} else {
   1396 			Com_Error( ERR_FATAL, "ZIP FILE FSEEK NOT YET IMPLEMENTED\n" );
   1397 			return -1;
   1398 		}
   1399 	} else {
   1400 		FILE *file;
   1401 		file = FS_FileForHandle(f);
   1402 		switch( origin ) {
   1403 		case FS_SEEK_CUR:
   1404 			_origin = SEEK_CUR;
   1405 			break;
   1406 		case FS_SEEK_END:
   1407 			_origin = SEEK_END;
   1408 			break;
   1409 		case FS_SEEK_SET:
   1410 			_origin = SEEK_SET;
   1411 			break;
   1412 		default:
   1413 			_origin = SEEK_CUR;
   1414 			Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" );
   1415 			break;
   1416 		}
   1417 
   1418 		return fseek( file, offset, _origin );
   1419 	}
   1420 }
   1421 
   1422 
   1423 /*
   1424 ======================================================================================
   1425 
   1426 CONVENIENCE FUNCTIONS FOR ENTIRE FILES
   1427 
   1428 ======================================================================================
   1429 */
   1430 
   1431 int	FS_FileIsInPAK(const char *filename, int *pChecksum ) {
   1432 	searchpath_t	*search;
   1433 	pack_t			*pak;
   1434 	fileInPack_t	*pakFile;
   1435 	long			hash = 0;
   1436 
   1437 	if ( !fs_searchpaths ) {
   1438 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1439 	}
   1440 
   1441 	if ( !filename ) {
   1442 		Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" );
   1443 	}
   1444 
   1445 	// qpaths are not supposed to have a leading slash
   1446 	if ( filename[0] == '/' || filename[0] == '\\' ) {
   1447 		filename++;
   1448 	}
   1449 
   1450 	// make absolutely sure that it can't back up the path.
   1451 	// The searchpaths do guarantee that something will always
   1452 	// be prepended, so we don't need to worry about "c:" or "//limbo" 
   1453 	if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
   1454 		return -1;
   1455 	}
   1456 
   1457 	//
   1458 	// search through the path, one element at a time
   1459 	//
   1460 
   1461 	for ( search = fs_searchpaths ; search ; search = search->next ) {
   1462 		//
   1463 		if (search->pack) {
   1464 			hash = FS_HashFileName(filename, search->pack->hashSize);
   1465 		}
   1466 		// is the element a pak file?
   1467 		if ( search->pack && search->pack->hashTable[hash] ) {
   1468 			// disregard if it doesn't match one of the allowed pure pak files
   1469 			if ( !FS_PakIsPure(search->pack) ) {
   1470 				continue;
   1471 			}
   1472 
   1473 			// look through all the pak file elements
   1474 			pak = search->pack;
   1475 			pakFile = pak->hashTable[hash];
   1476 			do {
   1477 				// case and separator insensitive comparisons
   1478 				if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
   1479 					if (pChecksum) {
   1480 						*pChecksum = pak->pure_checksum;
   1481 					}
   1482 					return 1;
   1483 				}
   1484 				pakFile = pakFile->next;
   1485 			} while(pakFile != NULL);
   1486 		}
   1487 	}
   1488 	return -1;
   1489 }
   1490 
   1491 /*
   1492 ============
   1493 FS_ReadFile
   1494 
   1495 Filename are relative to the quake search path
   1496 a null buffer will just return the file length without loading
   1497 ============
   1498 */
   1499 int FS_ReadFile( const char *qpath, void **buffer ) {
   1500 	fileHandle_t	h;
   1501 	byte*			buf;
   1502 	qboolean		isConfig;
   1503 	int				len;
   1504 
   1505 	if ( !fs_searchpaths ) {
   1506 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1507 	}
   1508 
   1509 	if ( !qpath || !qpath[0] ) {
   1510 		Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" );
   1511 	}
   1512 
   1513 	buf = NULL;	// quiet compiler warning
   1514 
   1515 	// if this is a .cfg file and we are playing back a journal, read
   1516 	// it from the journal file
   1517 	if ( strstr( qpath, ".cfg" ) ) {
   1518 		isConfig = qtrue;
   1519 		if ( com_journal && com_journal->integer == 2 ) {
   1520 			int		r;
   1521 
   1522 			Com_DPrintf( "Loading %s from journal file.\n", qpath );
   1523 			r = FS_Read( &len, sizeof( len ), com_journalDataFile );
   1524 			if ( r != sizeof( len ) ) {
   1525 				if (buffer != NULL) *buffer = NULL;
   1526 				return -1;
   1527 			}
   1528 			// if the file didn't exist when the journal was created
   1529 			if (!len) {
   1530 				if (buffer == NULL) {
   1531 					return 1;			// hack for old journal files
   1532 				}
   1533 				*buffer = NULL;
   1534 				return -1;
   1535 			}
   1536 			if (buffer == NULL) {
   1537 				return len;
   1538 			}
   1539 
   1540 			buf = Hunk_AllocateTempMemory(len+1);
   1541 			*buffer = buf;
   1542 
   1543 			r = FS_Read( buf, len, com_journalDataFile );
   1544 			if ( r != len ) {
   1545 				Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
   1546 			}
   1547 
   1548 			fs_loadCount++;
   1549 			fs_loadStack++;
   1550 
   1551 			// guarantee that it will have a trailing 0 for string operations
   1552 			buf[len] = 0;
   1553 
   1554 			return len;
   1555 		}
   1556 	} else {
   1557 		isConfig = qfalse;
   1558 	}
   1559 
   1560 	// look for it in the filesystem or pack files
   1561 	len = FS_FOpenFileRead( qpath, &h, qfalse );
   1562 	if ( h == 0 ) {
   1563 		if ( buffer ) {
   1564 			*buffer = NULL;
   1565 		}
   1566 		// if we are journalling and it is a config file, write a zero to the journal file
   1567 		if ( isConfig && com_journal && com_journal->integer == 1 ) {
   1568 			Com_DPrintf( "Writing zero for %s to journal file.\n", qpath );
   1569 			len = 0;
   1570 			FS_Write( &len, sizeof( len ), com_journalDataFile );
   1571 			FS_Flush( com_journalDataFile );
   1572 		}
   1573 		return -1;
   1574 	}
   1575 	
   1576 	if ( !buffer ) {
   1577 		if ( isConfig && com_journal && com_journal->integer == 1 ) {
   1578 			Com_DPrintf( "Writing len for %s to journal file.\n", qpath );
   1579 			FS_Write( &len, sizeof( len ), com_journalDataFile );
   1580 			FS_Flush( com_journalDataFile );
   1581 		}
   1582 		FS_FCloseFile( h);
   1583 		return len;
   1584 	}
   1585 
   1586 	fs_loadCount++;
   1587 	fs_loadStack++;
   1588 
   1589 	buf = Hunk_AllocateTempMemory(len+1);
   1590 	*buffer = buf;
   1591 
   1592 	FS_Read (buf, len, h);
   1593 
   1594 	// guarantee that it will have a trailing 0 for string operations
   1595 	buf[len] = 0;
   1596 	FS_FCloseFile( h );
   1597 
   1598 	// if we are journalling and it is a config file, write it to the journal file
   1599 	if ( isConfig && com_journal && com_journal->integer == 1 ) {
   1600 		Com_DPrintf( "Writing %s to journal file.\n", qpath );
   1601 		FS_Write( &len, sizeof( len ), com_journalDataFile );
   1602 		FS_Write( buf, len, com_journalDataFile );
   1603 		FS_Flush( com_journalDataFile );
   1604 	}
   1605 	return len;
   1606 }
   1607 
   1608 /*
   1609 =============
   1610 FS_FreeFile
   1611 =============
   1612 */
   1613 void FS_FreeFile( void *buffer ) {
   1614 	if ( !fs_searchpaths ) {
   1615 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1616 	}
   1617 	if ( !buffer ) {
   1618 		Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
   1619 	}
   1620 	fs_loadStack--;
   1621 
   1622 	Hunk_FreeTempMemory( buffer );
   1623 
   1624 	// if all of our temp files are free, clear all of our space
   1625 	if ( fs_loadStack == 0 ) {
   1626 		Hunk_ClearTempMemory();
   1627 	}
   1628 }
   1629 
   1630 /*
   1631 ============
   1632 FS_WriteFile
   1633 
   1634 Filename are reletive to the quake search path
   1635 ============
   1636 */
   1637 void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
   1638 	fileHandle_t f;
   1639 
   1640 	if ( !fs_searchpaths ) {
   1641 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1642 	}
   1643 
   1644 	if ( !qpath || !buffer ) {
   1645 		Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
   1646 	}
   1647 
   1648 	f = FS_FOpenFileWrite( qpath );
   1649 	if ( !f ) {
   1650 		Com_Printf( "Failed to open %s\n", qpath );
   1651 		return;
   1652 	}
   1653 
   1654 	FS_Write( buffer, size, f );
   1655 
   1656 	FS_FCloseFile( f );
   1657 }
   1658 
   1659 
   1660 
   1661 /*
   1662 ==========================================================================
   1663 
   1664 ZIP FILE LOADING
   1665 
   1666 ==========================================================================
   1667 */
   1668 
   1669 /*
   1670 =================
   1671 FS_LoadZipFile
   1672 
   1673 Creates a new pak_t in the search chain for the contents
   1674 of a zip file.
   1675 =================
   1676 */
   1677 static pack_t *FS_LoadZipFile( char *zipfile, const char *basename )
   1678 {
   1679 	fileInPack_t	*buildBuffer;
   1680 	pack_t			*pack;
   1681 	unzFile			uf;
   1682 	int				err;
   1683 	unz_global_info gi;
   1684 	char			filename_inzip[MAX_ZPATH];
   1685 	unz_file_info	file_info;
   1686 	int				i, len;
   1687 	long			hash;
   1688 	int				fs_numHeaderLongs;
   1689 	int				*fs_headerLongs;
   1690 	char			*namePtr;
   1691 
   1692 	fs_numHeaderLongs = 0;
   1693 
   1694 	uf = unzOpen(zipfile);
   1695 	err = unzGetGlobalInfo (uf,&gi);
   1696 
   1697 	if (err != UNZ_OK)
   1698 		return NULL;
   1699 
   1700 	fs_packFiles += gi.number_entry;
   1701 
   1702 	len = 0;
   1703 	unzGoToFirstFile(uf);
   1704 	for (i = 0; i < gi.number_entry; i++)
   1705 	{
   1706 		err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
   1707 		if (err != UNZ_OK) {
   1708 			break;
   1709 		}
   1710 		len += strlen(filename_inzip) + 1;
   1711 		unzGoToNextFile(uf);
   1712 	}
   1713 
   1714 	buildBuffer = Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len );
   1715 	namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t );
   1716 	fs_headerLongs = Z_Malloc( gi.number_entry * sizeof(int) );
   1717 
   1718 	// get the hash table size from the number of files in the zip
   1719 	// because lots of custom pk3 files have less than 32 or 64 files
   1720 	for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) {
   1721 		if (i > gi.number_entry) {
   1722 			break;
   1723 		}
   1724 	}
   1725 
   1726 	pack = Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *) );
   1727 	pack->hashSize = i;
   1728 	pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t ));
   1729 	for(i = 0; i < pack->hashSize; i++) {
   1730 		pack->hashTable[i] = NULL;
   1731 	}
   1732 
   1733 	Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) );
   1734 	Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) );
   1735 
   1736 	// strip .pk3 if needed
   1737 	if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) {
   1738 		pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0;
   1739 	}
   1740 
   1741 	pack->handle = uf;
   1742 	pack->numfiles = gi.number_entry;
   1743 	unzGoToFirstFile(uf);
   1744 
   1745 	for (i = 0; i < gi.number_entry; i++)
   1746 	{
   1747 		err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
   1748 		if (err != UNZ_OK) {
   1749 			break;
   1750 		}
   1751 		if (file_info.uncompressed_size > 0) {
   1752 			fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
   1753 		}
   1754 		Q_strlwr( filename_inzip );
   1755 		hash = FS_HashFileName(filename_inzip, pack->hashSize);
   1756 		buildBuffer[i].name = namePtr;
   1757 		strcpy( buildBuffer[i].name, filename_inzip );
   1758 		namePtr += strlen(filename_inzip) + 1;
   1759 		// store the file position in the zip
   1760 		unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos);
   1761 		//
   1762 		buildBuffer[i].next = pack->hashTable[hash];
   1763 		pack->hashTable[hash] = &buildBuffer[i];
   1764 		unzGoToNextFile(uf);
   1765 	}
   1766 
   1767 	pack->checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
   1768 	pack->pure_checksum = Com_BlockChecksumKey( fs_headerLongs, 4 * fs_numHeaderLongs, LittleLong(fs_checksumFeed) );
   1769 	pack->checksum = LittleLong( pack->checksum );
   1770 	pack->pure_checksum = LittleLong( pack->pure_checksum );
   1771 
   1772 	Z_Free(fs_headerLongs);
   1773 
   1774 	pack->buildBuffer = buildBuffer;
   1775 	return pack;
   1776 }
   1777 
   1778 /*
   1779 =================================================================================
   1780 
   1781 DIRECTORY SCANNING FUNCTIONS
   1782 
   1783 =================================================================================
   1784 */
   1785 
   1786 #define	MAX_FOUND_FILES	0x1000
   1787 
   1788 static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) {
   1789 	int len, at, newdep;
   1790 
   1791 	newdep = 0;
   1792 	zpath[0] = 0;
   1793 	len = 0;
   1794 	at = 0;
   1795 
   1796 	while(zname[at] != 0)
   1797 	{
   1798 		if (zname[at]=='/' || zname[at]=='\\') {
   1799 			len = at;
   1800 			newdep++;
   1801 		}
   1802 		at++;
   1803 	}
   1804 	strcpy(zpath, zname);
   1805 	zpath[len] = 0;
   1806 	*depth = newdep;
   1807 
   1808 	return len;
   1809 }
   1810 
   1811 /*
   1812 ==================
   1813 FS_AddFileToList
   1814 ==================
   1815 */
   1816 static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
   1817 	int		i;
   1818 
   1819 	if ( nfiles == MAX_FOUND_FILES - 1 ) {
   1820 		return nfiles;
   1821 	}
   1822 	for ( i = 0 ; i < nfiles ; i++ ) {
   1823 		if ( !Q_stricmp( name, list[i] ) ) {
   1824 			return nfiles;		// allready in list
   1825 		}
   1826 	}
   1827 	list[nfiles] = CopyString( name );
   1828 	nfiles++;
   1829 
   1830 	return nfiles;
   1831 }
   1832 
   1833 /*
   1834 ===============
   1835 FS_ListFilteredFiles
   1836 
   1837 Returns a uniqued list of files that match the given criteria
   1838 from all search paths
   1839 ===============
   1840 */
   1841 char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) {
   1842 	int				nfiles;
   1843 	char			**listCopy;
   1844 	char			*list[MAX_FOUND_FILES];
   1845 	searchpath_t	*search;
   1846 	int				i;
   1847 	int				pathLength;
   1848 	int				extensionLength;
   1849 	int				length, pathDepth, temp;
   1850 	pack_t			*pak;
   1851 	fileInPack_t	*buildBuffer;
   1852 	char			zpath[MAX_ZPATH];
   1853 
   1854 	if ( !fs_searchpaths ) {
   1855 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1856 	}
   1857 
   1858 	if ( !path ) {
   1859 		*numfiles = 0;
   1860 		return NULL;
   1861 	}
   1862 	if ( !extension ) {
   1863 		extension = "";
   1864 	}
   1865 
   1866 	pathLength = strlen( path );
   1867 	if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) {
   1868 		pathLength--;
   1869 	}
   1870 	extensionLength = strlen( extension );
   1871 	nfiles = 0;
   1872 	FS_ReturnPath(path, zpath, &pathDepth);
   1873 
   1874 	//
   1875 	// search through the path, one element at a time, adding to list
   1876 	//
   1877 	for (search = fs_searchpaths ; search ; search = search->next) {
   1878 		// is the element a pak file?
   1879 		if (search->pack) {
   1880 
   1881 			//ZOID:  If we are pure, don't search for files on paks that
   1882 			// aren't on the pure list
   1883 			if ( !FS_PakIsPure(search->pack) ) {
   1884 				continue;
   1885 			}
   1886 
   1887 			// look through all the pak file elements
   1888 			pak = search->pack;
   1889 			buildBuffer = pak->buildBuffer;
   1890 			for (i = 0; i < pak->numfiles; i++) {
   1891 				char	*name;
   1892 				int		zpathLen, depth;
   1893 
   1894 				// check for directory match
   1895 				name = buildBuffer[i].name;
   1896 				//
   1897 				if (filter) {
   1898 					// case insensitive
   1899 					if (!Com_FilterPath( filter, name, qfalse ))
   1900 						continue;
   1901 					// unique the match
   1902 					nfiles = FS_AddFileToList( name, list, nfiles );
   1903 				}
   1904 				else {
   1905 
   1906 					zpathLen = FS_ReturnPath(name, zpath, &depth);
   1907 
   1908 					if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) {
   1909 						continue;
   1910 					}
   1911 
   1912 					// check for extension match
   1913 					length = strlen( name );
   1914 					if ( length < extensionLength ) {
   1915 						continue;
   1916 					}
   1917 
   1918 					if ( Q_stricmp( name + length - extensionLength, extension ) ) {
   1919 						continue;
   1920 					}
   1921 					// unique the match
   1922 
   1923 					temp = pathLength;
   1924 					if (pathLength) {
   1925 						temp++;		// include the '/'
   1926 					}
   1927 					nfiles = FS_AddFileToList( name + temp, list, nfiles );
   1928 				}
   1929 			}
   1930 		} else if (search->dir) { // scan for files in the filesystem
   1931 			char	*netpath;
   1932 			int		numSysFiles;
   1933 			char	**sysFiles;
   1934 			char	*name;
   1935 
   1936 			// don't scan directories for files if we are pure or restricted
   1937 			if ( fs_restrict->integer || fs_numServerPaks ) {
   1938 		        continue;
   1939 		    } else {
   1940 				netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path );
   1941 				sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse );
   1942 				for ( i = 0 ; i < numSysFiles ; i++ ) {
   1943 					// unique the match
   1944 					name = sysFiles[i];
   1945 					nfiles = FS_AddFileToList( name, list, nfiles );
   1946 				}
   1947 				Sys_FreeFileList( sysFiles );
   1948 			}
   1949 		}		
   1950 	}
   1951 
   1952 	// return a copy of the list
   1953 	*numfiles = nfiles;
   1954 
   1955 	if ( !nfiles ) {
   1956 		return NULL;
   1957 	}
   1958 
   1959 	listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
   1960 	for ( i = 0 ; i < nfiles ; i++ ) {
   1961 		listCopy[i] = list[i];
   1962 	}
   1963 	listCopy[i] = NULL;
   1964 
   1965 	return listCopy;
   1966 }
   1967 
   1968 /*
   1969 =================
   1970 FS_ListFiles
   1971 =================
   1972 */
   1973 char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
   1974 	return FS_ListFilteredFiles( path, extension, NULL, numfiles );
   1975 }
   1976 
   1977 /*
   1978 =================
   1979 FS_FreeFileList
   1980 =================
   1981 */
   1982 void FS_FreeFileList( char **list ) {
   1983 	int		i;
   1984 
   1985 	if ( !fs_searchpaths ) {
   1986 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
   1987 	}
   1988 
   1989 	if ( !list ) {
   1990 		return;
   1991 	}
   1992 
   1993 	for ( i = 0 ; list[i] ; i++ ) {
   1994 		Z_Free( list[i] );
   1995 	}
   1996 
   1997 	Z_Free( list );
   1998 }
   1999 
   2000 
   2001 /*
   2002 ================
   2003 FS_GetFileList
   2004 ================
   2005 */
   2006 int	FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize ) {
   2007 	int		nFiles, i, nTotal, nLen;
   2008 	char **pFiles = NULL;
   2009 
   2010 	*listbuf = 0;
   2011 	nFiles = 0;
   2012 	nTotal = 0;
   2013 
   2014 	if (Q_stricmp(path, "$modlist") == 0) {
   2015 		return FS_GetModList(listbuf, bufsize);
   2016 	}
   2017 
   2018 	pFiles = FS_ListFiles(path, extension, &nFiles);
   2019 
   2020 	for (i =0; i < nFiles; i++) {
   2021 		nLen = strlen(pFiles[i]) + 1;
   2022 		if (nTotal + nLen + 1 < bufsize) {
   2023 			strcpy(listbuf, pFiles[i]);
   2024 			listbuf += nLen;
   2025 			nTotal += nLen;
   2026 		}
   2027 		else {
   2028 			nFiles = i;
   2029 			break;
   2030 		}
   2031 	}
   2032 
   2033 	FS_FreeFileList(pFiles);
   2034 
   2035 	return nFiles;
   2036 }
   2037 
   2038 /*
   2039 =======================
   2040 Sys_ConcatenateFileLists
   2041 
   2042 mkv: Naive implementation. Concatenates three lists into a
   2043      new list, and frees the old lists from the heap.
   2044 bk001129 - from cvs1.17 (mkv)
   2045 
   2046 FIXME TTimo those two should move to common.c next to Sys_ListFiles
   2047 =======================
   2048  */
   2049 static unsigned int Sys_CountFileList(char **list)
   2050 {
   2051   int i = 0;
   2052 
   2053   if (list)
   2054   {
   2055     while (*list)
   2056     {
   2057       list++;
   2058       i++;
   2059     }
   2060   }
   2061   return i;
   2062 }
   2063 
   2064 static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 )
   2065 {
   2066   int totalLength = 0;
   2067   char** cat = NULL, **dst, **src;
   2068 
   2069   totalLength += Sys_CountFileList(list0);
   2070   totalLength += Sys_CountFileList(list1);
   2071   totalLength += Sys_CountFileList(list2);
   2072 
   2073   /* Create new list. */
   2074   dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) );
   2075 
   2076   /* Copy over lists. */
   2077   if (list0)
   2078   {
   2079     for (src = list0; *src; src++, dst++)
   2080       *dst = *src;
   2081   }
   2082   if (list1)
   2083   {
   2084     for (src = list1; *src; src++, dst++)
   2085       *dst = *src;
   2086   }
   2087   if (list2)
   2088   {
   2089     for (src = list2; *src; src++, dst++)
   2090       *dst = *src;
   2091   }
   2092 
   2093   // Terminate the list
   2094   *dst = NULL;
   2095 
   2096   // Free our old lists.
   2097   // NOTE: not freeing their content, it's been merged in dst and still being used
   2098   if (list0) Z_Free( list0 );
   2099   if (list1) Z_Free( list1 );
   2100   if (list2) Z_Free( list2 );
   2101 
   2102   return cat;
   2103 }
   2104 
   2105 /*
   2106 ================
   2107 FS_GetModList
   2108 
   2109 Returns a list of mod directory names
   2110 A mod directory is a peer to baseq3 with a pk3 in it
   2111 The directories are searched in base path, cd path and home path
   2112 ================
   2113 */
   2114 int	FS_GetModList( char *listbuf, int bufsize ) {
   2115   int		nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen;
   2116   char **pFiles = NULL;
   2117   char **pPaks = NULL;
   2118   char *name, *path;
   2119   char descPath[MAX_OSPATH];
   2120   fileHandle_t descHandle;
   2121 
   2122   int dummy;
   2123   char **pFiles0 = NULL;
   2124   char **pFiles1 = NULL;
   2125   char **pFiles2 = NULL;
   2126   qboolean bDrop = qfalse;
   2127 
   2128   *listbuf = 0;
   2129   nMods = nPotential = nTotal = 0;
   2130 
   2131   pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue );
   2132   pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue );
   2133   pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue );
   2134   // we searched for mods in the three paths
   2135   // it is likely that we have duplicate names now, which we will cleanup below
   2136   pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 );
   2137   nPotential = Sys_CountFileList(pFiles);
   2138 
   2139   for ( i = 0 ; i < nPotential ; i++ ) {
   2140     name = pFiles[i];
   2141     // NOTE: cleaner would involve more changes
   2142     // ignore duplicate mod directories
   2143     if (i!=0) {
   2144       bDrop = qfalse;
   2145       for(j=0; j<i; j++)
   2146       {
   2147         if (Q_stricmp(pFiles[j],name)==0) {
   2148           // this one can be dropped
   2149           bDrop = qtrue;
   2150           break;
   2151         }
   2152       }
   2153     }
   2154     if (bDrop) {
   2155       continue;
   2156     }
   2157     // we drop "baseq3" "." and ".."
   2158     if (Q_stricmp(name, "baseq3") && Q_stricmpn(name, ".", 1)) {
   2159       // now we need to find some .pk3 files to validate the mod
   2160       // NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?)
   2161       // we didn't keep the information when we merged the directory names, as to what OS Path it was found under
   2162       //   so it could be in base path, cd path or home path
   2163       //   we will try each three of them here (yes, it's a bit messy)
   2164       path = FS_BuildOSPath( fs_basepath->string, name, "" );
   2165       nPaks = 0;
   2166       pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); 
   2167       Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present
   2168 
   2169       /* Try on cd path */
   2170       if( nPaks <= 0 ) {
   2171         path = FS_BuildOSPath( fs_cdpath->string, name, "" );
   2172         nPaks = 0;
   2173         pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
   2174         Sys_FreeFileList( pPaks );
   2175       }
   2176 
   2177       /* try on home path */
   2178       if ( nPaks <= 0 )
   2179       {
   2180         path = FS_BuildOSPath( fs_homepath->string, name, "" );
   2181         nPaks = 0;
   2182         pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
   2183         Sys_FreeFileList( pPaks );
   2184       }
   2185 
   2186       if (nPaks > 0) {
   2187         nLen = strlen(name) + 1;
   2188         // nLen is the length of the mod path
   2189         // we need to see if there is a description available
   2190         descPath[0] = '\0';
   2191         strcpy(descPath, name);
   2192         strcat(descPath, "/description.txt");
   2193         nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle );
   2194         if ( nDescLen > 0 && descHandle) {
   2195           FILE *file;
   2196           file = FS_FileForHandle(descHandle);
   2197           Com_Memset( descPath, 0, sizeof( descPath ) );
   2198           nDescLen = fread(descPath, 1, 48, file);
   2199           if (nDescLen >= 0) {
   2200             descPath[nDescLen] = '\0';
   2201           }
   2202           FS_FCloseFile(descHandle);
   2203         } else {
   2204           strcpy(descPath, name);
   2205         }
   2206         nDescLen = strlen(descPath) + 1;
   2207 
   2208         if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) {
   2209           strcpy(listbuf, name);
   2210           listbuf += nLen;
   2211           strcpy(listbuf, descPath);
   2212           listbuf += nDescLen;
   2213           nTotal += nLen + nDescLen;
   2214           nMods++;
   2215         }
   2216         else {
   2217           break;
   2218         }
   2219       }
   2220     }
   2221   }
   2222   Sys_FreeFileList( pFiles );
   2223 
   2224   return nMods;
   2225 }
   2226 
   2227 
   2228 
   2229 
   2230 //============================================================================
   2231 
   2232 /*
   2233 ================
   2234 FS_Dir_f
   2235 ================
   2236 */
   2237 void FS_Dir_f( void ) {
   2238 	char	*path;
   2239 	char	*extension;
   2240 	char	**dirnames;
   2241 	int		ndirs;
   2242 	int		i;
   2243 
   2244 	if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) {
   2245 		Com_Printf( "usage: dir <directory> [extension]\n" );
   2246 		return;
   2247 	}
   2248 
   2249 	if ( Cmd_Argc() == 2 ) {
   2250 		path = Cmd_Argv( 1 );
   2251 		extension = "";
   2252 	} else {
   2253 		path = Cmd_Argv( 1 );
   2254 		extension = Cmd_Argv( 2 );
   2255 	}
   2256 
   2257 	Com_Printf( "Directory of %s %s\n", path, extension );
   2258 	Com_Printf( "---------------\n" );
   2259 
   2260 	dirnames = FS_ListFiles( path, extension, &ndirs );
   2261 
   2262 	for ( i = 0; i < ndirs; i++ ) {
   2263 		Com_Printf( "%s\n", dirnames[i] );
   2264 	}
   2265 	FS_FreeFileList( dirnames );
   2266 }
   2267 
   2268 /*
   2269 ===========
   2270 FS_ConvertPath
   2271 ===========
   2272 */
   2273 void FS_ConvertPath( char *s ) {
   2274 	while (*s) {
   2275 		if ( *s == '\\' || *s == ':' ) {
   2276 			*s = '/';
   2277 		}
   2278 		s++;
   2279 	}
   2280 }
   2281 
   2282 /*
   2283 ===========
   2284 FS_PathCmp
   2285 
   2286 Ignore case and seprator char distinctions
   2287 ===========
   2288 */
   2289 int FS_PathCmp( const char *s1, const char *s2 ) {
   2290 	int		c1, c2;
   2291 	
   2292 	do {
   2293 		c1 = *s1++;
   2294 		c2 = *s2++;
   2295 
   2296 		if (c1 >= 'a' && c1 <= 'z') {
   2297 			c1 -= ('a' - 'A');
   2298 		}
   2299 		if (c2 >= 'a' && c2 <= 'z') {
   2300 			c2 -= ('a' - 'A');
   2301 		}
   2302 
   2303 		if ( c1 == '\\' || c1 == ':' ) {
   2304 			c1 = '/';
   2305 		}
   2306 		if ( c2 == '\\' || c2 == ':' ) {
   2307 			c2 = '/';
   2308 		}
   2309 		
   2310 		if (c1 < c2) {
   2311 			return -1;		// strings not equal
   2312 		}
   2313 		if (c1 > c2) {
   2314 			return 1;
   2315 		}
   2316 	} while (c1);
   2317 	
   2318 	return 0;		// strings are equal
   2319 }
   2320 
   2321 /*
   2322 ================
   2323 FS_SortFileList
   2324 ================
   2325 */
   2326 void FS_SortFileList(char **filelist, int numfiles) {
   2327 	int i, j, k, numsortedfiles;
   2328 	char **sortedlist;
   2329 
   2330 	sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) );
   2331 	sortedlist[0] = NULL;
   2332 	numsortedfiles = 0;
   2333 	for (i = 0; i < numfiles; i++) {
   2334 		for (j = 0; j < numsortedfiles; j++) {
   2335 			if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) {
   2336 				break;
   2337 			}
   2338 		}
   2339 		for (k = numsortedfiles; k > j; k--) {
   2340 			sortedlist[k] = sortedlist[k-1];
   2341 		}
   2342 		sortedlist[j] = filelist[i];
   2343 		numsortedfiles++;
   2344 	}
   2345 	Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) );
   2346 	Z_Free(sortedlist);
   2347 }
   2348 
   2349 /*
   2350 ================
   2351 FS_NewDir_f
   2352 ================
   2353 */
   2354 void FS_NewDir_f( void ) {
   2355 	char	*filter;
   2356 	char	**dirnames;
   2357 	int		ndirs;
   2358 	int		i;
   2359 
   2360 	if ( Cmd_Argc() < 2 ) {
   2361 		Com_Printf( "usage: fdir <filter>\n" );
   2362 		Com_Printf( "example: fdir *q3dm*.bsp\n");
   2363 		return;
   2364 	}
   2365 
   2366 	filter = Cmd_Argv( 1 );
   2367 
   2368 	Com_Printf( "---------------\n" );
   2369 
   2370 	dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs );
   2371 
   2372 	FS_SortFileList(dirnames, ndirs);
   2373 
   2374 	for ( i = 0; i < ndirs; i++ ) {
   2375 		FS_ConvertPath(dirnames[i]);
   2376 		Com_Printf( "%s\n", dirnames[i] );
   2377 	}
   2378 	Com_Printf( "%d files listed\n", ndirs );
   2379 	FS_FreeFileList( dirnames );
   2380 }
   2381 
   2382 /*
   2383 ============
   2384 FS_Path_f
   2385 
   2386 ============
   2387 */
   2388 void FS_Path_f( void ) {
   2389 	searchpath_t	*s;
   2390 	int				i;
   2391 
   2392 	Com_Printf ("Current search path:\n");
   2393 	for (s = fs_searchpaths; s; s = s->next) {
   2394 		if (s->pack) {
   2395 			Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles);
   2396 			if ( fs_numServerPaks ) {
   2397 				if ( !FS_PakIsPure(s->pack) ) {
   2398 					Com_Printf( "    not on the pure list\n" );
   2399 				} else {
   2400 					Com_Printf( "    on the pure list\n" );
   2401 				}
   2402 			}
   2403 		} else {
   2404 			Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir );
   2405 		}
   2406 	}
   2407 
   2408 
   2409 	Com_Printf( "\n" );
   2410 	for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
   2411 		if ( fsh[i].handleFiles.file.o ) {
   2412 			Com_Printf( "handle %i: %s\n", i, fsh[i].name );
   2413 		}
   2414 	}
   2415 }
   2416 
   2417 /*
   2418 ============
   2419 FS_TouchFile_f
   2420 
   2421 The only purpose of this function is to allow game script files to copy
   2422 arbitrary files furing an "fs_copyfiles 1" run.
   2423 ============
   2424 */
   2425 void FS_TouchFile_f( void ) {
   2426 	fileHandle_t	f;
   2427 
   2428 	if ( Cmd_Argc() != 2 ) {
   2429 		Com_Printf( "Usage: touchFile <file>\n" );
   2430 		return;
   2431 	}
   2432 
   2433 	FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse );
   2434 	if ( f ) {
   2435 		FS_FCloseFile( f );
   2436 	}
   2437 }
   2438 
   2439 //===========================================================================
   2440 
   2441 
   2442 static int QDECL paksort( const void *a, const void *b ) {
   2443 	char	*aa, *bb;
   2444 
   2445 	aa = *(char **)a;
   2446 	bb = *(char **)b;
   2447 
   2448 	return FS_PathCmp( aa, bb );
   2449 }
   2450 
   2451 /*
   2452 ================
   2453 FS_AddGameDirectory
   2454 
   2455 Sets fs_gamedir, adds the directory to the head of the path,
   2456 then loads the zip headers
   2457 ================
   2458 */
   2459 #define	MAX_PAKFILES	1024
   2460 static void FS_AddGameDirectory( const char *path, const char *dir ) {
   2461 	searchpath_t	*sp;
   2462 	int				i;
   2463 	searchpath_t	*search;
   2464 	pack_t			*pak;
   2465 	char			*pakfile;
   2466 	int				numfiles;
   2467 	char			**pakfiles;
   2468 	char			*sorted[MAX_PAKFILES];
   2469 
   2470 	// this fixes the case where fs_basepath is the same as fs_cdpath
   2471 	// which happens on full installs
   2472 	for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
   2473 		if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) {
   2474 			return;			// we've already got this one
   2475 		}
   2476 	}
   2477 	
   2478 	Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) );
   2479 
   2480 	//
   2481 	// add the directory to the search path
   2482 	//
   2483 	search = Z_Malloc (sizeof(searchpath_t));
   2484 	search->dir = Z_Malloc( sizeof( *search->dir ) );
   2485 
   2486 	Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) );
   2487 	Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) );
   2488 	search->next = fs_searchpaths;
   2489 	fs_searchpaths = search;
   2490 
   2491 	// find all pak files in this directory
   2492 	pakfile = FS_BuildOSPath( path, dir, "" );
   2493 	pakfile[ strlen(pakfile) - 1 ] = 0;	// strip the trailing slash
   2494 
   2495 	pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse );
   2496 
   2497 	// sort them so that later alphabetic matches override
   2498 	// earlier ones.  This makes pak1.pk3 override pak0.pk3
   2499 	if ( numfiles > MAX_PAKFILES ) {
   2500 		numfiles = MAX_PAKFILES;
   2501 	}
   2502 	for ( i = 0 ; i < numfiles ; i++ ) {
   2503 		sorted[i] = pakfiles[i];
   2504 	}
   2505 
   2506 	qsort( sorted, numfiles, 4, paksort );
   2507 
   2508 	for ( i = 0 ; i < numfiles ; i++ ) {
   2509 		pakfile = FS_BuildOSPath( path, dir, sorted[i] );
   2510 		if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 )
   2511 			continue;
   2512 		// store the game name for downloading
   2513 		strcpy(pak->pakGamename, dir);
   2514 
   2515 		search = Z_Malloc (sizeof(searchpath_t));
   2516 		search->pack = pak;
   2517 		search->next = fs_searchpaths;
   2518 		fs_searchpaths = search;
   2519 	}
   2520 
   2521 	// done
   2522 	Sys_FreeFileList( pakfiles );
   2523 }
   2524 
   2525 /*
   2526 ================
   2527 FS_idPak
   2528 ================
   2529 */
   2530 qboolean FS_idPak( char *pak, char *base ) {
   2531 	int i;
   2532 
   2533 	for (i = 0; i < NUM_ID_PAKS; i++) {
   2534 		if ( !FS_FilenameCompare(pak, va("%s/pak%d", base, i)) ) {
   2535 			break;
   2536 		}
   2537 	}
   2538 	if (i < NUM_ID_PAKS) {
   2539 		return qtrue;
   2540 	}
   2541 	return qfalse;
   2542 }
   2543 
   2544 /*
   2545 ================
   2546 FS_ComparePaks
   2547 
   2548 ----------------
   2549 dlstring == qtrue
   2550 
   2551 Returns a list of pak files that we should download from the server. They all get stored
   2552 in the current gamedir and an FS_Restart will be fired up after we download them all.
   2553 
   2554 The string is the format:
   2555 
   2556 @remotename@localname [repeat]
   2557 
   2558 static int		fs_numServerReferencedPaks;
   2559 static int		fs_serverReferencedPaks[MAX_SEARCH_PATHS];
   2560 static char		*fs_serverReferencedPakNames[MAX_SEARCH_PATHS];
   2561 
   2562 ----------------
   2563 dlstring == qfalse
   2564 
   2565 we are not interested in a download string format, we want something human-readable
   2566 (this is used for diagnostics while connecting to a pure server)
   2567 
   2568 ================
   2569 */
   2570 qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
   2571 	searchpath_t	*sp;
   2572 	qboolean havepak, badchecksum;
   2573 	int i;
   2574 
   2575 	if ( !fs_numServerReferencedPaks ) {
   2576 		return qfalse; // Server didn't send any pack information along
   2577 	}
   2578 
   2579 	*neededpaks = 0;
   2580 
   2581 	for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) {
   2582 		// Ok, see if we have this pak file
   2583 		badchecksum = qfalse;
   2584 		havepak = qfalse;
   2585 
   2586 		// never autodownload any of the id paks
   2587 		if ( FS_idPak(fs_serverReferencedPakNames[i], "baseq3") || FS_idPak(fs_serverReferencedPakNames[i], "missionpack") ) {
   2588 			continue;
   2589 		}
   2590 
   2591 		for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
   2592 			if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) {
   2593 				havepak = qtrue; // This is it!
   2594 				break;
   2595 			}
   2596 		}
   2597 
   2598 		if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { 
   2599 			// Don't got it
   2600 
   2601       if (dlstring)
   2602       {
   2603         // Remote name
   2604         Q_strcat( neededpaks, len, "@");
   2605         Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
   2606         Q_strcat( neededpaks, len, ".pk3" );
   2607 
   2608         // Local name
   2609         Q_strcat( neededpaks, len, "@");
   2610         // Do we have one with the same name?
   2611         if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
   2612         {
   2613           char st[MAX_ZPATH];
   2614           // We already have one called this, we need to download it to another name
   2615           // Make something up with the checksum in it
   2616           Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] );
   2617           Q_strcat( neededpaks, len, st );
   2618         } else
   2619         {
   2620           Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
   2621           Q_strcat( neededpaks, len, ".pk3" );
   2622         }
   2623       }
   2624       else
   2625       {
   2626         Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
   2627 			  Q_strcat( neededpaks, len, ".pk3" );
   2628         // Do we have one with the same name?
   2629         if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) )
   2630         {
   2631           Q_strcat( neededpaks, len, " (local file exists with wrong checksum)");
   2632         }
   2633         Q_strcat( neededpaks, len, "\n");
   2634       }
   2635 		}
   2636 	}
   2637 
   2638 	if ( *neededpaks ) {
   2639 		return qtrue;
   2640 	}
   2641 
   2642 	return qfalse; // We have them all
   2643 }
   2644 
   2645 /*
   2646 ================
   2647 FS_Shutdown
   2648 
   2649 Frees all resources and closes all files
   2650 ================
   2651 */
   2652 void FS_Shutdown( qboolean closemfp ) {
   2653 	searchpath_t	*p, *next;
   2654 	int	i;
   2655 
   2656 	for(i = 0; i < MAX_FILE_HANDLES; i++) {
   2657 		if (fsh[i].fileSize) {
   2658 			FS_FCloseFile(i);
   2659 		}
   2660 	}
   2661 
   2662 	// free everything
   2663 	for ( p = fs_searchpaths ; p ; p = next ) {
   2664 		next = p->next;
   2665 
   2666 		if ( p->pack ) {
   2667 			unzClose(p->pack->handle);
   2668 			Z_Free( p->pack->buildBuffer );
   2669 			Z_Free( p->pack );
   2670 		}
   2671 		if ( p->dir ) {
   2672 			Z_Free( p->dir );
   2673 		}
   2674 		Z_Free( p );
   2675 	}
   2676 
   2677 	// any FS_ calls will now be an error until reinitialized
   2678 	fs_searchpaths = NULL;
   2679 
   2680 	Cmd_RemoveCommand( "path" );
   2681 	Cmd_RemoveCommand( "dir" );
   2682 	Cmd_RemoveCommand( "fdir" );
   2683 	Cmd_RemoveCommand( "touchFile" );
   2684 
   2685 #ifdef FS_MISSING
   2686 	if (closemfp) {
   2687 		fclose(missingFiles);
   2688 	}
   2689 #endif
   2690 }
   2691 
   2692 void Com_AppendCDKey( const char *filename );
   2693 void Com_ReadCDKey( const char *filename );
   2694  
   2695 /*
   2696 ================
   2697 FS_ReorderPurePaks
   2698 NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*)
   2699   this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
   2700 ================
   2701 */
   2702 static void FS_ReorderPurePaks()
   2703 {
   2704 	searchpath_t *s;
   2705 	int i;
   2706 	searchpath_t **p_insert_index, // for linked list reordering
   2707 		**p_previous; // when doing the scan
   2708 	
   2709 	// only relevant when connected to pure server
   2710 	if ( !fs_numServerPaks )
   2711 		return;
   2712 	
   2713 	fs_reordered = qfalse;
   2714 	
   2715 	p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list 
   2716 	for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
   2717 		p_previous = p_insert_index; // track the pointer-to-current-item
   2718 		for (s = *p_insert_index; s; s = s->next) {
   2719 			// the part of the list before p_insert_index has been sorted already
   2720 			if (s->pack && fs_serverPaks[i] == s->pack->checksum) {
   2721 				fs_reordered = qtrue;
   2722 				// move this element to the insert list
   2723 				*p_previous = s->next;
   2724 				s->next = *p_insert_index;
   2725 				*p_insert_index = s;
   2726 				// increment insert list
   2727 				p_insert_index = &s->next;
   2728 				break; // iterate to next server pack
   2729 			}
   2730 			p_previous = &s->next; 
   2731 		}
   2732 	}
   2733 }
   2734 
   2735 /*
   2736 ================
   2737 FS_Startup
   2738 ================
   2739 */
   2740 static void FS_Startup( const char *gameName ) {
   2741         const char *homePath;
   2742 	cvar_t	*fs;
   2743 
   2744 	Com_Printf( "----- FS_Startup -----\n" );
   2745 
   2746 	fs_debug = Cvar_Get( "fs_debug", "0", 0 );
   2747 	fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT );
   2748 	fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT );
   2749 	fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT );
   2750 	fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
   2751   homePath = Sys_DefaultHomePath();
   2752   if (!homePath || !homePath[0]) {
   2753 		homePath = fs_basepath->string;
   2754 	}
   2755 	fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT );
   2756 	fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
   2757 	fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT );
   2758 
   2759 	// add search path elements in reverse priority order
   2760 	if (fs_cdpath->string[0]) {
   2761 		FS_AddGameDirectory( fs_cdpath->string, gameName );
   2762 	}
   2763 	if (fs_basepath->string[0]) {
   2764 		FS_AddGameDirectory( fs_basepath->string, gameName );
   2765 	}
   2766   // fs_homepath is somewhat particular to *nix systems, only add if relevant
   2767   // NOTE: same filtering below for mods and basegame
   2768 	if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
   2769 		FS_AddGameDirectory ( fs_homepath->string, gameName );
   2770 	}
   2771         
   2772 	// check for additional base game so mods can be based upon other mods
   2773 	if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) {
   2774 		if (fs_cdpath->string[0]) {
   2775 			FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string);
   2776 		}
   2777 		if (fs_basepath->string[0]) {
   2778 			FS_AddGameDirectory(fs_basepath->string, fs_basegame->string);
   2779 		}
   2780 		if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
   2781 			FS_AddGameDirectory(fs_homepath->string, fs_basegame->string);
   2782 		}
   2783 	}
   2784 
   2785 	// check for additional game folder for mods
   2786 	if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) {
   2787 		if (fs_cdpath->string[0]) {
   2788 			FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string);
   2789 		}
   2790 		if (fs_basepath->string[0]) {
   2791 			FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string);
   2792 		}
   2793 		if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
   2794 			FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string);
   2795 		}
   2796 	}
   2797 
   2798 	Com_ReadCDKey( "baseq3" );
   2799 	fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
   2800 	if (fs && fs->string[0] != 0) {
   2801 		Com_AppendCDKey( fs->string );
   2802 	}
   2803 
   2804 	// add our commands
   2805 	Cmd_AddCommand ("path", FS_Path_f);
   2806 	Cmd_AddCommand ("dir", FS_Dir_f );
   2807 	Cmd_AddCommand ("fdir", FS_NewDir_f );
   2808 	Cmd_AddCommand ("touchFile", FS_TouchFile_f );
   2809 
   2810 	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506
   2811 	// reorder the pure pk3 files according to server order
   2812 	FS_ReorderPurePaks();
   2813 	
   2814 	// print the current search paths
   2815 	FS_Path_f();
   2816 
   2817 	fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
   2818 
   2819 	Com_Printf( "----------------------\n" );
   2820 
   2821 #ifdef FS_MISSING
   2822 	if (missingFiles == NULL) {
   2823 		missingFiles = fopen( "\\missing.txt", "ab" );
   2824 	}
   2825 #endif
   2826 	Com_Printf( "%d files in pk3 files\n", fs_packFiles );
   2827 }
   2828 
   2829 
   2830 /*
   2831 ===================
   2832 FS_SetRestrictions
   2833 
   2834 Looks for product keys and restricts media add on ability
   2835 if the full version is not found
   2836 ===================
   2837 */
   2838 static void FS_SetRestrictions( void ) {
   2839 	searchpath_t	*path;
   2840 
   2841 #ifndef PRE_RELEASE_DEMO
   2842 	char	*productId;
   2843 
   2844 	// if fs_restrict is set, don't even look for the id file,
   2845 	// which allows the demo release to be tested even if
   2846 	// the full game is present
   2847 	if ( !fs_restrict->integer ) {
   2848 		// look for the full game id
   2849 		FS_ReadFile( "productid.txt", (void **)&productId );
   2850 		if ( productId ) {
   2851 			// check against the hardcoded string
   2852 			int		seed, i;
   2853 
   2854 			seed = 5000;
   2855 			for ( i = 0 ; i < sizeof( fs_scrambledProductId ) ; i++ ) {
   2856 				if ( ( fs_scrambledProductId[i] ^ (seed&255) ) != productId[i] ) {
   2857 					break;
   2858 				}
   2859 				seed = (69069 * seed + 1);
   2860 			}
   2861 
   2862 			FS_FreeFile( productId );
   2863 
   2864 			if ( i == sizeof( fs_scrambledProductId ) ) {
   2865 				return;	// no restrictions
   2866 			}
   2867 			Com_Error( ERR_FATAL, "Invalid product identification" );
   2868 		}
   2869 	}
   2870 #endif
   2871 	Cvar_Set( "fs_restrict", "1" );
   2872 
   2873 	Com_Printf( "\nRunning in restricted demo mode.\n\n" );
   2874 
   2875 	// restart the filesystem with just the demo directory
   2876 	FS_Shutdown(qfalse);
   2877 	FS_Startup( DEMOGAME );
   2878 
   2879 	// make sure that the pak file has the header checksum we expect
   2880 	for ( path = fs_searchpaths ; path ; path = path->next ) {
   2881 		if ( path->pack ) {
   2882 			// a tiny attempt to keep the checksum from being scannable from the exe
   2883 			if ( (path->pack->checksum ^ 0x02261994u) != (DEMO_PAK_CHECKSUM ^ 0x02261994u) ) {
   2884 				Com_Error( ERR_FATAL, "Corrupted pak0.pk3: %u", path->pack->checksum );
   2885 			}
   2886 		}
   2887 	}
   2888 }
   2889 
   2890 /*
   2891 =====================
   2892 FS_GamePureChecksum
   2893 
   2894 Returns the checksum of the pk3 from which the server loaded the qagame.qvm
   2895 =====================
   2896 */
   2897 const char *FS_GamePureChecksum( void ) {
   2898 	static char	info[MAX_STRING_TOKENS];
   2899 	searchpath_t *search;
   2900 
   2901 	info[0] = 0;
   2902 
   2903 	for ( search = fs_searchpaths ; search ; search = search->next ) {
   2904 		// is the element a pak file?
   2905 		if ( search->pack ) {
   2906 			if (search->pack->referenced & FS_QAGAME_REF) {
   2907 				Com_sprintf(info, sizeof(info), "%d", search->pack->checksum);
   2908 			}
   2909 		}
   2910 	}
   2911 
   2912 	return info;
   2913 }
   2914 
   2915 /*
   2916 =====================
   2917 FS_LoadedPakChecksums
   2918 
   2919 Returns a space separated string containing the checksums of all loaded pk3 files.
   2920 Servers with sv_pure set will get this string and pass it to clients.
   2921 =====================
   2922 */
   2923 const char *FS_LoadedPakChecksums( void ) {
   2924 	static char	info[BIG_INFO_STRING];
   2925 	searchpath_t	*search;
   2926 
   2927 	info[0] = 0;
   2928 
   2929 	for ( search = fs_searchpaths ; search ; search = search->next ) {
   2930 		// is the element a pak file? 
   2931 		if ( !search->pack ) {
   2932 			continue;
   2933 		}
   2934 
   2935 		Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
   2936 	}
   2937 
   2938 	return info;
   2939 }
   2940 
   2941 /*
   2942 =====================
   2943 FS_LoadedPakNames
   2944 
   2945 Returns a space separated string containing the names of all loaded pk3 files.
   2946 Servers with sv_pure set will get this string and pass it to clients.
   2947 =====================
   2948 */
   2949 const char *FS_LoadedPakNames( void ) {
   2950 	static char	info[BIG_INFO_STRING];
   2951 	searchpath_t	*search;
   2952 
   2953 	info[0] = 0;
   2954 
   2955 	for ( search = fs_searchpaths ; search ; search = search->next ) {
   2956 		// is the element a pak file?
   2957 		if ( !search->pack ) {
   2958 			continue;
   2959 		}
   2960 
   2961 		if (*info) {
   2962 			Q_strcat(info, sizeof( info ), " " );
   2963 		}
   2964 		Q_strcat( info, sizeof( info ), search->pack->pakBasename );
   2965 	}
   2966 
   2967 	return info;
   2968 }
   2969 
   2970 /*
   2971 =====================
   2972 FS_LoadedPakPureChecksums
   2973 
   2974 Returns a space separated string containing the pure checksums of all loaded pk3 files.
   2975 Servers with sv_pure use these checksums to compare with the checksums the clients send
   2976 back to the server.
   2977 =====================
   2978 */
   2979 const char *FS_LoadedPakPureChecksums( void ) {
   2980 	static char	info[BIG_INFO_STRING];
   2981 	searchpath_t	*search;
   2982 
   2983 	info[0] = 0;
   2984 
   2985 	for ( search = fs_searchpaths ; search ; search = search->next ) {
   2986 		// is the element a pak file? 
   2987 		if ( !search->pack ) {
   2988 			continue;
   2989 		}
   2990 
   2991 		Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
   2992 	}
   2993 
   2994 	return info;
   2995 }
   2996 
   2997 /*
   2998 =====================
   2999 FS_ReferencedPakChecksums
   3000 
   3001 Returns a space separated string containing the checksums of all referenced pk3 files.
   3002 The server will send this to the clients so they can check which files should be auto-downloaded. 
   3003 =====================
   3004 */
   3005 const char *FS_ReferencedPakChecksums( void ) {
   3006 	static char	info[BIG_INFO_STRING];
   3007 	searchpath_t *search;
   3008 
   3009 	info[0] = 0;
   3010 
   3011 
   3012 	for ( search = fs_searchpaths ; search ; search = search->next ) {
   3013 		// is the element a pak file?
   3014 		if ( search->pack ) {
   3015 			if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
   3016 				Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) );
   3017 			}
   3018 		}
   3019 	}
   3020 
   3021 	return info;
   3022 }
   3023 
   3024 /*
   3025 =====================
   3026 FS_ReferencedPakPureChecksums
   3027 
   3028 Returns a space separated string containing the pure checksums of all referenced pk3 files.
   3029 Servers with sv_pure set will get this string back from clients for pure validation 
   3030 
   3031 The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..."
   3032 =====================
   3033 */
   3034 const char *FS_ReferencedPakPureChecksums( void ) {
   3035 	static char	info[BIG_INFO_STRING];
   3036 	searchpath_t	*search;
   3037 	int nFlags, numPaks, checksum;
   3038 
   3039 	info[0] = 0;
   3040 
   3041 	checksum = fs_checksumFeed;
   3042 	numPaks = 0;
   3043 	for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) {
   3044 		if (nFlags & FS_GENERAL_REF) {
   3045 			// add a delimter between must haves and general refs
   3046 			//Q_strcat(info, sizeof(info), "@ ");
   3047 			info[strlen(info)+1] = '\0';
   3048 			info[strlen(info)+2] = '\0';
   3049 			info[strlen(info)] = '@';
   3050 			info[strlen(info)] = ' ';
   3051 		}
   3052 		for ( search = fs_searchpaths ; search ; search = search->next ) {
   3053 			// is the element a pak file and has it been referenced based on flag?
   3054 			if ( search->pack && (search->pack->referenced & nFlags)) {
   3055 				Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) );
   3056 				if (nFlags & (FS_CGAME_REF | FS_UI_REF)) {
   3057 					break;
   3058 				}
   3059 				checksum ^= search->pack->pure_checksum;
   3060 				numPaks++;
   3061 			}
   3062 		}
   3063 		if (fs_fakeChkSum != 0) {
   3064 			// only added if a non-pure file is referenced
   3065 			Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) );
   3066 		}
   3067 	}
   3068 	// last checksum is the encoded number of referenced pk3s
   3069 	checksum ^= numPaks;
   3070 	Q_strcat( info, sizeof( info ), va("%i ", checksum ) );
   3071 
   3072 	return info;
   3073 }
   3074 
   3075 /*
   3076 =====================
   3077 FS_ReferencedPakNames
   3078 
   3079 Returns a space separated string containing the names of all referenced pk3 files.
   3080 The server will send this to the clients so they can check which files should be auto-downloaded. 
   3081 =====================
   3082 */
   3083 const char *FS_ReferencedPakNames( void ) {
   3084 	static char	info[BIG_INFO_STRING];
   3085 	searchpath_t	*search;
   3086 
   3087 	info[0] = 0;
   3088 
   3089 	// we want to return ALL pk3's from the fs_game path
   3090 	// and referenced one's from baseq3
   3091 	for ( search = fs_searchpaths ; search ; search = search->next ) {
   3092 		// is the element a pak file?
   3093 		if ( search->pack ) {
   3094 			if (*info) {
   3095 				Q_strcat(info, sizeof( info ), " " );
   3096 			}
   3097 			if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) {
   3098 				Q_strcat( info, sizeof( info ), search->pack->pakGamename );
   3099 				Q_strcat( info, sizeof( info ), "/" );
   3100 				Q_strcat( info, sizeof( info ), search->pack->pakBasename );
   3101 			}
   3102 		}
   3103 	}
   3104 
   3105 	return info;
   3106 }
   3107 
   3108 /*
   3109 =====================
   3110 FS_ClearPakReferences
   3111 =====================
   3112 */
   3113 void FS_ClearPakReferences( int flags ) {
   3114 	searchpath_t *search;
   3115 
   3116 	if ( !flags ) {
   3117 		flags = -1;
   3118 	}
   3119 	for ( search = fs_searchpaths; search; search = search->next ) {
   3120 		// is the element a pak file and has it been referenced?
   3121 		if ( search->pack ) {
   3122 			search->pack->referenced &= ~flags;
   3123 		}
   3124 	}
   3125 }
   3126 
   3127 
   3128 /*
   3129 =====================
   3130 FS_PureServerSetLoadedPaks
   3131 
   3132 If the string is empty, all data sources will be allowed.
   3133 If not empty, only pk3 files that match one of the space
   3134 separated checksums will be checked for files, with the
   3135 exception of .cfg and .dat files.
   3136 =====================
   3137 */
   3138 void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) {
   3139 	int		i, c, d;
   3140 
   3141 	Cmd_TokenizeString( pakSums );
   3142 
   3143 	c = Cmd_Argc();
   3144 	if ( c > MAX_SEARCH_PATHS ) {
   3145 		c = MAX_SEARCH_PATHS;
   3146 	}
   3147 
   3148 	fs_numServerPaks = c;
   3149 
   3150 	for ( i = 0 ; i < c ; i++ ) {
   3151 		fs_serverPaks[i] = atoi( Cmd_Argv( i ) );
   3152 	}
   3153 
   3154 	if (fs_numServerPaks) {
   3155 		Com_DPrintf( "Connected to a pure server.\n" );
   3156 	}
   3157 	else
   3158 	{
   3159 		if (fs_reordered)
   3160 		{
   3161 			// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540
   3162 			// force a restart to make sure the search order will be correct
   3163 			Com_DPrintf( "FS search reorder is required\n" );
   3164 			FS_Restart(fs_checksumFeed);
   3165 			return;
   3166 		}
   3167 	}
   3168 
   3169 	for ( i = 0 ; i < c ; i++ ) {
   3170 		if (fs_serverPakNames[i]) {
   3171 			Z_Free(fs_serverPakNames[i]);
   3172 		}
   3173 		fs_serverPakNames[i] = NULL;
   3174 	}
   3175 	if ( pakNames && *pakNames ) {
   3176 		Cmd_TokenizeString( pakNames );
   3177 
   3178 		d = Cmd_Argc();
   3179 		if ( d > MAX_SEARCH_PATHS ) {
   3180 			d = MAX_SEARCH_PATHS;
   3181 		}
   3182 
   3183 		for ( i = 0 ; i < d ; i++ ) {
   3184 			fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) );
   3185 		}
   3186 	}
   3187 }
   3188 
   3189 /*
   3190 =====================
   3191 FS_PureServerSetReferencedPaks
   3192 
   3193 The checksums and names of the pk3 files referenced at the server
   3194 are sent to the client and stored here. The client will use these
   3195 checksums to see if any pk3 files need to be auto-downloaded. 
   3196 =====================
   3197 */
   3198 void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
   3199 	int		i, c, d;
   3200 
   3201 	Cmd_TokenizeString( pakSums );
   3202 
   3203 	c = Cmd_Argc();
   3204 	if ( c > MAX_SEARCH_PATHS ) {
   3205 		c = MAX_SEARCH_PATHS;
   3206 	}
   3207 
   3208 	fs_numServerReferencedPaks = c;
   3209 
   3210 	for ( i = 0 ; i < c ; i++ ) {
   3211 		fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
   3212 	}
   3213 
   3214 	for ( i = 0 ; i < c ; i++ ) {
   3215 		if (fs_serverReferencedPakNames[i]) {
   3216 			Z_Free(fs_serverReferencedPakNames[i]);
   3217 		}
   3218 		fs_serverReferencedPakNames[i] = NULL;
   3219 	}
   3220 	if ( pakNames && *pakNames ) {
   3221 		Cmd_TokenizeString( pakNames );
   3222 
   3223 		d = Cmd_Argc();
   3224 		if ( d > MAX_SEARCH_PATHS ) {
   3225 			d = MAX_SEARCH_PATHS;
   3226 		}
   3227 
   3228 		for ( i = 0 ; i < d ; i++ ) {
   3229 			fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
   3230 		}
   3231 	}
   3232 }
   3233 
   3234 /*
   3235 ================
   3236 FS_InitFilesystem
   3237 
   3238 Called only at inital startup, not when the filesystem
   3239 is resetting due to a game change
   3240 ================
   3241 */
   3242 void FS_InitFilesystem( void ) {
   3243 	// allow command line parms to override our defaults
   3244 	// we have to specially handle this, because normal command
   3245 	// line variable sets don't happen until after the filesystem
   3246 	// has already been initialized
   3247 	Com_StartupVariable( "fs_cdpath" );
   3248 	Com_StartupVariable( "fs_basepath" );
   3249 	Com_StartupVariable( "fs_homepath" );
   3250 	Com_StartupVariable( "fs_game" );
   3251 	Com_StartupVariable( "fs_copyfiles" );
   3252 	Com_StartupVariable( "fs_restrict" );
   3253 
   3254 	// try to start up normally
   3255 	FS_Startup( BASEGAME );
   3256 
   3257 	// see if we are going to allow add-ons
   3258 	FS_SetRestrictions();
   3259 
   3260 	// if we can't find default.cfg, assume that the paths are
   3261 	// busted and error out now, rather than getting an unreadable
   3262 	// graphics screen when the font fails to load
   3263 	if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
   3264 		Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
   3265 		// bk001208 - SafeMode see below, FIXME?
   3266 	}
   3267 
   3268 	Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
   3269 	Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
   3270 
   3271   // bk001208 - SafeMode see below, FIXME?
   3272 }
   3273 
   3274 
   3275 /*
   3276 ================
   3277 FS_Restart
   3278 ================
   3279 */
   3280 void FS_Restart( int checksumFeed ) {
   3281 
   3282 	// free anything we currently have loaded
   3283 	FS_Shutdown(qfalse);
   3284 
   3285 	// set the checksum feed
   3286 	fs_checksumFeed = checksumFeed;
   3287 
   3288 	// clear pak references
   3289 	FS_ClearPakReferences(0);
   3290 
   3291 	// try to start up normally
   3292 	FS_Startup( BASEGAME );
   3293 
   3294 	// see if we are going to allow add-ons
   3295 	FS_SetRestrictions();
   3296 
   3297 	// if we can't find default.cfg, assume that the paths are
   3298 	// busted and error out now, rather than getting an unreadable
   3299 	// graphics screen when the font fails to load
   3300 	if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
   3301 		// this might happen when connecting to a pure server not using BASEGAME/pak0.pk3
   3302 		// (for instance a TA demo server)
   3303 		if (lastValidBase[0]) {
   3304 			FS_PureServerSetLoadedPaks("", "");
   3305 			Cvar_Set("fs_basepath", lastValidBase);
   3306 			Cvar_Set("fs_gamedirvar", lastValidGame);
   3307 			lastValidBase[0] = '\0';
   3308 			lastValidGame[0] = '\0';
   3309 			Cvar_Set( "fs_restrict", "0" );
   3310 			FS_Restart(checksumFeed);
   3311 			Com_Error( ERR_DROP, "Invalid game folder\n" );
   3312 			return;
   3313 		}
   3314 		Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
   3315 	}
   3316 
   3317 	// bk010116 - new check before safeMode
   3318 	if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) {
   3319 		// skip the q3config.cfg if "safe" is on the command line
   3320 		if ( !Com_SafeMode() ) {
   3321 			Cbuf_AddText ("exec q3config.cfg\n");
   3322 		}
   3323 	}
   3324 
   3325 	Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase));
   3326 	Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame));
   3327 
   3328 }
   3329 
   3330 /*
   3331 =================
   3332 FS_ConditionalRestart
   3333 restart if necessary
   3334 =================
   3335 */
   3336 qboolean FS_ConditionalRestart( int checksumFeed ) {
   3337 	if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) {
   3338 		FS_Restart( checksumFeed );
   3339 		return qtrue;
   3340 	}
   3341 	return qfalse;
   3342 }
   3343 
   3344 /*
   3345 ========================================================================================
   3346 
   3347 Handle based file calls for virtual machines
   3348 
   3349 ========================================================================================
   3350 */
   3351 
   3352 int		FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
   3353 	int		r;
   3354 	qboolean	sync;
   3355 
   3356 	sync = qfalse;
   3357 
   3358 	switch( mode ) {
   3359 	case FS_READ:
   3360 		r = FS_FOpenFileRead( qpath, f, qtrue );
   3361 		break;
   3362 	case FS_WRITE:
   3363 		*f = FS_FOpenFileWrite( qpath );
   3364 		r = 0;
   3365 		if (*f == 0) {
   3366 			r = -1;
   3367 		}
   3368 		break;
   3369 	case FS_APPEND_SYNC:
   3370 		sync = qtrue;
   3371 	case FS_APPEND:
   3372 		*f = FS_FOpenFileAppend( qpath );
   3373 		r = 0;
   3374 		if (*f == 0) {
   3375 			r = -1;
   3376 		}
   3377 		break;
   3378 	default:
   3379 		Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" );
   3380 		return -1;
   3381 	}
   3382 
   3383 	if (!f) {
   3384 		return r;
   3385 	}
   3386 
   3387 	if ( *f ) {
   3388 		if (fsh[*f].zipFile == qtrue) {
   3389 			fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z);
   3390 		} else {
   3391 			fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o);
   3392 		}
   3393 		fsh[*f].fileSize = r;
   3394 		fsh[*f].streamed = qfalse;
   3395 
   3396 		if (mode == FS_READ) {
   3397 			Sys_BeginStreamedFile( *f, 0x4000 );
   3398 			fsh[*f].streamed = qtrue;
   3399 		}
   3400 	}
   3401 	fsh[*f].handleSync = sync;
   3402 
   3403 	return r;
   3404 }
   3405 
   3406 int		FS_FTell( fileHandle_t f ) {
   3407 	int pos;
   3408 	if (fsh[f].zipFile == qtrue) {
   3409 		pos = unztell(fsh[f].handleFiles.file.z);
   3410 	} else {
   3411 		pos = ftell(fsh[f].handleFiles.file.o);
   3412 	}
   3413 	return pos;
   3414 }
   3415 
   3416 void	FS_Flush( fileHandle_t f ) {
   3417 	fflush(fsh[f].handleFiles.file.o);
   3418 }
   3419