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