1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20
21 #include "qcommon.h"
22 #include <zlib.h>
23 #include "unzip.h"
24
25 /*
26 =============================================================================
27
28 QUAKE FILESYSTEM
29
30 =============================================================================
31 */
32
33 #define MAX_FILES_IN_PK2 0x4000
34 #define MAX_FILE_HANDLES 8
35
36 #define FS_Malloc( size ) Z_TagMalloc( size, TAG_FILESYSTEM )
37 #define FS_CopyString( string ) Z_TagCopyString( string, TAG_FILESYSTEM )
38
39 #ifdef _WIN32
40 #define FS_strcmp Q_stricmp
41 #else
42 #define FS_strcmp strcmp
43 #endif
44
45 //
46 // in memory
47 //
48
49 typedef struct packfile_s {
50 char *name;
51 int filepos;
52 int filelen;
53
54 struct packfile_s *hashNext;
55 } packfile_t;
56
57 typedef struct pack_s {
58 unzFile zFile;
59 FILE *fp;
60 int numfiles;
61 packfile_t *files;
62 packfile_t **fileHash;
63 int hashSize;
64 char filename[1];
65 } pack_t;
66
67 typedef enum fsFileType_e {
68 FS_FREE,
69 FS_REAL,
70 FS_PAK,
71 FS_PK2,
72 FS_GZIP,
73 FS_BAD
74 } fsFileType_t;
75
76 typedef struct fsFile_s {
77 char name[MAX_QPATH];
78 char fullpath[MAX_OSPATH];
79 fsFileType_t type;
80 uint32 mode;
81 FILE *fp;
82 void *zfp;
83 packfile_t *pak;
84 qboolean unique;
85 int length;
86 } fsFile_t;
87
88 typedef struct searchpath_s {
89 pack_t *pack; // only one of filename / pack will be used
90 struct searchpath_s *next;
91 char filename[1];
92 } searchpath_t;
93
94 typedef struct fsLink_s {
95 char *target;
96 int targetLength, nameLength;
97 struct fsLink_s *next;
98 char name[1];
99 } fsLink_t;
100
101 static char fs_gamedir[MAX_OSPATH];
102
103 static cvar_t *fs_basedir;
104 static cvar_t *fs_cddir;
105 static cvar_t *fs_game;
106 static cvar_t *fs_debug;
107 static cvar_t *fs_restrict_mask;
108
109 static searchpath_t *fs_searchpaths;
110 static searchpath_t *fs_base_searchpaths;
111
112 static fsLink_t *fs_links;
113
114 static fsFile_t fs_files[MAX_FILE_HANDLES];
115
116 static qboolean fs_fileFromPak;
117
118 fsAPI_t fs;
119
120 void FS_AddGameDirectory( const char *fmt, ... )
121 __attribute__(( format( printf, 1, 2 ) ));
122
123 /*
124
125 All of Quake's data access is through a hierchal file system,
126 but the contents of the file system can be transparently merged from several sources.
127
128 The "base directory" is the path to the directory holding the quake.exe and all game directories.
129 The sys_* files pass this to host_init in quakeparms_t->basedir.
130 This can be overridden with the "-basedir" command line parm to allow code debugging in a different directory.
131 The base directory is only used during filesystem initialization.
132
133 The "game directory" is the first tree on the search path and directory that
134 all generated files (savegames, screenshots, demos, config files) will be saved to.
135 This can be overridden with the "-game" command line parameter.
136 The game directory can never be changed while quake is executing.
137 This is a precacution against having a malicious server instruct clients to write files over areas they shouldn't.
138
139 */
140
141 /*
142 ================
143 FS_DPrintf
144 ================
145 */
FS_DPrintf(char * format,...)146 static void FS_DPrintf( char *format, ... ) {
147 va_list argptr;
148 char string[MAXPRINTMSG];
149
150 if( !fs_debug || !fs_debug->integer ) {
151 return;
152 }
153
154 va_start( argptr, format );
155 Q_vsnprintf( string, sizeof( string ), format, argptr );
156 va_end( argptr );
157
158 Com_Printf( S_COLOR_CYAN "%s", string );
159 }
160
161 /*
162 ================
163 FS_AllocHandle
164 ================
165 */
FS_AllocHandle(fileHandle_t * f,const char * name)166 static fsFile_t *FS_AllocHandle( fileHandle_t *f, const char *name ) {
167 fsFile_t *file;
168 int i;
169
170 for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) {
171 if( file->type == FS_FREE ) {
172 break;
173 }
174 }
175
176 if( i == MAX_FILE_HANDLES ) {
177 Com_Error( ERR_FATAL, "FS_AllocHandle: none free" );
178 }
179
180 Q_strncpyz( file->name, name, sizeof( file->name ) );
181 Q_strlwr( file->name );
182 *f = i + 1;
183 return file;
184 }
185
186 /*
187 ================
188 FS_FileForHandle
189 ================
190 */
FS_FileForHandle(fileHandle_t f)191 static fsFile_t *FS_FileForHandle( fileHandle_t f ) {
192 fsFile_t *file;
193
194 if( f <= 0 || f >= MAX_FILE_HANDLES + 1 ) {
195 Com_Error( ERR_FATAL, "FS_FileForHandle: invalid handle: %i", f );
196 }
197
198 file = &fs_files[f - 1];
199 if( file->type == FS_FREE ) {
200 Com_Error( ERR_FATAL, "FS_FileForHandle: free file: %i", f );
201 }
202
203 if( file->type < FS_FREE || file->type >= FS_BAD ) {
204 Com_Error( ERR_FATAL, "FS_FileForHandle: invalid file type: %i", file->type );
205 }
206
207 return file;
208 }
209
FS_ValidatePath(const char * s)210 qboolean FS_ValidatePath( const char *s ) {
211 const char *start;
212 int back;
213
214 // check for leading slash
215 // check for empty path
216 if( *s == '/' || *s == '\\' || *s == 0 ) {
217 return qfalse;
218 }
219
220 back = 0;
221 start = s;
222 while( *s ) {
223 // check for ".."
224 if( *s == '.' && s[1] == '.' ) {
225 if( back > 1 ) {
226 return qfalse;
227 }
228 back++;
229 }
230 if( *s == '/' || *s == '\\' ) {
231 // check for two slashes in a row
232 // check for trailing slash
233 if( ( s[1] == '/' || s[1] == '\\' || s[1] == 0 ) ) {
234 return qfalse;
235 }
236 }
237 if( *s == ':' ) {
238 // check for "X:\"
239 if( s[1] == '\\' || s[1] == '/' ) {
240 return qfalse;
241 }
242 }
243 s++;
244 }
245
246 if( s - start > MAX_QPATH ) {
247 return qfalse;
248 }
249
250 return qtrue;
251 }
252
253 /*
254 ================
255 FS_ConvertToSysPath
256 ================
257 */
FS_ConvertToSysPath(char * path)258 static char *FS_ConvertToSysPath( char *path ) {
259 char *s;
260
261 s = path;
262 while( *s ) {
263 if( *s == '/' || *s == '\\' ) {
264 *s = PATH_SEP_CHAR;
265 }
266 s++;
267 }
268
269 return path;
270
271 }
272
273 /*
274 ================
275 FS_GetFileLength
276
277 Gets current file length.
278 For GZIP files, returns uncompressed length
279 (very slow operation because it has to uncompress the whole file).
280 ================
281 */
FS_GetFileLength(fileHandle_t f)282 int FS_GetFileLength( fileHandle_t f ) {
283 fsFile_t *file = FS_FileForHandle( f );
284 int pos, length;
285
286 if( ( file->mode & FS_MODE_MASK ) == FS_MODE_READ ) {
287 if( file->type == FS_GZIP ) {
288 goto gzipHack;
289 }
290 return file->length;
291 }
292
293 switch( file->type ) {
294 case FS_REAL:
295 pos = ftell( file->fp );
296 fseek( file->fp, 0, SEEK_END );
297 length = ftell( file->fp );
298 fseek( file->fp, pos, SEEK_SET );
299 break;
300 case FS_GZIP:
301 gzipHack:
302 pos = gztell( file->zfp );
303 gzseek( file->zfp, 0x7FFFFFFF, SEEK_SET );
304 length = gztell( file->zfp );
305 gzseek( file->zfp, pos, SEEK_SET );
306 break;
307 default:
308 Com_Error( ERR_FATAL, "FS_GetFileLength: bad file type" );
309 length = -1;
310 break;
311 }
312
313 return length;
314 }
315
FS_GetFileLengthNoCache(fileHandle_t f)316 int FS_GetFileLengthNoCache( fileHandle_t f ) {
317 fsFile_t *file = FS_FileForHandle( f );
318 int pos, length;
319
320 switch( file->type ) {
321 case FS_REAL:
322 pos = ftell( file->fp );
323 fseek( file->fp, 0, SEEK_END );
324 length = ftell( file->fp );
325 fseek( file->fp, pos, SEEK_SET );
326 break;
327 case FS_GZIP:
328 pos = gztell( file->zfp );
329 gzseek( file->zfp, 0x7FFFFFFF, SEEK_SET );
330 length = gztell( file->zfp );
331 gzseek( file->zfp, pos, SEEK_SET );
332 break;
333 case FS_PAK:
334 case FS_PK2:
335 length = file->length;
336 break;
337 default:
338 Com_Error( ERR_FATAL, "FS_GetFileLengthNoCache: bad file type" );
339 length = -1;
340 break;
341 }
342
343 return length;
344 }
345
346 /*
347 ================
348 FS_GetFileName
349 ================
350 */
FS_GetFileName(fileHandle_t f)351 const char *FS_GetFileName( fileHandle_t f ) {
352 return ( FS_FileForHandle( f ) )->name;
353 }
354
FS_GetFileFullPath(fileHandle_t f)355 const char *FS_GetFileFullPath( fileHandle_t f ) {
356 return ( FS_FileForHandle( f ) )->fullpath;
357 }
358
359 /*
360 ============
361 FS_CreatePath
362
363 Creates any directories needed to store the given filename.
364 Expects a fully qualified path.
365 ============
366 */
FS_CreatePath(const char * path)367 void FS_CreatePath( const char *path ) {
368 char buffer[MAX_OSPATH];
369 char *ofs;
370
371 Q_strncpyz( buffer, path, sizeof( buffer ) );
372 FS_ConvertToSysPath( buffer );
373
374 FS_DPrintf( "FS_CreatePath( '%s' )\n", buffer );
375
376 for( ofs = buffer + 1; *ofs; ofs++ ) {
377 if( *ofs == PATH_SEP_CHAR ) {
378 // create the directory
379 *ofs = 0;
380 Sys_Mkdir( buffer );
381 *ofs = PATH_SEP_CHAR;
382 }
383 }
384 }
385
386
387 /*
388 ==============
389 FS_FCloseFile
390 ==============
391 */
FS_FCloseFile(fileHandle_t f)392 void FS_FCloseFile( fileHandle_t f ) {
393 fsFile_t *file = FS_FileForHandle( f );
394
395 FS_DPrintf( "FS_FCloseFile( '%s' )\n", file->fullpath );
396
397 switch( file->type ) {
398 case FS_REAL:
399 fclose( file->fp );
400 break;
401 case FS_PAK:
402 if( file->unique ) {
403 fclose( file->fp );
404 }
405 break;
406 case FS_GZIP:
407 gzclose( file->zfp );
408 break;
409 case FS_PK2:
410 unzCloseCurrentFile( file->zfp );
411 if( file->unique ) {
412 unzClose( file->zfp );
413 }
414 break;
415 default:
416 break;
417 }
418
419 /* don't clear name and mode, so post-restart reopening works */
420 file->type = FS_FREE;
421 file->fp = NULL;
422 file->zfp = NULL;
423 file->pak = NULL;
424 file->unique = qfalse;
425 }
426
427 /*
428 ============
429 FS_FOpenFileWrite
430
431 In case of GZIP files, returns *raw* (compressed) length!
432 ============
433 */
FS_FOpenFileWrite(fsFile_t * file)434 static int FS_FOpenFileWrite( fsFile_t *file ) {
435 FILE *fp;
436 gzFile zfp;
437 char *modeStr;
438 fsFileType_t type;
439 uint32 mode;
440 char *ext;
441
442 #ifdef _WIN32
443 /* allow writing into basedir on Windows */
444 if( ( file->mode & FS_PATH_MASK ) == FS_PATH_BASE ) {
445 Com_sprintf( file->fullpath, sizeof( file->fullpath ),
446 "%s/" BASEDIRNAME "/%s", fs_basedir->string, file->name );
447 } else
448 #endif
449 {
450 Com_sprintf( file->fullpath, sizeof( file->fullpath ),
451 "%s/%s", fs_gamedir, file->name );
452 }
453
454 mode = file->mode & FS_MODE_MASK;
455 switch( mode ) {
456 case FS_MODE_APPEND:
457 modeStr = "ab";
458 break;
459 case FS_MODE_WRITE:
460 modeStr = "wb";
461 break;
462 case FS_MODE_RDWR:
463 modeStr = "r+b";
464 break;
465 default:
466 Com_Error( ERR_FATAL, "FS_FOpenFileWrite( '%s' ): invalid mode mask",
467 file->fullpath );
468 modeStr = NULL;
469 break;
470 }
471
472 FS_ConvertToSysPath( file->fullpath );
473
474 FS_CreatePath( file->fullpath );
475
476 fp = fopen( file->fullpath, modeStr );
477
478 if( !fp ) {
479 FS_DPrintf( "FS_FOpenFileWrite: fopen( '%s', '%s' ) failed\n",
480 file->fullpath, modeStr );
481 return -1;
482 }
483
484 type = FS_REAL;
485 if( !( file->mode & FS_FLAG_RAW ) ) {
486 ext = COM_FileExtension( file->fullpath );
487 if( !strcmp( ext, ".gz" ) ) {
488 zfp = gzdopen( fileno( fp ), modeStr );
489 if( !zfp ) {
490 FS_DPrintf( "FS_FOpenFileWrite: gzopen( '%s', '%s' ) failed\n",
491 file->fullpath, modeStr );
492 fclose( fp );
493 return -1;
494 }
495 file->zfp = zfp;
496 type = FS_GZIP;
497 }
498 }
499
500 FS_DPrintf( "FS_FOpenFileWrite( '%s' )\n", file->fullpath );
501
502 file->fp = fp;
503 file->type = type;
504 file->length = 0;
505 file->unique = qtrue;
506
507 if( mode == FS_MODE_WRITE ) {
508 return 0;
509 }
510
511 if( mode == FS_MODE_RDWR ) {
512 fseek( fp, 0, SEEK_END );
513 }
514
515 return ftell( fp );
516 }
517
518
519 /*
520 ===========
521 FS_FOpenFileRead
522
523 Finds the file in the search path.
524 Fills fsFile_t and returns file length.
525 In case of GZIP files, returns *raw* (compressed) length!
526 Used for streaming data out of either a pak file or
527 a seperate file.
528 ===========
529 */
FS_FOpenFileRead(fsFile_t * file,qboolean unique)530 static int FS_FOpenFileRead( fsFile_t *file, qboolean unique ) {
531 searchpath_t *search;
532 pack_t *pak;
533 uint32 hash;
534 packfile_t *entry;
535 FILE *fp;
536 void *zfp;
537 fsFileType_t type;
538 int pos, length;
539 char *ext;
540
541 fs_fileFromPak = qfalse;
542
543 //
544 // search through the path, one element at a time
545 //
546 if( ( file->mode & FS_PATH_MASK ) == FS_PATH_BASE ) {
547 search = fs_base_searchpaths;
548 } else {
549 search = fs_searchpaths;
550 }
551
552 hash = Com_HashPath( file->name, 0 );
553
554 for( ; search; search = search->next ) {
555 if( ( file->mode & FS_PATH_MASK ) == FS_PATH_GAME ) {
556 if( fs_searchpaths != fs_base_searchpaths && search == fs_base_searchpaths ) {
557 /* consider baseq2 a gamedir if no gamedir loaded */
558 break;
559 }
560 }
561
562 // is the element a pak file?
563 if( search->pack ) {
564 if( ( file->mode & FS_TYPE_MASK ) == FS_TYPE_REAL ) {
565 continue;
566 }
567 // look through all the pak file elements
568 pak = search->pack;
569 entry = pak->fileHash[ hash & ( pak->hashSize - 1 ) ];
570 for( ; entry; entry = entry->hashNext ) {
571 // both entry->name and file->name should be already lowercased
572 if( !strcmp( entry->name, file->name ) ) {
573 // found it!
574 fs_fileFromPak = qtrue;
575
576 Com_sprintf( file->fullpath, sizeof( file->fullpath ), "%s/%s", pak->filename, file->name );
577
578 // open a new file on the pakfile
579 if( pak->zFile ) {
580 if( unique ) {
581 zfp = unzReOpen( pak->filename, pak->zFile );
582 if( !zfp ) {
583 Com_Error( ERR_FATAL, "FS_FOpenFileRead: unzReOpen( '%s' ) failed", pak->filename );
584 }
585 } else {
586 zfp = pak->zFile;
587 }
588 if( unzSetCurrentFileInfoPosition( zfp, entry->filepos ) == -1 ) {
589 Com_Error( ERR_FATAL, "FS_FOpenFileRead: unzSetCurrentFileInfoPosition( '%s/%s' ) failed", pak->filename, entry->name );
590 }
591 if( unzOpenCurrentFile( zfp ) != UNZ_OK ) {
592 Com_Error( ERR_FATAL, "FS_FOpenFileRead: unzReOpen( '%s/%s' ) failed", pak->filename, entry->name );
593 }
594
595 file->zfp = zfp;
596 file->type = FS_PK2;
597 } else {
598 if( unique ) {
599 fp = fopen( pak->filename, "rb" );
600 if( !fp ) {
601 Com_Error( ERR_FATAL, "Couldn't reopen %s", pak->filename );
602 }
603 } else {
604 fp = pak->fp;
605 }
606
607 fseek( fp, entry->filepos, SEEK_SET );
608
609 file->fp = fp;
610 file->type = FS_PAK;
611 }
612
613 length = entry->filelen;
614 file->pak = entry;
615 file->length = length;
616 file->unique = unique;
617
618 FS_DPrintf( "FS_FOpenFileRead( '%s/%s' )\n", pak->filename, file->name );
619
620 return length;
621 }
622 }
623 } else {
624 if( ( file->mode & FS_TYPE_MASK ) == FS_TYPE_PAK ) {
625 continue;
626 }
627 // check a file in the directory tree
628 Com_sprintf( file->fullpath, sizeof( file->fullpath ), "%s/%s",
629 search->filename, file->name );
630
631 FS_ConvertToSysPath( file->fullpath );
632
633 fp = fopen( file->fullpath, "rb" );
634 if( !fp ) {
635 continue;
636 }
637
638 type = FS_REAL;
639 if( !( file->mode & FS_FLAG_RAW ) ) {
640 ext = COM_FileExtension( file->fullpath );
641 if( !strcmp( ext, ".gz" ) ) {
642 zfp = gzdopen( fileno( fp ), "rb" );
643 if( !zfp ) {
644 Com_WPrintf( "gzopen( '%s', 'rb' ) failed, "
645 "not a GZIP file?\n", file->fullpath );
646 fclose( fp );
647 return -1;
648 }
649 file->zfp = zfp;
650 type = FS_GZIP;
651 }
652 }
653
654 FS_DPrintf( "FS_FOpenFileRead( '%s' )\n", file->fullpath );
655
656 file->fp = fp;
657 file->type = type;
658 file->unique = qtrue;
659
660 pos = ftell( fp );
661 fseek( fp, 0, SEEK_END );
662 length = ftell( fp );
663 fseek( fp, pos, SEEK_SET );
664
665 file->length = length;
666
667 return length;
668 }
669 }
670
671 FS_DPrintf( "FS_FOpenFileRead( '%s' ): couldn't find\n", file->name );
672
673 return -1;
674 }
675
676 /*
677 =================
678 FS_LastFileFromPak
679 =================
680 */
FS_LastFileFromPak(void)681 qboolean FS_LastFileFromPak( void ) {
682 return fs_fileFromPak;
683 }
684
685
686 /*
687 =================
688 FS_ReadFile
689
690 Properly handles partial reads
691 =================
692 */
693 void CDAudio_Stop( void );
694
695 #define MAX_READ 0x40000 // read in blocks of 256k
696
FS_Read(void * buffer,int len,fileHandle_t hFile)697 int FS_Read( void *buffer, int len, fileHandle_t hFile ) {
698 int block, remaining;
699 int read;
700 byte *buf;
701 int tries;
702 fsFile_t *file = FS_FileForHandle( hFile );
703
704 buf = (byte *)buffer;
705
706 // read in chunks for progress bar
707 remaining = len;
708 tries = 0;
709 read = 0;
710 while( remaining ) {
711 block = remaining;
712 if( block > MAX_READ )
713 block = MAX_READ;
714 switch( file->type ) {
715 case FS_REAL:
716 case FS_PAK:
717 read = fread( buf, 1, block, file->fp );
718 break;
719 case FS_GZIP:
720 read = gzread( file->zfp, buf, block );
721 break;
722 case FS_PK2:
723 read = unzReadCurrentFile( file->zfp, buf, block );
724 break;
725 default:
726 break;
727 }
728 if( read == 0 ) {
729 #ifndef DEDICATED_ONLY
730 // we might have been trying to read from a CD
731 if( !tries ) {
732 tries = 1;
733 CDAudio_Stop();
734 } else
735 #endif // DEDICATED_ONLY
736 {
737 return len - remaining;
738 }
739 }
740
741 if( read == -1 )
742 Com_Error( ERR_FATAL, "FS_Read: -1 bytes read" );
743
744 // do some progress bar thing heref...
745
746 remaining -= read;
747 buf += read;
748 }
749
750 return len;
751 }
752
753 /*
754 =================
755 FS_Write
756
757 Properly handles partial writes
758 =================
759 */
760 #define MAX_WRITE 0x40000 // write in blocks of 256k
FS_Write(const void * buffer,int len,fileHandle_t hFile)761 int FS_Write( const void *buffer, int len, fileHandle_t hFile ) {
762 int block, remaining;
763 int write;
764 byte *buf;
765 fsFile_t *file = FS_FileForHandle( hFile );
766
767 buf = (byte *)buffer;
768
769 // read in chunks for progress bar
770 remaining = len;
771 write = 0;
772 while( remaining ) {
773 block = remaining;
774 if( block > MAX_WRITE )
775 block = MAX_WRITE;
776 switch( file->type ) {
777 case FS_REAL:
778 write = fwrite( buf, 1, block, file->fp );
779 break;
780 case FS_GZIP:
781 write = gzwrite( file->zfp, buf, block );
782 break;
783 default:
784 Com_Error( ERR_FATAL, "FS_Write: illegal file type" );
785 break;
786 }
787 if( write == 0 ) {
788 return len - remaining;
789 }
790
791 if( write == -1 ) {
792 Com_Error( ERR_FATAL, "FS_Write: -1 bytes written" );
793 }
794
795 // do some progress bar thing here...
796
797 remaining -= write;
798 buf += write;
799 }
800
801
802 if( ( file->mode & FS_FLUSH_MASK ) == FS_FLUSH_SYNC ) {
803 switch( file->type ) {
804 case FS_REAL:
805 fflush( file->fp );
806 break;
807 case FS_GZIP:
808 gzflush( file->zfp, Z_FINISH );
809 break;
810 default:
811 break;
812 }
813 }
814
815 return len;
816 }
817
818
FS_ExpandLinks(const char * filename)819 static char *FS_ExpandLinks( const char *filename ) {
820 static char buffer[MAX_QPATH];
821 fsLink_t *l;
822 int length;
823
824 length = strlen( filename );
825 for( l = fs_links; l; l = l->next ) {
826 if( l->nameLength > length ) {
827 continue;
828 }
829 if( !Q_stricmpn( l->name, filename, l->nameLength ) ) {
830 if( l->targetLength + length - l->nameLength > MAX_QPATH - 1 ) {
831 FS_DPrintf( "FS_ExpandLinks( '%s' ): MAX_QPATH exceeded\n",
832 filename );
833 return ( char * )filename;
834 }
835 memcpy( buffer, l->target, l->targetLength );
836 strcpy( buffer + l->targetLength, filename + l->nameLength );
837 FS_DPrintf( "FS_ExpandLinks( '%s' ) --> '%s'\n", filename, buffer );
838 return buffer;
839 }
840 }
841
842 return ( char * )filename;
843 }
844
845 /*
846 ============
847 FS_FOpenFile
848 ============
849 */
FS_FOpenFile(const char * filename,fileHandle_t * f,uint32 mode)850 int FS_FOpenFile( const char *filename, fileHandle_t *f, uint32 mode ) {
851 fsFile_t *file;
852 fileHandle_t hFile;
853 int ret = -1;
854
855 if( !filename || !f ) {
856 Com_Error( ERR_FATAL, "FS_FOpenFile: NULL" );
857 }
858
859 *f = 0;
860
861 if( !fs_searchpaths ) {
862 return -1; /* not yet initialized */
863 }
864
865 if( ( mode & FS_MODE_MASK ) == FS_MODE_READ ) {
866 filename = FS_ExpandLinks( filename );
867 }
868
869 if( !FS_ValidatePath( filename ) ) {
870 FS_DPrintf( "FS_FOpenFile: refusing invalid path: %s\n", filename );
871 return -1;
872 }
873
874 /* allocate new file handle */
875 file = FS_AllocHandle( &hFile, filename );
876 file->mode = mode;
877
878 mode &= FS_MODE_MASK;
879 switch( mode ) {
880 case FS_MODE_READ:
881 ret = FS_FOpenFileRead( file, qtrue );
882 break;
883 case FS_MODE_WRITE:
884 case FS_MODE_APPEND:
885 case FS_MODE_RDWR:
886 ret = FS_FOpenFileWrite( file );
887 break;
888 default:
889 Com_Error( ERR_FATAL, "FS_FOpenFile: illegal mode: %u", mode );
890 break;
891 }
892
893 /* if succeeded, store file handle */
894 if( ret != -1 ) {
895 *f = hFile;
896 }
897
898 return ret;
899 }
900
901
902 /*
903 ============
904 FS_Tell
905 ============
906 */
FS_Tell(fileHandle_t f)907 int FS_Tell( fileHandle_t f ) {
908 fsFile_t *file = FS_FileForHandle( f );
909 int length;
910
911 switch( file->type ) {
912 case FS_REAL:
913 length = ftell( file->fp );
914 break;
915 case FS_GZIP:
916 length = gztell( file->zfp );
917 break;
918 case FS_PAK:
919 length = ftell( file->fp ) - file->pak->filepos;
920 break;
921 case FS_PK2:
922 length = unztell( file->zfp );
923 break;
924 default:
925 length = -1;
926 break;
927 }
928
929 return length;
930 }
931
932 /*
933 ============
934 FS_RawTell
935 ============
936 */
FS_RawTell(fileHandle_t f)937 int FS_RawTell( fileHandle_t f ) {
938 fsFile_t *file = FS_FileForHandle( f );
939 int length;
940
941 switch( file->type ) {
942 case FS_REAL:
943 case FS_GZIP:
944 length = ftell( file->fp );
945 break;
946 case FS_PAK:
947 length = ftell( file->fp ) - file->pak->filepos;
948 break;
949 case FS_PK2:
950 length = unztell( file->zfp );
951 break;
952 default:
953 length = -1;
954 break;
955 }
956
957 return length;
958 }
959
960 #define MAX_LOAD_BUFFER 0x80000 // 512k
961
962 /* static buffer for small, possibly nested file loads */
963 static byte loadBuffer[MAX_LOAD_BUFFER];
964 static int loadInuse;
965 static int loadStack;
966
967 static int loadCount;
968 static int loadCountStatic;
969
970 /* very simple one-file cache for *.bsp files */
971 static byte *cachedBytes;
972 static char cachedPath[MAX_QPATH];
973 static int cachedLength;
974 static int cachedInuse;
975
976
977 /*
978 ============
979 FS_LoadFile
980
981 Filenames are relative to the quake search path
982 a null buffer will just return the file length without loading
983 ============
984 */
FS_LoadFileEx(const char * path,void ** buffer,uint32 flags)985 int FS_LoadFileEx( const char *path, void **buffer, uint32 flags ) {
986 fsFile_t *file;
987 fileHandle_t f;
988 byte *buf;
989 int length;
990
991 if( !path ) {
992 Com_Error( ERR_FATAL, "FS_LoadFile: NULL" );
993 }
994
995 if( buffer ) {
996 *buffer = NULL;
997 }
998
999 if( !fs_searchpaths ) {
1000 return -1; /* not yet initialized */
1001 }
1002
1003 path = FS_ExpandLinks( path );
1004
1005 if( !FS_ValidatePath( path ) ) {
1006 FS_DPrintf( "FS_LoadFile: refusing invalid path: %s\n", path );
1007 return -1;
1008 }
1009
1010 if( buffer && ( flags & FS_FLAG_CACHE ) ) {
1011 if( !strcmp( cachedPath, path ) ) {
1012 //Com_Printf( S_COLOR_MAGENTA"cached: %s\n", path );
1013 *buffer = cachedBytes;
1014 cachedInuse++;
1015 return cachedLength;
1016 }
1017 }
1018
1019 /* allocate new file handle */
1020 file = FS_AllocHandle( &f, path );
1021 flags &= ~FS_MODE_MASK;
1022 file->mode = flags | FS_MODE_READ;
1023
1024 /* look for it in the filesystem or pack files */
1025 length = FS_FOpenFileRead( file, qfalse );
1026 if( length == -1 ) {
1027 return -1;
1028 }
1029
1030 /* get real file length */
1031 length = FS_GetFileLength( f );
1032
1033 if( buffer ) {
1034 if( !( flags & FS_FLAG_CACHE ) && loadInuse + length < MAX_LOAD_BUFFER ) {
1035 buf = &loadBuffer[loadInuse];
1036 loadInuse += length + 1;
1037 loadInuse = ( loadInuse + 3 ) & ~3;
1038 loadStack++;
1039 loadCountStatic++;
1040 } else {
1041 //Com_Printf( S_COLOR_MAGENTA"alloc: %s\n", path );
1042 buf = FS_Malloc( length + 1 );
1043 loadCount++;
1044 }
1045 *buffer = buf;
1046
1047 FS_Read( buf, length, f );
1048 buf[length] = 0;
1049
1050 if( flags & FS_FLAG_CACHE ) {
1051 FS_FlushCache();
1052 cachedBytes = buf;
1053 cachedLength = length;
1054 strcpy( cachedPath, path );
1055 cachedInuse = 1;
1056 }
1057 }
1058
1059 FS_FCloseFile( f );
1060
1061 return length;
1062 }
1063
FS_LoadFile(const char * path,void ** buffer)1064 int FS_LoadFile( const char *path, void **buffer ) {
1065 return FS_LoadFileEx( path, buffer, 0 );
1066 }
1067
1068
1069 /*
1070 =============
1071 FS_FreeFile
1072 =============
1073 */
FS_FreeFile(void * buffer)1074 void FS_FreeFile( void *buffer ) {
1075 if( !buffer ) {
1076 Com_Error( ERR_FATAL, "FS_FreeFile: NULL" );
1077 }
1078 if( ( byte * )buffer == cachedBytes ) {
1079 if( cachedInuse == 0 ) {
1080 Com_Error( ERR_FATAL, "FS_FreeFile: cachedInuse is zero" );
1081 }
1082 cachedInuse--;
1083 return;
1084 }
1085 if( ( byte * )buffer >= loadBuffer && ( byte * )buffer < loadBuffer + MAX_LOAD_BUFFER ) {
1086 if( loadStack == 0 ) {
1087 Com_Error( ERR_FATAL, "FS_FreeFile: loadStack is zero" );
1088 }
1089 loadStack--;
1090 if( loadStack == 0 ) {
1091 loadInuse = 0;
1092 }
1093 } else {
1094 Z_Free( buffer );
1095 }
1096 }
1097
FS_FlushCache(void)1098 void FS_FlushCache( void ) {
1099 if( cachedInuse ) {
1100 Com_Error( ERR_FATAL, "FS_FlushCache: cachedInuse is nonzero" );
1101 }
1102 if( !cachedBytes ) {
1103 return;
1104 }
1105 Z_Free( cachedBytes );
1106 cachedBytes = NULL;
1107 cachedLength = 0;
1108 cachedPath[0] = 0;
1109 }
1110
1111 /*
1112 =============
1113 FS_CopyFile
1114 =============
1115 */
FS_CopyFile(const char * src,const char * dst)1116 qboolean FS_CopyFile( const char *src, const char *dst ) {
1117 fileHandle_t hSrc, hDst;
1118 byte buffer[MAX_READ];
1119 int len;
1120 int size;
1121
1122 FS_DPrintf( "FS_CopyFile( '%s', '%s' )\n", src, dst );
1123
1124 size = FS_FOpenFile( src, &hSrc, FS_MODE_READ );
1125 if( !hSrc ) {
1126 return qfalse;
1127 }
1128
1129 FS_FOpenFile( dst, &hDst, FS_MODE_WRITE );
1130 if( !hDst ) {
1131 FS_FCloseFile( hSrc );
1132 return qfalse;
1133 }
1134
1135 while( size ) {
1136 len = size;
1137 if( len > sizeof( buffer ) ) {
1138 len = sizeof( buffer );
1139 }
1140 if( !( len = FS_Read( buffer, len, hSrc ) ) ) {
1141 break;
1142 }
1143 FS_Write( buffer, len, hDst );
1144 size -= len;
1145 }
1146
1147 FS_FCloseFile( hSrc );
1148 FS_FCloseFile( hDst );
1149
1150 if( size ) {
1151 return qfalse;
1152 }
1153
1154 return qtrue;
1155 }
1156
1157 /*
1158 ================
1159 FS_RemoveFile
1160 ================
1161 */
FS_RemoveFile(const char * filename)1162 qboolean FS_RemoveFile( const char *filename ) {
1163 char path[MAX_OSPATH];
1164
1165 if( !FS_ValidatePath( filename ) ) {
1166 FS_DPrintf( "FS_RemoveFile: refusing invalid path: %s\n", filename );
1167 return qfalse;
1168 }
1169
1170 Com_sprintf( path, sizeof( path ), "%s/%s", fs_gamedir, filename );
1171 FS_ConvertToSysPath( path );
1172
1173 if( !Sys_RemoveFile( path ) ) {
1174 return qfalse;
1175 }
1176
1177 return qtrue;
1178 }
1179
1180 /*
1181 ================
1182 FS_RemoveFile
1183 ================
1184 */
FS_RenameFile(const char * from,const char * to)1185 qboolean FS_RenameFile( const char *from, const char *to ) {
1186 char frompath[MAX_OSPATH];
1187 char topath[MAX_OSPATH];
1188
1189 if( !FS_ValidatePath( from ) || !FS_ValidatePath( to ) ) {
1190 FS_DPrintf( "FS_RenameFile: %s, %s: refusing invalid path\n", from, to );
1191 return qfalse;
1192 }
1193
1194 Com_sprintf( frompath, sizeof( frompath ), "%s/%s", fs_gamedir, from );
1195 Com_sprintf( topath, sizeof( topath ), "%s/%s", fs_gamedir, to );
1196
1197 FS_ConvertToSysPath( frompath );
1198 FS_ConvertToSysPath( topath );
1199
1200 if( !Sys_RenameFile( frompath, topath ) ) {
1201 FS_DPrintf( "FS_RenameFile: Sys_RenameFile( '%s', '%s' ) failed\n", frompath, topath );
1202 return qfalse;
1203 }
1204
1205 return qtrue;
1206 }
1207
1208 /*
1209 ================
1210 FS_FPrintf
1211 ================
1212 */
FS_FPrintf(fileHandle_t f,const char * format,...)1213 void FS_FPrintf( fileHandle_t f, const char *format, ... ) {
1214 va_list argptr;
1215 char string[MAXPRINTMSG];
1216 int len;
1217
1218 va_start( argptr, format );
1219 len = Q_vsnprintf( string, sizeof( string ), format, argptr );
1220 va_end( argptr );
1221
1222 FS_Write( string, len, f );
1223 }
1224
1225 /*
1226 =================
1227 FS_LoadPakFile
1228
1229 Takes an explicit (not game tree related) path to a pak file.
1230
1231 Loads the header and directory, adding the files at the beginning
1232 of the list so they override previous pack files.
1233 =================
1234 */
FS_LoadPakFile(const char * packfile)1235 static pack_t *FS_LoadPakFile( const char *packfile ) {
1236 dpackheader_t header;
1237 int i;
1238 packfile_t *file;
1239 dpackfile_t *dfile;
1240 int numpackfiles;
1241 char *names;
1242 int namesLength;
1243 pack_t *pack;
1244 FILE *packhandle;
1245 dpackfile_t info[MAX_FILES_IN_PACK];
1246 int hashSize;
1247 uint32 hash;
1248 int len;
1249
1250 packhandle = fopen( packfile, "rb" );
1251 if( !packhandle ) {
1252 Com_WPrintf( "Couldn't open %s\n", packfile );
1253 return NULL;
1254 }
1255
1256 fread( &header, 1, sizeof( header ), packhandle );
1257
1258 if( LittleLong( header.ident ) != IDPAKHEADER ) {
1259 Com_WPrintf( "%s is not a 'PACK' file\n", packfile );
1260 goto fail;
1261 }
1262 header.dirofs = LittleLong( header.dirofs );
1263 header.dirlen = LittleLong( header.dirlen );
1264
1265 if( header.dirlen % sizeof( dpackfile_t ) ) {
1266 Com_WPrintf( "%s has bad directory length\n", packfile );
1267 goto fail;
1268 }
1269
1270 numpackfiles = header.dirlen / sizeof( dpackfile_t );
1271
1272 if( numpackfiles > MAX_FILES_IN_PACK ) {
1273 Com_WPrintf( "%s has too many files, %i > %i\n", packfile, numpackfiles, MAX_FILES_IN_PACK );
1274 //if( numpackfiles > MAX_FILES_IN_PK2 ) {
1275 goto fail;
1276 //}
1277 }
1278
1279 fseek( packhandle, header.dirofs, SEEK_SET );
1280 fread( info, 1, header.dirlen, packhandle );
1281
1282 namesLength = 0;
1283 for( i = 0, dfile = info; i < numpackfiles; i++, dfile++ ) {
1284 dfile->name[sizeof( dfile->name ) - 1] = 0;
1285 namesLength += strlen( dfile->name ) + 1;
1286 }
1287
1288 hashSize = Q_CeilPowerOfTwo( numpackfiles );
1289 if( hashSize > 32 ) {
1290 hashSize >>= 1;
1291 }
1292
1293 len = strlen( packfile );
1294 pack = FS_Malloc( sizeof( pack_t ) +
1295 numpackfiles * sizeof( packfile_t ) +
1296 hashSize * sizeof( packfile_t * ) +
1297 namesLength + len );
1298 strcpy( pack->filename, packfile );
1299 pack->fp = packhandle;
1300 pack->zFile = NULL;
1301 pack->numfiles = numpackfiles;
1302 pack->hashSize = hashSize;
1303 pack->files = ( packfile_t * )( ( byte * )pack + sizeof( pack_t ) + len );
1304 pack->fileHash = ( packfile_t ** )( pack->files + numpackfiles );
1305 names = ( char * )( pack->fileHash + hashSize );
1306 memset( pack->fileHash, 0, hashSize * sizeof( packfile_t * ) );
1307
1308 // parse the directory
1309 for( i = 0, file = pack->files, dfile = info; i < pack->numfiles; i++, file++, dfile++ ) {
1310 len = strlen( dfile->name ) + 1;
1311 file->name = names;
1312
1313 strcpy( file->name, dfile->name );
1314 Q_strlwr( file->name );
1315
1316 file->filepos = LittleLong( dfile->filepos );
1317 file->filelen = LittleLong( dfile->filelen );
1318
1319 hash = Com_HashPath( file->name, hashSize );
1320 file->hashNext = pack->fileHash[hash];
1321 pack->fileHash[hash] = file;
1322
1323 names += len;
1324 }
1325
1326 FS_DPrintf( "Added pakfile '%s', %d files, %d hash table entries\n",
1327 packfile, numpackfiles, hashSize );
1328
1329 return pack;
1330
1331 fail:
1332 fclose( packhandle );
1333 return NULL;
1334 }
1335
1336
1337
1338 /*
1339 =================
1340 FS_LoadZipFile
1341 =================
1342 */
FS_LoadZipFile(const char * packfile)1343 static pack_t *FS_LoadZipFile( const char *packfile ) {
1344 int i;
1345 packfile_t *file;
1346 char *names;
1347 int numFiles;
1348 pack_t *pack;
1349 unzFile zFile;
1350 unz_global_info zGlobalInfo;
1351 unz_file_info zInfo;
1352 char name[MAX_QPATH];
1353 int namesLength;
1354 int hashSize;
1355 uint32 hash;
1356 int len;
1357
1358 zFile = unzOpen( packfile );
1359 if( !zFile ) {
1360 Com_WPrintf( "unzOpen() failed on %s\n", packfile );
1361 return NULL;
1362 }
1363
1364 if( unzGetGlobalInfo( zFile, &zGlobalInfo ) != UNZ_OK ) {
1365 Com_WPrintf( "unzGetGlobalInfo() failed on %s\n", packfile );
1366 goto fail;
1367 }
1368
1369 numFiles = zGlobalInfo.number_entry;
1370 if( numFiles > MAX_FILES_IN_PK2 ) {
1371 Com_WPrintf( "%s has too many files, %i > %i\n", packfile, numFiles, MAX_FILES_IN_PK2 );
1372 goto fail;
1373 }
1374
1375 if( unzGoToFirstFile( zFile ) != UNZ_OK ) {
1376 Com_WPrintf( "unzGoToFirstFile() failed on %s\n", packfile );
1377 goto fail;
1378 }
1379
1380 namesLength = 0;
1381 for( i = 0; i < numFiles; i++ ) {
1382 if( unzGetCurrentFileInfo( zFile, &zInfo, name, sizeof( name ), NULL, 0, NULL, 0 ) != UNZ_OK ) {
1383 Com_WPrintf( "unzGetCurrentFileInfo() failed on %s\n", packfile );
1384 goto fail;
1385 }
1386
1387 namesLength += strlen( name ) + 1;
1388
1389 if( i != numFiles - 1 && unzGoToNextFile( zFile ) != UNZ_OK ) {
1390 Com_WPrintf( "unzGoToNextFile() failed on %s\n", packfile );
1391
1392 }
1393 }
1394
1395 hashSize = Q_CeilPowerOfTwo( numFiles );
1396 if( hashSize > 32 ) {
1397 hashSize >>= 1;
1398 }
1399
1400 len = strlen( packfile );
1401 len = ( len + 3 ) & ~3;
1402 pack = FS_Malloc( sizeof( pack_t ) +
1403 numFiles * sizeof( packfile_t ) +
1404 hashSize * sizeof( packfile_t * ) +
1405 namesLength + len );
1406 strcpy( pack->filename, packfile );
1407 pack->zFile = zFile;
1408 pack->fp = NULL;
1409 pack->numfiles = numFiles;
1410 pack->hashSize = hashSize;
1411 pack->files = ( packfile_t * )( ( byte * )pack + sizeof( pack_t ) + len );
1412 pack->fileHash = ( packfile_t ** )( pack->files + numFiles );
1413 names = ( char * )( pack->fileHash + hashSize );
1414 memset( pack->fileHash, 0, hashSize * sizeof( packfile_t * ) );
1415
1416 // parse the directory
1417 unzGoToFirstFile( zFile );
1418
1419 for( i = 0, file = pack->files; i < numFiles; i++, file++ ) {
1420 unzGetCurrentFileInfo( zFile, &zInfo, name, sizeof( name ), NULL, 0, NULL, 0 );
1421 Q_strlwr( name );
1422
1423 len = strlen( name ) + 1;
1424 file->name = names;
1425
1426 strcpy( file->name, name );
1427 file->filepos = unzGetCurrentFileInfoPosition( zFile );
1428 file->filelen = zInfo.uncompressed_size;
1429
1430 hash = Com_HashPath( file->name, hashSize );
1431 file->hashNext = pack->fileHash[hash];
1432 pack->fileHash[hash] = file;
1433
1434 names += len;
1435
1436 if( i != numFiles - 1 ) {
1437 unzGoToNextFile( zFile );
1438 }
1439 }
1440
1441 FS_DPrintf( "Added zipfile '%s', %d files, %d hash table entries\n",
1442 packfile, numFiles, hashSize );
1443
1444 return pack;
1445
1446 fail:
1447 unzClose( zFile );
1448 return NULL;
1449 }
1450
pakcmp(const void * p1,const void * p2)1451 static int pakcmp( const void *p1, const void *p2 ) {
1452 const char *s1 = *( const char ** )p1;
1453 const char *s2 = *( const char ** )p2;
1454
1455 if( !strncmp( s1, "pak", 3 ) ) {
1456 if( strncmp( s2, "pak", 3 ) ) {
1457 return -1;
1458 }
1459 } else if( !strncmp( s2, "pak", 3 ) ) {
1460 return 1;
1461 }
1462
1463 return strcmp( s1, s2 );
1464 }
1465
FS_LoadPackFiles(const char * extension,pack_t * (loadfunc)(const char *))1466 static void FS_LoadPackFiles( const char *extension, pack_t *(loadfunc)( const char * ) ) {
1467 int i;
1468 searchpath_t *search;
1469 pack_t *pak;
1470 char ** list;
1471 int numFiles;
1472 char path[MAX_OSPATH];
1473
1474 list = Sys_ListFiles( fs_gamedir, extension, FS_SEARCH_NOSORT, &numFiles );
1475 if( !list ) {
1476 return;
1477 }
1478 qsort( list, numFiles, sizeof( list[0] ), pakcmp );
1479 for( i = 0; i < numFiles; i++ ) {
1480 Com_sprintf( path, sizeof( path ), "%s/%s", fs_gamedir, list[i] );
1481 pak = (*loadfunc)( path );
1482 if( !pak )
1483 continue;
1484 search = FS_Malloc( sizeof( searchpath_t ) );
1485 search->filename[0] = 0;
1486 search->pack = pak;
1487 search->next = fs_searchpaths;
1488 fs_searchpaths = search;
1489 }
1490
1491 Sys_FreeFileList( list );
1492
1493 }
1494
1495 /*
1496 ================
1497 FS_AddGameDirectory
1498
1499 Sets fs_gamedir, adds the directory to the head of the path,
1500 then loads and adds pak*.pak, then anything else in alphabethical order.
1501 ================
1502 */
FS_AddGameDirectory(const char * fmt,...)1503 void FS_AddGameDirectory( const char *fmt, ... ) {
1504 va_list argptr;
1505 searchpath_t *search;
1506 int length;
1507
1508 va_start( argptr, fmt );
1509 Q_vsnprintf( fs_gamedir, sizeof( fs_gamedir ), fmt, argptr );
1510 va_end( argptr );
1511
1512 #ifdef _WIN32
1513 Com_ReplaceSeparators( fs_gamedir, '/' );
1514 Q_strlwr( fs_gamedir );
1515 #endif
1516
1517 //
1518 // add the directory to the search path
1519 //
1520 if( !( fs_restrict_mask->integer & 1 ) ) {
1521 length = strlen( fs_gamedir );
1522 search = FS_Malloc( sizeof( searchpath_t ) + length );
1523 search->pack = NULL;
1524 strcpy( search->filename, fs_gamedir );
1525 search->next = fs_searchpaths;
1526 fs_searchpaths = search;
1527 }
1528
1529 //
1530 // add any pak files in the format *.pak
1531 //
1532 if( !( fs_restrict_mask->integer & 2 ) ) {
1533 FS_LoadPackFiles( ".pak", FS_LoadPakFile );
1534 }
1535
1536 //
1537 // add any zip files in the format *.pk2
1538 //
1539 if( !( fs_restrict_mask->integer & 4 ) ) {
1540 FS_LoadPackFiles( ".pk2", FS_LoadZipFile );
1541 }
1542
1543 }
1544
1545 #ifdef __unix__
1546 /*
1547 ================
1548 FS_AddHomeAsGameDirectory
1549
1550 Adds ~/.q2pro/<dir> as a game directory.
1551 ================
1552 */
FS_AddHomeAsGameDirectory(char * dir)1553 void FS_AddHomeAsGameDirectory(char *dir)
1554 {
1555 char *homedir; /* Home directory. */
1556
1557 if ((homedir = getenv("HOME")) != NULL)
1558 FS_AddGameDirectory("%s/"HOMEDIRNAME"/%s", homedir, dir);
1559 }
1560 #endif
1561
1562 /*
1563 =================
1564 FS_GetModList
1565 =================
1566 */
1567 #define MAX_LISTED_MODS 32
1568
FS_GetModList(char ** list,int * count)1569 static void FS_GetModList( char **list, int *count ) {
1570 char path[MAX_OSPATH];
1571 FILE *fp;
1572 char **dirlist;
1573 int numDirs;
1574 int i;
1575
1576 if( !( dirlist = Sys_ListFiles( fs_basedir->string, NULL, FS_SEARCHDIRS_ONLY, &numDirs ) ) ) {
1577 return;
1578 }
1579
1580 for( i = 0; i < numDirs; i++ ) {
1581 if( !strcmp( dirlist[i], BASEDIRNAME ) ) {
1582 continue;
1583 }
1584
1585 #ifdef _WIN32
1586 Com_sprintf( path, sizeof( path ), "%s/%s/gamex86.dll", fs_basedir->string, dirlist[i] );
1587 #else
1588 Com_sprintf( path, sizeof( path ), "%s/%s/game.so", fs_basedir->string, dirlist[i] );
1589 #endif
1590
1591 if( !( fp = fopen( path, "rb" ) ) ) {
1592 continue;
1593 }
1594 fclose( fp );
1595
1596 Com_sprintf( path, sizeof( path ), "%s/%s/description.txt", fs_basedir->string, dirlist[i] );
1597
1598 if( ( fp = fopen( path, "r" ) ) != NULL ) {
1599 Q_strncpyz( path, va( "%s\n", dirlist[i] ), sizeof( path ) - MAX_QPATH );
1600 fgets( path + strlen( path ), MAX_QPATH, fp );
1601 fclose( fp );
1602 list[*count] = FS_CopyString( path );
1603 } else {
1604 list[*count] = FS_CopyString( dirlist[i] );
1605 }
1606
1607 if( (*count)++ == MAX_LISTED_MODS ) {
1608 break;
1609 }
1610 }
1611
1612 Sys_FreeFileList( dirlist );
1613 }
1614
1615 /*
1616 =================
1617 FS_CopyExtraInfo
1618 =================
1619 */
FS_CopyExtraInfo(const char * name,const fsFileInfo_t * info)1620 char *FS_CopyExtraInfo( const char *name, const fsFileInfo_t *info ) {
1621 char *out;
1622 int length;
1623
1624 if( !name ) {
1625 return NULL;
1626 }
1627
1628 length = strlen( name ) + 1;
1629
1630 out = FS_Malloc( sizeof( *info ) + length );
1631 strcpy( out, name );
1632
1633 memcpy( out + length, info, sizeof( *info ) );
1634
1635 return out;
1636 }
1637
FS_WildCmp_r(const char * filter,const char * string)1638 static int FS_WildCmp_r( const char *filter, const char *string ) {
1639 switch( *filter ) {
1640 case '\0':
1641 case ';':
1642 return !*string;
1643
1644 case '*':
1645 return FS_WildCmp_r( filter + 1, string ) || (*string && FS_WildCmp_r( filter, string + 1 ));
1646
1647 case '?':
1648 return *string && FS_WildCmp_r( filter + 1, string + 1 );
1649
1650 default:
1651 return ((*filter == *string) || (Q_toupper( *filter ) == Q_toupper( *string ))) && FS_WildCmp_r( filter + 1, string + 1 );
1652 }
1653 }
1654
1655
1656
FS_WildCmp(const char * filter,const char * string)1657 int FS_WildCmp( const char *filter, const char *string ) {
1658 do {
1659 if( FS_WildCmp_r( filter, string ) ) {
1660 return 1;
1661 }
1662 filter = strchr( filter, ';' );
1663 if( filter ) filter++;
1664 } while( filter );
1665
1666 return 0;
1667 }
1668
1669
1670
1671 /*
1672 =================
1673 FS_ListFiles
1674 =================
1675 */
FS_ListFiles(const char * path,const char * extension,uint32 flags,int * numFiles)1676 char **FS_ListFiles( const char *path, const char *extension,
1677 uint32 flags, int *numFiles )
1678 {
1679 searchpath_t *search;
1680 char *listedFiles[MAX_LISTED_FILES];
1681 int count, total;
1682 char buffer[MAX_QPATH];
1683 char **dirlist;
1684 int numFilesInDir;
1685 char **list;
1686 int i, length;
1687 char *name, *filename;
1688 fsFileInfo_t info;
1689
1690 if( flags & FS_SEARCH_BYFILTER ) {
1691 if( !extension ) {
1692 Com_Error( ERR_FATAL, "FS_ListFiles: NULL filter" );
1693 }
1694 }
1695
1696 if( !path ) {
1697 path = "";
1698 }
1699
1700 count = 0;
1701
1702 if( numFiles ) {
1703 *numFiles = 0;
1704 }
1705
1706 if( !strcmp( path, "$modlist" ) ) {
1707 FS_GetModList( listedFiles, &count );
1708 } else {
1709 switch( flags & FS_PATH_MASK ) {
1710 case FS_PATH_BASE:
1711 search = fs_base_searchpaths;
1712 break;
1713 default:
1714 search = fs_searchpaths;
1715 break;
1716 }
1717
1718 memset( &info, 0, sizeof( info ) );
1719
1720 for( ; search; search = search->next ) {
1721 if( search->pack ) {
1722 if( ( flags & FS_TYPE_MASK ) == FS_TYPE_REAL ) {
1723 /* don't search in paks */
1724 continue;
1725 }
1726
1727 // TODO: add directory search support for pak files
1728 if( ( flags & FS_SEARCHDIRS_MASK ) == FS_SEARCHDIRS_ONLY ) {
1729 continue;
1730 }
1731 if( ( flags & FS_PATH_MASK ) == FS_PATH_GAME ) {
1732 if( fs_searchpaths != fs_base_searchpaths && search == fs_base_searchpaths ) {
1733 /* consider baseq2 a gamedir if no gamedir loaded */
1734 break;
1735 }
1736 }
1737 if( flags & FS_SEARCH_BYFILTER ) {
1738 for( i = 0; i < search->pack->numfiles; i++ ) {
1739 name = search->pack->files[i].name;
1740
1741 // check path
1742 filename = name;
1743 if( *path ) {
1744 length = strlen( path );
1745 if( strncmp( name, path, length ) ) {
1746 continue;
1747 }
1748 filename += length + 1;
1749 }
1750
1751 // check filter
1752 if( !FS_WildCmp( extension, filename ) ) {
1753 continue;
1754 }
1755
1756 // copy filename
1757 if( count == MAX_LISTED_FILES ) {
1758 break;
1759 }
1760
1761 if( !( flags & FS_SEARCH_SAVEPATH ) ) {
1762 filename = COM_SkipPath( filename );
1763 }
1764 if( flags & FS_SEARCH_EXTRAINFO ) {
1765 info.fileSize = search->pack->files[i].filelen;
1766 listedFiles[count++] = FS_CopyExtraInfo( filename, &info );
1767 } else {
1768 listedFiles[count++] = FS_CopyString( filename );
1769 }
1770 }
1771 } else {
1772 for( i = 0; i < search->pack->numfiles; i++ ) {
1773 name = search->pack->files[i].name;
1774
1775 // check path
1776 if( *path ) {
1777 COM_FilePath( name, buffer, sizeof( buffer ) );
1778 if( strcmp( path, buffer ) ) {
1779 continue;
1780 }
1781 }
1782
1783 // check extension
1784 if( extension && strcmp( extension, COM_FileExtension( name ) ) ) {
1785 continue;
1786 }
1787
1788 // copy filename
1789 if( count == MAX_LISTED_FILES ) {
1790 break;
1791 }
1792 if( !( flags & FS_SEARCH_SAVEPATH ) ) {
1793 name = COM_SkipPath( name );
1794 }
1795 if( flags & FS_SEARCH_EXTRAINFO ) {
1796 info.fileSize = search->pack->files[i].filelen;
1797 listedFiles[count++] = FS_CopyExtraInfo( name, &info );
1798 } else {
1799 listedFiles[count++] = FS_CopyString( name );
1800 }
1801 }
1802 }
1803 } else {
1804 if( ( flags & FS_TYPE_MASK ) == FS_TYPE_PAK ) {
1805 /* don't search in OS filesystem */
1806 continue;
1807 }
1808
1809 Q_strncpyz( buffer, search->filename, sizeof( buffer ) );
1810 if( *path ) {
1811 Q_strcat( buffer, sizeof( buffer ), "/" );
1812 Q_strcat( buffer, sizeof( buffer ), path );
1813 }
1814
1815 if( flags & FS_SEARCH_BYFILTER ) {
1816 dirlist = Sys_ListFiles( buffer, extension, flags|FS_SEARCH_NOSORT, &numFilesInDir );
1817 } else {
1818 dirlist = Sys_ListFiles( buffer, extension, flags|FS_SEARCH_NOSORT, &numFilesInDir );
1819 }
1820
1821 if( !dirlist ) {
1822 continue;
1823 }
1824
1825 for( i = 0; i < numFilesInDir; i++ ) {
1826 if( count == MAX_LISTED_FILES ) {
1827 break;
1828 }
1829 name = dirlist[i];
1830 if( ( flags & FS_SEARCH_SAVEPATH ) && !( flags & FS_SEARCH_BYFILTER ) ) {
1831 // skip search path
1832 name += strlen( search->filename ) + 1;
1833 }
1834 if( flags & FS_SEARCH_EXTRAINFO ) {
1835 listedFiles[count++] = FS_CopyExtraInfo( name, ( fsFileInfo_t * )( dirlist[i] + strlen( dirlist[i] ) + 1 ) );
1836 } else {
1837 listedFiles[count++] = FS_CopyString( name );
1838 }
1839 }
1840 Sys_FreeFileList( dirlist );
1841
1842 }
1843 if( count == MAX_LISTED_FILES ) {
1844 break;
1845 }
1846 }
1847 }
1848
1849 if( !count ) {
1850 return NULL;
1851 }
1852
1853 // sort alphabetically (ignoring FS_SEARCH_NOSORT)
1854 qsort( listedFiles, count, sizeof( listedFiles[0] ), SortStrcmp );
1855
1856 // remove duplicates
1857 total = 1;
1858 for( i = 1; i < count; i++ ) {
1859 if( !strcmp( listedFiles[i-1], listedFiles[i] ) ) {
1860 Z_Free( listedFiles[i-1] );
1861 listedFiles[i-1] = NULL;
1862 } else {
1863 total++;
1864 }
1865 }
1866
1867 list = FS_Malloc( sizeof( char * ) * ( total + 1 ) );
1868
1869 total = 0;
1870 for( i = 0; i < count; i++ ) {
1871 if( listedFiles[i] ) {
1872 list[total++] = listedFiles[i];
1873 }
1874 }
1875 list[total] = NULL;
1876
1877 if( numFiles ) {
1878 *numFiles = total;
1879 }
1880
1881 return list;
1882
1883
1884 }
1885
1886 /*
1887 =================
1888 FS_FreeFileList
1889 =================
1890 */
FS_FreeFileList(char ** list)1891 void FS_FreeFileList( char **list ) {
1892 char **p;
1893
1894 if( !list ) {
1895 Com_Error( ERR_FATAL, "FS_FreeFileList: NULL" );
1896 }
1897
1898 p = list;
1899 while( *p ) {
1900 Z_Free( *p++ );
1901 }
1902
1903 Z_Free( list );
1904 }
1905
1906 /*
1907 =================
1908 FS_CopyFile_f
1909
1910 extract file from *.pak, *.pk2 or *.gz
1911 =================
1912 */
FS_CopyFile_f(void)1913 static void FS_CopyFile_f( void ) {
1914 if( Cmd_Argc() < 2 ) {
1915 Com_Printf( "Usage: %s <sourcePath> <destPath>\n", Cmd_Argv( 0 ) );
1916 return;
1917 }
1918
1919 if( FS_CopyFile( Cmd_Argv( 1 ), Cmd_Argv( 2 ) ) ) {
1920 Com_Printf( "File copied successfully\n" );
1921 } else {
1922 Com_Printf( "Failed to copy file\n" );
1923 }
1924 }
1925
1926 /*
1927 ============
1928 FS_FDir_f
1929 ============
1930 */
FS_FDir_f(void)1931 static void FS_FDir_f( void ) {
1932 char **dirnames;
1933 int ndirs = 0;
1934 int i;
1935 char *filter;
1936
1937 if( Cmd_Argc() < 2 ) {
1938 Com_Printf( "Usage: %s <filter> [fullPath]\n", Cmd_Argv( 0 ) );
1939 return;
1940 }
1941
1942 filter = Cmd_Argv( 1 );
1943
1944 i = FS_SEARCH_BYFILTER;
1945 if( Cmd_Argc() > 2 ) {
1946 i |= FS_SEARCH_SAVEPATH;
1947 }
1948
1949 if( ( dirnames = FS_ListFiles( NULL, filter, i, &ndirs ) ) != NULL ) {
1950 for( i = 0; i < ndirs; i++ ) {
1951 Com_Printf( "%s\n", dirnames[i] );
1952 }
1953 FS_FreeFileList( dirnames );
1954 }
1955 Com_Printf( "%i files listed\n", ndirs );
1956
1957 }
1958
1959 /*
1960 ============
1961 FS_Dir_f
1962 ============
1963 */
FS_Dir_f(void)1964 static void FS_Dir_f( void ) {
1965 char **dirnames;
1966 int ndirs = 0;
1967 int i;
1968 char *ext;
1969
1970 if( Cmd_Argc() < 2 ) {
1971 Com_Printf( "Usage: %s <directory> [.extension]\n", Cmd_Argv( 0 ) );
1972 return;
1973 }
1974 if( Cmd_Argc() > 2 ) {
1975 ext = Cmd_Argv( 2 );
1976 } else {
1977 ext = NULL;
1978 }
1979 dirnames = FS_ListFiles( Cmd_Argv( 1 ), ext, 0, &ndirs );
1980 if( dirnames ) {
1981 for( i = 0; i < ndirs; i++ ) {
1982 Com_Printf( "%s\n", dirnames[i] );
1983 }
1984 FS_FreeFileList( dirnames );
1985 }
1986 Com_Printf( "%i files listed\n", ndirs );
1987
1988 }
1989
1990 /*
1991 ============
1992 FS_WhereIs_f
1993 ============
1994 */
FS_WhereIs_f(void)1995 static void FS_WhereIs_f( void ) {
1996 searchpath_t *search;
1997 pack_t *pak;
1998 packfile_t *entry;
1999 uint32 hash;
2000 char filename[MAX_QPATH];
2001 char fullpath[MAX_OSPATH];
2002 char *path;
2003 fsFileInfo_t info;
2004
2005 if( Cmd_Argc() < 2 ) {
2006 Com_Printf( "Usage: %s <path>\n", Cmd_Argv( 0 ) );
2007 return;
2008 }
2009
2010 Cmd_ArgvBuffer( 1, filename, sizeof( filename ) );
2011 Q_strlwr( filename );
2012
2013 path = FS_ExpandLinks( filename );
2014 if( path != filename ) {
2015 Com_Printf( "%s linked to %s\n", filename, path );
2016 }
2017
2018 hash = Com_HashPath( path, 0 );
2019
2020 for( search = fs_searchpaths; search; search = search->next ) {
2021 // is the element a pak file?
2022 if( search->pack ) {
2023 // look through all the pak file elements
2024 pak = search->pack;
2025 entry = pak->fileHash[ hash & ( pak->hashSize - 1 ) ];
2026 for( ; entry; entry = entry->hashNext ) {
2027 // both entry->name and filename should be already lowercased
2028 if( !strcmp( entry->name, path ) ) {
2029 Com_Printf( "%s/%s (%d bytes)\n", pak->filename,
2030 path, entry->filelen );
2031 return;
2032 }
2033 }
2034 } else {
2035 Com_sprintf( fullpath, sizeof( fullpath ), "%s/%s",
2036 search->filename, path );
2037 FS_ConvertToSysPath( fullpath );
2038 if( Sys_GetFileInfo( fullpath, &info ) ) {
2039 Com_Printf( "%s/%s (%d bytes)\n", search->filename, filename,
2040 info.fileSize );
2041 return;
2042 }
2043 }
2044
2045 }
2046
2047 Com_Printf( "%s was not found\n", path );
2048 }
2049
2050 /*
2051 ============
2052 FS_Path_f
2053 ============
2054 */
FS_Path_f(void)2055 void FS_Path_f( void ) {
2056 searchpath_t *s;
2057 int numFilesInPAK;
2058 int numFilesInPK2;
2059
2060 numFilesInPAK = 0;
2061 numFilesInPK2 = 0;
2062
2063 Com_Printf( "Current search path:\n" );
2064 for( s = fs_searchpaths; s; s = s->next ) {
2065 if( s->pack ) {
2066 if( s->pack->zFile ) {
2067 numFilesInPK2 += s->pack->numfiles;
2068 } else {
2069 numFilesInPAK += s->pack->numfiles;
2070 }
2071 }
2072
2073 //if( s == fs_base_searchpaths )
2074 // Com_Printf( "----------\n" );
2075 if( s->pack )
2076 Com_Printf( "%s (%i files)\n", s->pack->filename, s->pack->numfiles );
2077 else
2078 Com_Printf( "%s\n", s->filename );
2079 }
2080
2081 if( !( fs_restrict_mask->integer & 2 ) ) {
2082 Com_Printf( "%i files in PAK files\n", numFilesInPAK );
2083 }
2084
2085 if( !( fs_restrict_mask->integer & 4 ) ) {
2086 Com_Printf( "%i files in PK2 files\n", numFilesInPK2 );
2087 }
2088
2089 }
2090
2091 /*
2092 ================
2093 FS_Stats_f
2094 ================
2095 */
FS_Stats_f(void)2096 static void FS_Stats_f( void ) {
2097 searchpath_t *path;
2098 pack_t *pack, *maxpack = NULL;
2099 packfile_t *file, *max = NULL;
2100 int i;
2101 int len, maxLen = 0;
2102 int totalHashSize, totalLen;
2103
2104 totalHashSize = totalLen = 0;
2105 for( path = fs_searchpaths; path; path = path->next ) {
2106 if( !( pack = path->pack ) ) {
2107 continue;
2108 }
2109 for( i = 0; i < pack->hashSize; i++ ) {
2110 if( !( file = pack->fileHash[i] ) ) {
2111 continue;
2112 }
2113 len = 0;
2114 for( ; file ; file = file->hashNext ) {
2115 len++;
2116 }
2117 if( maxLen < len ) {
2118 max = pack->fileHash[i];
2119 maxpack = pack;
2120 maxLen = len;
2121 }
2122 totalLen += len;
2123 totalHashSize++;
2124 }
2125 //totalHashSize += pack->hashSize;
2126 }
2127
2128 Com_Printf( "LoadFile counter: %d\n", loadCount );
2129 Com_Printf( "Static LoadFile counter: %d\n", loadCountStatic );
2130
2131 if( !totalHashSize ) {
2132 Com_Printf( "No stats to display\n" );
2133 return;
2134 }
2135
2136 Com_Printf( "Maximum hash bucket length is %d, average is %.2f\n", maxLen, ( float )totalLen / totalHashSize );
2137 if( max ) {
2138 Com_Printf( "Dumping longest bucket (%s):\n", maxpack->filename );
2139 for( file = max; file ; file = file->hashNext ) {
2140 Com_Printf( "%s\n", file->name );
2141 }
2142 }
2143 }
2144
FS_Link_g(const char * partial,int state)2145 static const char *FS_Link_g( const char *partial, int state ) {
2146 static int length;
2147 static fsLink_t *link;
2148 char *name;
2149
2150 if( !state ) {
2151 length = strlen( partial );
2152 link = fs_links;
2153 }
2154
2155 while( link ) {
2156 name = link->name;
2157 link = link->next;
2158 if( !Q_stricmpn( partial, name, length ) ) {
2159 return name;
2160 }
2161 }
2162
2163 return NULL;
2164 }
2165
FS_UnLink_f(void)2166 static void FS_UnLink_f( void ) {
2167 fsLink_t *l, *next, **back;
2168 char *name;
2169
2170 if( Cmd_CheckParam( "h", "help" ) ) {
2171 usage:
2172 Com_Printf( "Usage: %s <name>\n"
2173 "Deletes the specified symbolic link.\n",
2174 Cmd_Argv( 0 ) );
2175 return;
2176 }
2177
2178 if( Cmd_CheckParam( "a", "all" ) ) {
2179 for( l = fs_links; l; l = next ) {
2180 next = l->next;
2181 Z_Free( l->target );
2182 Z_Free( l );
2183 }
2184 fs_links = NULL;
2185 return;
2186 }
2187
2188 if( Cmd_Argc() != 2 ) {
2189 goto usage;
2190 }
2191
2192 name = Cmd_Argv( 1 );
2193 for( l = fs_links, back = &fs_links; l; l = l->next ) {
2194 if( !Q_stricmp( l->name, name ) ) {
2195 break;
2196 }
2197 back = &l->next;
2198 }
2199
2200 if( !l ) {
2201 Com_Printf( "Symbolic link '%s' does not exist.\n", name );
2202 return;
2203 }
2204
2205 *back = l->next;
2206 Z_Free( l->target );
2207 Z_Free( l );
2208 }
2209
FS_Link_f(void)2210 static void FS_Link_f( void ) {
2211 int argc, length, count;
2212 fsLink_t *l;
2213 char *name, *target;
2214
2215 argc = Cmd_Argc();
2216 if( argc == 1 ) {
2217 for( l = fs_links, count = 0; l; l = l->next, count++ ) {
2218 Com_Printf( "%s --> %s\n", l->name, l->target );
2219 }
2220 Com_Printf( "------------------\n"
2221 "%d symbolic links listed.\n", count );
2222 return;
2223 }
2224 if( Cmd_CheckParam( "h", "help" ) || argc != 3 ) {
2225 Com_Printf( "Usage: %s <name> <target>\n"
2226 "Creates symbolic link to target with the specified name.\n"
2227 "Virtual quake paths are accepted.\n"
2228 "Links are effective only for reading.\n",
2229 Cmd_Argv( 0 ) );
2230 return;
2231 }
2232
2233 name = Cmd_Argv( 1 );
2234 for( l = fs_links; l; l = l->next ) {
2235 if( !Q_stricmp( l->name, name ) ) {
2236 break;
2237 }
2238 }
2239
2240 if( !l ) {
2241 length = strlen( name );
2242 l = FS_Malloc( sizeof( *l ) + length );
2243 strcpy( l->name, name );
2244 l->nameLength = length;
2245 l->next = fs_links;
2246 fs_links = l;
2247 } else {
2248 Z_Free( l->target );
2249 }
2250
2251 target = Cmd_Argv( 2 );
2252 l->target = FS_CopyString( target );
2253 l->targetLength = strlen( target );
2254 }
2255
2256 /*
2257 ================
2258 FS_NextPath
2259
2260 Allows enumerating all of the directories in the search path
2261 ================
2262 */
FS_NextPath(char * prevpath)2263 char *FS_NextPath (char *prevpath)
2264 {
2265 searchpath_t *s;
2266 char *prev;
2267
2268 if (!prevpath)
2269 return fs_gamedir;
2270
2271 prev = fs_gamedir;
2272 for (s=fs_searchpaths ; s ; s=s->next)
2273 {
2274 if (s->pack)
2275 continue;
2276 if (prevpath == prev)
2277 return s->filename;
2278 prev = s->filename;
2279 }
2280
2281 return NULL;
2282 }
2283
2284
2285
2286 /*
2287 ============
2288 FS_Gamedir
2289
2290 Called to find where to write a file (demos, savegames, etc)
2291 ============
2292 */
FS_Gamedir(void)2293 char *FS_Gamedir( void ) {
2294 return fs_gamedir;
2295 }
2296
FS_FreeSearchPath(searchpath_t * path)2297 static void FS_FreeSearchPath( searchpath_t *path ) {
2298 pack_t *pak;
2299
2300 if( ( pak = path->pack ) != NULL ) {
2301 if( pak->zFile ) {
2302 unzClose( pak->zFile );
2303 } else {
2304 fclose( pak->fp );
2305 }
2306 Z_Free( pak );
2307 }
2308
2309 Z_Free( path );
2310 }
2311
2312 /*
2313 ================
2314 FS_Shutdown
2315 ================
2316 */
FS_Shutdown(qboolean total)2317 void FS_Shutdown( qboolean total ) {
2318 searchpath_t *path, *next;
2319 fsLink_t *l, *nextLink;
2320 fsFile_t *file;
2321 int i;
2322
2323 if( !fs_searchpaths ) {
2324 return;
2325 }
2326
2327 if( total ) {
2328 /* close file handles */
2329 for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) {
2330 if( file->type != FS_FREE ) {
2331 Com_WPrintf( "FS_Shutdown: closing handle %i: '%s'\n",
2332 i + 1, file->name );
2333 FS_FCloseFile( i + 1 );
2334 }
2335 }
2336
2337 /* free symbolic links */
2338 for( l = fs_links; l; l = nextLink ) {
2339 nextLink = l->next;
2340 Z_Free( l->target );
2341 Z_Free( l );
2342 }
2343
2344 fs_links = NULL;
2345 }
2346
2347 /* free search paths */
2348 for( path = fs_searchpaths; path; path = next ) {
2349 next = path->next;
2350 FS_FreeSearchPath( path );
2351 }
2352
2353 fs_searchpaths = NULL;
2354
2355 FS_FlushCache();
2356
2357 if( total ) {
2358 Z_LeakTest( TAG_FILESYSTEM );
2359 }
2360
2361 Cmd_RemoveCommand( "path" );
2362 Cmd_RemoveCommand( "fdir" );
2363 Cmd_RemoveCommand( "dir" );
2364 Cmd_RemoveCommand( "copyfile" );
2365 Cmd_RemoveCommand( "fs_stats" );
2366 Cmd_RemoveCommand( "link" );
2367 Cmd_RemoveCommand( "unlink" );
2368 }
2369
2370 /*
2371 ================
2372 FS_DefaultGamedir
2373 ================
2374 */
FS_DefaultGamedir(void)2375 static void FS_DefaultGamedir( void ) {
2376 #ifdef __unix__
2377 FS_AddHomeAsGameDirectory(BASEDIRNAME);
2378 #else
2379 /* write to baseq2pro on Windows */
2380 Com_sprintf( fs_gamedir, sizeof( fs_gamedir ), "%s/"INITDIRNAME,
2381 fs_basedir->string );
2382
2383 Cvar_Set( "game", "" );
2384 Cvar_Set( "gamedir", "" );
2385 #endif
2386 }
2387
2388
2389 /*
2390 ================
2391 FS_SetupGamedir
2392
2393 Sets the gamedir and path to a different directory.
2394 ================
2395 */
FS_SetupGamedir(void)2396 static void FS_SetupGamedir( void ) {
2397 fs_game = Cvar_Get( "game", "", CVAR_LATCHED|CVAR_SERVERINFO );
2398 fs_game->subsystem = CVAR_SYSTEM_FILES;
2399
2400 if( !fs_game->string[0] || !FS_strcmp( fs_game->string, BASEDIRNAME ) ||
2401 !FS_strcmp( fs_game->string, INITDIRNAME ) )
2402 {
2403 FS_DefaultGamedir();
2404 return;
2405 }
2406
2407 if( !FS_ValidatePath( fs_game->string ) || strchr( fs_game->string, '/' ) ||
2408 strchr( fs_game->string, '\\' ) || strchr( fs_game->string, ':' ) )
2409 {
2410 Com_WPrintf( "Gamedir should be a single filename, not a path. "
2411 "Falling back to default.\n" );
2412 FS_DefaultGamedir();
2413 return;
2414 }
2415
2416 // this one is left for compatibility with server browsers, etc
2417 Cvar_FullSet( "gamedir", fs_game->string, CVAR_ROM|CVAR_SERVERINFO,
2418 CVAR_SET_DIRECT );
2419
2420 if( fs_cddir->string[0] )
2421 FS_AddGameDirectory( "%s/%s", fs_cddir->string, fs_game->string );
2422
2423 FS_AddGameDirectory("%s/%s", DATADIR, fs_game->string);
2424 FS_AddGameDirectory("%s/%s", LIBDIR, fs_game->string);
2425 FS_AddGameDirectory( "%s/%s", fs_basedir->string, fs_game->string );
2426 FS_AddHomeAsGameDirectory(fs_game->string);
2427
2428 }
2429
FS_SafeToRestart(void)2430 qboolean FS_SafeToRestart( void ) {
2431 fsFile_t *file;
2432 int i;
2433
2434 /* make sure no files are opened for reading */
2435 for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) {
2436 if( file->type == FS_FREE ) {
2437 continue;
2438 }
2439 if( file->mode == FS_MODE_READ ) {
2440 return qfalse;
2441 }
2442 }
2443
2444 return qtrue;
2445 }
2446
2447 /*
2448 ================
2449 FS_Restart
2450 ================
2451 */
FS_Restart(void)2452 void FS_Restart( void ) {
2453 fsFile_t *file;
2454 int i;
2455 fileHandle_t temp;
2456 searchpath_t *path, *next;
2457
2458 Com_Printf( "---------- FS_Restart ----------\n" );
2459
2460 /* temporary disable logfile */
2461 temp = com_logFile;
2462 com_logFile = 0;
2463
2464 /* make sure no files are opened for reading */
2465 for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) {
2466 if( file->type == FS_FREE ) {
2467 continue;
2468 }
2469 if( file->mode == FS_MODE_READ ) {
2470 Com_Error( ERR_FATAL, "FS_Restart: closing handle %i: %s",
2471 i + 1, file->name );
2472 }
2473 }
2474
2475 if( fs_restrict_mask->latched_string ) {
2476 /* perform full reset */
2477 FS_Shutdown( qfalse );
2478 FS_Init();
2479 } else {
2480 /* just change gamedir */
2481 for( path = fs_searchpaths; path != fs_base_searchpaths; path = next ) {
2482 next = path->next;
2483 FS_FreeSearchPath( path );
2484 }
2485
2486 fs_searchpaths = fs_base_searchpaths;
2487
2488 FS_SetupGamedir();
2489 FS_Path_f();
2490 }
2491
2492 /* re-enable logfile */
2493 com_logFile = temp;
2494
2495 Com_Printf( "--------------------------------\n" );
2496 }
2497
2498 /*
2499 ================
2500 FS_FillAPI
2501 ================
2502 */
FS_FillAPI(fsAPI_t * api)2503 void FS_FillAPI( fsAPI_t *api ) {
2504 api->LoadFile = FS_LoadFile;
2505 api->LoadFileEx = FS_LoadFileEx;
2506 api->FreeFile = FS_FreeFile;
2507 api->FOpenFile = FS_FOpenFile;
2508 api->FCloseFile = FS_FCloseFile;
2509 api->Tell = FS_Tell;
2510 api->RawTell = FS_RawTell;
2511 api->Read = FS_Read;
2512 api->Write = FS_Write;
2513 api->ListFiles = FS_ListFiles;
2514 api->FreeFileList = FS_FreeFileList;
2515 }
2516
2517 /*
2518 ================
2519 FS_Init
2520 ================
2521 */
FS_Init(void)2522 void FS_Init( void ) {
2523 int startTime, i;
2524
2525 startTime = Sys_Milliseconds();
2526
2527 Com_Printf( "---------- FS_Init ----------\n" );
2528
2529 Cmd_AddCommand( "path", FS_Path_f );
2530 Cmd_AddCommand( "fdir", FS_FDir_f );
2531 Cmd_AddCommand( "dir", FS_Dir_f );
2532 Cmd_AddCommand( "copyfile", FS_CopyFile_f );
2533 Cmd_AddCommand( "fs_stats", FS_Stats_f );
2534 Cmd_AddCommand( "whereis", FS_WhereIs_f );
2535 Cmd_AddCommandEx( "link", FS_Link_f, FS_Link_g );
2536 Cmd_AddCommandEx( "unlink", FS_UnLink_f, FS_Link_g );
2537
2538 fs_debug = Cvar_Get( "fs_debug", "0", 0 );
2539 #ifdef _WIN32_WCE
2540 fs_restrict_mask = Cvar_Get( "fs_restrict_mask", "0", CVAR_NOSET );
2541 #else
2542 fs_restrict_mask = Cvar_Get( "fs_restrict_mask", "4", CVAR_NOSET );
2543 #endif
2544 fs_restrict_mask->subsystem = CVAR_SYSTEM_FILES;
2545
2546 if( ( fs_restrict_mask->integer & 7 ) == 7 ) {
2547 Com_WPrintf( "Invalid fs_restrict_mask value %d. "
2548 "Falling back to default.\n",
2549 fs_restrict_mask->integer );
2550 Cvar_SetInteger( "fs_restrict_mask", 4 );
2551 }
2552
2553
2554 //
2555 // basedir <path>
2556 // allows the game to run from outside the data tree
2557 //
2558
2559 fs_basedir = Cvar_Get( "basedir", Sys_GetCurrentDirectory(), CVAR_NOSET );
2560
2561 /* strip trailing slash */
2562 if( fs_basedir->string[0] ) {
2563 i = strlen( fs_basedir->string ) - 1;
2564 if( fs_basedir->string[i] == '/' ||
2565 fs_basedir->string[i] == '\\' )
2566 {
2567 fs_basedir->string[i] = 0;
2568 }
2569 }
2570
2571 //
2572 // cddir <path>
2573 // Logically concatenates the cddir after the basedir
2574 // allows the game to run from outside the data tree
2575 //
2576 fs_cddir = Cvar_Get( "cddir", "", CVAR_NOSET );
2577 if( fs_cddir->string[0] ) {
2578 i = strlen( fs_cddir->string ) - 1;
2579 if( fs_cddir->string[i] == '/' ||
2580 fs_cddir->string[i] == '\\' )
2581 {
2582 fs_cddir->string[i] = 0;
2583 }
2584 FS_AddGameDirectory( "%s/"BASEDIRNAME, fs_cddir->string );
2585 }
2586
2587 //
2588 // start up with baseq2 by default
2589 //
2590 FS_AddGameDirectory("%s/"BASEDIRNAME, DATADIR);
2591 FS_AddGameDirectory("%s/"BASEDIRNAME, LIBDIR);
2592 FS_AddGameDirectory( "%s/"BASEDIRNAME, fs_basedir->string );
2593 FS_AddHomeAsGameDirectory(BASEDIRNAME);
2594
2595 FS_AddGameDirectory("%s/"INITDIRNAME, DATADIR);
2596 FS_AddGameDirectory("%s/"INITDIRNAME, LIBDIR);
2597 FS_AddGameDirectory( "%s/"INITDIRNAME, fs_basedir->string );
2598 FS_AddHomeAsGameDirectory(INITDIRNAME);
2599
2600 fs_base_searchpaths = fs_searchpaths;
2601
2602 //
2603 // check for game override
2604 //
2605
2606 FS_SetupGamedir();
2607
2608 FS_Path_f();
2609
2610 FS_FillAPI( &fs );
2611
2612 Com_Printf( "%i msec to init filesystem\n", Sys_Milliseconds() - startTime );
2613 Com_Printf( "-----------------------------\n" );
2614 }
2615
2616 /*
2617 ================
2618 FS_NeedRestart
2619 ================
2620 */
FS_NeedRestart(void)2621 qboolean FS_NeedRestart( void ) {
2622 if( fs_game->latched_string || fs_restrict_mask->latched_string ) {
2623 return qtrue;
2624 }
2625
2626 return qfalse;
2627 }
2628
2629
2630 // TODO: remove it
Developer_searchpath(int who)2631 int Developer_searchpath( int who ) {
2632 if( !strcmp( fs_game->string, "xatrix" ) ) {
2633 return 1;
2634 }
2635
2636 if( !strcmp( fs_game->string, "rogue" ) ) {
2637 return 1;
2638 }
2639
2640 return 0;
2641
2642
2643 }
2644
2645
2646
2647
2648
2649