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