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