1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (“RTCW MP Source Code”).
8 
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW MP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RTCW MP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 
30 /*****************************************************************************
31  * name:		files.c
32  *
33  * desc:		handle based filesystem for Quake III Arena
34  *
35  *
36  *****************************************************************************/
37 
38 
39 #include "q_shared.h"
40 #include "qcommon.h"
41 #include "../zlib-1.2.11/unzip.h"
42 
43 /*
44 =============================================================================
45 
46 QUAKE3 FILESYSTEM
47 
48 All of Quake's data access is through a hierarchical file system, but the contents of
49 the file system can be transparently merged from several sources.
50 
51 A "qpath" is a reference to game file data.  MAX_ZPATH is 256 characters, which must include
52 a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any
53 references outside the quake directory system.
54 
55 The "base path" is the path to the directory holding all the game directories and usually
56 the executable.  It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3"
57 command line to allow code debugging in a different directory.  Basepath cannot
58 be modified at all after startup.  Any files that are created (demos, screenshots,
59 etc) will be created relative to the base path, so base path should usually be writable.
60 
61 The "home path" is the path used for all write access. On win32 systems we have "base path"
62 == "home path", but on *nix systems the base installation is usually readonly, and
63 "home path" points to ~/.q3a or similar
64 
65 The user can also install custom mods and content in "home path", so it should be searched
66 along with "home path" and "cd path" for game content.
67 
68 
69 The "base game" is the directory under the paths where data comes from by default, and
70 can be either "baseq3" or "demoq3".
71 
72 The "current game" may be the same as the base game, or it may be the name of another
73 directory under the paths that should be searched for files before looking in the base game.
74 This is the basis for addons.
75 
76 Clients automatically set the game directory after receiving a gamestate from a server,
77 so only servers need to worry about +set fs_game.
78 
79 No other directories outside of the base game and current game will ever be referenced by
80 filesystem functions.
81 
82 To save disk space and speed loading, directory trees can be collapsed into zip files.
83 The files use a ".pk3" extension to prevent users from unzipping them accidentally, but
84 otherwise the are simply normal uncompressed zip files.  A game directory can have multiple
85 zip files of the form "pak0.pk3", "pak1.pk3", etc.  Zip files are searched in decending order
86 from the highest number to the lowest, and will always take precedence over the filesystem.
87 This allows a pk3 distributed as a patch to override all existing data.
88 
89 Because we will have updated executables freely available online, there is no point to
90 trying to restrict demo / oem versions of the game with code changes.  Demo / oem versions
91 should be exactly the same executables as release versions, but with different data that
92 automatically restricts where game media can come from to prevent add-ons from working.
93 
94 File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths
95 structure and stop on the first successful hit. fs_searchpaths is built with successive
96 calls to FS_AddGameDirectory
97 
98 Additionaly, we search in several subdirectories:
99 current game is the current mode
100 base game is a variable to allow mods based on other mods
101 (such as baseq3 + missionpack content combination in a mod for instance)
102 BASEGAME is the hardcoded base game ("baseq3")
103 
104 e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places:
105 
106 home path + current game's zip files
107 home path + current game's directory
108 base path + current game's zip files
109 base path + current game's directory
110 cd path + current game's zip files
111 cd path + current game's directory
112 
113 home path + base game's zip file
114 home path + base game's directory
115 base path + base game's zip file
116 base path + base game's directory
117 cd path + base game's zip file
118 cd path + base game's directory
119 
120 home path + BASEGAME's zip file
121 home path + BASEGAME's directory
122 base path + BASEGAME's zip file
123 base path + BASEGAME's directory
124 cd path + BASEGAME's zip file
125 cd path + BASEGAME's directory
126 
127 server download, to be written to home path + current game's directory
128 
129 
130 The filesystem can be safely shutdown and reinitialized with different
131 basedir / cddir / game combinations, but all other subsystems that rely on it
132 (sound, video) must also be forced to restart.
133 
134 Because the same files are loaded by both the clip model (CM_) and renderer (TR_)
135 subsystems, a simple single-file caching scheme is used.  The CM_ subsystems will
136 load the file with a request to cache.  Only one file will be kept cached at a time,
137 so any models that are going to be referenced by both subsystems should alternate
138 between the CM_ load function and the ref load function.
139 
140 TODO: A qpath that starts with a leading slash will always refer to the base game, even if another
141 game is currently active.  This allows character models, skins, and sounds to be downloaded
142 to a common directory no matter which game is active.
143 
144 How to prevent downloading zip files?
145 Pass pk3 file names in systeminfo, and download before FS_Restart()?
146 
147 Aborting a download disconnects the client from the server.
148 
149 How to mark files as downloadable?  Commercial add-ons won't be downloadable.
150 
151 Non-commercial downloads will want to download the entire zip file.
152 the game would have to be reset to actually read the zip in
153 
154 Auto-update information
155 
156 Path separators
157 
158 Casing
159 
160   separate server gamedir and client gamedir, so if the user starts
161   a local game after having connected to a network game, it won't stick
162   with the network game.
163 
164   allow menu options for game selection?
165 
166 Read / write config to floppy option.
167 
168 Different version coexistance?
169 
170 When building a pak file, make sure a wolfconfig.cfg isn't present in it,
171 or configs will never get loaded from disk!
172 
173   todo:
174 
175   downloading (outside fs?)
176   game directory passing and restarting
177 
178 =============================================================================
179 
180 */
181 
182 // TTimo: moved to qcommon.h
183 // NOTE: could really do with a cvar
184 //#define	BASEGAME			"main"
185 //#define DEMOGAME            "demomain"
186 
187 // every time a new demo pk3 file is built, this checksum must be updated.
188 // the easiest way to get it is to just run the game and see what it spits out
189 //DHM - Nerve :: Wolf Multiplayer demo checksum
190 // NOTE TTimo: always needs the 'u' for unsigned int (gcc)
191 #ifndef STANDALONE
192 #define DEMO_PAK0_CHECKSUM   2031778175u
193 
194 static const unsigned int pak_checksums[] = {
195 	1886207346u
196 };
197 
198 static const unsigned int mppak_checksums[] = {
199 	764840216u,
200 	-1023558518u,
201 	125907563u,
202 	131270674u,
203 	-137448799u,
204 	2149774797u
205 };
206 
207 #ifndef DEDICATED
208 static const unsigned int binpak_checksums[] = {
209 	4022537001u,	// mp_bin.pk3 (1.4/1.41)
210 	2925068652u,	// mp_bin0.pk3 (1.51c)
211 	3375536862u,	// Degeneration (1.11)
212 	2977960779u,	// Omni-Bot (0.81/0.82)
213 	618492725u,	// Omnit-Bot (0.85)
214 	2205235539u,	// OSP (0.83)
215 	718455065u,	// OSP (0.9)
216 	220076119u,	// Wild West (1.613)
217 	2849242342u	// Wolftactics (1.0/1.01)
218 };
219 #endif
220 
221 #endif
222 
223 #define MAX_ZPATH           256
224 #define MAX_SEARCH_PATHS    4096
225 #define MAX_FILEHASH_SIZE   1024
226 
227 typedef struct fileInPack_s {
228 	char                    *name;      // name of the file
229 	unsigned long pos;                  // file info position in zip
230 	unsigned long			len;		// uncompress file size
231 	struct  fileInPack_s*   next;       // next file in the hash
232 } fileInPack_t;
233 
234 typedef struct {
235 	char pakPathname[MAX_OSPATH];		    // c:\quake3\baseq3
236 	char pakFilename[MAX_OSPATH];               // c:\quake3\baseq3\pak0.pk3
237 	char pakBasename[MAX_OSPATH];               // pak0
238 	char pakGamename[MAX_OSPATH];               // baseq3
239 	unzFile handle;                             // handle to zip file
240 	int checksum;                               // regular checksum
241 	int pure_checksum;                          // checksum for pure
242 	int numfiles;                               // number of files in pk3
243 	int referenced;                             // referenced file flags
244 	int hashSize;                               // hash table size (power of 2)
245 	fileInPack_t*   *hashTable;                 // hash table
246 	fileInPack_t*   buildBuffer;                // buffer with the filenames etc.
247 } pack_t;
248 
249 typedef struct {
250 	char path[MAX_OSPATH];              // c:\quake3
251 	char		fullpath[MAX_OSPATH];		// c:\quake3\baseq3
252 	char gamedir[MAX_OSPATH];           // baseq3
253 	qboolean	allowUnzippedDLLs;		// whether to load unzipped dlls from directory
254 } directory_t;
255 
256 typedef struct searchpath_s {
257 	struct searchpath_s *next;
258 
259 	pack_t      *pack;      // only one of pack / dir will be non NULL
260 	directory_t *dir;
261 } searchpath_t;
262 
263 static char fs_gamedir[MAX_OSPATH];         // this will be a single file name with no separators
264 static cvar_t      *fs_debug;
265 static cvar_t      *fs_homepath;
266 
267 #ifdef __APPLE__
268 // Also search the .app bundle for .pk3 files
269 static  cvar_t          *fs_apppath;
270 #endif
271 
272 #ifndef STANDALONE
273 static	cvar_t		*fs_steampath;
274 static	cvar_t		*fs_gogpath;
275 #endif
276 
277 static cvar_t      *fs_basepath;
278 static cvar_t      *fs_basegame;
279 static cvar_t      *fs_gamedirvar;
280 static searchpath_t    *fs_searchpaths;
281 static int fs_readCount;                    // total bytes read
282 static int fs_loadCount;                    // total files read
283 static int fs_loadStack;                    // total files in memory
284 static int fs_packFiles = 0;                    // total number of files in packs
285 
286 static int fs_checksumFeed;
287 
288 typedef union qfile_gus {
289 	FILE*       o;
290 	unzFile z;
291 } qfile_gut;
292 
293 typedef struct qfile_us {
294 	qfile_gut file;
295 	qboolean unique;
296 } qfile_ut;
297 
298 typedef struct {
299 	qfile_ut handleFiles;
300 	qboolean handleSync;
301 	int fileSize;
302 	int zipFilePos;
303 	int zipFileLen;
304 	qboolean zipFile;
305 	char name[MAX_ZPATH];
306 } fileHandleData_t;
307 
308 static fileHandleData_t fsh[MAX_FILE_HANDLES];
309 
310 // TTimo - show_bug.cgi?id=540
311 // wether we did a reorder on the current search path when joining the server
312 static qboolean fs_reordered;
313 
314 // never load anything from pk3 files that are not present at the server when pure
315 // ex: when fs_numServerPaks != 0, FS_FOpenFileRead won't load anything outside of pk3 except .cfg .menu .game .dat
316 static int fs_numServerPaks = 0;
317 static int fs_serverPaks[MAX_SEARCH_PATHS];                     // checksums
318 static char     *fs_serverPakNames[MAX_SEARCH_PATHS];           // pk3 names
319 
320 // only used for autodownload, to make sure the client has at least
321 // all the pk3 files that are referenced at the server side
322 static int fs_numServerReferencedPaks;
323 static int fs_serverReferencedPaks[MAX_SEARCH_PATHS];               // checksums
324 static char     *fs_serverReferencedPakNames[MAX_SEARCH_PATHS];     // pk3 names
325 
326 // last valid game folder used
327 char lastValidBase[MAX_OSPATH];
328 char lastValidComBaseGame[MAX_OSPATH];
329 char lastValidFsBaseGame[MAX_OSPATH];
330 char lastValidGame[MAX_OSPATH];
331 
332 #ifdef FS_MISSING
333 FILE*       missingFiles = NULL;
334 #endif
335 
336 /* C99 defines __func__ */
337 #if __STDC_VERSION__ < 199901L
338 #  if __GNUC__ >= 2 || _MSC_VER >= 1300
339 #    define __func__ __FUNCTION__
340 #  else
341 #    define __func__ "(unknown)"
342 #  endif
343 #endif
344 
345 /*
346 ==============
347 FS_Initialized
348 ==============
349 */
350 
FS_Initialized(void)351 qboolean FS_Initialized( void ) {
352 	return ( fs_searchpaths != NULL );
353 }
354 
355 /*
356 =================
357 FS_PakIsPure
358 =================
359 */
FS_PakIsPure(pack_t * pack)360 qboolean FS_PakIsPure( pack_t *pack ) {
361 	int i;
362 
363 	if ( fs_numServerPaks ) {
364 		for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
365 			// FIXME: also use hashed file names
366 			// NOTE TTimo: a pk3 with same checksum but different name would be validated too
367 			//   I don't see this as allowing for any exploit, it would only happen if the client does manips of its file names 'not a bug'
368 			if ( pack->checksum == fs_serverPaks[i] ) {
369 				return qtrue;       // on the approved list
370 			}
371 		}
372 		return qfalse;  // not on the pure server pak list
373 	}
374 	return qtrue;
375 }
376 
377 
378 /*
379 =================
380 FS_LoadStack
381 return load stack
382 =================
383 */
FS_LoadStack(void)384 int FS_LoadStack( void ) {
385 	return fs_loadStack;
386 }
387 
388 /*
389 ================
390 return a hash value for the filename
391 ================
392 */
FS_HashFileName(const char * fname,int hashSize)393 static long FS_HashFileName( const char *fname, int hashSize ) {
394 	int i;
395 	long hash;
396 	char letter;
397 
398 	hash = 0;
399 	i = 0;
400 	while ( fname[i] != '\0' ) {
401 		letter = tolower( fname[i] );
402 		if ( letter == '.' ) {
403 			break;                          // don't include extension
404 		}
405 		if ( letter == '\\' ) {
406 			letter = '/';                   // damn path names
407 		}
408 		if ( letter == PATH_SEP ) {
409 			letter = '/';                           // damn path names
410 		}
411 		hash += (long)( letter ) * ( i + 119 );
412 		i++;
413 	}
414 	hash = ( hash ^ ( hash >> 10 ) ^ ( hash >> 20 ) );
415 	hash &= ( hashSize - 1 );
416 	return hash;
417 }
418 
FS_HandleForFile(void)419 static fileHandle_t FS_HandleForFile( void ) {
420 	int i;
421 
422 	for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
423 		if ( fsh[i].handleFiles.file.o == NULL ) {
424 			return i;
425 		}
426 	}
427 	Com_Error( ERR_DROP, "FS_HandleForFile: none free" );
428 	return 0;
429 }
430 
FS_FileForHandle(fileHandle_t f)431 static FILE *FS_FileForHandle( fileHandle_t f ) {
432 	if ( f < 1 || f >= MAX_FILE_HANDLES ) {
433 		Com_Error( ERR_DROP, "FS_FileForHandle: out of range" );
434 	}
435 	if ( fsh[f].zipFile == qtrue ) {
436 		Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" );
437 	}
438 	if ( !fsh[f].handleFiles.file.o ) {
439 		Com_Error( ERR_DROP, "FS_FileForHandle: NULL" );
440 	}
441 
442 	return fsh[f].handleFiles.file.o;
443 }
444 
FS_ForceFlush(fileHandle_t f)445 void    FS_ForceFlush( fileHandle_t f ) {
446 	FILE *file;
447 
448 	file = FS_FileForHandle( f );
449 	setvbuf( file, NULL, _IONBF, 0 );
450 }
451 
452 /*
453 ================
454 FS_fplength
455 ================
456 */
457 
FS_fplength(FILE * h)458 long FS_fplength(FILE *h)
459 {
460 	long		pos;
461 	long		end;
462 
463 	pos = ftell(h);
464 	fseek(h, 0, SEEK_END);
465 	end = ftell(h);
466 	fseek(h, pos, SEEK_SET);
467 
468 	return end;
469 }
470 
471 /*
472 ================
473 FS_filelength
474 
475 If this is called on a non-unique FILE (from a pak file),
476 it will return the size of the pak file, not the expected
477 size of the file.
478 ================
479 */
FS_filelength(fileHandle_t f)480 long FS_filelength(fileHandle_t f)
481 {
482 	FILE	*h;
483 
484 	h = FS_FileForHandle(f);
485 
486 	if(h == NULL)
487 	        return -1;
488         else
489 	        return FS_fplength(h);
490 }
491 
492 /*
493 ====================
494 FS_ReplaceSeparators
495 
496 Fix things up differently for win/unix/mac
497 ====================
498 */
FS_ReplaceSeparators(char * path)499 static void FS_ReplaceSeparators( char *path ) {
500 	char    *s;
501 	qboolean lastCharWasSep = qfalse;
502 
503 	for ( s = path ; *s ; s++ ) {
504 		if ( *s == '/' || *s == '\\' ) {
505 			if ( !lastCharWasSep ) {
506 				*s = PATH_SEP;
507 				lastCharWasSep = qtrue;
508 			} else {
509 				memmove (s, s + 1, strlen (s));
510 			}
511 		} else {
512 			lastCharWasSep = qfalse;
513 		}
514 	}
515 }
516 
517 /*
518 ===================
519 FS_BuildOSPath
520 
521 Qpath may have either forward or backwards slashes
522 ===================
523 */
FS_BuildOSPath(const char * base,const char * game,const char * qpath)524 char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) {
525 	char temp[MAX_OSPATH];
526 	static char ospath[2][MAX_OSPATH];
527 	static int toggle;
528 
529 	toggle ^= 1;        // flip-flop to allow two returns without clash
530 
531 	if ( !game || !game[0] ) {
532 		game = fs_gamedir;
533 	}
534 
535 	Com_sprintf( temp, sizeof( temp ), "/%s/%s", game, qpath );
536 	FS_ReplaceSeparators( temp );
537 	Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp );
538 
539 	return ospath[toggle];
540 }
541 
542 
543 /*
544 ============
545 FS_CreatePath
546 
547 Creates any directories needed to store the given filename
548 ============
549 */
FS_CreatePath(char * OSPath)550 qboolean FS_CreatePath( char *OSPath ) {
551 	char    *ofs;
552 	char	path[MAX_OSPATH];
553 
554 	// make absolutely sure that it can't back up the path
555 	// FIXME: is c: allowed???
556 	if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
557 		Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath );
558 		return qtrue;
559 	}
560 
561 	Q_strncpyz( path, OSPath, sizeof( path ) );
562 	FS_ReplaceSeparators( path );
563 
564 	// Skip creation of the root directory as it will always be there
565 	ofs = strchr( path, PATH_SEP );
566 	if ( ofs != NULL ) {
567 		ofs++;
568 	}
569 
570 	for (; ofs != NULL && *ofs ; ofs++) {
571 		if (*ofs == PATH_SEP) {
572 			// create the directory
573 			*ofs = 0;
574 			if (!Sys_Mkdir (path)) {
575 				Com_Error( ERR_FATAL, "FS_CreatePath: failed to create path \"%s\"",
576 					path );
577 			}
578 			*ofs = PATH_SEP;
579 		}
580 	}
581 	return qfalse;
582 }
583 
584 /*
585 =================
586 FS_CheckFilenameIsMutable
587 
588 ERR_FATAL if trying to maniuplate a file with the platform library, QVM, or pk3 extension
589 =================
590  */
FS_CheckFilenameIsMutable(const char * filename,const char * function)591 static void FS_CheckFilenameIsMutable( const char *filename,
592 		const char *function )
593 {
594 	// Check if the filename ends with the library, QVM, or pk3 extension
595 	if( Sys_DllExtension( filename )
596 		|| COM_CompareExtension( filename, ".qvm" )
597 		|| COM_CompareExtension( filename, ".pk3" ) )
598 	{
599 		Com_Error( ERR_FATAL, "%s: Not allowed to manipulate '%s' due "
600 			"to %s extension", function, filename, COM_GetExtension( filename ) );
601 	}
602 }
603 
604 /*
605 ===========
606 FS_Remove
607 
608 ===========
609 */
FS_Remove(const char * osPath)610 void FS_Remove( const char *osPath ) {
611 	FS_CheckFilenameIsMutable( osPath, __func__ );
612 
613 	remove( osPath );
614 }
615 
616 /*
617 ===========
618 FS_HomeRemove
619 
620 ===========
621 */
FS_HomeRemove(const char * homePath)622 void FS_HomeRemove( const char *homePath ) {
623 	FS_CheckFilenameIsMutable( homePath, __func__ );
624 
625 	remove( FS_BuildOSPath( fs_homepath->string,
626 			fs_gamedir, homePath ) );
627 }
628 
629 /*
630 ================
631 FS_FileInPathExists
632 
633 Tests if path and file exists
634 ================
635 */
FS_FileInPathExists(const char * testpath)636 qboolean FS_FileInPathExists(const char *testpath)
637 {
638 	FILE *filep;
639 
640 	filep = Sys_FOpen(testpath, "rb");
641 
642 	if(filep)
643 	{
644 		fclose(filep);
645 		return qtrue;
646 	}
647 
648 	return qfalse;
649 }
650 
651 /*
652 ================
653 FS_FileExists
654 
655 Tests if the file exists in the current gamedir, this DOES NOT
656 search the paths.  This is to determine if opening a file to write
657 (which always goes into the current gamedir) will cause any overwrites.
658 NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards
659 ================
660 */
FS_FileExists(const char * file)661 qboolean FS_FileExists(const char *file)
662 {
663 	return FS_FileInPathExists(FS_BuildOSPath(fs_homepath->string, fs_gamedir, file));
664 }
665 
666 /*
667 ================
668 FS_SV_FileExists
669 
670 Tests if the file exists
671 ================
672 */
FS_SV_FileExists(const char * file)673 qboolean FS_SV_FileExists( const char *file )
674 {
675 	char *testpath;
676 
677 	testpath = FS_BuildOSPath( fs_homepath->string, file, "");
678 	testpath[strlen(testpath)-1] = '\0';
679 
680 	return FS_FileInPathExists(testpath);
681 }
682 
683 /*
684 ===========
685 FS_SV_FOpenFileWrite
686 
687 ===========
688 */
FS_SV_FOpenFileWrite(const char * filename)689 fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) {
690 	char *ospath;
691 	fileHandle_t	f;
692 
693 	if ( !fs_searchpaths ) {
694 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
695 	}
696 
697 	ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
698 	ospath[strlen(ospath)-1] = '\0';
699 
700 	f = FS_HandleForFile();
701 	fsh[f].zipFile = qfalse;
702 
703 	if ( fs_debug->integer ) {
704 		Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath );
705 	}
706 
707 //	FS_CheckFilenameIsMutable( ospath, __func__ );
708 
709 	if( FS_CreatePath( ospath ) ) {
710 		return 0;
711 	}
712 
713 	Com_DPrintf( "writing to: %s\n", ospath );
714 	fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "wb" );
715 
716 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
717 
718 	fsh[f].handleSync = qfalse;
719 	if (!fsh[f].handleFiles.file.o) {
720 		f = 0;
721 	}
722 	return f;
723 }
724 
725 /*
726 ===========
727 FS_SV_FOpenFileRead
728 Search for a file somewhere below the home path then base path
729 in that order
730 ===========
731 */
FS_SV_FOpenFileRead(const char * filename,fileHandle_t * fp)732 long FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) {
733 	char *ospath;
734 	fileHandle_t f = 0;
735 
736 	if ( !fs_searchpaths ) {
737 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
738 	}
739 
740 	f = FS_HandleForFile();
741 	fsh[f].zipFile = qfalse;
742 
743 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
744 
745 	// don't let sound stutter
746 //	S_ClearSoundBuffer();
747 
748 	// search homepath
749 	ospath = FS_BuildOSPath( fs_homepath->string, filename, "" );
750 	// remove trailing slash
751 	ospath[strlen( ospath ) - 1] = '\0';
752 
753 	if ( fs_debug->integer ) {
754 		Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath );
755 	}
756 
757 	fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "rb" );
758 	fsh[f].handleSync = qfalse;
759 	if (!fsh[f].handleFiles.file.o)
760 	{
761 		// If fs_homepath == fs_basepath, don't bother
762 		if (Q_stricmp(fs_homepath->string,fs_basepath->string))
763 		{
764 			// search basepath
765 			ospath = FS_BuildOSPath( fs_basepath->string, filename, "" );
766 			ospath[strlen(ospath)-1] = '\0';
767 
768 			if ( fs_debug->integer )
769 			{
770 				Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath );
771 			}
772 
773 			fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "rb" );
774 			fsh[f].handleSync = qfalse;
775 		}
776 
777 #ifndef STANDALONE
778 		// Check fs_steampath
779 		if (!fsh[f].handleFiles.file.o && fs_steampath->string[0])
780 		{
781 			ospath = FS_BuildOSPath( fs_steampath->string, filename, "" );
782 			ospath[strlen(ospath)-1] = '\0';
783 
784 			if ( fs_debug->integer )
785 			{
786 				Com_Printf( "FS_SV_FOpenFileRead (fs_steampath): %s\n", ospath );
787 			}
788 
789 			fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "rb" );
790 			fsh[f].handleSync = qfalse;
791 		}
792 
793 		// Check fs_gogpath
794 		if (!fsh[f].handleFiles.file.o && fs_gogpath->string[0])
795 		{
796 			ospath = FS_BuildOSPath( fs_gogpath->string, filename, "" );
797 			ospath[strlen(ospath)-1] = '\0';
798 
799 			if ( fs_debug->integer )
800 			{
801 				Com_Printf( "FS_SV_FOpenFileRead (fs_gogpath): %s\n", ospath );
802 			}
803 
804 			fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "rb" );
805 			fsh[f].handleSync = qfalse;
806 		}
807 #endif
808 
809 
810 		if ( !fsh[f].handleFiles.file.o )
811 		{
812 			f = 0;
813 		}
814 	}
815 
816 	*fp = f;
817 	if ( f ) {
818 		return FS_filelength( f );
819 	}
820 	return -1;
821 }
822 
823 
824 /*
825 ===========
826 FS_SV_Rename
827 
828 ===========
829 */
FS_SV_Rename(const char * from,const char * to,qboolean safe)830 void FS_SV_Rename( const char *from, const char *to, qboolean safe ) {
831 	char            *from_ospath, *to_ospath;
832 
833 	if ( !fs_searchpaths ) {
834 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
835 	}
836 
837 	// don't let sound stutter
838 //	S_ClearSoundBuffer();
839 
840 	from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" );
841 	to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" );
842 	from_ospath[strlen( from_ospath ) - 1] = '\0';
843 	to_ospath[strlen( to_ospath ) - 1] = '\0';
844 
845 	if ( fs_debug->integer ) {
846 		Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath );
847 	}
848 
849 	if ( safe ) {
850 		FS_CheckFilenameIsMutable( to_ospath, __func__ );
851 	}
852 
853 	rename(from_ospath, to_ospath);
854 }
855 
856 
857 
858 
859 /*
860 ===========
861 FS_Rename
862 
863 ===========
864 */
FS_Rename(const char * from,const char * to)865 void FS_Rename( const char *from, const char *to ) {
866 	char            *from_ospath, *to_ospath;
867 
868 	if ( !fs_searchpaths ) {
869 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
870 	}
871 
872 	// don't let sound stutter
873 //	S_ClearSoundBuffer();
874 
875 	from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from );
876 	to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to );
877 
878 	if ( fs_debug->integer ) {
879 		Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath );
880 	}
881 
882 	FS_CheckFilenameIsMutable( to_ospath, __func__ );
883 
884 	rename(from_ospath, to_ospath);
885 }
886 
887 /*
888 ==============
889 FS_FCloseFile
890 
891 If the FILE pointer is an open pak file, leave it open.
892 
893 For some reason, other dll's can't just cal fclose()
894 on files returned by FS_FOpenFile...
895 ==============
896 */
FS_FCloseFile(fileHandle_t f)897 void FS_FCloseFile( fileHandle_t f ) {
898 	if ( !fs_searchpaths ) {
899 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
900 	}
901 
902 	if ( fsh[f].zipFile == qtrue ) {
903 		unzCloseCurrentFile( fsh[f].handleFiles.file.z );
904 		if ( fsh[f].handleFiles.unique ) {
905 			unzClose( fsh[f].handleFiles.file.z );
906 		}
907 		Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
908 		return;
909 	}
910 
911 	// we didn't find it as a pak, so close it as a unique file
912 	if ( fsh[f].handleFiles.file.o ) {
913 		fclose( fsh[f].handleFiles.file.o );
914 	}
915 	Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
916 }
917 
918 /*
919 ===========
920 FS_FOpenFileWrite
921 
922 ===========
923 */
FS_FOpenFileWrite(const char * filename)924 fileHandle_t FS_FOpenFileWrite( const char *filename ) {
925 	char            *ospath;
926 	fileHandle_t f;
927 
928 	if ( !fs_searchpaths ) {
929 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
930 	}
931 
932 	f = FS_HandleForFile();
933 	fsh[f].zipFile = qfalse;
934 
935 	ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
936 
937 	if ( fs_debug->integer ) {
938 		Com_Printf( "FS_FOpenFileWrite: %s\n", ospath );
939 	}
940 
941 //	FS_CheckFilenameIsMutable( ospath, __func__ );
942 
943 	if ( FS_CreatePath( ospath ) ) {
944 		return 0;
945 	}
946 
947 	// enabling the following line causes a recursive function call loop
948 	// when running with +set logfile 1 +set developer 1
949 	//Com_DPrintf( "writing to: %s\n", ospath );
950 	fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "wb" );
951 
952 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
953 
954 	fsh[f].handleSync = qfalse;
955 	if ( !fsh[f].handleFiles.file.o ) {
956 		f = 0;
957 	}
958 	return f;
959 }
960 
961 /*
962 ===========
963 FS_FOpenFileAppend
964 
965 ===========
966 */
FS_FOpenFileAppend(const char * filename)967 fileHandle_t FS_FOpenFileAppend( const char *filename ) {
968 	char            *ospath;
969 	fileHandle_t f;
970 
971 	if ( !fs_searchpaths ) {
972 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
973 	}
974 
975 	f = FS_HandleForFile();
976 	fsh[f].zipFile = qfalse;
977 
978 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
979 
980 	// don't let sound stutter
981 //	S_ClearSoundBuffer();
982 
983 	ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
984 
985 	if ( fs_debug->integer ) {
986 		Com_Printf( "FS_FOpenFileAppend: %s\n", ospath );
987 	}
988 
989 	FS_CheckFilenameIsMutable( ospath, __func__ );
990 
991 	if ( FS_CreatePath( ospath ) ) {
992 		return 0;
993 	}
994 
995 	fsh[f].handleFiles.file.o = Sys_FOpen( ospath, "ab" );
996 	fsh[f].handleSync = qfalse;
997 	if ( !fsh[f].handleFiles.file.o ) {
998 		f = 0;
999 	}
1000 	return f;
1001 }
1002 
1003 /*
1004 ===========
1005 FS_FCreateOpenPipeFile
1006 
1007 ===========
1008 */
FS_FCreateOpenPipeFile(const char * filename)1009 fileHandle_t FS_FCreateOpenPipeFile( const char *filename ) {
1010 	char	    		*ospath;
1011 	FILE					*fifo;
1012 	fileHandle_t	f;
1013 
1014 	if ( !fs_searchpaths ) {
1015 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
1016 	}
1017 
1018 	f = FS_HandleForFile();
1019 	fsh[f].zipFile = qfalse;
1020 
1021 	Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) );
1022 
1023 	// don't let sound stutter
1024 //	S_ClearSoundBuffer();
1025 
1026 	ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
1027 
1028 	if ( fs_debug->integer ) {
1029 		Com_Printf( "FS_FCreateOpenPipeFile: %s\n", ospath );
1030 	}
1031 
1032 	FS_CheckFilenameIsMutable( ospath, __func__ );
1033 
1034 	fifo = Sys_Mkfifo( ospath );
1035 	if( fifo ) {
1036 		fsh[f].handleFiles.file.o = fifo;
1037 		fsh[f].handleSync = qfalse;
1038 	}
1039 	else
1040 	{
1041 		Com_Printf( S_COLOR_YELLOW "WARNING: Could not create new com_pipefile at %s. "
1042 			"com_pipefile will not be used.\n", ospath );
1043 		f = 0;
1044 	}
1045 
1046 	return f;
1047 }
1048 
1049 /*
1050 ===========
1051 FS_FilenameCompare
1052 
1053 Ignore case and seprator char distinctions
1054 ===========
1055 */
FS_FilenameCompare(const char * s1,const char * s2)1056 qboolean FS_FilenameCompare( const char *s1, const char *s2 ) {
1057 	int c1, c2;
1058 
1059 	do {
1060 		c1 = *s1++;
1061 		c2 = *s2++;
1062 
1063 		if ( c1 >= 'a' && c1 <= 'z' ) {
1064 			c1 -= ( 'a' - 'A' );
1065 		}
1066 		if ( c2 >= 'a' && c2 <= 'z' ) {
1067 			c2 -= ( 'a' - 'A' );
1068 		}
1069 
1070 		if ( c1 == '\\' || c1 == ':' ) {
1071 			c1 = '/';
1072 		}
1073 		if ( c2 == '\\' || c2 == ':' ) {
1074 			c2 = '/';
1075 		}
1076 
1077 		if ( c1 != c2 ) {
1078 			return qtrue;      // strings not equal
1079 		}
1080 	} while ( c1 );
1081 
1082 	return qfalse;       // strings are equal
1083 }
1084 
1085 /*
1086 ===========
1087 FS_IsExt
1088 
1089 Return qtrue if ext matches file extension filename
1090 ===========
1091 */
1092 
FS_IsExt(const char * filename,const char * ext,int namelen)1093 qboolean FS_IsExt(const char *filename, const char *ext, int namelen)
1094 {
1095 	int extlen;
1096 
1097 	extlen = strlen(ext);
1098 
1099 	if(extlen > namelen)
1100 		return qfalse;
1101 
1102 	filename += namelen - extlen;
1103 
1104 	return !Q_stricmp(filename, ext);
1105 }
1106 
1107 /*
1108 ===========
1109 FS_IsDemoExt
1110 
1111 Return qtrue if filename has a demo extension
1112 ===========
1113 */
1114 
FS_IsDemoExt(const char * filename,int namelen)1115 qboolean FS_IsDemoExt(const char *filename, int namelen)
1116 {
1117 	char *ext_test;
1118 	int index, protocol;
1119 
1120 	ext_test = strrchr(filename, '.');
1121 	if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1))
1122 	{
1123 		protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT));
1124 
1125 		if(protocol == com_protocol->integer)
1126 			return qtrue;
1127 
1128 #ifdef LEGACY_PROTOCOL
1129                 if(protocol == com_legacyprotocol->integer)
1130                         return qtrue;
1131 #endif
1132 
1133 		for(index = 0; demo_protocols[index]; index++)
1134 		{
1135 			if(demo_protocols[index] == protocol)
1136 			return qtrue;
1137 		}
1138 	}
1139 
1140 	return qfalse;
1141 }
1142 
1143 /*
1144 ===========
1145 FS_ShiftedStrStr
1146 ===========
1147 */
FS_ShiftedStrStr(const char * string,const char * substring,int shift)1148 char *FS_ShiftedStrStr( const char *string, const char *substring, int shift ) {
1149 	char buf[MAX_STRING_TOKENS];
1150 	int i;
1151 
1152 	for ( i = 0; substring[i]; i++ ) {
1153 		buf[i] = substring[i] + shift;
1154 	}
1155 	buf[i] = '\0';
1156 	return strstr( string, buf );
1157 }
1158 
1159 /*
1160 ==========
1161 FS_ShiftStr
1162 perform simple string shifting to avoid scanning from the exe
1163 ==========
1164 */
FS_ShiftStr(const char * string,int shift)1165 char *FS_ShiftStr( const char *string, int shift ) {
1166 	static char buf[MAX_STRING_CHARS];
1167 	int i,l;
1168 
1169 	l = strlen( string );
1170 	for ( i = 0; i < l; i++ ) {
1171 		buf[i] = string[i] + shift;
1172 	}
1173 	buf[i] = '\0';
1174 	return buf;
1175 }
1176 
1177 /*
1178 ===========
1179 FS_FOpenFileReadDir
1180 
1181 Tries opening file "filename" in searchpath "search"
1182 Returns filesize and an open FILE pointer.
1183 ===========
1184 */
1185 extern qboolean		com_fullyInitialized;
1186 
1187 // see FS_FOpenFileRead_Filtered
1188 static int fs_filter_flag = 0;
1189 
FS_FOpenFileReadDir(const char * filename,searchpath_t * search,fileHandle_t * file,qboolean uniqueFILE,qboolean unpure)1190 long FS_FOpenFileReadDir(const char *filename, searchpath_t *search, fileHandle_t *file, qboolean uniqueFILE, qboolean unpure)
1191 {
1192 	long			hash;
1193 	pack_t		*pak;
1194 	fileInPack_t	*pakFile;
1195 	directory_t	*dir;
1196 	char		*netpath;
1197 	FILE		*filep;
1198 	int			len;
1199 
1200 	if(filename == NULL)
1201 		Com_Error(ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed");
1202 
1203 	// qpaths are not supposed to have a leading slash
1204 	if(filename[0] == '/' || filename[0] == '\\')
1205 		filename++;
1206 
1207 	// make absolutely sure that it can't back up the path.
1208 	// The searchpaths do guarantee that something will always
1209 	// be prepended, so we don't need to worry about "c:" or "//limbo"
1210 	if(strstr(filename, ".." ) || strstr(filename, "::"))
1211 	{
1212 	        if(file == NULL)
1213 	                return qfalse;
1214 
1215 		*file = 0;
1216 		return -1;
1217 	}
1218 
1219 	// make sure the rtcwkey file is only readable by the WolfMP.exe at initialization
1220 	// any other time the key should only be accessed in memory using the provided functions
1221 	if(com_fullyInitialized && strstr(filename, "rtcwkey"))
1222 	{
1223 	        if(file == NULL)
1224 	                return qfalse;
1225 
1226 		*file = 0;
1227 		return -1;
1228 	}
1229 
1230 	if(file == NULL)
1231 	{
1232 		// just wants to see if file is there
1233 
1234 		// is the element a pak file?
1235 		if(search->pack)
1236 		{
1237 			hash = FS_HashFileName(filename, search->pack->hashSize);
1238 
1239                         if(search->pack->hashTable[hash] && !(fs_filter_flag & FS_EXCLUDE_PK3))
1240                         {
1241 				// look through all the pak file elements
1242 				pak = search->pack;
1243 				pakFile = pak->hashTable[hash];
1244 
1245 				do
1246 				{
1247 					// case and separator insensitive comparisons
1248 					if(!FS_FilenameCompare(pakFile->name, filename))
1249 					{
1250 						// found it!
1251 						if(pakFile->len)
1252         						return pakFile->len;
1253                                                 else
1254                                                 {
1255                                                         // It's not nice, but legacy code depends
1256                                                         // on positive value if file exists no matter
1257                                                         // what size
1258                                                         return 1;
1259                                                 }
1260 					}
1261 
1262 					pakFile = pakFile->next;
1263 				} while(pakFile != NULL);
1264 			}
1265 		}
1266 		else if(search->dir && !(fs_filter_flag & FS_EXCLUDE_DIR))
1267 		{
1268 			dir = search->dir;
1269 
1270 			netpath = FS_BuildOSPath(dir->path, dir->gamedir, filename);
1271 			filep = Sys_FOpen(netpath, "rb");
1272 
1273 			if(filep)
1274 			{
1275 			        len = FS_fplength(filep);
1276 				fclose(filep);
1277 
1278 				if(len)
1279         				return len;
1280                                 else
1281                                         return 1;
1282 			}
1283 		}
1284 
1285 		return 0;
1286 	}
1287 
1288 	*file = FS_HandleForFile();
1289 	fsh[*file].handleFiles.unique = uniqueFILE;
1290 
1291 	// is the element a pak file?
1292 	if(search->pack)
1293 	{
1294 		hash = FS_HashFileName(filename, search->pack->hashSize);
1295 
1296 		if(search->pack->hashTable[hash] && !(fs_filter_flag & FS_EXCLUDE_PK3))
1297 		{
1298 			// disregard if it doesn't match one of the allowed pure pak files
1299 			if(!unpure && !FS_PakIsPure(search->pack))
1300 			{
1301 				*file = 0;
1302 				return -1;
1303 			}
1304 
1305 			// look through all the pak file elements
1306 			pak = search->pack;
1307 			pakFile = pak->hashTable[hash];
1308 
1309 			do
1310 			{
1311 				// case and separator insensitive comparisons
1312 				if(!FS_FilenameCompare(pakFile->name, filename))
1313 				{
1314 					// found it!
1315 
1316 					// Mark the pak as having been referenced and mark specifics on cgame and ui.
1317 					// Shaders, txt, and arena files by themselves do not count as a reference as
1318 					// these are loaded from all pk3s
1319 					len = strlen(filename);
1320 
1321 					if (!(pak->referenced & FS_GENERAL_REF))
1322 					{
1323 						if(!FS_IsExt(filename, ".shader", len) &&
1324 						   !FS_IsExt(filename, ".txt", len) &&
1325 						   !FS_IsExt(filename, ".cfg", len) &&
1326 						   !FS_IsExt(filename, ".config", len) &&
1327 						   !FS_IsExt(filename, ".bot", len) &&
1328 						   !FS_IsExt(filename, ".arena", len) &&
1329 						   !FS_IsExt(filename, ".menu", len) &&
1330 //						   Q_stricmp(filename, "vm/qagame.qvm") != 0 && // Commented out for now
1331 						   !strstr(filename, "levelshots"))
1332 						{
1333 							pak->referenced |= FS_GENERAL_REF;
1334 						}
1335 					}
1336 
1337 					if(strstr(filename, Sys_GetDLLName( "cgame" )))
1338 						pak->referenced |= FS_CGAME_REF;
1339 
1340 					if(strstr(filename, Sys_GetDLLName( "ui" )))
1341 						pak->referenced |= FS_UI_REF;
1342 
1343 					if(strstr(filename, "cgame.mp.qvm"))
1344 						pak->referenced |= FS_CGAME_REF;
1345 
1346 					if(strstr(filename, "ui.mp.qvm"))
1347 						pak->referenced |= FS_UI_REF;
1348 
1349 					// DHM -- Nerve :: Don't allow singleplayer maps to be loaded from pak0
1350 					if ( Q_stricmp( filename + len - 4, ".bsp" ) == 0 &&
1351 						 Q_stricmp( pak->pakBasename, "pak0" ) == 0 ) {
1352 
1353 						*file = 0;
1354 						return -1;
1355 					}
1356 
1357 					if(uniqueFILE)
1358 					{
1359 						// open a new file on the pakfile
1360 						fsh[*file].handleFiles.file.z = unzOpen(pak->pakFilename);
1361 
1362 						if(fsh[*file].handleFiles.file.z == NULL)
1363 							Com_Error(ERR_FATAL, "Couldn't open %s", pak->pakFilename);
1364 					}
1365 					else
1366 						fsh[*file].handleFiles.file.z = pak->handle;
1367 
1368 					Q_strncpyz(fsh[*file].name, filename, sizeof(fsh[*file].name));
1369 					fsh[*file].zipFile = qtrue;
1370 
1371 					// set the file position in the zip file (also sets the current file info)
1372 					unzSetOffset(fsh[*file].handleFiles.file.z, pakFile->pos);
1373 
1374 					// open the file in the zip
1375 					unzOpenCurrentFile(fsh[*file].handleFiles.file.z);
1376 					fsh[*file].zipFilePos = pakFile->pos;
1377 					fsh[*file].zipFileLen = pakFile->len;
1378 
1379 					if(fs_debug->integer)
1380 					{
1381 						Com_Printf("FS_FOpenFileRead: %s (found in '%s')\n",
1382 							filename, pak->pakFilename);
1383 					}
1384 
1385 					return pakFile->len;
1386 				}
1387 
1388 				pakFile = pakFile->next;
1389 			} while(pakFile != NULL);
1390 		}
1391 	}
1392 	else if(search->dir && !(fs_filter_flag & FS_EXCLUDE_DIR))
1393 	{
1394 		// check a file in the directory tree
1395 
1396 		// if we are running restricted, the only files we
1397 		// will allow to come from the directory are .cfg files
1398 		len = strlen(filename);
1399 		// FIXME TTimo I'm not sure about the fs_numServerPaks test
1400 		// if you are using FS_ReadFile to find out if a file exists,
1401 		//   this test can make the search fail although the file is in the directory
1402 		// I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8
1403 		// turned out I used FS_FileExists instead
1404 		if(!unpure && fs_numServerPaks)
1405 		{
1406 			if(!FS_IsExt(filename, ".cfg", len) &&		// for config files
1407 			   !FS_IsExt(filename, ".menu", len) &&		// menu files
1408 			   !FS_IsExt(filename, ".game", len) &&		// menu files
1409 			   !FS_IsExt(filename, ".dat", len) &&		// for journal files
1410 			   !FS_IsDemoExt(filename, len))			// demos
1411 			{
1412 				*file = 0;
1413 				return -1;
1414 			}
1415 		}
1416 
1417 		dir = search->dir;
1418 
1419 		netpath = FS_BuildOSPath(dir->path, dir->gamedir, filename);
1420 		filep = Sys_FOpen(netpath, "rb");
1421 
1422 		if (filep == NULL)
1423 		{
1424 			*file = 0;
1425                         return -1;
1426 		}
1427 
1428 		Q_strncpyz(fsh[*file].name, filename, sizeof(fsh[*file].name));
1429 		fsh[*file].zipFile = qfalse;
1430 
1431 		if(fs_debug->integer)
1432 		{
1433 			Com_Printf("FS_FOpenFileRead: %s (found in '%s%c%s')\n", filename,
1434 				dir->path, PATH_SEP, dir->gamedir);
1435 		}
1436 
1437 		fsh[*file].handleFiles.file.o = filep;
1438 		return FS_fplength(filep);
1439 	}
1440 
1441 	*file = 0;
1442 	return -1;
1443 }
1444 
1445 /*
1446 ===========
1447 FS_FOpenFileRead
1448 
1449 Finds the file in the search path.
1450 Returns filesize and an open FILE pointer.
1451 Used for streaming data out of either a
1452 separate file or a ZIP file.
1453 ===========
1454 */
FS_FOpenFileRead(const char * filename,fileHandle_t * file,qboolean uniqueFILE)1455 long FS_FOpenFileRead(const char *filename, fileHandle_t *file, qboolean uniqueFILE)
1456 {
1457 	searchpath_t *search;
1458 	long len;
1459 	qboolean isLocalConfig;
1460 
1461 	if(!fs_searchpaths)
1462 		Com_Error(ERR_FATAL, "Filesystem call made without initialization");
1463 
1464 	isLocalConfig = !strcmp(filename, "autoexec.cfg") || !strcmp(filename, Q3CONFIG_CFG);
1465 	for(search = fs_searchpaths; search; search = search->next)
1466 	{
1467 		// autoexec.cfg and wolfconfig_mp.cfg can only be loaded outside of pk3 files.
1468 		if (isLocalConfig && search->pack)
1469 			continue;
1470 
1471 	        len = FS_FOpenFileReadDir(filename, search, file, uniqueFILE, qfalse);
1472 
1473 	        if(file == NULL)
1474 	        {
1475 	                if(len > 0)
1476 	                        return len;
1477 	        }
1478 	        else
1479 	        {
1480 	                if(len >= 0 && *file)
1481 	                        return len;
1482 	        }
1483 
1484 	}
1485 
1486 #ifdef FS_MISSING
1487 	if(missingFiles)
1488 		fprintf(missingFiles, "%s\n", filename);
1489 #endif
1490 
1491 	if(file)
1492 	{
1493 		*file = 0;
1494 		return -1;
1495 	}
1496 	else
1497 	{
1498 		// When file is NULL, we're querying the existance of the file
1499 		// If we've got here, it doesn't exist
1500 		return 0;
1501 	}
1502 }
1503 
FS_FOpenFileRead_Filtered(const char * qpath,fileHandle_t * file,qboolean uniqueFILE,int filter_flag)1504 int FS_FOpenFileRead_Filtered( const char *qpath, fileHandle_t *file, qboolean uniqueFILE, int filter_flag ) {
1505 	int ret;
1506 
1507 	fs_filter_flag = filter_flag;
1508 	ret = FS_FOpenFileRead( qpath, file, uniqueFILE );
1509 	fs_filter_flag = 0;
1510 
1511 	return ret;
1512 }
1513 
1514 /*
1515 =================
1516 FS_FindVM
1517 
1518 Find a suitable VM file in search path order.
1519 
1520 In each searchpath try:
1521  - open DLL file
1522  - open QVM file if QVM loading enabled
1523 
1524 write found DLL or QVM to "found" and return VMI_NATIVE if DLL, VMI_COMPILED if QVM
1525 Return the searchpath in "startSearch".
1526 =================
1527 */
1528 
FS_FindVM(void ** startSearch,char * found,int foundlen,const char * name,qboolean unpure,int enableQvm)1529 int FS_FindVM(void **startSearch, char *found, int foundlen, const char *name, qboolean unpure, int enableQvm)
1530 {
1531 	searchpath_t *search, *lastSearch;
1532 	directory_t *dir;
1533 	pack_t *pack;
1534 	char dllName[MAX_OSPATH], qvmName[MAX_OSPATH];
1535 	char *netpath;
1536 
1537 	if(!fs_searchpaths)
1538 		Com_Error(ERR_FATAL, "Filesystem call made without initialization");
1539 
1540 	if(enableQvm)
1541 		Com_sprintf(qvmName, sizeof(qvmName), "vm/%s.mp.qvm", name);
1542 
1543 	Q_strncpyz(dllName, Sys_GetDLLName(name), sizeof(dllName));
1544 
1545 	lastSearch = *startSearch;
1546 	if(*startSearch == NULL)
1547 		search = fs_searchpaths;
1548 	else
1549 		search = lastSearch->next;
1550 
1551 	while(search)
1552 	{
1553 		if(search->dir && (unpure || !Q_stricmp(name, "qagame")))
1554 		{
1555 			dir = search->dir;
1556 
1557 			netpath = FS_BuildOSPath(dir->path, dir->gamedir, dllName);
1558 
1559 			if(enableQvm && FS_FOpenFileReadDir(qvmName, search, NULL, qfalse, unpure) > 0)
1560 			{
1561 				*startSearch = search;
1562 				return VMI_COMPILED;
1563 			}
1564 
1565 			if(dir->allowUnzippedDLLs && FS_FileInPathExists(netpath))
1566 			{
1567 				Q_strncpyz(found, netpath, foundlen);
1568 				*startSearch = search;
1569 
1570 				return VMI_NATIVE;
1571 			}
1572 		}
1573 		else if(search->pack)
1574 		{
1575 			pack = search->pack;
1576 
1577 		        if(lastSearch && lastSearch->pack)
1578 		        {
1579 		                // make sure we only try loading one VM file per game dir
1580 		                // i.e. if VM from pak7.pk3 fails we won't try one from pak6.pk3
1581 
1582 		                if(!FS_FilenameCompare(lastSearch->pack->pakPathname, pack->pakPathname))
1583                                 {
1584                                         search = search->next;
1585                                         continue;
1586                                 }
1587 		        }
1588 
1589 			if(enableQvm && FS_FOpenFileReadDir(qvmName, search, NULL, qfalse, unpure) > 0)
1590 			{
1591 				*startSearch = search;
1592 
1593 				return VMI_COMPILED;
1594 			}
1595 
1596 #ifndef DEDICATED
1597 			// extract the dlls from the mp_bin.pk3 so
1598 			// that they can be referenced
1599 			if (Q_stricmp(name, "qagame"))
1600 			{
1601 				netpath = FS_BuildOSPath(fs_homepath->string, pack->pakGamename, dllName);
1602 #ifndef STANDALONE
1603 				int index;
1604 				qboolean whitelisted = qfalse;
1605 
1606 				// Check whether this pak's checksum is on the whitelist
1607 				for(index = 0; index < ARRAY_LEN(binpak_checksums); index++)
1608 				{
1609 					if(pack->checksum == binpak_checksums[index])
1610 					{
1611 						whitelisted = FS_CL_ExtractFromPakFile(search, netpath, dllName, NULL);
1612 					}
1613 				}
1614 
1615 				if (FS_FOpenFileReadDir(dllName, search, NULL, qfalse, unpure) > 0
1616 						&& whitelisted)
1617 #else
1618 				if (FS_FOpenFileReadDir(dllName, search, NULL, qfalse, unpure) > 0
1619 						&& FS_CL_ExtractFromPakFile(search, netpath, dllName, NULL))
1620 #endif
1621 				{
1622 					Com_Printf( "Loading %s dll from %s\n", name, pack->pakFilename );
1623 					Q_strncpyz(found, netpath, foundlen);
1624 					*startSearch = search;
1625 
1626 					return VMI_NATIVE;
1627 				}
1628 			}
1629 #endif
1630 		}
1631 
1632 		search = search->next;
1633 	}
1634 
1635 	return -1;
1636 }
1637 
1638 // TTimo
1639 // relevant to client only
1640 #if !defined( DEDICATED )
1641 /*
1642 ==================
1643 FS_CL_ExtractFromPakFile
1644 
1645 NERVE - SMF - Extracts the latest file from a pak file.
1646 
1647 Compares packed file against extracted file. If no differences, does not copy.
1648 This is necessary for exe/dlls which may or may not be locked.
1649 
1650 NOTE TTimo:
1651   fullpath gives the full OS path to the dll that will potentially be loaded
1652 	on win32 it's always in fs_basepath/<fs_game>/
1653 	on linux it can be in fs_homepath/<fs_game>/ or fs_basepath/<fs_game>/
1654   the dll is extracted to fs_homepath (== fs_basepath on win32) if needed
1655 
1656   the return value doesn't tell wether file was extracted or not, it just says wether it's ok to continue
1657   (i.e. either the right file was extracted successfully, or it was already present)
1658 
1659   cvar_lastVersion is the optional name of a CVAR_ARCHIVE used to store the wolf version for the last extracted .so
1660   show_bug.cgi?id=463
1661 
1662 ==================
1663 */
FS_CL_ExtractFromPakFile(void * searchpath,const char * fullpath,const char * filename,const char * cvar_lastVersion)1664 qboolean FS_CL_ExtractFromPakFile( void *searchpath, const char *fullpath, const char *filename, const char *cvar_lastVersion ) {
1665 	int srcLength;
1666 	int destLength;
1667 	unsigned char   *srcData;
1668 	unsigned char   *destData;
1669 	qboolean needToCopy;
1670 	FILE            *destHandle;
1671 	int	read;
1672 
1673 	needToCopy = qtrue;
1674 
1675 	// read in compressed file
1676 	srcLength = FS_ReadFileDir(filename, searchpath, qfalse, (void **)&srcData);
1677 
1678 	// if its not in the pak, we bail
1679 	if ( srcLength == -1 ) {
1680 		return qfalse;
1681 	}
1682 
1683 	// read in local file
1684 	destHandle = Sys_FOpen( fullpath, "rb" );
1685 
1686 	// if we have a local file, we need to compare the two
1687 	if ( destHandle ) {
1688 		fseek( destHandle, 0, SEEK_END );
1689 		destLength = ftell( destHandle );
1690 		fseek( destHandle, 0, SEEK_SET );
1691 
1692 		if ( destLength > 0 ) {
1693 			destData = (unsigned char*)Z_Malloc( destLength );
1694 
1695 			read = fread( destData, 1, destLength, destHandle );
1696 
1697 			if (read == 0) {
1698 				Com_Error (ERR_FATAL, "FS_CL_ExtractFromPakFile: 0 bytes read");
1699 			}
1700 
1701 			// compare files
1702 			if ( destLength == srcLength ) {
1703 				int i;
1704 
1705 				for ( i = 0; i < destLength; i++ ) {
1706 					if ( destData[i] != srcData[i] ) {
1707 						break;
1708 					}
1709 				}
1710 
1711 				if ( i == destLength ) {
1712 					needToCopy = qfalse;
1713 				}
1714 			}
1715 
1716 			Z_Free( destData ); // TTimo
1717 		}
1718 
1719 		fclose( destHandle );
1720 	}
1721 
1722 	// write file
1723 	if ( needToCopy ) {
1724 		fileHandle_t f;
1725 
1726 		Com_DPrintf("FS_ExtractFromPakFile: FS_FOpenFileWrite '%s'\n", filename);
1727 		f = FS_FOpenFileWrite( filename );
1728 		if ( !f ) {
1729 			Com_Printf( "Failed to open %s\n", filename );
1730 			return qfalse;
1731 		}
1732 
1733 		FS_Write( srcData, srcLength, f );
1734 
1735 		FS_FCloseFile( f );
1736 
1737 #ifdef __linux__
1738 		// show_bug.cgi?id=463
1739 		// need to keep track of what versions we extract
1740 		if ( cvar_lastVersion ) {
1741 			Cvar_Set( cvar_lastVersion, Cvar_VariableString( "version" ) );
1742 		}
1743 #endif
1744 	}
1745 
1746 	FS_FreeFile( srcData );
1747 	return qtrue;
1748 }
1749 #endif
1750 
1751 /*
1752 ==============
1753 FS_Delete
1754 TTimo - this was not in the 1.30 filesystem code
1755 using fs_homepath for the file to remove
1756 ==============
1757 */
FS_Delete(char * filename)1758 int FS_Delete( char *filename ) {
1759 #if 0 // Stub...not used in MP
1760 	char *ospath;
1761 
1762 	if ( !fs_searchpaths ) {
1763 		Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" );
1764 	}
1765 
1766 	if ( !filename || filename[0] == 0 ) {
1767 		return 0;
1768 	}
1769 
1770 	// for safety, only allow deletion from the save directory
1771 	if ( Q_strncmp( filename, "save/", 5 ) != 0 ) {
1772 		return 0;
1773 	}
1774 
1775 	ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename );
1776 
1777 	if ( remove( ospath ) != -1 ) {  // success
1778 		return 1;
1779 	}
1780 #endif
1781 
1782 	return 0;
1783 }
1784 
1785 /*
1786 =================
1787 FS_Read
1788 
1789 Properly handles partial reads
1790 =================
1791 */
FS_Read(void * buffer,int len,fileHandle_t f)1792 int FS_Read( void *buffer, int len, fileHandle_t f ) {
1793 	int block, remaining;
1794 	int read;
1795 	byte    *buf;
1796 	int tries;
1797 
1798 	if ( !fs_searchpaths ) {
1799 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
1800 	}
1801 
1802 	if ( !f ) {
1803 		return 0;
1804 	}
1805 
1806 	buf = (byte *)buffer;
1807 	fs_readCount += len;
1808 
1809 	if ( fsh[f].zipFile == qfalse ) {
1810 		remaining = len;
1811 		tries = 0;
1812 		while ( remaining ) {
1813 			block = remaining;
1814 			read = fread( buf, 1, block, fsh[f].handleFiles.file.o );
1815 			if ( read == 0 ) {
1816 				// we might have been trying to read from a CD, which
1817 				// sometimes returns a 0 read on windows
1818 				if ( !tries ) {
1819 					tries = 1;
1820 				} else {
1821 					return len - remaining;   //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read");
1822 				}
1823 			}
1824 
1825 			if ( read == -1 ) {
1826 				Com_Error( ERR_FATAL, "FS_Read: -1 bytes read" );
1827 			}
1828 
1829 			remaining -= read;
1830 			buf += read;
1831 		}
1832 		return len;
1833 	} else {
1834 		return unzReadCurrentFile( fsh[f].handleFiles.file.z, buffer, len );
1835 	}
1836 }
1837 
1838 /*
1839 =================
1840 FS_Write
1841 
1842 Properly handles partial writes
1843 =================
1844 */
FS_Write(const void * buffer,int len,fileHandle_t h)1845 int FS_Write( const void *buffer, int len, fileHandle_t h ) {
1846 	int block, remaining;
1847 	int written;
1848 	byte    *buf;
1849 	int tries;
1850 	FILE    *f;
1851 
1852 	if ( !fs_searchpaths ) {
1853 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
1854 	}
1855 
1856 	if ( !h ) {
1857 		return 0;
1858 	}
1859 
1860 	f = FS_FileForHandle( h );
1861 	buf = (byte *)buffer;
1862 
1863 	remaining = len;
1864 	tries = 0;
1865 	while ( remaining ) {
1866 		block = remaining;
1867 		written = fwrite( buf, 1, block, f );
1868 		if ( written == 0 ) {
1869 			if ( !tries ) {
1870 				tries = 1;
1871 			} else {
1872 				Com_Printf( "FS_Write: 0 bytes written\n" );
1873 				return 0;
1874 			}
1875 		}
1876 
1877 		if ( written == -1 ) {
1878 			Com_Printf( "FS_Write: -1 bytes written\n" );
1879 			return 0;
1880 		}
1881 
1882 		remaining -= written;
1883 		buf += written;
1884 	}
1885 	if ( fsh[h].handleSync ) {
1886 		fflush( f );
1887 	}
1888 	return len;
1889 }
1890 
FS_Printf(fileHandle_t h,const char * fmt,...)1891 void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) {
1892 	va_list argptr;
1893 	char msg[MAXPRINTMSG];
1894 
1895 	va_start( argptr,fmt );
1896 	Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
1897 	va_end( argptr );
1898 
1899 	FS_Write( msg, strlen( msg ), h );
1900 }
1901 
1902 #define PK3_SEEK_BUFFER_SIZE 65536
1903 
1904 /*
1905 =================
1906 FS_Seek
1907 
1908 =================
1909 */
FS_Seek(fileHandle_t f,long offset,int origin)1910 int FS_Seek( fileHandle_t f, long offset, int origin ) {
1911 	int		_origin;
1912 
1913 	if ( !fs_searchpaths ) {
1914 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
1915 		return -1;
1916 	}
1917 
1918 	if (fsh[f].zipFile == qtrue) {
1919 		//FIXME: this is really, really crappy
1920 		//(but better than what was here before)
1921 		byte	buffer[PK3_SEEK_BUFFER_SIZE];
1922 		int		remainder;
1923 		int		currentPosition = FS_FTell( f );
1924 
1925 		// change negative offsets into FS_SEEK_SET
1926 		if ( offset < 0 ) {
1927 			switch( origin ) {
1928 				case FS_SEEK_END:
1929 					remainder = fsh[f].zipFileLen + offset;
1930 					break;
1931 
1932 				case FS_SEEK_CUR:
1933 					remainder = currentPosition + offset;
1934 					break;
1935 
1936 				case FS_SEEK_SET:
1937 				default:
1938 					remainder = 0;
1939 					break;
1940 			}
1941 
1942 			if ( remainder < 0 ) {
1943 				remainder = 0;
1944 			}
1945 
1946 			origin = FS_SEEK_SET;
1947 		} else {
1948 			if ( origin == FS_SEEK_END ) {
1949 				remainder = fsh[f].zipFileLen - currentPosition + offset;
1950 			} else {
1951 				remainder = offset;
1952 			}
1953 		}
1954 
1955 		switch( origin ) {
1956 			case FS_SEEK_SET:
1957 				if ( remainder == currentPosition ) {
1958 					return offset;
1959 				}
1960 				unzSetOffset(fsh[f].handleFiles.file.z, fsh[f].zipFilePos);
1961 				unzOpenCurrentFile(fsh[f].handleFiles.file.z);
1962 				//fallthrough
1963 
1964 			case FS_SEEK_END:
1965 			case FS_SEEK_CUR:
1966 				while( remainder > PK3_SEEK_BUFFER_SIZE ) {
1967 					FS_Read( buffer, PK3_SEEK_BUFFER_SIZE, f );
1968 					remainder -= PK3_SEEK_BUFFER_SIZE;
1969 				}
1970 				FS_Read( buffer, remainder, f );
1971 				return offset;
1972 
1973 			default:
1974 				Com_Error( ERR_FATAL, "Bad origin in FS_Seek" );
1975 				return -1;
1976 		}
1977 	} else {
1978 		FILE *file;
1979 		file = FS_FileForHandle(f);
1980 		switch( origin ) {
1981 		case FS_SEEK_CUR:
1982 			_origin = SEEK_CUR;
1983 			break;
1984 		case FS_SEEK_END:
1985 			_origin = SEEK_END;
1986 			break;
1987 		case FS_SEEK_SET:
1988 			_origin = SEEK_SET;
1989 			break;
1990 		default:
1991 			Com_Error( ERR_FATAL, "Bad origin in FS_Seek" );
1992 			break;
1993 		}
1994 
1995 		return fseek( file, offset, _origin );
1996 	}
1997 }
1998 
1999 
2000 /*
2001 ======================================================================================
2002 
2003 CONVENIENCE FUNCTIONS FOR ENTIRE FILES
2004 
2005 ======================================================================================
2006 */
2007 
FS_FileIsInPAK(const char * filename,int * pChecksum)2008 int FS_FileIsInPAK( const char *filename, int *pChecksum ) {
2009 	searchpath_t    *search;
2010 	pack_t          *pak;
2011 	fileInPack_t    *pakFile;
2012 	long hash = 0;
2013 
2014 	if ( !fs_searchpaths ) {
2015 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
2016 	}
2017 
2018 	if ( !filename ) {
2019 		Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed" );
2020 	}
2021 
2022 	// qpaths are not supposed to have a leading slash
2023 	if ( filename[0] == '/' || filename[0] == '\\' ) {
2024 		filename++;
2025 	}
2026 
2027 	// make absolutely sure that it can't back up the path.
2028 	// The searchpaths do guarantee that something will always
2029 	// be prepended, so we don't need to worry about "c:" or "//limbo"
2030 	if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) {
2031 		return -1;
2032 	}
2033 
2034 	//
2035 	// search through the path, one element at a time
2036 	//
2037 
2038 	for ( search = fs_searchpaths ; search ; search = search->next ) {
2039 		//
2040 		if ( search->pack ) {
2041 			hash = FS_HashFileName( filename, search->pack->hashSize );
2042 		}
2043 		// is the element a pak file?
2044 		if ( search->pack && search->pack->hashTable[hash] ) {
2045 			// disregard if it doesn't match one of the allowed pure pak files
2046 			if ( !FS_PakIsPure( search->pack ) ) {
2047 				continue;
2048 			}
2049 
2050 			// look through all the pak file elements
2051 			pak = search->pack;
2052 			pakFile = pak->hashTable[hash];
2053 			do {
2054 				// case and separator insensitive comparisons
2055 				if ( !FS_FilenameCompare( pakFile->name, filename ) ) {
2056 					if ( pChecksum ) {
2057 						*pChecksum = pak->pure_checksum;
2058 					}
2059 					return 1;
2060 				}
2061 				pakFile = pakFile->next;
2062 			} while ( pakFile != NULL );
2063 		}
2064 	}
2065 	return -1;
2066 }
2067 
2068 /*
2069 ============
2070 FS_ReadFileDir
2071 
2072 Filename are relative to the quake search path
2073 a null buffer will just return the file length without loading
2074 If searchPath is non-NULL search only in that specific search path
2075 ============
2076 */
FS_ReadFileDir(const char * qpath,void * searchPath,qboolean unpure,void ** buffer)2077 long FS_ReadFileDir(const char *qpath, void *searchPath, qboolean unpure, void **buffer)
2078 {
2079 	fileHandle_t	h;
2080 	searchpath_t	*search;
2081 	byte*			buf;
2082 	qboolean		isConfig;
2083 	long				len;
2084 
2085 	if ( !fs_searchpaths ) {
2086 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
2087 	}
2088 
2089 	if ( !qpath || !qpath[0] ) {
2090 		Com_Error( ERR_FATAL, "FS_ReadFile with empty name" );
2091 	}
2092 
2093 	buf = NULL;	// quiet compiler warning
2094 
2095 	// if this is a .cfg file and we are playing back a journal, read
2096 	// it from the journal file
2097 	if ( strstr( qpath, ".cfg" ) ) {
2098 		isConfig = qtrue;
2099 		if ( com_journal && com_journal->integer == 2 ) {
2100 			int		r;
2101 
2102 			Com_DPrintf( "Loading %s from journal file.\n", qpath );
2103 			r = FS_Read( &len, sizeof( len ), com_journalDataFile );
2104 			if ( r != sizeof( len ) ) {
2105 				if (buffer != NULL) *buffer = NULL;
2106 				return -1;
2107 			}
2108 			// if the file didn't exist when the journal was created
2109 			if (!len) {
2110 				if (buffer == NULL) {
2111 					return 1;			// hack for old journal files
2112 				}
2113 				*buffer = NULL;
2114 				return -1;
2115 			}
2116 			if (buffer == NULL) {
2117 				return len;
2118 			}
2119 
2120 			buf = Hunk_AllocateTempMemory(len+1);
2121 			*buffer = buf;
2122 
2123 			r = FS_Read( buf, len, com_journalDataFile );
2124 			if ( r != len ) {
2125 				Com_Error( ERR_FATAL, "Read from journalDataFile failed" );
2126 			}
2127 
2128 			fs_loadCount++;
2129 			fs_loadStack++;
2130 
2131 			// guarantee that it will have a trailing 0 for string operations
2132 			buf[len] = 0;
2133 
2134 			return len;
2135 		}
2136 	} else {
2137 		isConfig = qfalse;
2138 	}
2139 
2140 	search = searchPath;
2141 
2142 	if(search == NULL)
2143 	{
2144 		// look for it in the filesystem or pack files
2145 		len = FS_FOpenFileRead(qpath, &h, qfalse);
2146         }
2147 	else
2148 	{
2149 		// look for it in a specific search path only
2150 		len = FS_FOpenFileReadDir(qpath, search, &h, qfalse, unpure);
2151 	}
2152 
2153 	if ( h == 0 ) {
2154 		if ( buffer ) {
2155 			*buffer = NULL;
2156 		}
2157 		// if we are journalling and it is a config file, write a zero to the journal file
2158 		if ( isConfig && com_journal && com_journal->integer == 1 ) {
2159 			Com_DPrintf( "Writing zero for %s to journal file.\n", qpath );
2160 			len = 0;
2161 			FS_Write( &len, sizeof( len ), com_journalDataFile );
2162 			FS_Flush( com_journalDataFile );
2163 		}
2164 		return -1;
2165 	}
2166 
2167 	if ( !buffer ) {
2168 		if ( isConfig && com_journal && com_journal->integer == 1 ) {
2169 			Com_DPrintf( "Writing len for %s to journal file.\n", qpath );
2170 			FS_Write( &len, sizeof( len ), com_journalDataFile );
2171 			FS_Flush( com_journalDataFile );
2172 		}
2173 		FS_FCloseFile( h);
2174 		return len;
2175 	}
2176 
2177 	fs_loadCount++;
2178 	fs_loadStack++;
2179 
2180 	buf = Hunk_AllocateTempMemory(len+1);
2181 	*buffer = buf;
2182 
2183 	FS_Read (buf, len, h);
2184 
2185 	// guarantee that it will have a trailing 0 for string operations
2186 	buf[len] = 0;
2187 	FS_FCloseFile( h );
2188 
2189 	// if we are journalling and it is a config file, write it to the journal file
2190 	if ( isConfig && com_journal && com_journal->integer == 1 ) {
2191 		Com_DPrintf( "Writing %s to journal file.\n", qpath );
2192 		FS_Write( &len, sizeof( len ), com_journalDataFile );
2193 		FS_Write( buf, len, com_journalDataFile );
2194 		FS_Flush( com_journalDataFile );
2195 	}
2196 	return len;
2197 }
2198 
2199 /*
2200 ============
2201 FS_ReadFile
2202 
2203 Filename are relative to the quake search path
2204 a null buffer will just return the file length without loading
2205 ============
2206 */
FS_ReadFile(const char * qpath,void ** buffer)2207 long FS_ReadFile(const char *qpath, void **buffer)
2208 {
2209 	return FS_ReadFileDir(qpath, NULL, qfalse, buffer);
2210 }
2211 
2212 /*
2213 =============
2214 FS_FreeFile
2215 =============
2216 */
FS_FreeFile(void * buffer)2217 void FS_FreeFile( void *buffer ) {
2218 	if ( !fs_searchpaths ) {
2219 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
2220 	}
2221 	if ( !buffer ) {
2222 		Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" );
2223 	}
2224 	fs_loadStack--;
2225 
2226 	Hunk_FreeTempMemory( buffer );
2227 
2228 	// if all of our temp files are free, clear all of our space
2229 	if ( fs_loadStack == 0 ) {
2230 		Hunk_ClearTempMemory();
2231 	}
2232 }
2233 
2234 /*
2235 ============
2236 FS_WriteFile
2237 
2238 Filenames are relative to the quake search path
2239 ============
2240 */
FS_WriteFile(const char * qpath,const void * buffer,int size)2241 void FS_WriteFile( const char *qpath, const void *buffer, int size ) {
2242 	fileHandle_t f;
2243 
2244 	if ( !fs_searchpaths ) {
2245 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
2246 	}
2247 
2248 	if ( !qpath || !buffer ) {
2249 		Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" );
2250 	}
2251 
2252 	f = FS_FOpenFileWrite( qpath );
2253 	if ( !f ) {
2254 		Com_Printf( "Failed to open %s\n", qpath );
2255 		return;
2256 	}
2257 
2258 	FS_Write( buffer, size, f );
2259 
2260 	FS_FCloseFile( f );
2261 }
2262 
2263 
2264 
2265 /*
2266 ==========================================================================
2267 
2268 ZIP FILE LOADING
2269 
2270 ==========================================================================
2271 */
2272 
2273 /*
2274 =================
2275 FS_LoadZipFile
2276 
2277 Creates a new pak_t in the search chain for the contents
2278 of a zip file.
2279 =================
2280 */
FS_LoadZipFile(const char * zipfile,const char * basename)2281 static pack_t *FS_LoadZipFile(const char *zipfile, const char *basename ) {
2282 	fileInPack_t    *buildBuffer;
2283 	pack_t          *pack;
2284 	unzFile uf;
2285 	int err;
2286 	unz_global_info gi;
2287 	char filename_inzip[MAX_ZPATH];
2288 	unz_file_info file_info;
2289 	int i, len;
2290 	long hash;
2291 	int fs_numHeaderLongs;
2292 	int             *fs_headerLongs;
2293 	char            *namePtr;
2294 
2295 	fs_numHeaderLongs = 0;
2296 
2297 	uf = unzOpen( zipfile );
2298 	err = unzGetGlobalInfo( uf,&gi );
2299 
2300 	if ( err != UNZ_OK ) {
2301 		return NULL;
2302 	}
2303 
2304 	fs_packFiles += gi.number_entry;
2305 
2306 	len = 0;
2307 	unzGoToFirstFile( uf );
2308 	for ( i = 0; i < gi.number_entry; i++ )
2309 	{
2310 		err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 );
2311 		if ( err != UNZ_OK ) {
2312 			break;
2313 		}
2314 		len += strlen( filename_inzip ) + 1;
2315 		unzGoToNextFile( uf );
2316 	}
2317 
2318 	buildBuffer = Z_Malloc( ( gi.number_entry * sizeof( fileInPack_t ) ) + len );
2319 	namePtr = ( (char *) buildBuffer ) + gi.number_entry * sizeof( fileInPack_t );
2320 	fs_headerLongs = Z_Malloc( ( gi.number_entry + 1 ) * sizeof(int) );
2321 	fs_headerLongs[ fs_numHeaderLongs++ ] = LittleLong( fs_checksumFeed );
2322 
2323 	// get the hash table size from the number of files in the zip
2324 	// because lots of custom pk3 files have less than 32 or 64 files
2325 	for ( i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1 ) {
2326 		if ( i > gi.number_entry ) {
2327 			break;
2328 		}
2329 	}
2330 
2331 	pack = Z_Malloc( sizeof( pack_t ) + i * sizeof( fileInPack_t * ) );
2332 	pack->hashSize = i;
2333 	pack->hashTable = ( fileInPack_t ** )( ( (char *) pack ) + sizeof( pack_t ) );
2334 	for ( i = 0; i < pack->hashSize; i++ ) {
2335 		pack->hashTable[i] = NULL;
2336 	}
2337 
2338 	Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) );
2339 	Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) );
2340 
2341 	// strip .pk3 if needed
2342 	if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) {
2343 		pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0;
2344 	}
2345 
2346 	pack->handle = uf;
2347 	pack->numfiles = gi.number_entry;
2348 	unzGoToFirstFile( uf );
2349 
2350 	for ( i = 0; i < gi.number_entry; i++ )
2351 	{
2352 		err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof( filename_inzip ), NULL, 0, NULL, 0 );
2353 		if ( err != UNZ_OK ) {
2354 			break;
2355 		}
2356 		if ( file_info.uncompressed_size > 0 ) {
2357 			fs_headerLongs[fs_numHeaderLongs++] = LittleLong( file_info.crc );
2358 		}
2359 		Q_strlwr( filename_inzip );
2360 		hash = FS_HashFileName( filename_inzip, pack->hashSize );
2361 		buildBuffer[i].name = namePtr;
2362 		strcpy( buildBuffer[i].name, filename_inzip );
2363 		namePtr += strlen( filename_inzip ) + 1;
2364 		// store the file position in the zip
2365 		buildBuffer[i].pos = unzGetOffset(uf);
2366 		buildBuffer[i].len = file_info.uncompressed_size;
2367 		buildBuffer[i].next = pack->hashTable[hash];
2368 		pack->hashTable[hash] = &buildBuffer[i];
2369 		unzGoToNextFile( uf );
2370 	}
2371 
2372 	pack->checksum = Com_BlockChecksum( &fs_headerLongs[ 1 ], sizeof(*fs_headerLongs) * ( fs_numHeaderLongs - 1 ) );
2373 	pack->pure_checksum = Com_BlockChecksum( fs_headerLongs, sizeof(*fs_headerLongs) * fs_numHeaderLongs );
2374 	pack->checksum = LittleLong( pack->checksum );
2375 	pack->pure_checksum = LittleLong( pack->pure_checksum );
2376 
2377 	Z_Free( fs_headerLongs );
2378 
2379 	pack->buildBuffer = buildBuffer;
2380 	return pack;
2381 }
2382 
2383 /*
2384 =================
2385 FS_FreePak
2386 
2387 Frees a pak structure and releases all associated resources
2388 =================
2389 */
2390 
FS_FreePak(pack_t * thepak)2391 static void FS_FreePak(pack_t *thepak)
2392 {
2393 	unzClose(thepak->handle);
2394 	Z_Free(thepak->buildBuffer);
2395 	Z_Free(thepak);
2396 }
2397 
2398 /*
2399 =================
2400 FS_GetZipChecksum
2401 
2402 Compares whether the given pak file matches a referenced checksum
2403 =================
2404 */
FS_CompareZipChecksum(const char * zipfile)2405 qboolean FS_CompareZipChecksum(const char *zipfile)
2406 {
2407 	pack_t *thepak;
2408 	int index, checksum;
2409 
2410 	thepak = FS_LoadZipFile(zipfile, "");
2411 
2412 	if(!thepak)
2413 		return qfalse;
2414 
2415 	checksum = thepak->checksum;
2416 	FS_FreePak(thepak);
2417 
2418 	for(index = 0; index < fs_numServerReferencedPaks; index++)
2419 	{
2420 		if(checksum == fs_serverReferencedPaks[index])
2421 			return qtrue;
2422 	}
2423 
2424 	return qfalse;
2425 }
2426 
2427 /*
2428 =================================================================================
2429 
2430 DIRECTORY SCANNING FUNCTIONS
2431 
2432 =================================================================================
2433 */
2434 
2435 #define MAX_FOUND_FILES 0x1000
2436 
FS_ReturnPath(const char * zname,char * zpath,int * depth)2437 static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) {
2438 	int len, at, newdep;
2439 
2440 	newdep = 0;
2441 	zpath[0] = 0;
2442 	len = 0;
2443 	at = 0;
2444 
2445 	while ( zname[at] != 0 )
2446 	{
2447 		if ( zname[at] == '/' || zname[at] == '\\' ) {
2448 			len = at;
2449 			newdep++;
2450 		}
2451 		at++;
2452 	}
2453 	strcpy( zpath, zname );
2454 	zpath[len] = 0;
2455 	*depth = newdep;
2456 
2457 	return len;
2458 }
2459 
2460 /*
2461 ==================
2462 FS_AddFileToList
2463 ==================
2464 */
FS_AddFileToList(char * name,char * list[MAX_FOUND_FILES],int nfiles)2465 static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) {
2466 	int i;
2467 
2468 	if ( nfiles == MAX_FOUND_FILES - 1 ) {
2469 		return nfiles;
2470 	}
2471 	for ( i = 0 ; i < nfiles ; i++ ) {
2472 		if ( !Q_stricmp( name, list[i] ) ) {
2473 			return nfiles;      // already in list
2474 		}
2475 	}
2476 	list[nfiles] = CopyString( name );
2477 	nfiles++;
2478 
2479 	return nfiles;
2480 }
2481 
2482 /*
2483 ===============
2484 FS_ListFilteredFiles
2485 
2486 Returns a uniqued list of files that match the given criteria
2487 from all search paths
2488 ===============
2489 */
FS_ListFilteredFiles(const char * path,const char * extension,char * filter,int * numfiles,qboolean allowNonPureFilesOnDisk)2490 char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles, qboolean allowNonPureFilesOnDisk ) {
2491 	int nfiles;
2492 	char            **listCopy;
2493 	char            *list[MAX_FOUND_FILES];
2494 	searchpath_t    *search;
2495 	int i;
2496 	int pathLength;
2497 	int extensionLength;
2498 	int length, pathDepth, temp;
2499 	pack_t          *pak;
2500 	fileInPack_t    *buildBuffer;
2501 	char zpath[MAX_ZPATH];
2502 
2503 	if ( !fs_searchpaths ) {
2504 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
2505 	}
2506 
2507 	if ( !path ) {
2508 		*numfiles = 0;
2509 		return NULL;
2510 	}
2511 	if ( !extension ) {
2512 		extension = "";
2513 	}
2514 
2515 	pathLength = strlen( path );
2516 	if ( path[pathLength - 1] == '\\' || path[pathLength - 1] == '/' ) {
2517 		pathLength--;
2518 	}
2519 	extensionLength = strlen( extension );
2520 	nfiles = 0;
2521 	FS_ReturnPath( path, zpath, &pathDepth );
2522 
2523 	//
2524 	// search through the path, one element at a time, adding to list
2525 	//
2526 	for ( search = fs_searchpaths ; search ; search = search->next ) {
2527 		// is the element a pak file?
2528 		if ( search->pack ) {
2529 
2530 			//ZOID:  If we are pure, don't search for files on paks that
2531 			// aren't on the pure list
2532 			if ( !FS_PakIsPure( search->pack ) ) {
2533 				continue;
2534 			}
2535 
2536 			// look through all the pak file elements
2537 			pak = search->pack;
2538 			buildBuffer = pak->buildBuffer;
2539 			for ( i = 0; i < pak->numfiles; i++ ) {
2540 				char    *name;
2541 				int zpathLen, depth;
2542 
2543 				// check for directory match
2544 				name = buildBuffer[i].name;
2545 				//
2546 				if ( filter ) {
2547 					// case insensitive
2548 					if ( !Com_FilterPath( filter, name, qfalse ) ) {
2549 						continue;
2550 					}
2551 					// unique the match
2552 					nfiles = FS_AddFileToList( name, list, nfiles );
2553 				} else {
2554 
2555 					zpathLen = FS_ReturnPath( name, zpath, &depth );
2556 
2557 					if ( ( depth - pathDepth ) > 2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) {
2558 						continue;
2559 					}
2560 
2561 					// check for extension match
2562 					length = strlen( name );
2563 					if ( length < extensionLength ) {
2564 						continue;
2565 					}
2566 
2567 					if ( Q_stricmp( name + length - extensionLength, extension ) ) {
2568 						continue;
2569 					}
2570 					// unique the match
2571 
2572 					temp = pathLength;
2573 					if ( pathLength ) {
2574 						temp++;     // include the '/'
2575 					}
2576 					nfiles = FS_AddFileToList( name + temp, list, nfiles );
2577 				}
2578 			}
2579 		} else if ( search->dir ) { // scan for files in the filesystem
2580 			char    *netpath;
2581 			int numSysFiles;
2582 			char    **sysFiles;
2583 			char    *name;
2584 
2585 			// don't scan directories for files if we are pure or restricted
2586 			if ( fs_numServerPaks && !allowNonPureFilesOnDisk ) {
2587 				continue;
2588 			} else {
2589 				netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path );
2590 				sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse );
2591 				for ( i = 0 ; i < numSysFiles ; i++ ) {
2592 					// unique the match
2593 					name = sysFiles[i];
2594 					nfiles = FS_AddFileToList( name, list, nfiles );
2595 				}
2596 				Sys_FreeFileList( sysFiles );
2597 			}
2598 		}
2599 	}
2600 
2601 	// return a copy of the list
2602 	*numfiles = nfiles;
2603 
2604 	if ( !nfiles ) {
2605 		return NULL;
2606 	}
2607 
2608 	listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) );
2609 	for ( i = 0 ; i < nfiles ; i++ ) {
2610 		listCopy[i] = list[i];
2611 	}
2612 	listCopy[i] = NULL;
2613 
2614 	return listCopy;
2615 }
2616 
2617 /*
2618 =================
2619 FS_ListFiles
2620 =================
2621 */
FS_ListFiles(const char * path,const char * extension,int * numfiles)2622 char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) {
2623 	return FS_ListFilteredFiles( path, extension, NULL, numfiles, qfalse );
2624 }
2625 
2626 /*
2627 =================
2628 FS_FreeFileList
2629 =================
2630 */
FS_FreeFileList(char ** list)2631 void FS_FreeFileList( char **list ) {
2632 	int i;
2633 
2634 	if ( !fs_searchpaths ) {
2635 		Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
2636 	}
2637 
2638 	if ( !list ) {
2639 		return;
2640 	}
2641 
2642 	for ( i = 0 ; list[i] ; i++ ) {
2643 		Z_Free( list[i] );
2644 	}
2645 
2646 	Z_Free( list );
2647 }
2648 
2649 
2650 /*
2651 ================
2652 FS_GetFileList
2653 ================
2654 */
FS_GetFileList(const char * path,const char * extension,char * listbuf,int bufsize)2655 int FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize ) {
2656 	int nFiles, i, nTotal, nLen;
2657 	char **pFiles = NULL;
2658 
2659 	*listbuf = 0;
2660 	nFiles = 0;
2661 	nTotal = 0;
2662 
2663 	if ( Q_stricmp( path, "$modlist" ) == 0 ) {
2664 		return FS_GetModList( listbuf, bufsize );
2665 	}
2666 
2667 	pFiles = FS_ListFiles( path, extension, &nFiles );
2668 
2669 	for ( i = 0; i < nFiles; i++ ) {
2670 		nLen = strlen( pFiles[i] ) + 1;
2671 		if ( nTotal + nLen + 1 < bufsize ) {
2672 			strcpy( listbuf, pFiles[i] );
2673 			listbuf += nLen;
2674 			nTotal += nLen;
2675 		} else {
2676 			nFiles = i;
2677 			break;
2678 		}
2679 	}
2680 
2681 	FS_FreeFileList( pFiles );
2682 
2683 	return nFiles;
2684 }
2685 
2686 /*
2687 =======================
2688 Sys_ConcatenateFileLists
2689 
2690 mkv: Naive implementation. Concatenates three lists into a
2691      new list, and frees the old lists from the heap.
2692 
2693 FIXME TTimo those two should move to common.c next to Sys_ListFiles
2694 =======================
2695  */
Sys_CountFileList(char ** list)2696 static unsigned int Sys_CountFileList(char **list)
2697 {
2698 	int i = 0;
2699 
2700 	if (list)
2701 	{
2702 		while (*list)
2703 		{
2704 			list++;
2705 			i++;
2706 		}
2707 	}
2708 	return i;
2709 }
2710 
Sys_ConcatenateFileLists(char ** list0,char ** list1)2711 static char** Sys_ConcatenateFileLists( char **list0, char **list1 )
2712 {
2713 	int totalLength = 0;
2714 	char** cat = NULL, **dst, **src;
2715 
2716 	totalLength += Sys_CountFileList(list0);
2717 	totalLength += Sys_CountFileList(list1);
2718 
2719 	/* Create new list. */
2720 	dst = cat = Z_Malloc( ( totalLength + 1 ) * sizeof( char* ) );
2721 
2722 	/* Copy over lists. */
2723 	if (list0)
2724 	{
2725 		for (src = list0; *src; src++, dst++)
2726 			*dst = *src;
2727 	}
2728 	if (list1)
2729 	{
2730 		for (src = list1; *src; src++, dst++)
2731 			*dst = *src;
2732 	}
2733 
2734 	// Terminate the list
2735 	*dst = NULL;
2736 
2737 	// Free our old lists.
2738 	// NOTE: not freeing their content, it's been merged in dst and still being used
2739 	if (list0) Z_Free( list0 );
2740 	if (list1) Z_Free( list1 );
2741 
2742 	return cat;
2743 }
2744 
2745 /*
2746 ================
2747 FS_GetModDescription
2748 ================
2749 */
FS_GetModDescription(const char * modDir,char * description,int descriptionLen)2750 void FS_GetModDescription( const char *modDir, char *description, int descriptionLen ) {
2751 	fileHandle_t	descHandle;
2752 	char			descPath[MAX_QPATH];
2753 	int				nDescLen;
2754 	FILE			*file;
2755 
2756 	Com_sprintf( descPath, sizeof ( descPath ), "%s%cdescription.txt", modDir, PATH_SEP );
2757 	nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle );
2758 
2759 	if ( nDescLen > 0 ) {
2760 		file = FS_FileForHandle(descHandle);
2761 		Com_Memset( description, 0, descriptionLen );
2762 		nDescLen = fread(description, 1, descriptionLen, file);
2763 		if (nDescLen >= 0) {
2764 			description[nDescLen] = '\0';
2765 		}
2766 	} else {
2767 		Q_strncpyz( description, modDir, descriptionLen );
2768 	}
2769 
2770 	if ( descHandle ) {
2771 		FS_FCloseFile( descHandle );
2772 	}
2773 }
2774 
2775 /*
2776 ================
2777 FS_GetModList
2778 
2779 Returns a list of mod directory names
2780 A mod directory is a peer to baseq3 with a pk3 in it
2781 The directories are searched in base path, cd path and home path
2782 ================
2783 */
FS_GetModList(char * listbuf,int bufsize)2784 int	FS_GetModList( char *listbuf, int bufsize ) {
2785 	int		nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen;
2786 	char **pFiles = NULL;
2787 	char **pPaks = NULL;
2788 	char *name, *path;
2789 	char description[MAX_OSPATH];
2790 
2791 	int dummy;
2792 	char **pFiles0 = NULL;
2793 	char **pFiles1 = NULL;
2794 #ifndef STANDALONE
2795 	char **pFiles2 = NULL;
2796 	char **pFiles3 = NULL;
2797 	char **pFiles4 = NULL;
2798 	char **pFiles5 = NULL;
2799 #endif
2800 
2801 	qboolean bDrop = qfalse;
2802 
2803 	*listbuf = 0;
2804 	nMods = nTotal = 0;
2805 
2806 	pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue );
2807 	pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue );
2808 #ifndef STANDALONE
2809 	pFiles2 = Sys_ListFiles( fs_steampath->string, NULL, NULL, &dummy, qtrue );
2810 	pFiles3 = Sys_ListFiles( fs_gogpath->string, NULL, NULL, &dummy, qtrue );
2811 #endif
2812 	// we searched for mods in up to four paths
2813 	// it is likely that we have duplicate names now, which we will cleanup below
2814 #ifndef STANDALONE
2815 	pFiles4 = Sys_ConcatenateFileLists( pFiles0, pFiles1 );
2816 	pFiles5 = Sys_ConcatenateFileLists( pFiles2, pFiles3 );
2817 	pFiles = Sys_ConcatenateFileLists( pFiles4, pFiles5 );
2818 #else
2819 	pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1 );
2820 #endif
2821 
2822 	nPotential = Sys_CountFileList(pFiles);
2823 
2824 	for ( i = 0 ; i < nPotential ; i++ ) {
2825 		name = pFiles[i];
2826 		// NOTE: cleaner would involve more changes
2827 		// ignore duplicate mod directories
2828 		if (i!=0) {
2829 			bDrop = qfalse;
2830 			for(j=0; j<i; j++)
2831 			{
2832 				if (Q_stricmp(pFiles[j],name)==0) {
2833 					// this one can be dropped
2834 					bDrop = qtrue;
2835 					break;
2836 				}
2837 			}
2838 		}
2839 		if (bDrop) {
2840 			continue;
2841 		}
2842 		// we drop "baseq3" "." and ".."
2843 		if (Q_stricmp(name, com_basegame->string) && Q_stricmpn(name, ".", 1)) {
2844 			// now we need to find some .pk3 files to validate the mod
2845 			// NOTE TTimo: (actually I'm not sure why .. what if it's a mod under developement with no .pk3?)
2846 			// we didn't keep the information when we merged the directory names, as to what OS Path it was found under
2847 			//   so it could be in base path, cd path or home path
2848 			//   we will try each three of them here (yes, it's a bit messy)
2849 			path = FS_BuildOSPath( fs_basepath->string, name, "" );
2850 			nPaks = 0;
2851 			pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse);
2852 			Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present
2853 
2854 			/* try on home path */
2855 			if ( nPaks <= 0 )
2856 			{
2857 				path = FS_BuildOSPath( fs_homepath->string, name, "" );
2858 				nPaks = 0;
2859 				pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
2860 				Sys_FreeFileList( pPaks );
2861 			}
2862 
2863 #ifndef STANDALONE
2864 			/* try on steam path */
2865 			if ( nPaks <= 0 )
2866 			{
2867 				path = FS_BuildOSPath( fs_steampath->string, name, "" );
2868 				nPaks = 0;
2869 				pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
2870 				Sys_FreeFileList( pPaks );
2871 			}
2872 
2873 			/* try on gog path */
2874 			if ( nPaks <= 0 )
2875 			{
2876 				path = FS_BuildOSPath( fs_gogpath->string, name, "" );
2877 				nPaks = 0;
2878 				pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse );
2879 				Sys_FreeFileList( pPaks );
2880 			}
2881 #endif
2882 
2883 			if (nPaks > 0) {
2884 				nLen = strlen(name) + 1;
2885 				// nLen is the length of the mod path
2886 				// we need to see if there is a description available
2887 				FS_GetModDescription( name, description, sizeof( description ) );
2888 				nDescLen = strlen(description) + 1;
2889 
2890 				if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) {
2891 					strcpy(listbuf, name);
2892 					listbuf += nLen;
2893 					strcpy(listbuf, description);
2894 					listbuf += nDescLen;
2895 					nTotal += nLen + nDescLen;
2896 					nMods++;
2897 				}
2898 				else {
2899 					break;
2900 				}
2901 			}
2902 		}
2903 	}
2904 	Sys_FreeFileList( pFiles );
2905 
2906 	return nMods;
2907 }
2908 
2909 
2910 
2911 
2912 //============================================================================
2913 
2914 /*
2915 ================
2916 FS_Dir_f
2917 ================
2918 */
FS_Dir_f(void)2919 void FS_Dir_f( void ) {
2920 	char    *path;
2921 	char    *extension;
2922 	char    **dirnames;
2923 	int ndirs;
2924 	int i;
2925 
2926 	if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) {
2927 		Com_Printf( "usage: dir <directory> [extension]\n" );
2928 		return;
2929 	}
2930 
2931 	if ( Cmd_Argc() == 2 ) {
2932 		path = Cmd_Argv( 1 );
2933 		extension = "";
2934 	} else {
2935 		path = Cmd_Argv( 1 );
2936 		extension = Cmd_Argv( 2 );
2937 	}
2938 
2939 	Com_Printf( "Directory of %s %s\n", path, extension );
2940 	Com_Printf( "---------------\n" );
2941 
2942 	dirnames = FS_ListFiles( path, extension, &ndirs );
2943 
2944 	for ( i = 0; i < ndirs; i++ ) {
2945 		Com_Printf( "%s\n", dirnames[i] );
2946 	}
2947 	FS_FreeFileList( dirnames );
2948 }
2949 
2950 /*
2951 ===========
2952 FS_ConvertPath
2953 ===========
2954 */
FS_ConvertPath(char * s)2955 void FS_ConvertPath( char *s ) {
2956 	while ( *s ) {
2957 		if ( *s == '\\' || *s == ':' ) {
2958 			*s = '/';
2959 		}
2960 		s++;
2961 	}
2962 }
2963 
2964 /*
2965 ===========
2966 FS_PathCmp
2967 
2968 Ignore case and seprator char distinctions
2969 ===========
2970 */
FS_PathCmp(const char * s1,const char * s2)2971 int FS_PathCmp( const char *s1, const char *s2 ) {
2972 	int c1, c2;
2973 
2974 	do {
2975 		c1 = *s1++;
2976 		c2 = *s2++;
2977 
2978 		if ( c1 >= 'a' && c1 <= 'z' ) {
2979 			c1 -= ( 'a' - 'A' );
2980 		}
2981 		if ( c2 >= 'a' && c2 <= 'z' ) {
2982 			c2 -= ( 'a' - 'A' );
2983 		}
2984 
2985 		if ( c1 == '\\' || c1 == ':' ) {
2986 			c1 = '/';
2987 		}
2988 		if ( c2 == '\\' || c2 == ':' ) {
2989 			c2 = '/';
2990 		}
2991 
2992 		if ( c1 < c2 ) {
2993 			return -1;      // strings not equal
2994 		}
2995 		if ( c1 > c2 ) {
2996 			return 1;
2997 		}
2998 	} while ( c1 );
2999 
3000 	return 0;       // strings are equal
3001 }
3002 
3003 /*
3004 ================
3005 FS_SortFileList
3006 ================
3007 */
FS_SortFileList(char ** filelist,int numfiles)3008 void FS_SortFileList( char **filelist, int numfiles ) {
3009 	int i, j, k, numsortedfiles;
3010 	char **sortedlist;
3011 
3012 	sortedlist = Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ) );
3013 	sortedlist[0] = NULL;
3014 	numsortedfiles = 0;
3015 	for ( i = 0; i < numfiles; i++ ) {
3016 		for ( j = 0; j < numsortedfiles; j++ ) {
3017 			if ( FS_PathCmp( filelist[i], sortedlist[j] ) < 0 ) {
3018 				break;
3019 			}
3020 		}
3021 		for ( k = numsortedfiles; k > j; k-- ) {
3022 			sortedlist[k] = sortedlist[k - 1];
3023 		}
3024 		sortedlist[j] = filelist[i];
3025 		numsortedfiles++;
3026 	}
3027 	Com_Memcpy( filelist, sortedlist, numfiles * sizeof( *filelist ) );
3028 	Z_Free( sortedlist );
3029 }
3030 
3031 /*
3032 ================
3033 FS_NewDir_f
3034 ================
3035 */
FS_NewDir_f(void)3036 void FS_NewDir_f( void ) {
3037 	char    *filter;
3038 	char    **dirnames;
3039 	int ndirs;
3040 	int i;
3041 
3042 	if ( Cmd_Argc() < 2 ) {
3043 		Com_Printf( "usage: fdir <filter>\n" );
3044 		Com_Printf( "example: fdir *q3dm*.bsp\n" );
3045 		return;
3046 	}
3047 
3048 	filter = Cmd_Argv( 1 );
3049 
3050 	Com_Printf( "---------------\n" );
3051 
3052 	dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs, qfalse );
3053 
3054 	FS_SortFileList( dirnames, ndirs );
3055 
3056 	for ( i = 0; i < ndirs; i++ ) {
3057 		FS_ConvertPath( dirnames[i] );
3058 		Com_Printf( "%s\n", dirnames[i] );
3059 	}
3060 	Com_Printf( "%d files listed\n", ndirs );
3061 	FS_FreeFileList( dirnames );
3062 }
3063 
3064 /*
3065 ============
3066 FS_Path_f
3067 
3068 ============
3069 */
FS_Path_f(void)3070 void FS_Path_f( void ) {
3071 	searchpath_t    *s;
3072 	int i;
3073 
3074 	Com_Printf( "Current search path:\n" );
3075 	for ( s = fs_searchpaths; s; s = s->next ) {
3076 		if ( s->pack ) {
3077 			Com_Printf( "%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles );
3078 			if ( fs_numServerPaks ) {
3079 				if ( !FS_PakIsPure( s->pack ) ) {
3080 					Com_Printf( "    not on the pure list\n" );
3081 				} else {
3082 					Com_Printf( "    on the pure list\n" );
3083 				}
3084 			}
3085 		} else {
3086 			Com_Printf ("%s%c%s\n", s->dir->path, PATH_SEP, s->dir->gamedir );
3087 		}
3088 	}
3089 
3090 	Com_Printf( "\n" );
3091 	for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) {
3092 		if ( fsh[i].handleFiles.file.o ) {
3093 			Com_Printf( "handle %i: %s\n", i, fsh[i].name );
3094 		}
3095 	}
3096 }
3097 
3098 /*
3099 ============
3100 FS_TouchFile_f
3101 ============
3102 */
FS_TouchFile_f(void)3103 void FS_TouchFile_f( void ) {
3104 	fileHandle_t f;
3105 
3106 	if ( Cmd_Argc() != 2 ) {
3107 		Com_Printf( "Usage: touchFile <file>\n" );
3108 		return;
3109 	}
3110 
3111 	FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse );
3112 	if ( f ) {
3113 		FS_FCloseFile( f );
3114 	}
3115 }
3116 
3117 /*
3118 ============
3119 FS_Which
3120 ============
3121 */
3122 
FS_Which(const char * filename,void * searchPath)3123 qboolean FS_Which(const char *filename, void *searchPath)
3124 {
3125 	searchpath_t *search = searchPath;
3126 
3127 	if(FS_FOpenFileReadDir(filename, search, NULL, qfalse, qfalse) > 0)
3128 	{
3129 		if(search->pack)
3130 		{
3131 			Com_Printf("File \"%s\" found in \"%s\"\n", filename, search->pack->pakFilename);
3132 			return qtrue;
3133 		}
3134 		else if(search->dir)
3135 		{
3136 			Com_Printf( "File \"%s\" found at \"%s\"\n", filename, search->dir->fullpath);
3137 			return qtrue;
3138 		}
3139 	}
3140 
3141 	return qfalse;
3142 }
3143 
3144 /*
3145 ============
3146 FS_Which_f
3147 ============
3148 */
FS_Which_f(void)3149 void FS_Which_f( void ) {
3150 	searchpath_t	*search;
3151 	char		*filename;
3152 
3153 	filename = Cmd_Argv(1);
3154 
3155 	if ( !filename[0] ) {
3156 		Com_Printf( "Usage: which <file>\n" );
3157 		return;
3158 	}
3159 
3160 	// qpaths are not supposed to have a leading slash
3161 	if ( filename[0] == '/' || filename[0] == '\\' ) {
3162 		filename++;
3163 	}
3164 
3165 	// just wants to see if file is there
3166 	for(search = fs_searchpaths; search; search = search->next)
3167 	{
3168 		if(FS_Which(filename, search))
3169 			return;
3170 	}
3171 
3172 	Com_Printf("File not found: \"%s\"\n", filename);
3173 }
3174 
3175 //===========================================================================
3176 
3177 
paksort(const void * a,const void * b)3178 static int QDECL paksort( const void *a, const void *b ) {
3179 	char    *aa, *bb;
3180 
3181 	aa = *(char **)a;
3182 	bb = *(char **)b;
3183 
3184 	return FS_PathCmp( aa, bb );
3185 }
3186 
3187 /*
3188 ================
3189 FS_AddGameDirectory
3190 
3191 Sets fs_gamedir, adds the directory to the head of the path,
3192 then loads the zip headers
3193 ================
3194 */
3195 #define MAX_PAKFILES    1024
FS_AddGameDirectory(const char * path,const char * dir,qboolean allowUnzippedDLLs)3196 void FS_AddGameDirectory( const char *path, const char *dir, qboolean allowUnzippedDLLs ) {
3197 	searchpath_t    *sp;
3198 	int i;
3199 	searchpath_t    *search;
3200 	pack_t          *pak;
3201 	char            curpath[MAX_OSPATH + 1], *pakfile;
3202 	int numfiles;
3203 	char            **pakfiles;
3204 	char            *sorted[MAX_PAKFILES];
3205 
3206 	// find all pak files in this directory
3207 	Q_strncpyz(curpath, FS_BuildOSPath(path, dir, ""), sizeof(curpath));
3208 	curpath[strlen(curpath) - 1] = '\0';	// strip the trailing slash
3209 
3210 	// this fixes the case where fs_basepath is the same as fs_cdpath
3211 	// which happens on full installs
3212 	for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
3213 		if ( sp->dir && !Q_stricmp( sp->dir->path, path ) && !Q_stricmp( sp->dir->gamedir, dir ) ) {
3214 			return;         // we've already got this one
3215 		}
3216 	}
3217 
3218 	Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) );
3219 
3220 	// find all pak files in this directory
3221 	pakfile = FS_BuildOSPath( path, dir, "" );
3222 	pakfile[ strlen( pakfile ) - 1 ] = 0; // strip the trailing slash
3223 
3224 	pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse );
3225 
3226 	// sort them so that later alphabetic matches override
3227 	// earlier ones.  This makes pak1.pk3 override pak0.pk3
3228 	if ( numfiles > MAX_PAKFILES ) {
3229 		numfiles = MAX_PAKFILES;
3230 	}
3231 	for ( i = 0 ; i < numfiles ; i++ ) {
3232 		sorted[i] = pakfiles[i];
3233 // JPW NERVE KLUDGE: sorry, temp mod mp_* to _p_* so "mp_pak*" gets alphabetically sorted before "pak*"
3234 
3235 		if ( !Q_strncmp( sorted[i],"mp_",3 ) ) {
3236 			memcpy( sorted[i],"zz",2 );
3237 		}
3238 
3239 // jpw
3240 	}
3241 
3242 	qsort( sorted, numfiles, sizeof(char*), paksort );
3243 
3244 	for ( i = 0 ; i < numfiles ; i++ ) {
3245 		if ( Q_strncmp( sorted[i],"sp_",3 ) ) { // JPW NERVE -- exclude sp_*
3246 // JPW NERVE KLUDGE: fix filenames broken in mp/sp/pak sort above
3247 
3248 			if ( !Q_strncmp( sorted[i],"zz_",3 ) ) {
3249 				memcpy( sorted[i],"mp",2 );
3250 			}
3251 
3252 // jpw
3253 			pakfile = FS_BuildOSPath( path, dir, sorted[i] );
3254 			if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 ) {
3255 				continue;
3256 			}
3257 			Q_strncpyz(pak->pakPathname, curpath, sizeof(pak->pakPathname));
3258 			// store the game name for downloading
3259 			strcpy( pak->pakGamename, dir );
3260 
3261 			search = Z_Malloc( sizeof( searchpath_t ) );
3262 			search->pack = pak;
3263 			search->next = fs_searchpaths;
3264 			fs_searchpaths = search;
3265 		}
3266 	}
3267 
3268 	// done
3269 	Sys_FreeFileList( pakfiles );
3270 
3271 	//
3272 	// add the directory to the search path
3273 	//
3274 	search = Z_Malloc (sizeof(searchpath_t));
3275 	search->dir = Z_Malloc( sizeof( *search->dir ) );
3276 
3277 	Q_strncpyz(search->dir->path, path, sizeof(search->dir->path));
3278 	Q_strncpyz(search->dir->fullpath, curpath, sizeof(search->dir->fullpath));
3279 	Q_strncpyz(search->dir->gamedir, dir, sizeof(search->dir->gamedir));
3280 	search->dir->allowUnzippedDLLs = allowUnzippedDLLs;
3281 
3282 	search->next = fs_searchpaths;
3283 	fs_searchpaths = search;
3284 }
3285 
3286 /*
3287 ================
3288 FS_idPak
3289 ================
3290 */
FS_idPak(char * pak,char * base,int numPaks)3291 qboolean FS_idPak(char *pak, char *base, int numPaks) {
3292 	int i;
3293 
3294 	if ( !FS_FilenameCompare( pak, va( "%s/mp_bin", base ) ) ) {
3295 		return qtrue;
3296 	}
3297 
3298 	for ( i = 0; i < NUM_ID_PAKS; i++ ) {
3299 		if ( !FS_FilenameCompare( pak, va( "%s/mp_bin%d", base, i ) ) ) {
3300 			break;
3301 		}
3302 		if ( !FS_FilenameCompare( pak, va( "%s/pak%d", base, i ) ) ) {
3303 			break;
3304 		}
3305 // JPW NERVE -- this fn prevents external sources from downloading/overwriting official files, so exclude both SP and MP files from this list as well
3306 		if ( !FS_FilenameCompare( pak, va( "%s/mp_pak%d",base,i ) ) ) {
3307 			break;
3308 		}
3309 		if ( !FS_FilenameCompare( pak, va( "%s/sp_pak%d",base,i + 1) ) ) {
3310 			break;
3311 		}
3312 // jpw
3313 	}
3314 	if ( i < numPaks ) {
3315 		return qtrue;
3316 	}
3317 	return qfalse;
3318 }
3319 
3320 /*
3321 ================
3322 FS_CheckDirTraversal
3323 
3324 Check whether the string contains stuff like "../" to prevent directory traversal bugs
3325 and return qtrue if it does.
3326 ================
3327 */
3328 
FS_CheckDirTraversal(const char * checkdir)3329 qboolean FS_CheckDirTraversal(const char *checkdir)
3330 {
3331 	if(strstr(checkdir, "../") || strstr(checkdir, "..\\"))
3332 		return qtrue;
3333 
3334 	return qfalse;
3335 }
3336 
3337 /*
3338 ================
3339 FS_InvalidGameDir
3340 
3341 return true if path is a reference to current directory or directory traversal
3342 or a sub-directory
3343 ================
3344 */
FS_InvalidGameDir(const char * gamedir)3345 qboolean FS_InvalidGameDir( const char *gamedir ) {
3346 	if ( !strcmp( gamedir, "." ) || !strcmp( gamedir, ".." )
3347 		|| strchr( gamedir, '/' ) || strchr( gamedir, '\\' ) ) {
3348 		return qtrue;
3349 	}
3350 
3351 	return qfalse;
3352 }
3353 
3354 /*
3355 ================
3356 FS_ComparePaks
3357 
3358 ----------------
3359 dlstring == qtrue
3360 
3361 Returns a list of pak files that we should download from the server. They all get stored
3362 in the current gamedir and an FS_Restart will be fired up after we download them all.
3363 
3364 The string is the format:
3365 
3366 @remotename@localname [repeat]
3367 
3368 static int		fs_numServerReferencedPaks;
3369 static int		fs_serverReferencedPaks[MAX_SEARCH_PATHS];
3370 static char		*fs_serverReferencedPakNames[MAX_SEARCH_PATHS];
3371 
3372 ----------------
3373 dlstring == qfalse
3374 
3375 we are not interested in a download string format, we want something human-readable
3376 (this is used for diagnostics while connecting to a pure server)
3377 
3378 ================
3379 */
FS_ComparePaks(char * neededpaks,int len,qboolean dlstring)3380 qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) {
3381 	searchpath_t    *sp;
3382 	qboolean havepak;
3383 	char *origpos = neededpaks;
3384 	int i;
3385 
3386 	if ( !fs_numServerReferencedPaks )
3387 		return qfalse; // Server didn't send any pack information along
3388 
3389 	*neededpaks = 0;
3390 
3391 	for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ )
3392 	{
3393 		// Ok, see if we have this pak file
3394 		havepak = qfalse;
3395 
3396 		// never autodownload any of the id paks
3397 		if(FS_idPak(fs_serverReferencedPakNames[i], BASEGAME, NUM_ID_PAKS))
3398 		{
3399 			continue;
3400 		}
3401 
3402 		// Make sure the server cannot make us write to non-quake3 directories.
3403 		if(FS_CheckDirTraversal(fs_serverReferencedPakNames[i]))
3404 		{
3405 			Com_Printf("WARNING: Invalid download name %s\n", fs_serverReferencedPakNames[i]);
3406 			continue;
3407 		}
3408 
3409 		for ( sp = fs_searchpaths ; sp ; sp = sp->next ) {
3410 			if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) {
3411 				havepak = qtrue; // This is it!
3412 				break;
3413 			}
3414 		}
3415 
3416 		if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) {
3417 			// Don't got it
3418 
3419 			if ( dlstring ) {
3420 				// We need this to make sure we won't hit the end of the buffer or the server could
3421 				// overwrite non-pk3 files on clients by writing so much crap into neededpaks that
3422 				// Q_strcat cuts off the .pk3 extension.
3423 
3424 				origpos += strlen(origpos);
3425 
3426 				// Remote name
3427 				Q_strcat( neededpaks, len, "@" );
3428 				Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
3429 				Q_strcat( neededpaks, len, ".pk3" );
3430 
3431 				// Local name
3432 				Q_strcat( neededpaks, len, "@" );
3433 				// Do we have one with the same name?
3434 				if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) {
3435 					char st[MAX_ZPATH];
3436 					// We already have one called this, we need to download it to another name
3437 					// Make something up with the checksum in it
3438 					Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] );
3439 					Q_strcat( neededpaks, len, st );
3440 				} else
3441 				{
3442 					Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
3443 					Q_strcat( neededpaks, len, ".pk3" );
3444 				}
3445 
3446 				// Find out whether it might have overflowed the buffer and don't add this file to the
3447 				// list if that is the case.
3448 				if(strlen(origpos) + (origpos - neededpaks) >= len - 1)
3449 				{
3450 					*origpos = '\0';
3451 					break;
3452 				}
3453 			} else
3454 			{
3455 				Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] );
3456 				Q_strcat( neededpaks, len, ".pk3" );
3457 				// Do we have one with the same name?
3458 				if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) {
3459 					Q_strcat( neededpaks, len, " (local file exists with wrong checksum)" );
3460 				}
3461 				Q_strcat( neededpaks, len, "\n" );
3462 			}
3463 		}
3464 	}
3465 
3466 	if ( *neededpaks ) {
3467 		Com_Printf( "Need paks: %s\n", neededpaks );
3468 		return qtrue;
3469 	}
3470 
3471 	return qfalse; // We have them all
3472 }
3473 
3474 /*
3475 ================
3476 FS_Shutdown
3477 
3478 Frees all resources.
3479 ================
3480 */
FS_Shutdown(qboolean closemfp)3481 void FS_Shutdown( qboolean closemfp ) {
3482 	searchpath_t    *p, *next;
3483 	int i;
3484 
3485 	for ( i = 0; i < MAX_FILE_HANDLES; i++ ) {
3486 		if ( fsh[i].fileSize ) {
3487 			FS_FCloseFile( i );
3488 		}
3489 	}
3490 
3491 	// free everything
3492 	for ( p = fs_searchpaths ; p ; p = next ) {
3493 		next = p->next;
3494 
3495 		if(p->pack)
3496 			FS_FreePak(p->pack);
3497 		if (p->dir)
3498 			Z_Free(p->dir);
3499 
3500 		Z_Free(p);
3501 	}
3502 
3503 	// any FS_ calls will now be an error until reinitialized
3504 	fs_searchpaths = NULL;
3505 
3506 	Cmd_RemoveCommand( "path" );
3507 	Cmd_RemoveCommand( "dir" );
3508 	Cmd_RemoveCommand( "fdir" );
3509 	Cmd_RemoveCommand( "touchFile" );
3510 	Cmd_RemoveCommand( "which" );
3511 
3512 #ifdef FS_MISSING
3513 	if ( closemfp ) {
3514 		fclose( missingFiles );
3515 	}
3516 #endif
3517 }
3518 
3519 /*
3520 ================
3521 FS_ReorderPurePaks
3522 NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*)
3523   this can lead to misleading situations, see show_bug.cgi?id=540
3524 ================
3525 */
FS_ReorderPurePaks(void)3526 static void FS_ReorderPurePaks( void ) {
3527 	searchpath_t *s;
3528 	int i;
3529 	searchpath_t **p_insert_index, // for linked list reordering
3530 	**p_previous;     // when doing the scan
3531 
3532 	fs_reordered = qfalse;
3533 
3534 	// only relevant when connected to pure server
3535 	if ( !fs_numServerPaks ) {
3536 		return;
3537 	}
3538 
3539 	p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list
3540 	for ( i = 0 ; i < fs_numServerPaks ; i++ ) {
3541 		p_previous = p_insert_index; // track the pointer-to-current-item
3542 		for ( s = *p_insert_index; s; s = s->next ) { // the part of the list before p_insert_index has been sorted already
3543 			if ( s->pack && fs_serverPaks[i] == s->pack->checksum ) {
3544 				fs_reordered = qtrue;
3545 				// move this element to the insert list
3546 				*p_previous = s->next;
3547 				s->next = *p_insert_index;
3548 				*p_insert_index = s;
3549 				// increment insert list
3550 				p_insert_index = &s->next;
3551 				break; // iterate to next server pack
3552 			}
3553 			p_previous = &s->next;
3554 		}
3555 	}
3556 
3557 }
3558 
3559 /*
3560 ================
3561 FS_Startup
3562 ================
3563 */
FS_Startup(const char * gameName)3564 static void FS_Startup( const char *gameName ) {
3565 	const char *homePath;
3566 
3567 	Com_Printf( "----- FS_Startup -----\n" );
3568 
3569 	fs_packFiles = 0;
3570 
3571 	fs_debug = Cvar_Get( "fs_debug", "0", 0 );
3572 	fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT|CVAR_PROTECTED );
3573 	fs_basegame = Cvar_Get( "fs_basegame", "", CVAR_INIT );
3574 	homePath = Sys_DefaultHomePath();
3575 	if (!homePath || !homePath[0]) {
3576 		homePath = fs_basepath->string;
3577 	}
3578 	fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT|CVAR_PROTECTED );
3579 	fs_gamedirvar = Cvar_Get( "fs_game", "", CVAR_INIT | CVAR_SYSTEMINFO );
3580 
3581 	if (!gameName[0]) {
3582 		Cvar_ForceReset( "com_basegame" );
3583 	}
3584 
3585 	if (!FS_FilenameCompare(fs_gamedirvar->string, gameName)) {
3586 		// This is the standard base game. Servers and clients should
3587 		// use "" and not the standard basegame name because this messes
3588 		// up pak file negotiation and lots of other stuff
3589 		Cvar_ForceReset( "fs_game" );
3590 	}
3591 
3592 	if (FS_InvalidGameDir(gameName)) {
3593 		Com_Error( ERR_DROP, "Invalid com_basegame '%s'", gameName );
3594 	}
3595 	if (FS_InvalidGameDir(fs_basegame->string)) {
3596 		Com_Error( ERR_DROP, "Invalid fs_basegame '%s'", fs_basegame->string );
3597 	}
3598 	if (FS_InvalidGameDir(fs_gamedirvar->string)) {
3599 		Com_Error( ERR_DROP, "Invalid fs_game '%s'", fs_gamedirvar->string );
3600 	}
3601 
3602 	// add search path elements in reverse priority order
3603 #ifndef STANDALONE
3604 	fs_gogpath = Cvar_Get ("fs_gogpath", Sys_GogPath(), CVAR_INIT|CVAR_PROTECTED );
3605 	if (fs_gogpath->string[0]) {
3606 		FS_AddGameDirectory( fs_gogpath->string, gameName, qtrue );
3607 	}
3608 
3609 	fs_steampath = Cvar_Get ("fs_steampath", Sys_SteamPath(), CVAR_INIT|CVAR_PROTECTED );
3610 	if (fs_steampath->string[0]) {
3611 		FS_AddGameDirectory( fs_steampath->string, gameName, qtrue );
3612 	}
3613 #endif
3614 
3615 	if ( fs_basepath->string[0] ) {
3616 		FS_AddGameDirectory( fs_basepath->string, gameName, qtrue );
3617 	}
3618 
3619 #ifdef __APPLE__
3620 	fs_apppath = Cvar_Get ("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT|CVAR_PROTECTED );
3621 	// Make MacOSX also include the base path included with the .app bundle
3622 	if (fs_apppath->string[0])
3623 		FS_AddGameDirectory(fs_apppath->string, gameName, qtrue);
3624 #endif
3625 
3626 	// NOTE: same filtering below for mods and basegame
3627 	if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) {
3628 		FS_CreatePath ( fs_homepath->string );
3629 		FS_AddGameDirectory( fs_homepath->string, gameName, qtrue );
3630 	}
3631 
3632 	// check for additional base game so mods can be based upon other mods
3633 	if ( fs_basegame->string[0] && Q_stricmp( fs_basegame->string, gameName ) ) {
3634 #ifndef STANDALONE
3635 		if (fs_gogpath->string[0]) {
3636 			FS_AddGameDirectory( fs_gogpath->string, fs_basegame->string, qtrue );
3637 		}
3638 
3639 		if ( fs_steampath->string[0] ) {
3640 			FS_AddGameDirectory( fs_steampath->string, fs_basegame->string, qtrue );
3641 		}
3642 #endif
3643 
3644 		if ( fs_basepath->string[0] ) {
3645 			FS_AddGameDirectory( fs_basepath->string, fs_basegame->string, qtrue );
3646 		}
3647 
3648 		if ( fs_homepath->string[0] && Q_stricmp( fs_homepath->string,fs_basepath->string ) ) {
3649 			FS_AddGameDirectory( fs_homepath->string, fs_basegame->string, qtrue );
3650 		}
3651 	}
3652 
3653 	// check for additional game folder for mods
3654 	if ( fs_gamedirvar->string[0] && Q_stricmp( fs_gamedirvar->string, gameName ) ) {
3655 #ifndef STANDALONE
3656 		if (fs_gogpath->string[0]) {
3657 			FS_AddGameDirectory( fs_gogpath->string, fs_gamedirvar->string, qtrue );
3658 		}
3659 
3660 		if ( fs_steampath->string[0] ) {
3661 			FS_AddGameDirectory( fs_steampath->string, fs_gamedirvar->string, qtrue );
3662 		}
3663 #endif
3664 
3665 		if ( fs_basepath->string[0] ) {
3666 			FS_AddGameDirectory( fs_basepath->string, fs_gamedirvar->string, qtrue );
3667 		}
3668 
3669 		if ( fs_homepath->string[0] && Q_stricmp( fs_homepath->string,fs_basepath->string ) ) {
3670 			FS_AddGameDirectory( fs_homepath->string, fs_gamedirvar->string, qtrue );
3671 		}
3672 	}
3673 
3674 #ifndef STANDALONE
3675 	if (!com_standalone->integer) {
3676 		Com_ReadCDKey(BASEGAME);
3677 		if (fs_gamedirvar->string[0]) {
3678 			Com_AppendCDKey(fs_gamedirvar->string);
3679 		}
3680 	}
3681 #endif
3682 
3683 	// add our commands
3684 	Cmd_AddCommand( "path", FS_Path_f );
3685 	Cmd_AddCommand( "dir", FS_Dir_f );
3686 	Cmd_AddCommand( "fdir", FS_NewDir_f );
3687 	Cmd_AddCommand( "touchFile", FS_TouchFile_f );
3688 	Cmd_AddCommand ("which", FS_Which_f );
3689 
3690 	// show_bug.cgi?id=506
3691 	// reorder the pure pk3 files according to server order
3692 	FS_ReorderPurePaks();
3693 
3694 	// print the current search paths
3695 	FS_Path_f();
3696 
3697 	fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
3698 
3699 	Com_Printf( "----------------------\n" );
3700 
3701 #ifdef FS_MISSING
3702 	if ( missingFiles == NULL ) {
3703 		missingFiles = Sys_FOpen( "\\missing.txt", "ab" );
3704 	}
3705 #endif
3706 	Com_Printf( "%d files in pk3 files\n", fs_packFiles );
3707 }
3708 
3709 #ifndef STANDALONE
3710 /*
3711 ===================
3712 FS_CheckMPPaks
3713 
3714 Check whether any of the original id pak files is present,
3715 and start up in standalone mode, if there are none and a
3716 different com_basegame was set.
3717 Note: If you're building a game that doesn't depend on the
3718 RTCW media mp_pak0.pk3, you'll want to remove this by defining
3719 STANDALONE in q_shared.h
3720 ===================
3721 */
FS_CheckMPPaks(void)3722 static void FS_CheckMPPaks( void )
3723 {
3724 	searchpath_t	*path;
3725 	pack_t		*curpack;
3726 	const char	*pakBasename;
3727 	unsigned int foundPak = 0;
3728 
3729 	for( path = fs_searchpaths; path; path = path->next )
3730 	{
3731 		if(!path->pack)
3732 			continue;
3733 
3734 		curpack = path->pack;
3735 		pakBasename = curpack->pakBasename;
3736 
3737 		if(!Q_stricmpn( curpack->pakGamename, BASEGAME, MAX_OSPATH )
3738 			&& strlen(pakBasename) == 7 && !Q_stricmpn( pakBasename, "mp_pak", 6 )
3739 			&& pakBasename[6] >= '0' && pakBasename[6] <= '0' + NUM_MP_PAKS - 1)
3740 		{
3741 			if( curpack->checksum != mppak_checksums[pakBasename[6]-'0'] )
3742 			{
3743 				if(pakBasename[6] == '0')
3744 				{
3745 					Com_Printf("\n\n"
3746 						"**************************************************\n"
3747 						"WARNING: " BASEGAME "/mp_pak0.pk3 is present but its checksum (%u)\n"
3748 						"is not correct. Please re-copy mp_pak0.pk3 from your\n"
3749 						"legitimate RTCW CDROM.\n"
3750 						"**************************************************\n\n\n",
3751 						curpack->checksum );
3752 				}
3753 				else
3754 				{
3755 					Com_Printf("\n\n"
3756 						"**************************************************\n"
3757 						"WARNING: " BASEGAME "/mp_pak%d.pk3 is present but its checksum (%u)\n"
3758 						"is not correct. Please re-install the point release\n"
3759 						"**************************************************\n\n\n",
3760 						pakBasename[6]-'0', curpack->checksum );
3761 				}
3762 			}
3763 
3764 			foundPak |= 1<<(pakBasename[6]-'0');
3765 		}
3766 		else
3767 		{
3768 			int index;
3769 
3770 			// Finally check whether this pak's checksum is listed because the user tried
3771 			// to trick us by renaming the file, and set foundPak's highest bit to indicate this case.
3772 
3773 			for(index = 0; index < ARRAY_LEN(mppak_checksums); index++)
3774 			{
3775 				if(curpack->checksum == mppak_checksums[index])
3776 				{
3777 					Com_Printf("\n\n"
3778 						"**************************************************\n"
3779 						"WARNING: %s is renamed pak file %s%cmp_pak%d.pk3\n"
3780 						"Running in standalone mode won't work\n"
3781 						"Please rename, or remove this file\n"
3782 						"**************************************************\n\n\n",
3783 						curpack->pakFilename, BASEGAME, PATH_SEP, index);
3784 
3785 
3786 					foundPak |= 0x80000000;
3787 				}
3788 			}
3789 
3790 		}
3791 	}
3792 
3793 	if(!foundPak && Q_stricmp(com_basegame->string, BASEGAME))
3794 	{
3795 		Cvar_Set("com_standalone", "1");
3796 	}
3797 	else
3798 		Cvar_Set("com_standalone", "0");
3799 
3800 
3801 	if(!com_standalone->integer && (foundPak & 0x3f) != 0x3f)
3802 	{
3803 		char errorText[MAX_STRING_CHARS] = "";
3804 		char missingPaks[MAX_STRING_CHARS] = "";
3805 		int i = 0;
3806 
3807 		if((foundPak & 0x3f) != 0x3f)
3808 		{
3809 			for( i = 0; i < NUM_MP_PAKS; i++ ) {
3810 				if ( !( foundPak & ( 1 << i ) ) ) {
3811 					Q_strcat( missingPaks, sizeof( missingPaks ), va( "mp_pak%d.pk3 ", i ) );
3812 				}
3813 			}
3814 
3815 			Q_strcat( errorText, sizeof( errorText ),
3816 				va( "\n\nPoint Release files are missing: %s \n"
3817 				"Please re-install the 1.41 point release.\n\n", missingPaks ) );
3818 		}
3819 
3820 		Com_Error(ERR_FATAL, "%s", errorText);
3821 	}
3822 }
3823 
3824 
3825 /*
3826 ===================
3827 FS_CheckPak0
3828 
3829 Check whether any of the original id pak files is present,
3830 and start up in standalone mode, if there are none and a
3831 different com_basegame was set.
3832 Note: If you're building a game that doesn't depend on the
3833 RTCW media pak0.pk3, you'll want to remove this by defining
3834 STANDALONE in q_shared.h
3835 ===================
3836 */
FS_CheckPak0(void)3837 static void FS_CheckPak0( void )
3838 {
3839 	searchpath_t	*path;
3840 	pack_t		*curpack;
3841 	const char	*pakBasename;
3842 	qboolean founddemo = qfalse;
3843 	unsigned int foundPak = 0;
3844 
3845 	for( path = fs_searchpaths; path; path = path->next )
3846 	{
3847 		if(!path->pack)
3848 			continue;
3849 
3850 		curpack = path->pack;
3851 		pakBasename = curpack->pakBasename;
3852 
3853 		if(!Q_stricmpn( curpack->pakGamename, "demomain", MAX_OSPATH )
3854 			&& !Q_stricmpn( pakBasename, "pak0", MAX_OSPATH ))
3855 		{
3856 			if(curpack->checksum == DEMO_PAK0_CHECKSUM)
3857 				founddemo = qtrue;
3858 		}
3859 
3860 		else if(!Q_stricmpn( curpack->pakGamename, BASEGAME, MAX_OSPATH )
3861 			&& strlen(pakBasename) == 4 && !Q_stricmpn( pakBasename, "pak", 3 )
3862 			&& pakBasename[3] >= '0' && pakBasename[3] <= '0' + NUM_ID_PAKS - 1)
3863 		{
3864 			if( curpack->checksum != pak_checksums[pakBasename[3]-'0'] )
3865 			{
3866 				if(pakBasename[3] == '0')
3867 				{
3868 					Com_Printf("\n\n"
3869 						"**************************************************\n"
3870 						"WARNING: " BASEGAME "/pak0.pk3 is present but its checksum (%u)\n"
3871 						"is not correct. Please re-copy pak0.pk3 from your\n"
3872 						"legitimate RTCW CDROM.\n"
3873 						"**************************************************\n\n\n",
3874 						curpack->checksum );
3875 
3876 						Com_Error(ERR_FATAL, NULL);
3877 				}
3878 				/*
3879 				else
3880 				{
3881 					Com_Printf("\n\n"
3882 						"**************************************************\n"
3883 						"WARNING: " BASEGAME "/pak%d.pk3 is present but its checksum (%u)\n"
3884 						"is not correct. Please re-install the point release\n"
3885 						"**************************************************\n\n\n",
3886 						pakBasename[3]-'0', curpack->checksum );
3887 				}
3888 				*/
3889 			}
3890 
3891 			foundPak |= 1<<(pakBasename[3]-'0');
3892 		}
3893 		else
3894 		{
3895 			int index;
3896 
3897 			// Finally check whether this pak's checksum is listed because the user tried
3898 			// to trick us by renaming the file, and set foundPak's highest bit to indicate this case.
3899 
3900 			for(index = 0; index < ARRAY_LEN(pak_checksums); index++)
3901 			{
3902 				if(curpack->checksum == pak_checksums[index])
3903 				{
3904 					Com_Printf("\n\n"
3905 						"**************************************************\n"
3906 						"WARNING: %s is renamed pak file %s%cpak%d.pk3\n"
3907 						"Running in standalone mode won't work\n"
3908 						"Please rename, or remove this file\n"
3909 						"**************************************************\n\n\n",
3910 						curpack->pakFilename, BASEGAME, PATH_SEP, index);
3911 
3912 
3913 					foundPak |= 0x80000000;
3914 				}
3915 			}
3916 		}
3917 	}
3918 
3919 	if(!foundPak && Q_stricmp(com_basegame->string, BASEGAME))
3920 	{
3921 		Cvar_Set("com_standalone", "1");
3922 	}
3923 	else
3924 		Cvar_Set("com_standalone", "0");
3925 
3926 	if(!com_standalone->integer)
3927 	{
3928 		if(!(foundPak & 0x01))
3929 		{
3930 			if(founddemo)
3931 			{
3932 				Com_Printf( "\n\n"
3933 					"**************************************************\n"
3934 					"WARNING: It looks like you're using pak0.pk3\n"
3935 					"from the demo. This may work fine, but it is not\n"
3936 					"guaranteed or supported.\n"
3937 					"**************************************************\n\n\n" );
3938 
3939 				foundPak |= 0x01;
3940 			}
3941 		}
3942 	}
3943 
3944 
3945 	if(!com_standalone->integer && (foundPak & 0x01) != 0x01)
3946 	{
3947 		char errorText[MAX_STRING_CHARS] = "";
3948 
3949 		if((foundPak & 0x01) != 0x01)
3950 		{
3951 			Q_strcat(errorText, sizeof(errorText),
3952 				"\n\n\"pak0.pk3\" is missing. Please copy it\n"
3953 				"from your legitimate RTCW CDROM.\n\n");
3954 		}
3955 
3956 		Q_strcat(errorText, sizeof(errorText),
3957 			va("Also check that your iortcw executable is in\n"
3958 				"the correct place and that every file\n"
3959 				"in the \"%s\" directory is present and readable.\n\n", BASEGAME));
3960 
3961 		Com_Error(ERR_FATAL, "%s", errorText);
3962 	}
3963 
3964 	if(!founddemo)
3965 		FS_CheckMPPaks();
3966 
3967 }
3968 #endif
3969 
3970 /*
3971 =====================
3972 FS_LoadedPakChecksums
3973 
3974 Returns a space separated string containing the checksums of all loaded pk3 files.
3975 Servers with sv_pure set will get this string and pass it to clients.
3976 =====================
3977 */
FS_LoadedPakChecksums(void)3978 const char *FS_LoadedPakChecksums( void ) {
3979 	static char info[BIG_INFO_STRING];
3980 	searchpath_t    *search;
3981 
3982 	info[0] = 0;
3983 
3984 	for ( search = fs_searchpaths ; search ; search = search->next ) {
3985 		// is the element a pak file?
3986 		if ( !search->pack ) {
3987 			continue;
3988 		}
3989 
3990 		Q_strcat( info, sizeof( info ), va( "%i ", search->pack->checksum ) );
3991 	}
3992 
3993 	return info;
3994 }
3995 
3996 /*
3997 =====================
3998 FS_LoadedPakNames
3999 
4000 Returns a space separated string containing the names of all loaded pk3 files.
4001 Servers with sv_pure set will get this string and pass it to clients.
4002 =====================
4003 */
FS_LoadedPakNames(void)4004 const char *FS_LoadedPakNames( void ) {
4005 	static char info[BIG_INFO_STRING];
4006 	searchpath_t    *search;
4007 
4008 	info[0] = 0;
4009 
4010 	for ( search = fs_searchpaths ; search ; search = search->next ) {
4011 		// is the element a pak file?
4012 		if ( !search->pack ) {
4013 			continue;
4014 		}
4015 
4016 		if ( *info ) {
4017 			Q_strcat( info, sizeof( info ), " " );
4018 		}
4019 		Q_strcat( info, sizeof( info ), search->pack->pakBasename );
4020 	}
4021 
4022 	return info;
4023 }
4024 
4025 /*
4026 =====================
4027 FS_LoadedPakPureChecksums
4028 
4029 Returns a space separated string containing the pure checksums of all loaded pk3 files.
4030 Servers with sv_pure use these checksums to compare with the checksums the clients send
4031 back to the server.
4032 =====================
4033 */
FS_LoadedPakPureChecksums(void)4034 const char *FS_LoadedPakPureChecksums( void ) {
4035 	static char info[BIG_INFO_STRING];
4036 	searchpath_t    *search;
4037 
4038 	info[0] = 0;
4039 
4040 	for ( search = fs_searchpaths ; search ; search = search->next ) {
4041 		// is the element a pak file?
4042 		if ( !search->pack ) {
4043 			continue;
4044 		}
4045 
4046 		Q_strcat( info, sizeof( info ), va( "%i ", search->pack->pure_checksum ) );
4047 	}
4048 
4049 	return info;
4050 }
4051 
4052 /*
4053 =====================
4054 FS_ReferencedPakChecksums
4055 
4056 Returns a space separated string containing the checksums of all referenced pk3 files.
4057 The server will send this to the clients so they can check which files should be auto-downloaded.
4058 =====================
4059 */
FS_ReferencedPakChecksums(void)4060 const char *FS_ReferencedPakChecksums( void ) {
4061 	static char info[BIG_INFO_STRING];
4062 	searchpath_t *search;
4063 
4064 	info[0] = 0;
4065 
4066 	for ( search = fs_searchpaths ; search ; search = search->next ) {
4067 		// is the element a pak file?
4068 		if ( search->pack ) {
4069 			if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, com_basegame->string, strlen(com_basegame->string))) {
4070 				Q_strcat( info, sizeof( info ), va( "%i ", search->pack->checksum ) );
4071 			}
4072 		}
4073 	}
4074 
4075 	return info;
4076 }
4077 
4078 /*
4079 =====================
4080 FS_ReferencedPakNames
4081 
4082 Returns a space separated string containing the names of all referenced pk3 files.
4083 The server will send this to the clients so they can check which files should be auto-downloaded.
4084 =====================
4085 */
FS_ReferencedPakNames(void)4086 const char *FS_ReferencedPakNames( void ) {
4087 	static char info[BIG_INFO_STRING];
4088 	searchpath_t    *search;
4089 
4090 	info[0] = 0;
4091 
4092 	// we want to return ALL pk3's from the fs_game path
4093 	// and referenced one's from baseq3
4094 	for ( search = fs_searchpaths ; search ; search = search->next ) {
4095 		// is the element a pak file?
4096 		if ( search->pack ) {
4097 			if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, com_basegame->string, strlen(com_basegame->string))) {
4098 				if (*info) {
4099 					Q_strcat(info, sizeof( info ), " " );
4100 				}
4101 				Q_strcat( info, sizeof( info ), search->pack->pakGamename );
4102 				Q_strcat( info, sizeof( info ), "/" );
4103 				Q_strcat( info, sizeof( info ), search->pack->pakBasename );
4104 			}
4105 		}
4106 	}
4107 
4108 	return info;
4109 }
4110 
4111 
4112 /*
4113 =====================
4114 FS_ReferencedPakPureChecksums
4115 
4116 Returns a space separated string containing the pure checksums of all referenced pk3 files.
4117 Servers with sv_pure set will get this string back from clients for pure validation
4118 
4119 The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..."
4120 =====================
4121 */
FS_ReferencedPakPureChecksums(void)4122 const char *FS_ReferencedPakPureChecksums( void ) {
4123 	static char info[BIG_INFO_STRING];
4124 	searchpath_t    *search;
4125 	int nFlags, numPaks, checksum;
4126 
4127 	info[0] = 0;
4128 
4129 	checksum = fs_checksumFeed;
4130 
4131 	numPaks = 0;
4132 	for ( nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1 ) {
4133 		if ( nFlags & FS_GENERAL_REF ) {
4134 			// add a delimter between must haves and general refs
4135 			//Q_strcat(info, sizeof(info), "@ ");
4136 			info[strlen( info ) + 1] = '\0';
4137 			info[strlen( info ) + 2] = '\0';
4138 			info[strlen( info )] = '@';
4139 			info[strlen( info )] = ' ';
4140 		}
4141 		for ( search = fs_searchpaths ; search ; search = search->next ) {
4142 			// is the element a pak file and has it been referenced based on flag?
4143 			if ( search->pack && ( search->pack->referenced & nFlags ) ) {
4144 				Q_strcat( info, sizeof( info ), va( "%i ", search->pack->pure_checksum ) );
4145 				if ( nFlags & ( FS_CGAME_REF | FS_UI_REF ) ) {
4146 					break;
4147 				}
4148 				checksum ^= search->pack->pure_checksum;
4149 				numPaks++;
4150 			}
4151 		}
4152 	}
4153 	// last checksum is the encoded number of referenced pk3s
4154 	checksum ^= numPaks;
4155 	Q_strcat( info, sizeof( info ), va( "%i ", checksum ) );
4156 
4157 	return info;
4158 }
4159 
4160 
4161 /*
4162 =====================
4163 FS_ClearPakReferences
4164 =====================
4165 */
FS_ClearPakReferences(int flags)4166 void FS_ClearPakReferences( int flags ) {
4167 	searchpath_t *search;
4168 
4169 	if ( !flags ) {
4170 		flags = -1;
4171 	}
4172 	for ( search = fs_searchpaths; search; search = search->next ) {
4173 		// is the element a pak file and has it been referenced?
4174 		if ( search->pack ) {
4175 			search->pack->referenced &= ~flags;
4176 		}
4177 	}
4178 }
4179 
4180 
4181 /*
4182 =====================
4183 FS_PureServerSetLoadedPaks
4184 
4185 If the string is empty, all data sources will be allowed.
4186 If not empty, only pk3 files that match one of the space
4187 separated checksums will be checked for files, with the
4188 exception of .cfg and .dat files.
4189 =====================
4190 */
FS_PureServerSetLoadedPaks(const char * pakSums,const char * pakNames)4191 void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) {
4192 	int i, c, d;
4193 
4194 	Cmd_TokenizeString( pakSums );
4195 
4196 	c = Cmd_Argc();
4197 	if ( c > MAX_SEARCH_PATHS ) {
4198 		c = MAX_SEARCH_PATHS;
4199 	}
4200 
4201 	fs_numServerPaks = c;
4202 
4203 	for ( i = 0 ; i < c ; i++ ) {
4204 		fs_serverPaks[i] = atoi( Cmd_Argv( i ) );
4205 	}
4206 
4207 	if ( fs_numServerPaks ) {
4208 		Com_DPrintf( "Connected to a pure server.\n" );
4209 	} else
4210 	{
4211 		if ( fs_reordered ) {
4212 			// show_bug.cgi?id=540
4213 			// force a restart to make sure the search order will be correct
4214 			Com_DPrintf( "FS search reorder is required\n" );
4215 			FS_Restart( fs_checksumFeed );
4216 			return;
4217 		}
4218 	}
4219 
4220 	for ( i = 0 ; i < c ; i++ ) {
4221 		if ( fs_serverPakNames[i] ) {
4222 			Z_Free( fs_serverPakNames[i] );
4223 		}
4224 		fs_serverPakNames[i] = NULL;
4225 	}
4226 	if ( pakNames && *pakNames ) {
4227 		Cmd_TokenizeString( pakNames );
4228 
4229 		d = Cmd_Argc();
4230 		if ( d > MAX_SEARCH_PATHS ) {
4231 			d = MAX_SEARCH_PATHS;
4232 		}
4233 
4234 		for ( i = 0 ; i < d ; i++ ) {
4235 			fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) );
4236 		}
4237 	}
4238 }
4239 
4240 /*
4241 =====================
4242 FS_PureServerSetReferencedPaks
4243 
4244 The checksums and names of the pk3 files referenced at the server
4245 are sent to the client and stored here. The client will use these
4246 checksums to see if any pk3 files need to be auto-downloaded.
4247 =====================
4248 */
FS_PureServerSetReferencedPaks(const char * pakSums,const char * pakNames)4249 void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) {
4250 	int i, c, d = 0;
4251 
4252 	Cmd_TokenizeString( pakSums );
4253 
4254 	c = Cmd_Argc();
4255 	if ( c > MAX_SEARCH_PATHS ) {
4256 		c = MAX_SEARCH_PATHS;
4257 	}
4258 
4259 	for ( i = 0 ; i < c ; i++ ) {
4260 		fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) );
4261 	}
4262 
4263 	for (i = 0 ; i < ARRAY_LEN(fs_serverReferencedPakNames); i++)
4264 	{
4265 		if(fs_serverReferencedPakNames[i])
4266 			Z_Free( fs_serverReferencedPakNames[i] );
4267 
4268 		fs_serverReferencedPakNames[i] = NULL;
4269 	}
4270 	if ( pakNames && *pakNames ) {
4271 		Cmd_TokenizeString( pakNames );
4272 
4273 		d = Cmd_Argc();
4274 
4275 		if(d > c)
4276 			d = c;
4277 
4278 		for ( i = 0 ; i < d ; i++ ) {
4279 			fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) );
4280 		}
4281 	}
4282 
4283 	// ensure that there are as many checksums as there are pak names.
4284 	if(d < c)
4285 		c = d;
4286 
4287 	fs_numServerReferencedPaks = c;
4288 }
4289 
4290 /*
4291 ================
4292 FS_InitFilesystem
4293 
4294 Called only at inital startup, not when the filesystem
4295 is resetting due to a game change
4296 ================
4297 */
FS_InitFilesystem(void)4298 void FS_InitFilesystem( void ) {
4299 	// allow command line parms to override our defaults
4300 	// we have to specially handle this, because normal command
4301 	// line variable sets don't happen until after the filesystem
4302 	// has already been initialized
4303 	Com_StartupVariable("fs_basepath");
4304 	Com_StartupVariable("fs_homepath");
4305 	Com_StartupVariable("fs_game");
4306 
4307 	if(!FS_FilenameCompare(Cvar_VariableString("fs_game"), com_basegame->string))
4308 		Cvar_Set("fs_game", "");
4309 
4310 	// try to start up normally
4311 	FS_Startup(com_basegame->string);
4312 
4313 #ifndef STANDALONE
4314 #ifndef UPDATE_SERVER
4315 	FS_CheckPak0( );
4316 #endif
4317 #endif
4318 
4319 #ifndef UPDATE_SERVER
4320 	// if we can't find default.cfg, assume that the paths are
4321 	// busted and error out now, rather than getting an unreadable
4322 	// graphics screen when the font fails to load
4323 	if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
4324 		// TTimo - added some verbosity, 'couldn't load default.cfg' confuses the hell out of users
4325 		Com_Error( ERR_FATAL, "Couldn't load default.cfg - I am missing essential files - verify your installation?" );
4326 	}
4327 #endif
4328 
4329 	Q_strncpyz( lastValidBase, fs_basepath->string, sizeof( lastValidBase ) );
4330 	Q_strncpyz( lastValidComBaseGame, com_basegame->string, sizeof( lastValidComBaseGame ) );
4331 	Q_strncpyz( lastValidFsBaseGame, fs_basegame->string, sizeof( lastValidFsBaseGame ) );
4332 	Q_strncpyz( lastValidGame, fs_gamedirvar->string, sizeof( lastValidGame ) );
4333 }
4334 
4335 
4336 /*
4337 ================
4338 FS_Restart
4339 ================
4340 */
FS_Restart(int checksumFeed)4341 void FS_Restart( int checksumFeed ) {
4342 	const char *lastGameDir;
4343 
4344 	// free anything we currently have loaded
4345 	FS_Shutdown( qfalse );
4346 
4347 	// set the checksum feed
4348 	fs_checksumFeed = checksumFeed;
4349 
4350 	// clear pak references
4351 	FS_ClearPakReferences( 0 );
4352 
4353 	// try to start up normally
4354 	FS_Startup(com_basegame->string);
4355 
4356 #ifndef STANDALONE
4357 	FS_CheckPak0( );
4358 #endif
4359 
4360 	// if we can't find default.cfg, assume that the paths are
4361 	// busted and error out now, rather than getting an unreadable
4362 	// graphics screen when the font fails to load
4363 	if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) {
4364 		// this might happen when connecting to a pure server not using BASEGAME/pak0.pk3
4365 		// (for instance a TA demo server)
4366 		if ( lastValidBase[0] ) {
4367 			FS_PureServerSetLoadedPaks( "", "" );
4368 			Cvar_Set( "fs_basepath", lastValidBase );
4369 			Cvar_Set( "com_basegame", lastValidComBaseGame );
4370 			Cvar_Set( "fs_basegame", lastValidFsBaseGame );
4371 			Cvar_Set( "fs_game", lastValidGame );
4372 			lastValidBase[0] = '\0';
4373 			lastValidComBaseGame[0] = '\0';
4374 			lastValidFsBaseGame[0] = '\0';
4375 			lastValidGame[0] = '\0';
4376 			FS_Restart( checksumFeed );
4377 			Com_Error( ERR_DROP, "Invalid game folder" );
4378 			return;
4379 		}
4380 		Com_Error( ERR_FATAL, "Couldn't load default.cfg" );
4381 	}
4382 
4383 	lastGameDir = ( lastValidGame[0] ) ? lastValidGame : lastValidComBaseGame;
4384 
4385 	if ( Q_stricmp( FS_GetCurrentGameDir(), lastGameDir ) ) {
4386 		Sys_RemovePIDFile( lastGameDir );
4387 		Sys_InitPIDFile( FS_GetCurrentGameDir() );
4388 
4389 		// skip the wolfconfig.cfg if "safe" is on the command line
4390 		if ( !Com_SafeMode() ) {
4391 			Cbuf_AddText("exec " Q3CONFIG_CFG "\n");
4392 		}
4393 	}
4394 
4395 	Q_strncpyz( lastValidBase, fs_basepath->string, sizeof( lastValidBase ) );
4396 	Q_strncpyz( lastValidComBaseGame, com_basegame->string, sizeof( lastValidComBaseGame ) );
4397 	Q_strncpyz( lastValidFsBaseGame, fs_basegame->string, sizeof( lastValidFsBaseGame ) );
4398 	Q_strncpyz( lastValidGame, fs_gamedirvar->string, sizeof( lastValidGame ) );
4399 
4400 }
4401 
4402 /*
4403 =================
4404 FS_ConditionalRestart
4405 
4406 Restart if necessary
4407 Return qtrue if restarting due to game directory changed, qfalse otherwise
4408 =================
4409 */
FS_ConditionalRestart(int checksumFeed,qboolean disconnect)4410 qboolean FS_ConditionalRestart(int checksumFeed, qboolean disconnect)
4411 {
4412 	if(fs_gamedirvar->modified)
4413 	{
4414 		if(FS_FilenameCompare(lastValidGame, fs_gamedirvar->string) &&
4415 		   (*lastValidGame || FS_FilenameCompare(fs_gamedirvar->string, com_basegame->string)) &&
4416 		   (*fs_gamedirvar->string || FS_FilenameCompare(lastValidGame, com_basegame->string)))
4417 		{
4418 			Com_GameRestart(checksumFeed, disconnect);
4419 			return qtrue;
4420 		}
4421 		else
4422 			fs_gamedirvar->modified = qfalse;
4423 	}
4424 
4425 	if(checksumFeed != fs_checksumFeed)
4426 		FS_Restart(checksumFeed);
4427 	else if(fs_numServerPaks && !fs_reordered)
4428 		FS_ReorderPurePaks();
4429 
4430 	return qfalse;
4431 }
4432 
4433 /*
4434 ========================================================================================
4435 
4436 Handle based file calls for virtual machines
4437 
4438 ========================================================================================
4439 */
4440 
FS_FOpenFileByMode(const char * qpath,fileHandle_t * f,fsMode_t mode)4441 int	FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
4442 	int r;
4443 	qboolean sync;
4444 
4445 	sync = qfalse;
4446 
4447 	switch( mode ) {
4448 		case FS_READ:
4449 			r = FS_FOpenFileRead( qpath, f, qtrue );
4450 			break;
4451 		case FS_WRITE:
4452 			*f = FS_FOpenFileWrite( qpath );
4453 			r = 0;
4454 			if (*f == 0) {
4455 				r = -1;
4456 			}
4457 			break;
4458 		case FS_APPEND_SYNC:
4459 			sync = qtrue;
4460 		case FS_APPEND:
4461 			*f = FS_FOpenFileAppend( qpath );
4462 			r = 0;
4463 			if (*f == 0) {
4464 				r = -1;
4465 			}
4466 			break;
4467 		default:
4468 			Com_Error( ERR_FATAL, "FS_FOpenFileByMode: bad mode" );
4469 			return -1;
4470 	}
4471 
4472 	if (!f) {
4473 		return r;
4474 	}
4475 
4476 	if ( *f ) {
4477 		fsh[*f].fileSize = r;
4478 	}
4479 	fsh[*f].handleSync = sync;
4480 
4481 	return r;
4482 }
4483 
FS_FTell(fileHandle_t f)4484 int     FS_FTell( fileHandle_t f ) {
4485 	int pos;
4486 	if ( fsh[f].zipFile == qtrue ) {
4487 		pos = unztell( fsh[f].handleFiles.file.z );
4488 	} else {
4489 		pos = ftell( fsh[f].handleFiles.file.o );
4490 	}
4491 	return pos;
4492 }
4493 
FS_Flush(fileHandle_t f)4494 void    FS_Flush( fileHandle_t f ) {
4495 	fflush( fsh[f].handleFiles.file.o );
4496 }
4497 
FS_FilenameCompletion(const char * dir,const char * ext,qboolean stripExt,void (* callback)(const char * s),qboolean allowNonPureFilesOnDisk)4498 void	FS_FilenameCompletion( const char *dir, const char *ext,
4499 		qboolean stripExt, void(*callback)(const char *s), qboolean allowNonPureFilesOnDisk ) {
4500 	char	**filenames;
4501 	int		nfiles;
4502 	int		i;
4503 	char	filename[ MAX_STRING_CHARS ];
4504 
4505 	filenames = FS_ListFilteredFiles( dir, ext, NULL, &nfiles, allowNonPureFilesOnDisk );
4506 
4507 	FS_SortFileList( filenames, nfiles );
4508 
4509 	for( i = 0; i < nfiles; i++ ) {
4510 		FS_ConvertPath( filenames[ i ] );
4511 		Q_strncpyz( filename, filenames[ i ], MAX_STRING_CHARS );
4512 
4513 		if( stripExt ) {
4514 			COM_StripExtension(filename, filename, sizeof(filename));
4515 		}
4516 
4517 		callback( filename );
4518 	}
4519 	FS_FreeFileList( filenames );
4520 }
4521 
FS_GetCurrentGameDir(void)4522 const char *FS_GetCurrentGameDir(void)
4523 {
4524 	if(fs_gamedirvar->string[0])
4525 		return fs_gamedirvar->string;
4526 
4527 	return com_basegame->string;
4528 }
4529 
4530 // CVE-2006-2082
4531 // compared requested pak against the names as we built them in FS_ReferencedPakNames
FS_VerifyPak(const char * pak)4532 qboolean FS_VerifyPak( const char *pak ) {
4533 	char teststring[ BIG_INFO_STRING ];
4534 	searchpath_t    *search;
4535 
4536 	for ( search = fs_searchpaths ; search ; search = search->next ) {
4537 		if ( search->pack ) {
4538 			Q_strncpyz( teststring, search->pack->pakGamename, sizeof( teststring ) );
4539 			Q_strcat( teststring, sizeof( teststring ), "/" );
4540 			Q_strcat( teststring, sizeof( teststring ), search->pack->pakBasename );
4541 			Q_strcat( teststring, sizeof( teststring ), ".pk3" );
4542 			if ( !Q_stricmp( teststring, pak ) ) {
4543 				return qtrue;
4544 			}
4545 		}
4546 	}
4547 	return qfalse;
4548 }
4549 
4550 
4551