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 // files.c
22 //
23 
24 #include "common.h"
25 #include "../include/minizip/unzip.h"
26 
27 #define FS_MAX_PAKS			1024
28 #define FS_MAX_HASHSIZE		1024
29 #define FS_MAX_FILEINDICES	1024
30 
31 cVar_t	*fs_basedir;
32 cVar_t	*fs_cddir;
33 cVar_t	*fs_game;
34 cVar_t	*fs_gamedircvar;
35 cVar_t	*fs_defaultPaks;
36 
37 /*
38 =============================================================================
39 
40 	IN-MEMORY PAK FILES
41 
42 =============================================================================
43 */
44 
45 typedef struct mPackFile_s {
46 	char					fileName[MAX_QPATH];
47 
48 	int						filePos;
49 	int						fileLen;
50 
51 	struct mPackFile_s		*hashNext;
52 } mPackFile_t;
53 
54 typedef struct mPack_s {
55 	char					name[MAX_OSPATH];
56 
57 	// Standard
58 	FILE					*pak;
59 
60 	// Compressed
61 	unzFile					*pkz;
62 
63 	// Information
64 	int						numFiles;
65 	mPackFile_t				*files;
66 
67 	struct mPackFile_s		*fileHashTree[FS_MAX_HASHSIZE];
68 } mPack_t;
69 
70 /*
71 =============================================================================
72 
73 	FILESYSTEM FUNCTIONALITY
74 
75 =============================================================================
76 */
77 
78 typedef struct fsLink_s {
79 	struct fsLink_s			*next;
80 	char					*from;
81 	int						fromLength;
82 	char					*to;
83 } fsLink_t;
84 
85 typedef struct fsPath_s {
86 	char					pathName[MAX_OSPATH];
87 	char					gamePath[MAX_OSPATH];
88 	mPack_t					*package;
89 
90 	struct fsPath_s			*next;
91 } fsPath_t;
92 
93 static char		fs_gameDir[MAX_OSPATH];
94 
95 static fsLink_t	*fs_fileLinks;
96 
97 static fsPath_t	*fs_searchPaths;
98 static fsPath_t	**fs_invSearchPaths;
99 static int		fs_numInvSearchPaths;
100 static fsPath_t	*fs_baseSearchPath;		// Without gamedirs
101 
102 /*
103 =============================================================================
104 
105 	FILE INDEXING
106 
107 =============================================================================
108 */
109 
110 typedef struct fsHandleIndex_s {
111 	char					name[MAX_QPATH];
112 
113 	qBool					inUse;
114 	fsOpenMode_t			openMode;
115 
116 	// One of these is always NULL
117 	FILE					*regFile;
118 	unzFile					*pkzFile;
119 } fsHandleIndex_t;
120 
121 static fsHandleIndex_t	fs_fileIndices[FS_MAX_FILEINDICES];
122 
123 /*
124 =============================================================================
125 
126 	ZLIB FUNCTIONS
127 
128 =============================================================================
129 */
130 
131 /*
132 ================
133 FS_ZLibDecompress
134 ================
135 */
FS_ZLibDecompress(byte * in,int inLen,byte * out,int outLen,int wbits)136 int FS_ZLibDecompress (byte *in, int inLen, byte *out, int outLen, int wbits)
137 {
138 	z_stream	zs;
139 	int			result;
140 
141 	memset (&zs, 0, sizeof (zs));
142 
143 	zs.next_in = in;
144 	zs.avail_in = 0;
145 
146 	zs.next_out = out;
147 	zs.avail_out = outLen;
148 
149 	result = inflateInit2 (&zs, wbits);
150 	if (result != Z_OK) {
151 		Sys_Error ("Error on inflateInit %d\nMessage: %s\n", result, zs.msg);
152 		return 0;
153 	}
154 
155 	zs.avail_in = inLen;
156 
157 	result = inflate (&zs, Z_FINISH);
158 	if (result != Z_STREAM_END) {
159 		Sys_Error ("Error on inflate %d\nMessage: %s\n", result, zs.msg);
160 		zs.total_out = 0;
161 	}
162 
163 	result = inflateEnd (&zs);
164 	if (result != Z_OK) {
165 		Sys_Error ("Error on inflateEnd %d\nMessage: %s\n", result, zs.msg);
166 		return 0;
167 	}
168 
169 	return zs.total_out;
170 }
171 
172 
173 /*
174 ================
175 FS_ZLibCompressChunk
176 ================
177 */
FS_ZLibCompressChunk(byte * in,int inLen,byte * out,int outLen,int method,int wbits)178 int FS_ZLibCompressChunk (byte *in, int inLen, byte *out, int outLen, int method, int wbits)
179 {
180 	z_stream	zs;
181 	int			result;
182 
183 	zs.next_in = in;
184 	zs.avail_in = inLen;
185 	zs.total_in = 0;
186 
187 	zs.next_out = out;
188 	zs.avail_out = outLen;
189 	zs.total_out = 0;
190 
191 	zs.msg = NULL;
192 	zs.state = NULL;
193 	zs.zalloc = Z_NULL;
194 	zs.zfree = Z_NULL;
195 	zs.opaque = NULL;
196 
197 	zs.data_type = Z_BINARY;
198 	zs.adler = 0;
199 	zs.reserved = 0;
200 
201 	result = deflateInit2 (&zs, method, Z_DEFLATED, wbits, 9, Z_DEFAULT_STRATEGY);
202 	if (result != Z_OK)
203 		return 0;
204 
205 	result = deflate(&zs, Z_FINISH);
206 	if (result != Z_STREAM_END)
207 		return 0;
208 
209 	result = deflateEnd(&zs);
210 	if (result != Z_OK)
211 		return 0;
212 
213 	return zs.total_out;
214 }
215 
216 /*
217 =============================================================================
218 
219 	HELPER FUNCTIONS
220 
221 =============================================================================
222 */
223 
224 /*
225 ================
226 __FileLen
227 ================
228 */
__FileLen(FILE * f)229 static int __FileLen (FILE *f)
230 {
231 	int		pos;
232 	int		end;
233 
234 	pos = ftell (f);
235 	fseek (f, 0, SEEK_END);
236 	end = ftell (f);
237 	fseek (f, pos, SEEK_SET);
238 
239 	return end;
240 }
241 
242 
243 /*
244 ============
245 FS_CreatePath
246 
247 Creates any directories needed to store the given filename
248 ============
249 */
FS_CreatePath(char * path)250 void FS_CreatePath (char *path)
251 {
252 	char	*ofs;
253 
254 	for (ofs=path+1 ; *ofs ; ofs++) {
255 		if (*ofs == '/') {
256 			// Create the directory
257 			*ofs = 0;
258 			Sys_Mkdir (path);
259 			*ofs = '/';
260 		}
261 	}
262 }
263 
264 
265 /*
266 ================
267 FS_CopyFile
268 ================
269 */
FS_CopyFile(char * src,char * dst)270 void FS_CopyFile (char *src, char *dst)
271 {
272 	FILE	*f1, *f2;
273 	int		l;
274 	byte	buffer[65536];
275 
276 	if (fs_developer->intVal)
277 		Com_Printf (0, "FS_CopyFile (%s, %s)\n", src, dst);
278 
279 	f1 = fopen (src, "rb");
280 	if (!f1)
281 		return;
282 	f2 = fopen (dst, "wb");
283 	if (!f2) {
284 		fclose (f1);
285 		return;
286 	}
287 
288 	for ( ; ; ) {
289 		l = fread (buffer, 1, sizeof (buffer), f1);
290 		if (!l)
291 			break;
292 		fwrite (buffer, 1, l, f2);
293 	}
294 
295 	fclose (f1);
296 	fclose (f2);
297 }
298 
299 /*
300 =============================================================================
301 
302 	FILE HANDLING
303 
304 =============================================================================
305 */
306 
307 /*
308 =================
309 FS_GetFreeHandle
310 =================
311 */
FS_GetFreeHandle(fsHandleIndex_t ** handle)312 static fileHandle_t FS_GetFreeHandle (fsHandleIndex_t **handle)
313 {
314 	fileHandle_t		i;
315 	fsHandleIndex_t		*hIndex;
316 
317 	for (i=0, hIndex=fs_fileIndices ; i<FS_MAX_FILEINDICES ; hIndex++, i++) {
318 		if (hIndex->inUse)
319 			continue;
320 
321 		hIndex->inUse = qTrue;
322 		*handle = hIndex;
323 		return i+1;
324 	}
325 
326 	Com_Error (ERR_FATAL, "FS_GetFreeHandle: no free handles!");
327 	return 0;
328 }
329 
330 
331 /*
332 =================
333 FS_GetHandle
334 =================
335 */
FS_GetHandle(fileHandle_t fileNum)336 static fsHandleIndex_t *FS_GetHandle (fileHandle_t fileNum)
337 {
338 	fsHandleIndex_t *hIndex;
339 
340 	if (fileNum < 0 || fileNum > FS_MAX_FILEINDICES)
341 		Com_Error (ERR_FATAL, "FS_GetHandle: invalid file number");
342 
343 	hIndex = &fs_fileIndices[fileNum-1];
344 	if (!hIndex->inUse)
345 		Com_Error (ERR_FATAL, "FS_GetHandle: invalid handle index");
346 
347 	return hIndex;
348 }
349 
350 
351 /*
352 ============
353 FS_FileLength
354 
355 Returns the TOTAL file size, not the position.
356 Make sure to move the position back after moving to the beginning of the file for the size lookup.
357 ============
358 */
FS_FileLength(fileHandle_t fileNum)359 int FS_FileLength (fileHandle_t fileNum)
360 {
361 	fsHandleIndex_t		*handle;
362 
363 	handle = FS_GetHandle (fileNum);
364 	if (handle->regFile) {
365 		return __FileLen (handle->regFile);
366 	}
367 	else if (handle->pkzFile) {
368 		// FIXME
369 		return -1;
370 	}
371 
372 	// Shouldn't happen...
373 	assert (0);
374 	return -1;
375 }
376 
377 
378 /*
379 ============
380 FS_Tell
381 
382 Returns the current file position.
383 ============
384 */
FS_Tell(fileHandle_t fileNum)385 int FS_Tell (fileHandle_t fileNum)
386 {
387 	fsHandleIndex_t	*handle;
388 
389 	handle = FS_GetHandle (fileNum);
390 	if (handle->regFile)
391 		return ftell (handle->regFile);
392 	else if (handle->pkzFile)
393 		return unztell (handle->pkzFile);
394 
395 	// Shouldn't happen...
396 	assert (0);
397 	return 0;
398 }
399 
400 
401 /*
402 =================
403 FS_Read
404 
405 Properly handles partial reads
406 =================
407 */
408 void CDAudio_Stop (void);
FS_Read(void * buffer,int len,fileHandle_t fileNum)409 int FS_Read (void *buffer, int len, fileHandle_t fileNum)
410 {
411 	fsHandleIndex_t	*handle;
412 	int				remaining;
413 	int				read;
414 	byte			*buf;
415 	qBool			tried;
416 
417 	handle = FS_GetHandle (fileNum);
418 	if (handle->openMode != FS_MODE_READ_BINARY)
419 		Com_Error (ERR_FATAL, "FS_Read: %s: was not opened in read mode", handle->name);
420 
421 	// Read in chunks for progress bar
422 	remaining = len;
423 	buf = (byte *)buffer;
424 
425 	tried = qFalse;
426 	if (handle->regFile) {
427 		// File
428 		while (remaining) {
429 			read = fread (buf, 1, remaining, handle->regFile);
430 			switch (read) {
431 			case 0:
432 				// We might have been trying to read from a CD
433 				if (!tried) {
434 					tried = qTrue;
435 #ifndef DEDICATED_ONLY
436 					if (!dedicated->intVal)
437 						CDAudio_Stop ();
438 #endif
439 				}
440 				else {
441 					if (fs_developer->intVal)
442 						Com_Printf (0, "FS_Read: 0 bytes read from \"%s\"", handle->name);
443 					return len - remaining;
444 				}
445 				break;
446 
447 			case -1:
448 				Com_Error (ERR_FATAL, "FS_Read: -1 bytes read from \"%s\"", handle->name);
449 				break;
450 			}
451 
452 			// Do some progress bar thing here
453 			remaining -= read;
454 			buf += read;
455 		}
456 
457 		return len;
458 	}
459 	else if (handle->pkzFile) {
460 		// Zip file
461 		while (remaining) {
462 			read = unzReadCurrentFile (handle->pkzFile, buf, remaining);
463 			switch (read) {
464 			case 0:
465 				// We might have been trying to read from a CD
466 				if (!tried) {
467 					tried = qTrue;
468 #ifndef DEDICATED_ONLY
469 					if (!dedicated->intVal)
470 						CDAudio_Stop ();
471 #endif
472 				}
473 				else {
474 					if (fs_developer->intVal)
475 						Com_Printf (0, "FS_Read: 0 bytes read from \"%s\"", handle->name);
476 					return len - remaining;
477 				}
478 				break;
479 
480 			case -1:
481 				Com_Error (ERR_FATAL, "FS_Read: -1 bytes read from \"%s\"", handle->name);
482 				break;
483 			}
484 
485 			// Do some progress bar thing here
486 			remaining -= read;
487 			buf += read;
488 		}
489 
490 		return len;
491 	}
492 
493 	// Shouldn't happen...
494 	assert (0);
495 	return 0;
496 }
497 
498 
499 /*
500 =================
501 FS_Write
502 =================
503 */
FS_Write(void * buffer,int size,fileHandle_t fileNum)504 int FS_Write (void *buffer, int size, fileHandle_t fileNum)
505 {
506 	fsHandleIndex_t	*handle;
507 	int				remaining;
508 	int				write;
509 	byte			*buf;
510 
511 	handle = FS_GetHandle (fileNum);
512 	switch (handle->openMode) {
513 	case FS_MODE_WRITE_BINARY:
514 	case FS_MODE_APPEND_BINARY:
515 	case FS_MODE_WRITE_TEXT:
516 	case FS_MODE_APPEND_TEXT:
517 		break;
518 	default:
519 		Com_Error (ERR_FATAL, "FS_Write: %s: was no opened in append/write mode", handle->name);
520 		break;
521 	}
522 	if (size < 0)
523 		Com_Error (ERR_FATAL, "FS_Write: size < 0");
524 
525 	// Write
526 	remaining = size;
527 	buf = (byte *)buffer;
528 
529 	if (handle->regFile) {
530 		// File
531 		while (remaining) {
532 			write = fwrite (buf, 1, remaining, handle->regFile);
533 
534 			switch (write) {
535 			case 0:
536 				if (fs_developer->intVal)
537 					Com_Printf (PRNT_ERROR, "FS_Write: 0 bytes written to %s\n", handle->name);
538 				return size - remaining;
539 
540 			case -1:
541 				Com_Error (ERR_FATAL, "FS_Write: -1 bytes written to %s", handle->name);
542 				break;
543 			}
544 
545 			remaining -= write;
546 			buf += write;
547 		}
548 
549 		return size;
550 	}
551 	else if (handle->pkzFile) {
552 		// Zip file
553 		Com_Error (ERR_FATAL, "FS_Write: can't write to zip file %s", handle->name);
554 	}
555 
556 	// Shouldn't happen...
557 	assert (0);
558 	return 0;
559 }
560 
561 
562 /*
563 =================
564 FS_Seek
565 =================
566 */
FS_Seek(fileHandle_t fileNum,int offset,fsSeekOrigin_t seekOrigin)567 void FS_Seek (fileHandle_t fileNum, int offset, fsSeekOrigin_t seekOrigin)
568 {
569 	fsHandleIndex_t	*handle;
570 	unz_file_info	info;
571 	int				remaining, r, len;
572 	byte			dummy[0x8000];
573 
574 	handle = FS_GetHandle (fileNum);
575 	if (handle->regFile) {
576 		// Seek through a regular file
577 		switch (seekOrigin) {
578 		case FS_SEEK_SET:
579 			fseek (handle->regFile, offset, SEEK_SET);
580 			break;
581 
582 		case FS_SEEK_CUR:
583 			fseek (handle->regFile, offset, SEEK_CUR);
584 			break;
585 
586 		case FS_SEEK_END:
587 			fseek (handle->regFile, offset, SEEK_END);
588 			break;
589 
590 		default:
591 			Com_Error (ERR_FATAL, "FS_Seek: bad origin (%i)", seekOrigin);
592 			break;
593 		}
594 	}
595 	else if (handle->pkzFile) {
596 		// Seek through a zip
597 		switch (seekOrigin) {
598 		case FS_SEEK_SET:
599 			remaining = offset;
600 			break;
601 
602 		case FS_SEEK_CUR:
603 			remaining = offset + unztell (handle->pkzFile);
604 			break;
605 
606 		case FS_SEEK_END:
607 			unzGetCurrentFileInfo (handle->pkzFile, &info, NULL, 0, NULL, 0, NULL, 0);
608 
609 			remaining = offset + info.uncompressed_size;
610 			break;
611 
612 		default:
613 			Com_Error (ERR_FATAL, "FS_Seek: bad origin (%i)", seekOrigin);
614 		}
615 
616 		// Reopen the file
617 		unzCloseCurrentFile (handle->pkzFile);
618 		unzOpenCurrentFile (handle->pkzFile);
619 
620 		// Skip until the desired offset is reached
621 		while (remaining) {
622 			len = remaining;
623 			if (len > sizeof (dummy))
624 				len = sizeof (dummy);
625 
626 			r = unzReadCurrentFile (handle->pkzFile, dummy, len);
627 			if (r <= 0)
628 				break;
629 
630 			remaining -= r;
631 		}
632 	}
633 	else
634 		assert (0);
635 }
636 
637 
638 /*
639 ==============
640 FS_OpenFileAppend
641 ==============
642 */
FS_OpenFileAppend(fsHandleIndex_t * handle,qBool binary)643 static int FS_OpenFileAppend (fsHandleIndex_t *handle, qBool binary)
644 {
645 	char	path[MAX_OSPATH];
646 
647 	Q_snprintfz (path, sizeof (path), "%s/%s", fs_gameDir, handle->name);
648 
649 	// Create the path if it doesn't exist
650 	FS_CreatePath (path);
651 
652 	// Open
653 	if (binary)
654 		handle->regFile = fopen (path, "ab");
655 	else
656 		handle->regFile = fopen (path, "at");
657 
658 	// Return length
659 	if (handle->regFile) {
660 		if (fs_developer->intVal)
661 			Com_Printf (0, "FS_OpenFileAppend: \"%s\"", path);
662 		return __FileLen (handle->regFile);
663 	}
664 
665 	// Failed to open
666 	if (fs_developer->intVal)
667 		Com_Printf (0, "FS_OpenFileAppend: couldn't open \"%s\"", path);
668 	return -1;
669 }
670 
671 
672 /*
673 ==============
674 FS_OpenFileWrite
675 ==============
676 */
FS_OpenFileWrite(fsHandleIndex_t * handle,qBool binary)677 static int FS_OpenFileWrite (fsHandleIndex_t *handle, qBool binary)
678 {
679 	char	path[MAX_OSPATH];
680 
681 	Q_snprintfz (path, sizeof (path), "%s/%s", fs_gameDir, handle->name);
682 
683 	// Create the path if it doesn't exist
684 	FS_CreatePath (path);
685 
686 	// Open
687 	if (binary)
688 		handle->regFile = fopen (path, "wb");
689 	else
690 		handle->regFile = fopen (path, "wt");
691 
692 	// Return length
693 	if (handle->regFile) {
694 		if (fs_developer->intVal)
695 			Com_Printf (0, "FS_OpenFileWrite: \"%s\"", path);
696 		return 0;
697 	}
698 
699 	// Failed to open
700 	if (fs_developer->intVal)
701 		Com_Printf (0, "FS_OpenFileWrite: couldn't open \"%s\"", path);
702 	return -1;
703 }
704 
705 
706 /*
707 ===========
708 FS_OpenFileRead
709 
710 Finds the file in the search path.
711 returns filesize and an open FILE *
712 Used for streaming data out of either a pak file or
713 a seperate file.
714 ===========
715 */
716 qBool	fs_fileFromPak = qFalse;
FS_OpenFileRead(fsHandleIndex_t * handle)717 static int FS_OpenFileRead (fsHandleIndex_t *handle)
718 {
719 	fsPath_t		*searchPath;
720 	char			netPath[MAX_OSPATH];
721 	mPack_t			*package;
722 	mPackFile_t		*searchFile;
723 	fsLink_t		*link;
724 	uint32			hashValue;
725 
726 	fs_fileFromPak = qFalse;
727 	// Check for links first
728 	for (link=fs_fileLinks ; link ; link=link->next) {
729 		if (!strncmp (handle->name, link->from, link->fromLength)) {
730 			Q_snprintfz (netPath, sizeof (netPath), "%s%s", link->to, handle->name+link->fromLength);
731 
732 			// Open
733 			handle->regFile = fopen (netPath, "rb");
734 
735 			// Return length
736 			if (fs_developer->intVal)
737 				Com_Printf (0, "FS_OpenFileRead: link file: %s\n", netPath);
738 			if (handle->regFile)
739 				return __FileLen (handle->regFile);
740 
741 			// Failed to load
742 			return -1;
743 		}
744 	}
745 
746 	// Calculate hash value
747 	hashValue = Com_HashFileName (handle->name, FS_MAX_HASHSIZE);
748 
749 	// Search through the path, one element at a time
750 	for (searchPath=fs_searchPaths ; searchPath ; searchPath=searchPath->next) {
751 		// Is the element a pak file?
752 		if (searchPath->package) {
753 			// Look through all the pak file elements
754 			package = searchPath->package;
755 
756 			for (searchFile=package->fileHashTree[hashValue] ; searchFile ; searchFile=searchFile->hashNext) {
757 				if (Q_stricmp (searchFile->fileName, handle->name))
758 					continue;
759 
760 				// Found it!
761 				fs_fileFromPak = qTrue;
762 
763 				if (package->pak) {
764 					if (fs_developer->intVal)
765 						Com_Printf (0, "FS_OpenFileRead: pack file %s : %s\n", package->name, handle->name);
766 
767 					// Open a new file on the pakfile
768 					handle->regFile = fopen (package->name, "rb");
769 					if (handle->regFile) {
770 						fseek (handle->regFile, searchFile->filePos, SEEK_SET);
771 						return searchFile->fileLen;
772 					}
773 				}
774 				else if (package->pkz) {
775 					if (fs_developer->intVal)
776 						Com_Printf (0, "FS_OpenFileRead: pkz file %s : %s\n", package->name, handle->name);
777 
778 					handle->pkzFile = unzOpen (package->name);
779 					if (handle->pkzFile) {
780 						if (unzSetOffset (handle->pkzFile, searchFile->filePos) == UNZ_OK) {
781 							if (unzOpenCurrentFile (handle->pkzFile) == UNZ_OK)
782 								return searchFile->fileLen;
783 						}
784 
785 						// Failed to locate/open
786 						unzClose (handle->pkzFile);
787 					}
788 				}
789 
790 				Com_Error (ERR_FATAL, "FS_OpenFileRead: couldn't reopen \"%s\"", handle->name);
791 			}
792 		}
793 		else {
794 			// Check a file in the directory tree
795 			Q_snprintfz (netPath, sizeof (netPath), "%s/%s", searchPath->pathName, handle->name);
796 
797 			handle->regFile = fopen (netPath, "rb");
798 			if (handle->regFile) {
799 				// Found it!
800 				if (fs_developer->intVal)
801 					Com_Printf (0, "FS_OpenFileRead: %s\n", netPath);
802 				return __FileLen (handle->regFile);
803 			}
804 		}
805 	}
806 
807 	if (fs_developer->intVal)
808 		Com_Printf (0, "FS_OpenFileRead: can't find %s\n", handle->name);
809 
810 	return -1;
811 }
812 
813 
814 /*
815 ===========
816 FS_OpenFile
817 ===========
818 */
FS_OpenFile(char * fileName,fileHandle_t * fileNum,fsOpenMode_t openMode)819 int FS_OpenFile (char *fileName, fileHandle_t *fileNum, fsOpenMode_t openMode)
820 {
821 	fsHandleIndex_t	*handle;
822 	int				fileSize;
823 
824 	*fileNum = FS_GetFreeHandle (&handle);
825 
826 	Q_strncpyz (handle->name, fileName, sizeof (handle->name));
827 	handle->openMode = openMode;
828 
829 	// Open under the desired mode
830 	switch (openMode) {
831 	case FS_MODE_APPEND_BINARY:
832 		fileSize = FS_OpenFileAppend (handle, qTrue);
833 		break;
834 	case FS_MODE_APPEND_TEXT:
835 		fileSize = FS_OpenFileAppend (handle, qFalse);
836 		break;
837 
838 	case FS_MODE_READ_BINARY:
839 		fileSize = FS_OpenFileRead (handle);
840 		break;
841 
842 	case FS_MODE_WRITE_BINARY:
843 		fileSize = FS_OpenFileWrite (handle, qTrue);
844 		break;
845 	case FS_MODE_WRITE_TEXT:
846 		fileSize = FS_OpenFileWrite (handle, qFalse);
847 		break;
848 
849 	default:
850 		Com_Error (ERR_FATAL, "FS_OpenFile: %s: invalid mode '%i'", handle->name, openMode);
851 		break;
852 	}
853 
854 	// Failed
855 	if (fileSize == -1) {
856 		memset (handle, 0, sizeof (fsHandleIndex_t));
857 		*fileNum = 0;
858 	}
859 
860 	return fileSize;
861 }
862 
863 
864 /*
865 ==============
866 FS_CloseFile
867 ==============
868 */
FS_CloseFile(fileHandle_t fileNum)869 void FS_CloseFile (fileHandle_t fileNum)
870 {
871 	fsHandleIndex_t	*handle;
872 
873 	// Get local handle
874 	handle = FS_GetHandle (fileNum);
875 	if (!handle->inUse)
876 		return;
877 
878 	// Close file/zip
879 	if (handle->regFile)
880 		fclose (handle->regFile);
881 	else if (handle->pkzFile) {
882 		unzCloseCurrentFile (handle->pkzFile);
883 		unzClose (handle->pkzFile);
884 	}
885 	else
886 		assert (0);
887 
888 	// Clear handle
889 	handle->inUse = qFalse;
890 	handle->name[0] = '\0';
891 	handle->pkzFile = NULL;
892 	handle->regFile = NULL;
893 }
894 
895 // ==========================================================================
896 
897 /*
898 ============
899 FS_LoadFile
900 
901 Filename are reletive to the egl search path.
902 A NULL buffer will just return the file length without loading.
903 -1 is returned if it wasn't found, 0 is returned if it's a blank file. In both cases a buffer is set to NULL.
904 ============
905 */
FS_LoadFile(char * path,void ** buffer,char * terminate)906 int FS_LoadFile (char *path, void **buffer, char *terminate)
907 {
908 	byte			*buf;
909 	int				fileLen;
910 	fileHandle_t	fileNum;
911 	uint32			termLen;
912 
913 	// Look for it in the filesystem or pack files
914 	fileLen = FS_OpenFile (path, &fileNum, FS_MODE_READ_BINARY);
915 	if (!fileNum || fileLen <= 0) {
916 		if (buffer)
917 			*buffer = NULL;
918 		if (fileNum)
919 			FS_CloseFile (fileNum);
920 		if (fileLen >= 0)
921 			return 0;
922 		return -1;
923 	}
924 
925 	// Just needed to get the length
926 	if (!buffer) {
927 		FS_CloseFile (fileNum);
928 		return fileLen;
929 	}
930 
931 	// Allocate a local buffer
932 	// If we're terminating, pad by one byte. Mem_PoolAlloc below will zero-fill...
933 	if (terminate)
934 		termLen = strlen (terminate);
935 	else
936 		termLen = 0;
937 	buf = Mem_PoolAlloc (fileLen+termLen, com_fileSysPool, 0);
938 	*buffer = buf;
939 
940 	// Copy the file data to a local buffer
941 	FS_Read (buf, fileLen, fileNum);
942 	FS_CloseFile (fileNum);
943 
944 	// Terminate if desired
945 	if (termLen)
946 		strncpy ((char *)buf+fileLen, terminate, termLen);
947 	return fileLen+termLen;
948 }
949 
950 
951 /*
952 =============
953 _FS_FreeFile
954 =============
955 */
_FS_FreeFile(void * buffer,const char * fileName,const int fileLine)956 void _FS_FreeFile (void *buffer, const char *fileName, const int fileLine)
957 {
958 	if (buffer)
959 		_Mem_Free (buffer, fileName, fileLine);
960 }
961 
962 // ==========================================================================
963 
964 /*
965 ============
966 FS_FileExists
967 
968 Filename are reletive to the egl search path.
969 Just like calling FS_LoadFile with a NULL buffer.
970 ============
971 */
FS_FileExists(char * path)972 int FS_FileExists (char *path)
973 {
974 	int				fileLen;
975 	fileHandle_t	fileNum;
976 
977 	// Look for it in the filesystem or pack files
978 	fileLen = FS_OpenFile (path, &fileNum, FS_MODE_READ_BINARY);
979 	if (!fileNum || fileLen <= 0)
980 		return -1;
981 
982 	// Just needed to get the length
983 	FS_CloseFile (fileNum);
984 	return fileLen;
985 }
986 
987 /*
988 =============================================================================
989 
990 	PACKAGE LOADING
991 
992 =============================================================================
993 */
994 
995 /*
996 =================
997 FS_LoadPAK
998 
999 Takes an explicit (not game tree related) path to a pak file.
1000 
1001 Loads the header and directory, adding the files at the beginning
1002 of the list so they override previous pack files.
1003 =================
1004 */
FS_LoadPAK(char * fileName,qBool complain)1005 mPack_t *FS_LoadPAK (char *fileName, qBool complain)
1006 {
1007 	dPackHeader_t	header;
1008 	mPackFile_t		*outPackFile;
1009 	mPack_t			*outPack;
1010 	FILE			*handle;
1011 	dPackFile_t		info[PAK_MAX_FILES];
1012 	int				i, numFiles;
1013 	uint32			hashValue;
1014 
1015 	// Open
1016 	handle = fopen (fileName, "rb");
1017 	if (!handle) {
1018 		if (complain)
1019 			Com_Printf (PRNT_ERROR, "FS_LoadPAK: couldn't open \"%s\"\n", fileName);
1020 		return NULL;
1021 	}
1022 
1023 	// Read header
1024 	fread (&header, 1, sizeof (header), handle);
1025 	if (LittleLong (header.ident) != PAK_HEADER) {
1026 		fclose (handle);
1027 		Com_Error (ERR_FATAL, "FS_LoadPAK: \"%s\" is not a packfile", fileName);
1028 	}
1029 
1030 	header.dirOfs = LittleLong (header.dirOfs);
1031 	header.dirLen = LittleLong (header.dirLen);
1032 
1033 	// Sanity checks
1034 	numFiles = header.dirLen / sizeof (dPackFile_t);
1035 	if (numFiles > PAK_MAX_FILES) {
1036 		fclose (handle);
1037 		Com_Error (ERR_FATAL, "FS_LoadPAK: \"%s\" has too many files (%i > %i)", fileName, numFiles, PAK_MAX_FILES);
1038 	}
1039 	if (numFiles <= 0) {
1040 		fclose (handle);
1041 		Com_Error (ERR_FATAL, "FS_LoadPAK: \"%s\" is empty", fileName);
1042 	}
1043 
1044 	// Read past header
1045 	fseek (handle, header.dirOfs, SEEK_SET);
1046 	fread (info, 1, header.dirLen, handle);
1047 
1048 	// Create pak
1049 	outPack = Mem_PoolAlloc (sizeof (mPack_t), com_fileSysPool, 0);
1050 	outPackFile = Mem_PoolAlloc (sizeof (mPackFile_t) * numFiles, com_fileSysPool, 0);
1051 
1052 	Q_strncpyz (outPack->name, fileName, sizeof (outPack->name));
1053 	outPack->pak = handle;
1054 	outPack->numFiles = numFiles;
1055 	outPack->files = outPackFile;
1056 
1057 	// Parse the directory
1058 	for (i=0 ; i<numFiles ; i++) {
1059 		Q_strncpyz (outPackFile->fileName, info[i].name, sizeof (outPackFile->fileName));
1060 		outPackFile->filePos = LittleLong (info[i].filePos);
1061 		outPackFile->fileLen = LittleLong (info[i].fileLen);
1062 
1063 		// Link it into the hash tree
1064 		hashValue = Com_HashFileName (outPackFile->fileName, FS_MAX_HASHSIZE);
1065 
1066 		outPackFile->hashNext = outPack->fileHashTree[hashValue];
1067 		outPack->fileHashTree[hashValue] = outPackFile;
1068 
1069 		// Next
1070 		outPackFile++;
1071 	}
1072 
1073 	Com_Printf (0, "FS_LoadPAK: loaded \"%s\"\n", fileName);
1074 	return outPack;
1075 }
1076 
1077 
1078 /*
1079 =================
1080 FS_LoadPKZ
1081 =================
1082 */
FS_LoadPKZ(char * fileName,qBool complain)1083 mPack_t *FS_LoadPKZ (char *fileName, qBool complain)
1084 {
1085 	mPackFile_t		*outPkzFile;
1086 	mPack_t			*outPkz;
1087 	unzFile			*handle;
1088 	unz_global_info	global;
1089 	unz_file_info	info;
1090 	char			name[MAX_QPATH];
1091 	int				status;
1092 	int				numFiles;
1093 	uint32			hashValue;
1094 
1095 	// Open
1096 	handle = unzOpen (fileName);
1097 	if (!handle) {
1098 		if (complain)
1099 			Com_Printf (PRNT_ERROR, "FS_LoadPKZ: couldn't open \"%s\"\n", fileName);
1100 		return NULL;
1101 	}
1102 
1103 	// Get global info
1104 	if (unzGetGlobalInfo (handle, &global) != UNZ_OK) {
1105 		unzClose (handle);
1106 		Com_Error (ERR_FATAL, "FS_LoadPKZ: \"%s\" is not a packfile", fileName);
1107 	}
1108 
1109 	// Sanity checks
1110 	numFiles = global.number_entry;
1111 	if (numFiles > PKZ_MAX_FILES) {
1112 		unzClose (handle);
1113 		Com_Error (ERR_FATAL, "FS_LoadPKZ: \"%s\" has too many files (%i > %i)", fileName, numFiles, PKZ_MAX_FILES);
1114 	}
1115 	if (numFiles <= 0) {
1116 		unzClose (handle);
1117 		Com_Error (ERR_FATAL, "FS_LoadPKZ: \"%s\" is empty", fileName);
1118 	}
1119 
1120 	// Create pak
1121 	outPkz = Mem_PoolAlloc (sizeof (mPack_t), com_fileSysPool, 0);
1122 	outPkzFile = Mem_PoolAlloc (sizeof (mPackFile_t) * numFiles, com_fileSysPool, 0);
1123 
1124 	Q_strncpyz (outPkz->name, fileName, sizeof (outPkz->name));
1125 	outPkz->pkz = handle;
1126 	outPkz->numFiles = numFiles;
1127 	outPkz->files = outPkzFile;
1128 
1129 	status = unzGoToFirstFile (handle);
1130 
1131 	while (status == UNZ_OK) {
1132 		unzGetCurrentFileInfo (handle, &info, name, MAX_QPATH, NULL, 0, NULL, 0);
1133 
1134 		Q_strncpyz (outPkzFile->fileName, name, sizeof (outPkzFile->fileName));
1135 		outPkzFile->filePos = unzGetOffset (handle);
1136 		outPkzFile->fileLen = info.uncompressed_size;
1137 
1138 		// Link it into the hash tree
1139 		hashValue = Com_HashFileName (outPkzFile->fileName, FS_MAX_HASHSIZE);
1140 
1141 		outPkzFile->hashNext = outPkz->fileHashTree[hashValue];
1142 		outPkz->fileHashTree[hashValue] = outPkzFile;
1143 
1144 		// Next
1145 		outPkzFile++;
1146 
1147 		status = unzGoToNextFile (handle);
1148 	}
1149 
1150 	Com_Printf (0, "FS_LoadPKZ: loaded \"%s\"\n", fileName);
1151 	return outPkz;
1152 }
1153 
1154 
1155 /*
1156 ================
1157 FS_AddGameDirectory
1158 
1159 Sets fs_gameDir, adds the directory to the head of the path, and loads *.pak/*.pkz
1160 ================
1161 */
FS_AddGameDirectory(char * dir,char * gamePath)1162 static void FS_AddGameDirectory (char *dir, char *gamePath)
1163 {
1164 	char		searchName[MAX_OSPATH];
1165 	char		*packFiles[FS_MAX_PAKS];
1166 	int			numPacks, i;
1167 	fsPath_t	*search;
1168 	mPack_t		*pak;
1169 	mPack_t		*pkz;
1170 
1171 	if (fs_developer->intVal)
1172 		Com_Printf (0, "FS_AddGameDirectory: adding \"%s\"\n", dir);
1173 
1174 	// Set as game directory
1175 	Q_strncpyz (fs_gameDir, dir, sizeof (fs_gameDir));
1176 
1177 	// Add the directory to the search path
1178 	search = Mem_PoolAlloc (sizeof (fsPath_t), com_fileSysPool, 0);
1179 	Q_strncpyz (search->pathName, dir, sizeof (search->pathName));
1180 	Q_strncpyz (search->gamePath, gamePath, sizeof (search->gamePath));
1181 	search->next = fs_searchPaths;
1182 	fs_searchPaths = search;
1183 
1184 	// Add any pak files in the format pak0.pak pak1.pak, ...
1185 	for (i=0 ; i<10 ; i++) {
1186 		Q_snprintfz (searchName, sizeof (searchName), "%s/pak%i.pak", dir, i);
1187 		pak = FS_LoadPAK (searchName, qFalse);
1188 		if (!pak)
1189 			continue;
1190 		search = Mem_PoolAlloc (sizeof (fsPath_t), com_fileSysPool, 0);
1191 		search->package = pak;
1192 		search->next = fs_searchPaths;
1193 		fs_searchPaths = search;
1194 	}
1195 
1196 	// Add the rest of the *.pak files
1197 	if (!fs_defaultPaks->intVal) {
1198 		numPacks = Sys_FindFiles (dir, "*/*.pak", packFiles, FS_MAX_PAKS, 0, qFalse, qTrue, qFalse);
1199 
1200 		for (i=0 ; i<numPacks ; i++) {
1201 			if (strstr (packFiles[i], "/pak0.pak") || strstr (packFiles[i], "/pak1.pak")
1202 			|| strstr (packFiles[i], "/pak2.pak") || strstr (packFiles[i], "/pak3.pak")
1203 			|| strstr (packFiles[i], "/pak4.pak") || strstr (packFiles[i], "/pak5.pak")
1204 			|| strstr (packFiles[i], "/pak6.pak") || strstr (packFiles[i], "/pak7.pak")
1205 			|| strstr (packFiles[i], "/pak8.pak") || strstr (packFiles[i], "/pak9.pak"))
1206 				continue; // FIXME :|
1207 
1208 			pak = FS_LoadPAK (packFiles[i], qTrue);
1209 			if (!pak)
1210 				continue;
1211 			search = Mem_PoolAlloc (sizeof (fsPath_t), com_fileSysPool, 0);
1212 			Q_strncpyz (search->pathName, dir, sizeof (search->pathName));
1213 			Q_strncpyz (search->gamePath, gamePath, sizeof (search->gamePath));
1214 			search->package = pak;
1215 			search->next = fs_searchPaths;
1216 			fs_searchPaths = search;
1217 		}
1218 
1219 		FS_FreeFileList (packFiles, numPacks);
1220 	}
1221 
1222 	// Load *.pkz files
1223 	numPacks = Sys_FindFiles (dir, "*/*.pkz", packFiles, FS_MAX_PAKS, 0, qFalse, qTrue, qFalse);
1224 
1225 	for (i=0 ; i<numPacks ; i++) {
1226 		pkz = FS_LoadPKZ (packFiles[i], qTrue);
1227 		if (!pkz)
1228 			continue;
1229 		search = Mem_PoolAlloc (sizeof (fsPath_t), com_fileSysPool, 0);
1230 		Q_strncpyz (search->pathName, dir, sizeof (search->pathName));
1231 		Q_strncpyz (search->gamePath, gamePath, sizeof (search->gamePath));
1232 		search->package = pkz;
1233 		search->next = fs_searchPaths;
1234 		fs_searchPaths = search;
1235 	}
1236 
1237 	FS_FreeFileList (packFiles, numPacks);
1238 }
1239 
1240 /*
1241 =============================================================================
1242 
1243 	GAME PATH
1244 
1245 =============================================================================
1246 */
1247 
1248 /*
1249 ============
1250 FS_Gamedir
1251 ============
1252 */
FS_Gamedir(void)1253 char *FS_Gamedir (void)
1254 {
1255 	if (*fs_gameDir)
1256 		return fs_gameDir;
1257 	else
1258 		return BASE_MODDIRNAME;
1259 }
1260 
1261 #ifdef HOMEDIR
1262 /*
1263  * FS_AddHomeAsGameDirectory
1264  * Add a game directory in "~/.egl". It can be used to set the last path, so
1265  * it will be used for writting.
1266  */
FS_AddHomeAsGameDirectory(char * dir)1267 void FS_AddHomeAsGameDirectory(char *dir)
1268 {
1269 	char	gdir[MAX_OSPATH];	/* Game directory. */
1270 	char	*homedir;		/* Home directory. */
1271 
1272 	if ((homedir = getenv("HOME")) != NULL) {
1273 		Q_snprintfz(gdir, sizeof(gdir), "%s/.egl/%s", homedir, dir);
1274 		FS_AddGameDirectory(gdir, dir);
1275 	}
1276 }
1277 #endif
1278 
1279 /*
1280 ================
1281 FS_SetGamedir
1282 
1283 Sets the gamedir and path to a different directory.
1284 ================
1285 */
FS_SetGamedir(char * dir,qBool firstTime)1286 void FS_SetGamedir (char *dir, qBool firstTime)
1287 {
1288 	fsPath_t	*next;
1289 	mPack_t		*package;
1290 	uint32		initTime;
1291 	int			i;
1292 
1293 	// Make sure it's not a path
1294 	if (strstr (dir, "..") || strchr (dir, '/') || strchr (dir, '\\') || strchr (dir, ':')) {
1295 		Com_Printf (PRNT_WARNING, "FS_SetGamedir: Gamedir should be a single directory name, not a path\n");
1296 		return;
1297 	}
1298 
1299 	// Free old inverted paths
1300 	if (fs_invSearchPaths)
1301 		Mem_Free (fs_invSearchPaths);
1302 
1303 	// Free up any current game dir info
1304 	for ( ; fs_searchPaths != fs_baseSearchPath ; fs_searchPaths=next) {
1305 		next = fs_searchPaths->next;
1306 
1307 		if (fs_searchPaths->package) {
1308 			package = fs_searchPaths->package;
1309 
1310 			if (package->pak)
1311 				fclose (package->pak);
1312 			else if (package->pkz)
1313 				unzClose (package->pkz);
1314 
1315 			Mem_Free (package->files);
1316 			Mem_Free (package);
1317 		}
1318 
1319 		Mem_Free (fs_searchPaths);
1320 	}
1321 
1322 	// Load packages
1323 	initTime = Sys_UMilliseconds ();
1324 	Com_Printf (0, "\n------------- Changing Game ------------\n");
1325 
1326 	Q_snprintfz (fs_gameDir, sizeof (fs_gameDir), "%s/%s", fs_basedir->string, dir);
1327 
1328 	if (!strcmp (dir, BASE_MODDIRNAME) || *dir == 0) {
1329 		Cvar_VariableSet (fs_gamedircvar, "", qTrue);
1330 		Cvar_VariableSet (fs_game, "", qTrue);
1331 	}
1332 	else {
1333 		Cvar_VariableSet (fs_gamedircvar, dir, qTrue);
1334 #ifdef __FreeBSD__
1335 		FS_AddGameDirectory (Q_VarArgs ("%s/%s", DATADIR, dir), dir);
1336 		FS_AddGameDirectory (Q_VarArgs ("%s/%s", LIBDIR, dir), dir);
1337 #endif
1338 		if (fs_cddir->string[0])
1339 			FS_AddGameDirectory (Q_VarArgs ("%s/%s", fs_cddir->string, dir), dir);
1340 
1341 		FS_AddGameDirectory (Q_VarArgs ("%s/%s", fs_basedir->string, dir), dir);
1342 #ifdef HOMEDIR
1343 		FS_AddHomeAsGameDirectory(dir);
1344 #endif
1345 	}
1346 
1347 	// Store a copy of the search paths inverted for FS_FindFiles
1348 	for (fs_numInvSearchPaths=0, next=fs_searchPaths ; next ; next=next->next, fs_numInvSearchPaths++);
1349 	fs_invSearchPaths = Mem_PoolAlloc (sizeof (fsPath_t) * fs_numInvSearchPaths, com_fileSysPool, 0);
1350 	for (i=fs_numInvSearchPaths-1, next=fs_searchPaths ; i>=0 ; next=next->next, i--)
1351 		fs_invSearchPaths[i] = next;
1352 
1353 	if (!firstTime) {
1354 		Com_Printf (0, "----------------------------------------\n");
1355 		Com_Printf (0, "init time: %ums\n", Sys_UMilliseconds()-initTime);
1356 		Com_Printf (0, "----------------------------------------\n");
1357 
1358 #ifndef DEDICATED_ONLY
1359 		// Forced to reload to flush old data
1360 		if (!dedicated->intVal) {
1361 			Cbuf_AddText ("exec default.cfg\n");
1362 			Cbuf_AddText ("exec config.cfg\n");
1363 			Cbuf_AddText ("exec eglcfg.cfg\n");
1364 			FS_ExecAutoexec ();
1365 			Cbuf_Execute ();
1366 			Cbuf_AddText ("vid_restart\nsnd_restart\n");
1367 			Cbuf_Execute ();
1368 		}
1369 #endif
1370 	}
1371 }
1372 
1373 
1374 /*
1375 =============
1376 FS_ExecAutoexec
1377 =============
1378 */
FS_ExecAutoexec(void)1379 void FS_ExecAutoexec (void)
1380 {
1381 	char	*dir;
1382 	char	name[MAX_QPATH];
1383 
1384 	dir = Cvar_GetStringValue ("gamedir");
1385 	if (*dir)
1386 		Q_snprintfz (name, sizeof (name), "%s/%s/autoexec.cfg", fs_basedir->string, dir);
1387 	else
1388 		Q_snprintfz (name, sizeof (name), "%s/%s/autoexec.cfg", fs_basedir->string, BASE_MODDIRNAME);
1389 
1390 	if (Sys_FindFirst (name, 0, (SFF_SUBDIR|SFF_HIDDEN|SFF_SYSTEM)))
1391 		Cbuf_AddText ("exec autoexec.cfg\n");
1392 
1393 	Sys_FindClose ();
1394 }
1395 
1396 /*
1397 =============================================================================
1398 
1399 	FILE SEARCHING
1400 
1401 =============================================================================
1402 */
1403 
1404 /*
1405 ================
1406 FS_FindFiles
1407 ================
1408 */
FS_FindFiles(char * path,char * filter,char * extension,char ** fileList,int maxFiles,qBool addGameDir,qBool recurse)1409 int FS_FindFiles (char *path, char *filter, char *extension, char **fileList, int maxFiles, qBool addGameDir, qBool recurse)
1410 {
1411 	fsPath_t	*search;
1412 	mPackFile_t	*packFile;
1413 	mPack_t		*pack;
1414 	int			fileCount;
1415 	char		*name;
1416 	char		dir[MAX_OSPATH];
1417 	char		ext[MAX_QEXT];
1418 	char		*dirFiles[FS_MAX_FINDFILES];
1419 	int			dirCount, i, j, k;
1420 
1421 	// Sanity check
1422 	if (maxFiles > FS_MAX_FINDFILES) {
1423 		Com_Printf (PRNT_ERROR, "FS_FindFiles: maxFiles(%i) > %i, forcing %i...\n", maxFiles, FS_MAX_FINDFILES, FS_MAX_FINDFILES);
1424 		maxFiles = FS_MAX_FINDFILES;
1425 	}
1426 
1427 	// Search through the path, one element at a time
1428 	fileCount = 0;
1429 	for (k=0 ; k<fs_numInvSearchPaths ; k++) {
1430 		search = fs_invSearchPaths[k];
1431 
1432 		if (search->package) {
1433 			// Pack file
1434 			pack = search->package;
1435 			for (i=0, packFile=pack->files ; i<pack->numFiles ; i++, packFile++) {
1436 				// Match path
1437 				if (!recurse) {
1438 					Com_FilePath (packFile->fileName, dir, sizeof (dir));
1439 					if (Q_stricmp (path, dir))
1440 						continue;
1441 				}
1442 				// Check path
1443 				else if (!strstr (packFile->fileName, path))
1444 					continue;
1445 
1446 				// Match extension
1447 				if (extension) {
1448 					Com_FileExtension (packFile->fileName, ext, sizeof (ext));
1449 
1450 					// Filter or compare
1451 					if (strchr (extension, '*')) {
1452 						if (!Q_WildcardMatch (extension, ext, 1))
1453 							continue;
1454 					}
1455 					else {
1456 						if (Q_stricmp (extension, ext))
1457 							continue;
1458 					}
1459 				}
1460 
1461 				// Match filter
1462 				if (filter) {
1463 					if (!Q_WildcardMatch (filter, packFile->fileName, 1))
1464 						continue;
1465 				}
1466 
1467 				// Found something
1468 				name = packFile->fileName;
1469 				if (fileCount < maxFiles) {
1470 					// Ignore duplicates
1471 					for (j=0 ; j<fileCount ; j++) {
1472 						if (!Q_stricmp (fileList[j], name))
1473 							break;
1474 					}
1475 
1476 					if (j == fileCount) {
1477 						if (addGameDir)
1478 							fileList[fileCount++] = Mem_PoolStrDup (Q_VarArgs ("%s/%s", search->gamePath, name), com_fileSysPool, 0);
1479 						else
1480 							fileList[fileCount++] = Mem_PoolStrDup (name, com_fileSysPool, 0);
1481 					}
1482 				}
1483 			}
1484 		}
1485 		else {
1486 			// Directory tree
1487 			Q_snprintfz (dir, sizeof (dir), "%s/%s", search->pathName, path);
1488 
1489 			if (extension) {
1490 				Q_snprintfz (ext, sizeof (ext), "*.%s", extension);
1491 				dirCount = Sys_FindFiles (dir, ext, dirFiles, FS_MAX_FINDFILES, 0, recurse, qTrue, qFalse);
1492 			}
1493 			else {
1494 				dirCount = Sys_FindFiles (dir, "*", dirFiles, FS_MAX_FINDFILES, 0, recurse, qTrue, qTrue);
1495 			}
1496 
1497 			for (i=0 ; i<dirCount ; i++) {
1498 				// Match filter
1499 				if (filter) {
1500 					if (!Q_WildcardMatch (filter, dirFiles[i]+strlen(search->pathName)+1, 1)) {
1501 						Mem_Free (dirFiles[i]);
1502 						continue;
1503 					}
1504 				}
1505 
1506 				// Found something
1507 				name = dirFiles[i] + strlen (search->pathName) + 1;
1508 
1509 				if (fileCount < maxFiles) {
1510 					// Ignore duplicates
1511 					for (j=0 ; j<fileCount ; j++) {
1512 						if (!Q_stricmp (fileList[j], name))
1513 							break;
1514 					}
1515 
1516 					if (j == fileCount) {
1517 						if (addGameDir)
1518 							fileList[fileCount++] = Mem_PoolStrDup (Q_VarArgs ("%s/%s", search->gamePath, name), com_fileSysPool, 0);
1519 						else
1520 							fileList[fileCount++] = Mem_PoolStrDup (name, com_fileSysPool, 0);
1521 					}
1522 				}
1523 
1524 				Mem_Free (dirFiles[i]);
1525 			}
1526 		}
1527 	}
1528 
1529 	return fileCount;
1530 }
1531 
1532 
1533 /*
1534 =============
1535 _FS_FreeFileList
1536 =============
1537 */
_FS_FreeFileList(char ** list,int num,const char * fileName,const int fileLine)1538 void _FS_FreeFileList (char **list, int num, const char *fileName, const int fileLine)
1539 {
1540 	int		i;
1541 
1542 	for (i=0 ; i<num ; i++) {
1543 		if (!list[i])
1544 			continue;
1545 
1546 		_Mem_Free (list[i], fileName, fileLine);
1547 		list[i] = NULL;
1548 	}
1549 }
1550 
1551 
1552 /*
1553 ================
1554 FS_NextPath
1555 
1556 Allows enumerating all of the directories in the search path
1557 ================
1558 */
FS_NextPath(char * prevPath)1559 char *FS_NextPath (char *prevPath)
1560 {
1561 	fsPath_t	*s;
1562 	char		*prev;
1563 
1564 	if (!prevPath)
1565 		return fs_gameDir;
1566 
1567 	prev = fs_gameDir;
1568 	for (s=fs_searchPaths ; s ; s=s->next) {
1569 		if (s->package)
1570 			continue;
1571 		if (prevPath == prev)
1572 			return s->pathName;
1573 		prev = s->pathName;
1574 	}
1575 
1576 	return NULL;
1577 }
1578 
1579 /*
1580 =============================================================================
1581 
1582 	CONSOLE FUNCTIONS
1583 
1584 =============================================================================
1585 */
1586 
1587 /*
1588 ================
1589 FS_Link_f
1590 
1591 Creates a fsLink_t
1592 ================
1593 */
FS_Link_f(void)1594 static void FS_Link_f (void)
1595 {
1596 	fsLink_t	*l, **prev;
1597 
1598 	if (Cmd_Argc () != 3) {
1599 		Com_Printf (0, "USAGE: link <from> <to>\n");
1600 		return;
1601 	}
1602 
1603 	// See if the link already exists
1604 	prev = &fs_fileLinks;
1605 	for (l=fs_fileLinks ; l ; l=l->next) {
1606 		if (!strcmp (l->from, Cmd_Argv (1))) {
1607 			Mem_Free (l->to);
1608 			if (!strlen (Cmd_Argv (2))) {
1609 				// Delete it
1610 				*prev = l->next;
1611 				Mem_Free (l->from);
1612 				Mem_Free (l);
1613 				return;
1614 			}
1615 			l->to = Mem_PoolStrDup (Cmd_Argv (2), com_fileSysPool, 0);
1616 			return;
1617 		}
1618 		prev = &l->next;
1619 	}
1620 
1621 	// Create a new link
1622 	l = Mem_PoolAlloc (sizeof (*l), com_fileSysPool, 0);
1623 	l->from = Mem_PoolStrDup (Cmd_Argv (1), com_fileSysPool, 0);
1624 	l->fromLength = strlen (l->from);
1625 	l->to = Mem_PoolStrDup (Cmd_Argv (2), com_fileSysPool, 0);
1626 	l->next = fs_fileLinks;
1627 	fs_fileLinks = l;
1628 }
1629 
1630 
1631 /*
1632 ============
1633 FS_ListHandles_f
1634 ============
1635 */
FS_ListHandles_f(void)1636 static void FS_ListHandles_f (void)
1637 {
1638 	fsHandleIndex_t	*index;
1639 	int				i;
1640 
1641 	Com_Printf (0, " #  mode name\n");
1642 	Com_Printf (0, "--- ---- ----------------\n");
1643 	for (i=0, index=&fs_fileIndices[0] ; i<FS_MAX_FILEINDICES ; index++, i++) {
1644 		if (!index->inUse)
1645 			continue;
1646 
1647 		Com_Printf (0, "%3i ", i+1);
1648 		switch (index->openMode) {
1649 		case FS_MODE_READ_BINARY:	Com_Printf (0, "RB ");	break;
1650 		case FS_MODE_WRITE_BINARY:	Com_Printf (0, "WB ");	break;
1651 		case FS_MODE_APPEND_BINARY:	Com_Printf (0, "AB ");	break;
1652 		case FS_MODE_WRITE_TEXT:	Com_Printf (0, "WT ");	break;
1653 		case FS_MODE_APPEND_TEXT:	Com_Printf (0, "AT ");	break;
1654 		}
1655 		Com_Printf (0, "%s\n", index->name);
1656 	}
1657 }
1658 
1659 
1660 /*
1661 ============
1662 FS_Path_f
1663 ============
1664 */
FS_Path_f(void)1665 static void FS_Path_f (void)
1666 {
1667 	fsPath_t	*s;
1668 	fsLink_t	*l;
1669 
1670 	Com_Printf (0, "Current search path:\n");
1671 	for (s=fs_searchPaths ; s ; s=s->next) {
1672 		if (s == fs_baseSearchPath)
1673 			Com_Printf (0, "----------\n");
1674 
1675 		if (s->package)
1676 			Com_Printf (0, "%s (%i files)\n", s->package->name, s->package->numFiles);
1677 		else
1678 			Com_Printf (0, "%s\n", s->pathName);
1679 	}
1680 
1681 	Com_Printf (0, "\nLinks:\n");
1682 	for (l=fs_fileLinks ; l ; l=l->next)
1683 		Com_Printf (0, "%s : %s\n", l->from, l->to);
1684 }
1685 
1686 /*
1687 =============================================================================
1688 
1689 	INIT / SHUTDOWN
1690 
1691 =============================================================================
1692 */
1693 
1694 /*
1695 ================
1696 FS_Init
1697 ================
1698 */
FS_Init(void)1699 void FS_Init (void)
1700 {
1701 	uint32		initTime;
1702 	fsPath_t	*next;
1703 	int			i;
1704 
1705 	initTime = Sys_UMilliseconds ();
1706 	Com_Printf (0, "\n------- Filesystem Initialization ------\n");
1707 
1708 	// Register commands/cvars
1709 	Cmd_AddCommand ("link",			FS_Link_f,			"");
1710 	Cmd_AddCommand ("listHandles",	FS_ListHandles_f,	"Lists active files");
1711 	Cmd_AddCommand ("path",			FS_Path_f,			"");
1712 
1713 	fs_basedir		= Cvar_Register ("basedir",			".",	CVAR_READONLY);
1714 	fs_cddir		= Cvar_Register ("cddir",			"",		CVAR_READONLY);
1715 	fs_game			= Cvar_Register ("game",			"",		CVAR_LATCH_SERVER|CVAR_SERVERINFO|CVAR_RESET_GAMEDIR);
1716 	fs_gamedircvar	= Cvar_Register ("gamedir",			"",		CVAR_SERVERINFO|CVAR_READONLY);
1717 	fs_defaultPaks	= Cvar_Register ("fs_defaultPaks",	"1",	CVAR_ARCHIVE);
1718 
1719 	// Load pak files
1720 #ifdef __FreeBSD__
1721 	FS_AddGameDirectory (Q_VarArgs ("%s/"BASE_MODDIRNAME, DATADIR), BASE_MODDIRNAME);
1722 	FS_AddGameDirectory (Q_VarArgs ("%s/"BASE_MODDIRNAME, LIBDIR), BASE_MODDIRNAME);
1723 #endif
1724 	if (fs_cddir->string[0])
1725 		FS_AddGameDirectory (Q_VarArgs ("%s/"BASE_MODDIRNAME, fs_cddir->string), BASE_MODDIRNAME);
1726 
1727 	FS_AddGameDirectory (Q_VarArgs ("%s/"BASE_MODDIRNAME, fs_basedir->string), BASE_MODDIRNAME);
1728 #ifdef HOMEDIR
1729 	FS_AddHomeAsGameDirectory(BASE_MODDIRNAME);
1730 #endif
1731 
1732 	// Any set gamedirs will be freed up to here
1733 	fs_baseSearchPath = fs_searchPaths;
1734 
1735 	// Load the game directory
1736 	if (fs_game->string[0]) {
1737 		FS_SetGamedir (fs_game->string, qTrue);
1738 #ifdef HOMEDIR
1739 		/* Create the writable directory if doesn't exist ("~/.egl"). */
1740 		FS_CreatePath(fs_gameDir);
1741 		Sys_Mkdir(fs_gameDir);
1742 		Com_Printf(0, "Using '%s' for writing.\n", fs_gameDir);
1743 #endif
1744 	}
1745 	else {
1746 		// Store a copy of the search paths inverted for FS_FindFiles
1747 		for (fs_numInvSearchPaths=0, next=fs_searchPaths ; next ; next=next->next, fs_numInvSearchPaths++);
1748 		fs_invSearchPaths = Mem_PoolAlloc (sizeof (fsPath_t) * fs_numInvSearchPaths, com_fileSysPool, 0);
1749 		for (i=fs_numInvSearchPaths-1, next=fs_searchPaths ; i>=0 ; next=next->next, i--)
1750 			fs_invSearchPaths[i] = next;
1751 	}
1752 
1753 	Com_Printf (0, "----------------------------------------\n");
1754 
1755 	// Check memory integrity
1756 	Mem_CheckPoolIntegrity (com_fileSysPool);
1757 
1758 	Com_Printf (0, "init time: %ums\n", Sys_UMilliseconds()-initTime);
1759 	Com_Printf (0, "----------------------------------------\n");
1760 }
1761