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