1 /*
2 	DarkPlaces file system
3 
4 	Copyright (C) 2003-2006 Mathieu Olivier
5 
6 	This program is free software; you can redistribute it and/or
7 	modify it under the terms of the GNU General Public License
8 	as published by the Free Software Foundation; either version 2
9 	of the License, or (at your option) any later version.
10 
11 	This program is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 
15 	See the GNU General Public License for more details.
16 
17 	You should have received a copy of the GNU General Public License
18 	along with this program; if not, write to:
19 
20 		Free Software Foundation, Inc.
21 		59 Temple Place - Suite 330
22 		Boston, MA  02111-1307, USA
23 */
24 
25 #include <limits.h>
26 #include <fcntl.h>
27 
28 #ifdef WIN32
29 # include <direct.h>
30 # include <io.h>
31 # include <shlobj.h>
32 # include <sys/stat.h>
33 # include <share.h>
34 #else
35 # include <pwd.h>
36 # include <sys/stat.h>
37 # include <unistd.h>
38 #endif
39 
40 #include "quakedef.h"
41 
42 #if TARGET_OS_IPHONE
43 // include SDL for IPHONEOS code
44 # include <SDL.h>
45 #endif
46 
47 #include "thread.h"
48 
49 #include "fs.h"
50 #include "wad.h"
51 
52 // Win32 requires us to add O_BINARY, but the other OSes don't have it
53 #ifndef O_BINARY
54 # define O_BINARY 0
55 #endif
56 
57 // In case the system doesn't support the O_NONBLOCK flag
58 #ifndef O_NONBLOCK
59 # define O_NONBLOCK 0
60 #endif
61 
62 // largefile support for Win32
63 #ifdef WIN32
64 #undef lseek
65 # define lseek _lseeki64
66 #endif
67 
68 // suppress deprecated warnings
69 #if _MSC_VER >= 1400
70 # define read _read
71 # define write _write
72 # define close _close
73 # define unlink _unlink
74 # define dup _dup
75 #endif
76 
77 #if USE_RWOPS
78 # include <SDL.h>
79 typedef SDL_RWops *filedesc_t;
80 # define FILEDESC_INVALID NULL
81 # define FILEDESC_ISVALID(fd) ((fd) != NULL)
82 # define FILEDESC_READ(fd,buf,count) ((fs_offset_t)SDL_RWread(fd, buf, 1, count))
83 # define FILEDESC_WRITE(fd,buf,count) ((fs_offset_t)SDL_RWwrite(fd, buf, 1, count))
84 # define FILEDESC_CLOSE SDL_RWclose
85 # define FILEDESC_SEEK SDL_RWseek
FILEDESC_DUP(const char * filename,filedesc_t fd)86 static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
87 	filedesc_t new_fd = SDL_RWFromFile(filename, "rb");
88 	if (SDL_RWseek(new_fd, SDL_RWseek(fd, 0, RW_SEEK_CUR), RW_SEEK_SET) < 0) {
89 		SDL_RWclose(new_fd);
90 		return NULL;
91 	}
92 	return new_fd;
93 }
94 # define unlink(name) Con_DPrintf("Sorry, no unlink support when trying to unlink %s.\n", (name))
95 #else
96 typedef int filedesc_t;
97 # define FILEDESC_INVALID -1
98 # define FILEDESC_ISVALID(fd) ((fd) != -1)
99 # define FILEDESC_READ read
100 # define FILEDESC_WRITE write
101 # define FILEDESC_CLOSE close
102 # define FILEDESC_SEEK lseek
FILEDESC_DUP(const char * filename,filedesc_t fd)103 static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
104 	return dup(fd);
105 }
106 #endif
107 
108 /** \page fs File System
109 
110 All of Quake's data access is through a hierchal file system, but the contents
111 of the file system can be transparently merged from several sources.
112 
113 The "base directory" is the path to the directory holding the quake.exe and
114 all game directories.  The sys_* files pass this to host_init in
115 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
116 line parm to allow code debugging in a different directory.  The base
117 directory is only used during filesystem initialization.
118 
119 The "game directory" is the first tree on the search path and directory that
120 all generated files (savegames, screenshots, demos, config files) will be
121 saved to.  This can be overridden with the "-game" command line parameter.
122 The game directory can never be changed while quake is executing.  This is a
123 precaution against having a malicious server instruct clients to write files
124 over areas they shouldn't.
125 
126 */
127 
128 
129 /*
130 =============================================================================
131 
132 CONSTANTS
133 
134 =============================================================================
135 */
136 
137 // Magic numbers of a ZIP file (big-endian format)
138 #define ZIP_DATA_HEADER	0x504B0304  // "PK\3\4"
139 #define ZIP_CDIR_HEADER	0x504B0102  // "PK\1\2"
140 #define ZIP_END_HEADER	0x504B0506  // "PK\5\6"
141 
142 // Other constants for ZIP files
143 #define ZIP_MAX_COMMENTS_SIZE		((unsigned short)0xFFFF)
144 #define ZIP_END_CDIR_SIZE			22
145 #define ZIP_CDIR_CHUNK_BASE_SIZE	46
146 #define ZIP_LOCAL_CHUNK_BASE_SIZE	30
147 
148 #ifdef LINK_TO_ZLIB
149 #include <zlib.h>
150 
151 #define qz_inflate inflate
152 #define qz_inflateEnd inflateEnd
153 #define qz_inflateInit2_ inflateInit2_
154 #define qz_inflateReset inflateReset
155 #define qz_deflateInit2_ deflateInit2_
156 #define qz_deflateEnd deflateEnd
157 #define qz_deflate deflate
158 #define Z_MEMLEVEL_DEFAULT 8
159 #else
160 
161 // Zlib constants (from zlib.h)
162 #define Z_SYNC_FLUSH	2
163 #define MAX_WBITS		15
164 #define Z_OK			0
165 #define Z_STREAM_END	1
166 #define Z_STREAM_ERROR  (-2)
167 #define Z_DATA_ERROR    (-3)
168 #define Z_MEM_ERROR     (-4)
169 #define Z_BUF_ERROR     (-5)
170 #define ZLIB_VERSION	"1.2.3"
171 
172 #define Z_BINARY 0
173 #define Z_DEFLATED 8
174 #define Z_MEMLEVEL_DEFAULT 8
175 
176 #define Z_NULL 0
177 #define Z_DEFAULT_COMPRESSION (-1)
178 #define Z_NO_FLUSH 0
179 #define Z_SYNC_FLUSH 2
180 #define Z_FULL_FLUSH 3
181 #define Z_FINISH 4
182 
183 // Uncomment the following line if the zlib DLL you have still uses
184 // the 1.1.x series calling convention on Win32 (WINAPI)
185 //#define ZLIB_USES_WINAPI
186 
187 
188 /*
189 =============================================================================
190 
191 TYPES
192 
193 =============================================================================
194 */
195 
196 /*! Zlib stream (from zlib.h)
197  * \warning: some pointers we don't use directly have
198  * been cast to "void*" for a matter of simplicity
199  */
200 typedef struct
201 {
202 	unsigned char			*next_in;	///< next input byte
203 	unsigned int	avail_in;	///< number of bytes available at next_in
204 	unsigned long	total_in;	///< total nb of input bytes read so far
205 
206 	unsigned char			*next_out;	///< next output byte should be put there
207 	unsigned int	avail_out;	///< remaining free space at next_out
208 	unsigned long	total_out;	///< total nb of bytes output so far
209 
210 	char			*msg;		///< last error message, NULL if no error
211 	void			*state;		///< not visible by applications
212 
213 	void			*zalloc;	///< used to allocate the internal state
214 	void			*zfree;		///< used to free the internal state
215 	void			*opaque;	///< private data object passed to zalloc and zfree
216 
217 	int				data_type;	///< best guess about the data type: ascii or binary
218 	unsigned long	adler;		///< adler32 value of the uncompressed data
219 	unsigned long	reserved;	///< reserved for future use
220 } z_stream;
221 #endif
222 
223 
224 /// inside a package (PAK or PK3)
225 #define QFILE_FLAG_PACKED (1 << 0)
226 /// file is compressed using the deflate algorithm (PK3 only)
227 #define QFILE_FLAG_DEFLATED (1 << 1)
228 /// file is actually already loaded data
229 #define QFILE_FLAG_DATA (1 << 2)
230 /// real file will be removed on close
231 #define QFILE_FLAG_REMOVE (1 << 3)
232 
233 #define FILE_BUFF_SIZE 2048
234 typedef struct
235 {
236 	z_stream	zstream;
237 	size_t		comp_length;			///< length of the compressed file
238 	size_t		in_ind, in_len;			///< input buffer current index and length
239 	size_t		in_position;			///< position in the compressed file
240 	unsigned char		input [FILE_BUFF_SIZE];
241 } ztoolkit_t;
242 
243 struct qfile_s
244 {
245 	int				flags;
246 	filedesc_t			handle;					///< file descriptor
247 	fs_offset_t		real_length;			///< uncompressed file size (for files opened in "read" mode)
248 	fs_offset_t		position;				///< current position in the file
249 	fs_offset_t		offset;					///< offset into the package (0 if external file)
250 	int				ungetc;					///< single stored character from ungetc, cleared to EOF when read
251 
252 	// Contents buffer
253 	fs_offset_t		buff_ind, buff_len;		///< buffer current index and length
254 	unsigned char			buff [FILE_BUFF_SIZE];
255 
256 	ztoolkit_t*		ztk;	///< For zipped files.
257 
258 	const unsigned char *data;	///< For data files.
259 
260 	const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
261 };
262 
263 
264 // ------ PK3 files on disk ------ //
265 
266 // You can get the complete ZIP format description from PKWARE website
267 
268 typedef struct pk3_endOfCentralDir_s
269 {
270 	unsigned int signature;
271 	unsigned short disknum;
272 	unsigned short cdir_disknum;	///< number of the disk with the start of the central directory
273 	unsigned short localentries;	///< number of entries in the central directory on this disk
274 	unsigned short nbentries;		///< total number of entries in the central directory on this disk
275 	unsigned int cdir_size;			///< size of the central directory
276 	unsigned int cdir_offset;		///< with respect to the starting disk number
277 	unsigned short comment_size;
278 	fs_offset_t prepended_garbage;
279 } pk3_endOfCentralDir_t;
280 
281 
282 // ------ PAK files on disk ------ //
283 typedef struct dpackfile_s
284 {
285 	char name[56];
286 	int filepos, filelen;
287 } dpackfile_t;
288 
289 typedef struct dpackheader_s
290 {
291 	char id[4];
292 	int dirofs;
293 	int dirlen;
294 } dpackheader_t;
295 
296 
297 /*! \name Packages in memory
298  * @{
299  */
300 /// the offset in packfile_t is the true contents offset
301 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
302 /// file compressed using the deflate algorithm
303 #define PACKFILE_FLAG_DEFLATED (1 << 1)
304 /// file is a symbolic link
305 #define PACKFILE_FLAG_SYMLINK (1 << 2)
306 
307 typedef struct packfile_s
308 {
309 	char name [MAX_QPATH];
310 	int flags;
311 	fs_offset_t offset;
312 	fs_offset_t packsize;	///< size in the package
313 	fs_offset_t realsize;	///< real file size (uncompressed)
314 } packfile_t;
315 
316 typedef struct pack_s
317 {
318 	char filename [MAX_OSPATH];
319 	char shortname [MAX_QPATH];
320 	filedesc_t handle;
321 	int ignorecase;  ///< PK3 ignores case
322 	int numfiles;
323 	qboolean vpack;
324 	packfile_t *files;
325 } pack_t;
326 //@}
327 
328 /// Search paths for files (including packages)
329 typedef struct searchpath_s
330 {
331 	// only one of filename / pack will be used
332 	char filename[MAX_OSPATH];
333 	pack_t *pack;
334 	struct searchpath_s *next;
335 } searchpath_t;
336 
337 
338 /*
339 =============================================================================
340 
341 FUNCTION PROTOTYPES
342 
343 =============================================================================
344 */
345 
346 void FS_Dir_f(void);
347 void FS_Ls_f(void);
348 void FS_Which_f(void);
349 
350 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
351 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
352 									fs_offset_t offset, fs_offset_t packsize,
353 									fs_offset_t realsize, int flags);
354 
355 
356 /*
357 =============================================================================
358 
359 VARIABLES
360 
361 =============================================================================
362 */
363 
364 mempool_t *fs_mempool;
365 void *fs_mutex = NULL;
366 
367 searchpath_t *fs_searchpaths = NULL;
368 const char *const fs_checkgamedir_missing = "missing";
369 
370 #define MAX_FILES_IN_PACK	65536
371 
372 char fs_userdir[MAX_OSPATH];
373 char fs_gamedir[MAX_OSPATH];
374 char fs_basedir[MAX_OSPATH];
375 static pack_t *fs_selfpack = NULL;
376 
377 // list of active game directories (empty if not running a mod)
378 int fs_numgamedirs = 0;
379 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
380 
381 // list of all gamedirs with modinfo.txt
382 gamedir_t *fs_all_gamedirs = NULL;
383 int fs_all_gamedirs_count = 0;
384 
385 cvar_t scr_screenshot_name = {CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"};
386 cvar_t fs_empty_files_in_pack_mark_deletions = {0, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"};
387 cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
388 
389 
390 /*
391 =============================================================================
392 
393 PRIVATE FUNCTIONS - PK3 HANDLING
394 
395 =============================================================================
396 */
397 
398 #ifndef LINK_TO_ZLIB
399 // Functions exported from zlib
400 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
401 # define ZEXPORT WINAPI
402 #else
403 # define ZEXPORT
404 #endif
405 
406 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
407 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
408 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
409 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
410 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
411 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
412 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
413 #endif
414 
415 #define qz_inflateInit2(strm, windowBits) \
416         qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
417 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
418         qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
419 
420 #ifndef LINK_TO_ZLIB
421 //        qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
422 
423 static dllfunction_t zlibfuncs[] =
424 {
425 	{"inflate",			(void **) &qz_inflate},
426 	{"inflateEnd",		(void **) &qz_inflateEnd},
427 	{"inflateInit2_",	(void **) &qz_inflateInit2_},
428 	{"inflateReset",	(void **) &qz_inflateReset},
429 	{"deflateInit2_",   (void **) &qz_deflateInit2_},
430 	{"deflateEnd",      (void **) &qz_deflateEnd},
431 	{"deflate",         (void **) &qz_deflate},
432 	{NULL, NULL}
433 };
434 
435 /// Handle for Zlib DLL
436 static dllhandle_t zlib_dll = NULL;
437 #endif
438 
439 #ifdef WIN32
440 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
441 static dllfunction_t shfolderfuncs[] =
442 {
443 	{"SHGetFolderPathA", (void **) &qSHGetFolderPath},
444 	{NULL, NULL}
445 };
446 static const char* shfolderdllnames [] =
447 {
448 	"shfolder.dll",  // IE 4, or Win NT and higher
449 	NULL
450 };
451 static dllhandle_t shfolder_dll = NULL;
452 
453 const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}};
454 #define qREFKNOWNFOLDERID const GUID *
455 #define qKF_FLAG_CREATE 0x8000
456 #define qKF_FLAG_NO_ALIAS 0x1000
457 static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
458 static dllfunction_t shell32funcs[] =
459 {
460 	{"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath},
461 	{NULL, NULL}
462 };
463 static const char* shell32dllnames [] =
464 {
465 	"shell32.dll",  // Vista and higher
466 	NULL
467 };
468 static dllhandle_t shell32_dll = NULL;
469 
470 static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit);
471 static void (WINAPI *qCoUninitialize)(void);
472 static void (WINAPI *qCoTaskMemFree)(LPVOID pv);
473 static dllfunction_t ole32funcs[] =
474 {
475 	{"CoInitializeEx", (void **) &qCoInitializeEx},
476 	{"CoUninitialize", (void **) &qCoUninitialize},
477 	{"CoTaskMemFree", (void **) &qCoTaskMemFree},
478 	{NULL, NULL}
479 };
480 static const char* ole32dllnames [] =
481 {
482 	"ole32.dll", // 2000 and higher
483 	NULL
484 };
485 static dllhandle_t ole32_dll = NULL;
486 #endif
487 
488 /*
489 ====================
490 PK3_CloseLibrary
491 
492 Unload the Zlib DLL
493 ====================
494 */
PK3_CloseLibrary(void)495 static void PK3_CloseLibrary (void)
496 {
497 #ifndef LINK_TO_ZLIB
498 	Sys_UnloadLibrary (&zlib_dll);
499 #endif
500 }
501 
502 
503 /*
504 ====================
505 PK3_OpenLibrary
506 
507 Try to load the Zlib DLL
508 ====================
509 */
PK3_OpenLibrary(void)510 static qboolean PK3_OpenLibrary (void)
511 {
512 #ifdef LINK_TO_ZLIB
513 	return true;
514 #else
515 	const char* dllnames [] =
516 	{
517 #if defined(WIN32)
518 # ifdef ZLIB_USES_WINAPI
519 		"zlibwapi.dll",
520 		"zlib.dll",
521 # else
522 		"zlib1.dll",
523 # endif
524 #elif defined(MACOSX)
525 		"libz.dylib",
526 #else
527 		"libz.so.1",
528 		"libz.so",
529 #endif
530 		NULL
531 	};
532 
533 	// Already loaded?
534 	if (zlib_dll)
535 		return true;
536 
537 	// Load the DLL
538 	return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
539 #endif
540 }
541 
542 /*
543 ====================
544 FS_HasZlib
545 
546 See if zlib is available
547 ====================
548 */
FS_HasZlib(void)549 qboolean FS_HasZlib(void)
550 {
551 #ifdef LINK_TO_ZLIB
552 	return true;
553 #else
554 	PK3_OpenLibrary(); // to be safe
555 	return (zlib_dll != 0);
556 #endif
557 }
558 
559 /*
560 ====================
561 PK3_GetEndOfCentralDir
562 
563 Extract the end of the central directory from a PK3 package
564 ====================
565 */
PK3_GetEndOfCentralDir(const char * packfile,filedesc_t packhandle,pk3_endOfCentralDir_t * eocd)566 static qboolean PK3_GetEndOfCentralDir (const char *packfile, filedesc_t packhandle, pk3_endOfCentralDir_t *eocd)
567 {
568 	fs_offset_t filesize, maxsize;
569 	unsigned char *buffer, *ptr;
570 	int ind;
571 
572 	// Get the package size
573 	filesize = FILEDESC_SEEK (packhandle, 0, SEEK_END);
574 	if (filesize < ZIP_END_CDIR_SIZE)
575 		return false;
576 
577 	// Load the end of the file in memory
578 	if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
579 		maxsize = filesize;
580 	else
581 		maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
582 	buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
583 	FILEDESC_SEEK (packhandle, filesize - maxsize, SEEK_SET);
584 	if (FILEDESC_READ (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
585 	{
586 		Mem_Free (buffer);
587 		return false;
588 	}
589 
590 	// Look for the end of central dir signature around the end of the file
591 	maxsize -= ZIP_END_CDIR_SIZE;
592 	ptr = &buffer[maxsize];
593 	ind = 0;
594 	while (BuffBigLong (ptr) != ZIP_END_HEADER)
595 	{
596 		if (ind == maxsize)
597 		{
598 			Mem_Free (buffer);
599 			return false;
600 		}
601 
602 		ind++;
603 		ptr--;
604 	}
605 
606 	memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
607 	eocd->signature = LittleLong (eocd->signature);
608 	eocd->disknum = LittleShort (eocd->disknum);
609 	eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
610 	eocd->localentries = LittleShort (eocd->localentries);
611 	eocd->nbentries = LittleShort (eocd->nbentries);
612 	eocd->cdir_size = LittleLong (eocd->cdir_size);
613 	eocd->cdir_offset = LittleLong (eocd->cdir_offset);
614 	eocd->comment_size = LittleShort (eocd->comment_size);
615 	eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files
616 	eocd->cdir_offset += eocd->prepended_garbage;
617 
618 	Mem_Free (buffer);
619 
620 	if (
621 			eocd->cdir_size > filesize ||
622 			eocd->cdir_offset >= filesize ||
623 			eocd->cdir_offset + eocd->cdir_size > filesize
624 	   )
625 	{
626 		// Obviously invalid central directory.
627 		return false;
628 	}
629 
630 	return true;
631 }
632 
633 
634 /*
635 ====================
636 PK3_BuildFileList
637 
638 Extract the file list from a PK3 file
639 ====================
640 */
PK3_BuildFileList(pack_t * pack,const pk3_endOfCentralDir_t * eocd)641 static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
642 {
643 	unsigned char *central_dir, *ptr;
644 	unsigned int ind;
645 	fs_offset_t remaining;
646 
647 	// Load the central directory in memory
648 	central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
649 	if (FILEDESC_SEEK (pack->handle, eocd->cdir_offset, SEEK_SET) == -1)
650 	{
651 		Mem_Free (central_dir);
652 		return -1;
653 	}
654 	if(FILEDESC_READ (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size)
655 	{
656 		Mem_Free (central_dir);
657 		return -1;
658 	}
659 
660 	// Extract the files properties
661 	// The parsing is done "by hand" because some fields have variable sizes and
662 	// the constant part isn't 4-bytes aligned, which makes the use of structs difficult
663 	remaining = eocd->cdir_size;
664 	pack->numfiles = 0;
665 	ptr = central_dir;
666 	for (ind = 0; ind < eocd->nbentries; ind++)
667 	{
668 		fs_offset_t namesize, count;
669 
670 		// Checking the remaining size
671 		if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
672 		{
673 			Mem_Free (central_dir);
674 			return -1;
675 		}
676 		remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
677 
678 		// Check header
679 		if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
680 		{
681 			Mem_Free (central_dir);
682 			return -1;
683 		}
684 
685 		namesize = BuffLittleShort (&ptr[28]);	// filename length
686 
687 		// Check encryption, compression, and attributes
688 		// 1st uint8  : general purpose bit flag
689 		//    Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
690 		//
691 		// LordHavoc: bit 3 would be a problem if we were scanning the archive
692 		// but is not a problem in the central directory where the values are
693 		// always real.
694 		//
695 		// bit 3 seems to always be set by the standard Mac OSX zip maker
696 		//
697 		// 2nd uint8 : external file attributes
698 		//    Check bits 3 (file is a directory) and 5 (file is a volume (?))
699 		if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
700 		{
701 			// Still enough bytes for the name?
702 			if (namesize < 0 || remaining < namesize || namesize >= (int)sizeof (*pack->files))
703 			{
704 				Mem_Free (central_dir);
705 				return -1;
706 			}
707 
708 			// WinZip doesn't use the "directory" attribute, so we need to check the name directly
709 			if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
710 			{
711 				char filename [sizeof (pack->files[0].name)];
712 				fs_offset_t offset, packsize, realsize;
713 				int flags;
714 
715 				// Extract the name (strip it if necessary)
716 				namesize = min(namesize, (int)sizeof (filename) - 1);
717 				memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
718 				filename[namesize] = '\0';
719 
720 				if (BuffLittleShort (&ptr[10]))
721 					flags = PACKFILE_FLAG_DEFLATED;
722 				else
723 					flags = 0;
724 				offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage);
725 				packsize = (unsigned int)BuffLittleLong (&ptr[20]);
726 				realsize = (unsigned int)BuffLittleLong (&ptr[24]);
727 
728 				switch(ptr[5]) // C_VERSION_MADE_BY_1
729 				{
730 					case 3: // UNIX_
731 					case 2: // VMS_
732 					case 16: // BEOS_
733 						if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
734 							// can't use S_ISLNK here, as this has to compile on non-UNIX too
735 							flags |= PACKFILE_FLAG_SYMLINK;
736 						break;
737 				}
738 
739 				FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
740 			}
741 		}
742 
743 		// Skip the name, additionnal field, and comment
744 		// 1er uint16 : extra field length
745 		// 2eme uint16 : file comment length
746 		count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
747 		ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
748 		remaining -= count;
749 	}
750 
751 	// If the package is empty, central_dir is NULL here
752 	if (central_dir != NULL)
753 		Mem_Free (central_dir);
754 	return pack->numfiles;
755 }
756 
757 
758 /*
759 ====================
760 FS_LoadPackPK3
761 
762 Create a package entry associated with a PK3 file
763 ====================
764 */
FS_LoadPackPK3FromFD(const char * packfile,filedesc_t packhandle,qboolean silent)765 static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle, qboolean silent)
766 {
767 	pk3_endOfCentralDir_t eocd;
768 	pack_t *pack;
769 	int real_nb_files;
770 
771 	if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
772 	{
773 		if(!silent)
774 			Con_Printf ("%s is not a PK3 file\n", packfile);
775 		FILEDESC_CLOSE(packhandle);
776 		return NULL;
777 	}
778 
779 	// Multi-volume ZIP archives are NOT allowed
780 	if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
781 	{
782 		Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
783 		FILEDESC_CLOSE(packhandle);
784 		return NULL;
785 	}
786 
787 	// We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
788 	// since eocd.nbentries is an unsigned 16 bits integer
789 #if MAX_FILES_IN_PACK < 65535
790 	if (eocd.nbentries > MAX_FILES_IN_PACK)
791 	{
792 		Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
793 		FILEDESC_CLOSE(packhandle);
794 		return NULL;
795 	}
796 #endif
797 
798 	// Create a package structure in memory
799 	pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
800 	pack->ignorecase = true; // PK3 ignores case
801 	strlcpy (pack->filename, packfile, sizeof (pack->filename));
802 	pack->handle = packhandle;
803 	pack->numfiles = eocd.nbentries;
804 	pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
805 
806 	real_nb_files = PK3_BuildFileList (pack, &eocd);
807 	if (real_nb_files < 0)
808 	{
809 		Con_Printf ("%s is not a valid PK3 file\n", packfile);
810 		FILEDESC_CLOSE(pack->handle);
811 		Mem_Free(pack);
812 		return NULL;
813 	}
814 
815 	Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files);
816 	return pack;
817 }
818 
819 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking);
FS_LoadPackPK3(const char * packfile)820 static pack_t *FS_LoadPackPK3 (const char *packfile)
821 {
822 	filedesc_t packhandle;
823 	packhandle = FS_SysOpenFiledesc (packfile, "rb", false);
824 	if (!FILEDESC_ISVALID(packhandle))
825 		return NULL;
826 	return FS_LoadPackPK3FromFD(packfile, packhandle, false);
827 }
828 
829 
830 /*
831 ====================
832 PK3_GetTrueFileOffset
833 
834 Find where the true file data offset is
835 ====================
836 */
PK3_GetTrueFileOffset(packfile_t * pfile,pack_t * pack)837 static qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
838 {
839 	unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
840 	fs_offset_t count;
841 
842 	// Already found?
843 	if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
844 		return true;
845 
846 	// Load the local file description
847 	if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
848 	{
849 		Con_Printf ("Can't seek in package %s\n", pack->filename);
850 		return false;
851 	}
852 	count = FILEDESC_READ (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
853 	if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
854 	{
855 		Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
856 		return false;
857 	}
858 
859 	// Skip name and extra field
860 	pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
861 
862 	pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
863 	return true;
864 }
865 
866 
867 /*
868 =============================================================================
869 
870 OTHER PRIVATE FUNCTIONS
871 
872 =============================================================================
873 */
874 
875 
876 /*
877 ====================
878 FS_AddFileToPack
879 
880 Add a file to the list of files contained into a package
881 ====================
882 */
FS_AddFileToPack(const char * name,pack_t * pack,fs_offset_t offset,fs_offset_t packsize,fs_offset_t realsize,int flags)883 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
884 									 fs_offset_t offset, fs_offset_t packsize,
885 									 fs_offset_t realsize, int flags)
886 {
887 	int (*strcmp_funct) (const char* str1, const char* str2);
888 	int left, right, middle;
889 	packfile_t *pfile;
890 
891 	strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
892 
893 	// Look for the slot we should put that file into (binary search)
894 	left = 0;
895 	right = pack->numfiles - 1;
896 	while (left <= right)
897 	{
898 		int diff;
899 
900 		middle = (left + right) / 2;
901 		diff = strcmp_funct (pack->files[middle].name, name);
902 
903 		// If we found the file, there's a problem
904 		if (!diff)
905 			Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
906 
907 		// If we're too far in the list
908 		if (diff > 0)
909 			right = middle - 1;
910 		else
911 			left = middle + 1;
912 	}
913 
914 	// We have to move the right of the list by one slot to free the one we need
915 	pfile = &pack->files[left];
916 	memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
917 	pack->numfiles++;
918 
919 	strlcpy (pfile->name, name, sizeof (pfile->name));
920 	pfile->offset = offset;
921 	pfile->packsize = packsize;
922 	pfile->realsize = realsize;
923 	pfile->flags = flags;
924 
925 	return pfile;
926 }
927 
928 
FS_mkdir(const char * path)929 static void FS_mkdir (const char *path)
930 {
931 	if(COM_CheckParm("-readonly"))
932 		return;
933 
934 #if WIN32
935 	if (_mkdir (path) == -1)
936 #else
937 	if (mkdir (path, 0777) == -1)
938 #endif
939 	{
940 		// No logging for this. The only caller is FS_CreatePath (which
941 		// calls it in ways that will intentionally produce EEXIST),
942 		// and its own callers always use the directory afterwards and
943 		// thus will detect failure that way.
944 	}
945 }
946 
947 
948 /*
949 ============
950 FS_CreatePath
951 
952 Only used for FS_OpenRealFile.
953 ============
954 */
FS_CreatePath(char * path)955 void FS_CreatePath (char *path)
956 {
957 	char *ofs, save;
958 
959 	for (ofs = path+1 ; *ofs ; ofs++)
960 	{
961 		if (*ofs == '/' || *ofs == '\\')
962 		{
963 			// create the directory
964 			save = *ofs;
965 			*ofs = 0;
966 			FS_mkdir (path);
967 			*ofs = save;
968 		}
969 	}
970 }
971 
972 
973 /*
974 ============
975 FS_Path_f
976 
977 ============
978 */
FS_Path_f(void)979 static void FS_Path_f (void)
980 {
981 	searchpath_t *s;
982 
983 	Con_Print("Current search path:\n");
984 	for (s=fs_searchpaths ; s ; s=s->next)
985 	{
986 		if (s->pack)
987 		{
988 			if(s->pack->vpack)
989 				Con_Printf("%sdir (virtual pack)\n", s->pack->filename);
990 			else
991 				Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
992 		}
993 		else
994 			Con_Printf("%s\n", s->filename);
995 	}
996 }
997 
998 
999 /*
1000 =================
1001 FS_LoadPackPAK
1002 =================
1003 */
1004 /*! Takes an explicit (not game tree related) path to a pak file.
1005  *Loads the header and directory, adding the files at the beginning
1006  *of the list so they override previous pack files.
1007  */
FS_LoadPackPAK(const char * packfile)1008 static pack_t *FS_LoadPackPAK (const char *packfile)
1009 {
1010 	dpackheader_t header;
1011 	int i, numpackfiles;
1012 	filedesc_t packhandle;
1013 	pack_t *pack;
1014 	dpackfile_t *info;
1015 
1016 	packhandle = FS_SysOpenFiledesc(packfile, "rb", false);
1017 	if (!FILEDESC_ISVALID(packhandle))
1018 		return NULL;
1019 	if(FILEDESC_READ (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
1020 	{
1021 		Con_Printf ("%s is not a packfile\n", packfile);
1022 		FILEDESC_CLOSE(packhandle);
1023 		return NULL;
1024 	}
1025 	if (memcmp(header.id, "PACK", 4))
1026 	{
1027 		Con_Printf ("%s is not a packfile\n", packfile);
1028 		FILEDESC_CLOSE(packhandle);
1029 		return NULL;
1030 	}
1031 	header.dirofs = LittleLong (header.dirofs);
1032 	header.dirlen = LittleLong (header.dirlen);
1033 
1034 	if (header.dirlen % sizeof(dpackfile_t))
1035 	{
1036 		Con_Printf ("%s has an invalid directory size\n", packfile);
1037 		FILEDESC_CLOSE(packhandle);
1038 		return NULL;
1039 	}
1040 
1041 	numpackfiles = header.dirlen / sizeof(dpackfile_t);
1042 
1043 	if (numpackfiles < 0 || numpackfiles > MAX_FILES_IN_PACK)
1044 	{
1045 		Con_Printf ("%s has %i files\n", packfile, numpackfiles);
1046 		FILEDESC_CLOSE(packhandle);
1047 		return NULL;
1048 	}
1049 
1050 	info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
1051 	FILEDESC_SEEK (packhandle, header.dirofs, SEEK_SET);
1052 	if(header.dirlen != FILEDESC_READ (packhandle, (void *)info, header.dirlen))
1053 	{
1054 		Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
1055 		Mem_Free(info);
1056 		FILEDESC_CLOSE(packhandle);
1057 		return NULL;
1058 	}
1059 
1060 	pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1061 	pack->ignorecase = true; // PAK is sensitive in Quake1 but insensitive in Quake2
1062 	strlcpy (pack->filename, packfile, sizeof (pack->filename));
1063 	pack->handle = packhandle;
1064 	pack->numfiles = 0;
1065 	pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
1066 
1067 	// parse the directory
1068 	for (i = 0;i < numpackfiles;i++)
1069 	{
1070 		fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos);
1071 		fs_offset_t size = (unsigned int)LittleLong (info[i].filelen);
1072 
1073 		// Ensure a zero terminated file name (required by format).
1074 		info[i].name[sizeof(info[i].name) - 1] = 0;
1075 
1076 		FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
1077 	}
1078 
1079 	Mem_Free(info);
1080 
1081 	Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles);
1082 	return pack;
1083 }
1084 
1085 /*
1086 ====================
1087 FS_LoadPackVirtual
1088 
1089 Create a package entry associated with a directory file
1090 ====================
1091 */
FS_LoadPackVirtual(const char * dirname)1092 static pack_t *FS_LoadPackVirtual (const char *dirname)
1093 {
1094 	pack_t *pack;
1095 	pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1096 	pack->vpack = true;
1097 	pack->ignorecase = false;
1098 	strlcpy (pack->filename, dirname, sizeof(pack->filename));
1099 	pack->handle = FILEDESC_INVALID;
1100 	pack->numfiles = -1;
1101 	pack->files = NULL;
1102 	Con_DPrintf("Added packfile %s (virtual pack)\n", dirname);
1103 	return pack;
1104 }
1105 
1106 /*
1107 ================
1108 FS_AddPack_Fullpath
1109 ================
1110 */
1111 /*! Adds the given pack to the search path.
1112  * The pack type is autodetected by the file extension.
1113  *
1114  * Returns true if the file was successfully added to the
1115  * search path or if it was already included.
1116  *
1117  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1118  * plain directories.
1119  *
1120  */
FS_AddPack_Fullpath(const char * pakfile,const char * shortname,qboolean * already_loaded,qboolean keep_plain_dirs)1121 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
1122 {
1123 	searchpath_t *search;
1124 	pack_t *pak = NULL;
1125 	const char *ext = FS_FileExtension(pakfile);
1126 	size_t l;
1127 
1128 	for(search = fs_searchpaths; search; search = search->next)
1129 	{
1130 		if(search->pack && !strcasecmp(search->pack->filename, pakfile))
1131 		{
1132 			if(already_loaded)
1133 				*already_loaded = true;
1134 			return true; // already loaded
1135 		}
1136 	}
1137 
1138 	if(already_loaded)
1139 		*already_loaded = false;
1140 
1141 	if(!strcasecmp(ext, "pk3dir"))
1142 		pak = FS_LoadPackVirtual (pakfile);
1143 	else if(!strcasecmp(ext, "pak"))
1144 		pak = FS_LoadPackPAK (pakfile);
1145 	else if(!strcasecmp(ext, "pk3"))
1146 		pak = FS_LoadPackPK3 (pakfile);
1147 	else if(!strcasecmp(ext, "obb")) // android apk expansion
1148 		pak = FS_LoadPackPK3 (pakfile);
1149 	else
1150 		Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
1151 
1152 	if(pak)
1153 	{
1154 		strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
1155 
1156 		//Con_DPrintf("  Registered pack with short name %s\n", shortname);
1157 		if(keep_plain_dirs)
1158 		{
1159 			// find the first item whose next one is a pack or NULL
1160 			searchpath_t *insertion_point = 0;
1161 			if(fs_searchpaths && !fs_searchpaths->pack)
1162 			{
1163 				insertion_point = fs_searchpaths;
1164 				for(;;)
1165 				{
1166 					if(!insertion_point->next)
1167 						break;
1168 					if(insertion_point->next->pack)
1169 						break;
1170 					insertion_point = insertion_point->next;
1171 				}
1172 			}
1173 			// If insertion_point is NULL, this means that either there is no
1174 			// item in the list yet, or that the very first item is a pack. In
1175 			// that case, we want to insert at the beginning...
1176 			if(!insertion_point)
1177 			{
1178 				search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1179 				search->next = fs_searchpaths;
1180 				fs_searchpaths = search;
1181 			}
1182 			else
1183 			// otherwise we want to append directly after insertion_point.
1184 			{
1185 				search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1186 				search->next = insertion_point->next;
1187 				insertion_point->next = search;
1188 			}
1189 		}
1190 		else
1191 		{
1192 			search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1193 			search->next = fs_searchpaths;
1194 			fs_searchpaths = search;
1195 		}
1196 		search->pack = pak;
1197 		if(pak->vpack)
1198 		{
1199 			dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
1200 			// if shortname ends with "pk3dir", strip that suffix to make it just "pk3"
1201 			// same goes for the name inside the pack structure
1202 			l = strlen(pak->shortname);
1203 			if(l >= 7)
1204 				if(!strcasecmp(pak->shortname + l - 7, ".pk3dir"))
1205 					pak->shortname[l - 3] = 0;
1206 			l = strlen(pak->filename);
1207 			if(l >= 7)
1208 				if(!strcasecmp(pak->filename + l - 7, ".pk3dir"))
1209 					pak->filename[l - 3] = 0;
1210 		}
1211 		return true;
1212 	}
1213 	else
1214 	{
1215 		Con_Printf("unable to load pak \"%s\"\n", pakfile);
1216 		return false;
1217 	}
1218 }
1219 
1220 
1221 /*
1222 ================
1223 FS_AddPack
1224 ================
1225 */
1226 /*! Adds the given pack to the search path and searches for it in the game path.
1227  * The pack type is autodetected by the file extension.
1228  *
1229  * Returns true if the file was successfully added to the
1230  * search path or if it was already included.
1231  *
1232  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1233  * plain directories.
1234  */
FS_AddPack(const char * pakfile,qboolean * already_loaded,qboolean keep_plain_dirs)1235 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1236 {
1237 	char fullpath[MAX_OSPATH];
1238 	int index;
1239 	searchpath_t *search;
1240 
1241 	if(already_loaded)
1242 		*already_loaded = false;
1243 
1244 	// then find the real name...
1245 	search = FS_FindFile(pakfile, &index, true);
1246 	if(!search || search->pack)
1247 	{
1248 		Con_Printf("could not find pak \"%s\"\n", pakfile);
1249 		return false;
1250 	}
1251 
1252 	dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1253 
1254 	return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1255 }
1256 
1257 
1258 /*
1259 ================
1260 FS_AddGameDirectory
1261 
1262 Sets fs_gamedir, adds the directory to the head of the path,
1263 then loads and adds pak1.pak pak2.pak ...
1264 ================
1265 */
FS_AddGameDirectory(const char * dir)1266 static void FS_AddGameDirectory (const char *dir)
1267 {
1268 	int i;
1269 	stringlist_t list;
1270 	searchpath_t *search;
1271 
1272 	strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1273 
1274 	stringlistinit(&list);
1275 	listdirectory(&list, "", dir);
1276 	stringlistsort(&list, false);
1277 
1278 	// add any PAK package in the directory
1279 	for (i = 0;i < list.numstrings;i++)
1280 	{
1281 		if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1282 		{
1283 			FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1284 		}
1285 	}
1286 
1287 	// add any PK3 package in the directory
1288 	for (i = 0;i < list.numstrings;i++)
1289 	{
1290 		if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir"))
1291 		{
1292 			FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1293 		}
1294 	}
1295 
1296 	stringlistfreecontents(&list);
1297 
1298 	// Add the directory to the search path
1299 	// (unpacked files have the priority over packed files)
1300 	search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1301 	strlcpy (search->filename, dir, sizeof (search->filename));
1302 	search->next = fs_searchpaths;
1303 	fs_searchpaths = search;
1304 }
1305 
1306 
1307 /*
1308 ================
1309 FS_AddGameHierarchy
1310 ================
1311 */
FS_AddGameHierarchy(const char * dir)1312 static void FS_AddGameHierarchy (const char *dir)
1313 {
1314 	char vabuf[1024];
1315 	// Add the common game directory
1316 	FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir));
1317 
1318 	if (*fs_userdir)
1319 		FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir));
1320 }
1321 
1322 
1323 /*
1324 ============
1325 FS_FileExtension
1326 ============
1327 */
FS_FileExtension(const char * in)1328 const char *FS_FileExtension (const char *in)
1329 {
1330 	const char *separator, *backslash, *colon, *dot;
1331 
1332 	separator = strrchr(in, '/');
1333 	backslash = strrchr(in, '\\');
1334 	if (!separator || separator < backslash)
1335 		separator = backslash;
1336 	colon = strrchr(in, ':');
1337 	if (!separator || separator < colon)
1338 		separator = colon;
1339 
1340 	dot = strrchr(in, '.');
1341 	if (dot == NULL || (separator && (dot < separator)))
1342 		return "";
1343 
1344 	return dot + 1;
1345 }
1346 
1347 
1348 /*
1349 ============
1350 FS_FileWithoutPath
1351 ============
1352 */
FS_FileWithoutPath(const char * in)1353 const char *FS_FileWithoutPath (const char *in)
1354 {
1355 	const char *separator, *backslash, *colon;
1356 
1357 	separator = strrchr(in, '/');
1358 	backslash = strrchr(in, '\\');
1359 	if (!separator || separator < backslash)
1360 		separator = backslash;
1361 	colon = strrchr(in, ':');
1362 	if (!separator || separator < colon)
1363 		separator = colon;
1364 	return separator ? separator + 1 : in;
1365 }
1366 
1367 
1368 /*
1369 ================
1370 FS_ClearSearchPath
1371 ================
1372 */
FS_ClearSearchPath(void)1373 static void FS_ClearSearchPath (void)
1374 {
1375 	// unload all packs and directory information, close all pack files
1376 	// (if a qfile is still reading a pack it won't be harmed because it used
1377 	//  dup() to get its own handle already)
1378 	while (fs_searchpaths)
1379 	{
1380 		searchpath_t *search = fs_searchpaths;
1381 		fs_searchpaths = search->next;
1382 		if (search->pack && search->pack != fs_selfpack)
1383 		{
1384 			if(!search->pack->vpack)
1385 			{
1386 				// close the file
1387 				FILEDESC_CLOSE(search->pack->handle);
1388 				// free any memory associated with it
1389 				if (search->pack->files)
1390 					Mem_Free(search->pack->files);
1391 			}
1392 			Mem_Free(search->pack);
1393 		}
1394 		Mem_Free(search);
1395 	}
1396 }
1397 
FS_AddSelfPack(void)1398 static void FS_AddSelfPack(void)
1399 {
1400 	if(fs_selfpack)
1401 	{
1402 		searchpath_t *search;
1403 		search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1404 		search->next = fs_searchpaths;
1405 		search->pack = fs_selfpack;
1406 		fs_searchpaths = search;
1407 	}
1408 }
1409 
1410 
1411 /*
1412 ================
1413 FS_Rescan
1414 ================
1415 */
FS_Rescan(void)1416 void FS_Rescan (void)
1417 {
1418 	int i;
1419 	qboolean fs_modified = false;
1420 	qboolean reset = false;
1421 	char gamedirbuf[MAX_INPUTLINE];
1422 	char vabuf[1024];
1423 
1424 	if (fs_searchpaths)
1425 		reset = true;
1426 	FS_ClearSearchPath();
1427 
1428 	// automatically activate gamemode for the gamedirs specified
1429 	if (reset)
1430 		COM_ChangeGameTypeForGameDirs();
1431 
1432 	// add the game-specific paths
1433 	// gamedirname1 (typically id1)
1434 	FS_AddGameHierarchy (gamedirname1);
1435 	// update the com_modname (used for server info)
1436 	if (gamedirname2 && gamedirname2[0])
1437 		strlcpy(com_modname, gamedirname2, sizeof(com_modname));
1438 	else
1439 		strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1440 
1441 	// add the game-specific path, if any
1442 	// (only used for mission packs and the like, which should set fs_modified)
1443 	if (gamedirname2 && gamedirname2[0])
1444 	{
1445 		fs_modified = true;
1446 		FS_AddGameHierarchy (gamedirname2);
1447 	}
1448 
1449 	// -game <gamedir>
1450 	// Adds basedir/gamedir as an override game
1451 	// LordHavoc: now supports multiple -game directories
1452 	// set the com_modname (reported in server info)
1453 	*gamedirbuf = 0;
1454 	for (i = 0;i < fs_numgamedirs;i++)
1455 	{
1456 		fs_modified = true;
1457 		FS_AddGameHierarchy (fs_gamedirs[i]);
1458 		// update the com_modname (used server info)
1459 		strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1460 		if(i)
1461 			strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1462 		else
1463 			strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1464 	}
1465 	Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1466 
1467 	// add back the selfpack as new first item
1468 	FS_AddSelfPack();
1469 
1470 	// set the default screenshot name to either the mod name or the
1471 	// gamemode screenshot name
1472 	if (strcmp(com_modname, gamedirname1))
1473 		Cvar_SetQuick (&scr_screenshot_name, com_modname);
1474 	else
1475 		Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1476 
1477 	if((i = COM_CheckParm("-modname")) && i < com_argc - 1)
1478 		strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
1479 
1480 	// If "-condebug" is in the command line, remove the previous log file
1481 	if (COM_CheckParm ("-condebug") != 0)
1482 		unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
1483 
1484 	// look for the pop.lmp file and set registered to true if it is found
1485 	if (FS_FileExists("gfx/pop.lmp"))
1486 		Cvar_Set ("registered", "1");
1487 	switch(gamemode)
1488 	{
1489 	case GAME_NORMAL:
1490 	case GAME_HIPNOTIC:
1491 	case GAME_ROGUE:
1492 		if (!registered.integer)
1493 		{
1494 			if (fs_modified)
1495 				Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1496 			else
1497 				Con_Print("Playing shareware version.\n");
1498 		}
1499 		else
1500 			Con_Print("Playing registered version.\n");
1501 		break;
1502 	case GAME_STEELSTORM:
1503 		if (registered.integer)
1504 			Con_Print("Playing registered version.\n");
1505 		else
1506 			Con_Print("Playing shareware version.\n");
1507 		break;
1508 	default:
1509 		break;
1510 	}
1511 
1512 	// unload all wads so that future queries will return the new data
1513 	W_UnloadAll();
1514 }
1515 
FS_Rescan_f(void)1516 static void FS_Rescan_f(void)
1517 {
1518 	FS_Rescan();
1519 }
1520 
1521 /*
1522 ================
1523 FS_ChangeGameDirs
1524 ================
1525 */
1526 extern qboolean vid_opened;
FS_ChangeGameDirs(int numgamedirs,char gamedirs[][MAX_QPATH],qboolean complain,qboolean failmissing)1527 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1528 {
1529 	int i;
1530 	const char *p;
1531 
1532 	if (fs_numgamedirs == numgamedirs)
1533 	{
1534 		for (i = 0;i < numgamedirs;i++)
1535 			if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1536 				break;
1537 		if (i == numgamedirs)
1538 			return true; // already using this set of gamedirs, do nothing
1539 	}
1540 
1541 	if (numgamedirs > MAX_GAMEDIRS)
1542 	{
1543 		if (complain)
1544 			Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1545 		return false; // too many gamedirs
1546 	}
1547 
1548 	for (i = 0;i < numgamedirs;i++)
1549 	{
1550 		// if string is nasty, reject it
1551 		p = FS_CheckGameDir(gamedirs[i]);
1552 		if(!p)
1553 		{
1554 			if (complain)
1555 				Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1556 			return false; // nasty gamedirs
1557 		}
1558 		if(p == fs_checkgamedir_missing && failmissing)
1559 		{
1560 			if (complain)
1561 				Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1562 			return false; // missing gamedirs
1563 		}
1564 	}
1565 
1566 	Host_SaveConfig();
1567 
1568 	fs_numgamedirs = numgamedirs;
1569 	for (i = 0;i < fs_numgamedirs;i++)
1570 		strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1571 
1572 	// reinitialize filesystem to detect the new paks
1573 	FS_Rescan();
1574 
1575 	if (cls.demoplayback)
1576 	{
1577 		CL_Disconnect_f();
1578 		cls.demonum = 0;
1579 	}
1580 
1581 	// unload all sounds so they will be reloaded from the new files as needed
1582 	S_UnloadAllSounds_f();
1583 
1584 	// close down the video subsystem, it will start up again when the config finishes...
1585 	VID_Stop();
1586 	vid_opened = false;
1587 
1588 	// restart the video subsystem after the config is executed
1589 	Cbuf_InsertText("\nloadconfig\nvid_restart\n\n");
1590 
1591 	return true;
1592 }
1593 
1594 /*
1595 ================
1596 FS_GameDir_f
1597 ================
1598 */
FS_GameDir_f(void)1599 static void FS_GameDir_f (void)
1600 {
1601 	int i;
1602 	int numgamedirs;
1603 	char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1604 
1605 	if (Cmd_Argc() < 2)
1606 	{
1607 		Con_Printf("gamedirs active:");
1608 		for (i = 0;i < fs_numgamedirs;i++)
1609 			Con_Printf(" %s", fs_gamedirs[i]);
1610 		Con_Printf("\n");
1611 		return;
1612 	}
1613 
1614 	numgamedirs = Cmd_Argc() - 1;
1615 	if (numgamedirs > MAX_GAMEDIRS)
1616 	{
1617 		Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1618 		return;
1619 	}
1620 
1621 	for (i = 0;i < numgamedirs;i++)
1622 		strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1623 
1624 	if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1625 	{
1626 		// actually, changing during game would work fine, but would be stupid
1627 		Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1628 		return;
1629 	}
1630 
1631 	// halt demo playback to close the file
1632 	CL_Disconnect();
1633 
1634 	FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1635 }
1636 
FS_SysCheckGameDir(const char * gamedir,char * buf,size_t buflength)1637 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
1638 {
1639 	qboolean success;
1640 	qfile_t *f;
1641 	stringlist_t list;
1642 	fs_offset_t n;
1643 	char vabuf[1024];
1644 
1645 	stringlistinit(&list);
1646 	listdirectory(&list, gamedir, "");
1647 	success = list.numstrings > 0;
1648 	stringlistfreecontents(&list);
1649 
1650 	if(success)
1651 	{
1652 		f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
1653 		if(f)
1654 		{
1655 			n = FS_Read (f, buf, buflength - 1);
1656 			if(n >= 0)
1657 				buf[n] = 0;
1658 			else
1659 				*buf = 0;
1660 			FS_Close(f);
1661 		}
1662 		else
1663 			*buf = 0;
1664 		return buf;
1665 	}
1666 
1667 	return NULL;
1668 }
1669 
1670 /*
1671 ================
1672 FS_CheckGameDir
1673 ================
1674 */
FS_CheckGameDir(const char * gamedir)1675 const char *FS_CheckGameDir(const char *gamedir)
1676 {
1677 	const char *ret;
1678 	static char buf[8192];
1679 	char vabuf[1024];
1680 
1681 	if (FS_CheckNastyPath(gamedir, true))
1682 		return NULL;
1683 
1684 	ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
1685 	if(ret)
1686 	{
1687 		if(!*ret)
1688 		{
1689 			// get description from basedir
1690 			ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1691 			if(ret)
1692 				return ret;
1693 			return "";
1694 		}
1695 		return ret;
1696 	}
1697 
1698 	ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1699 	if(ret)
1700 		return ret;
1701 
1702 	return fs_checkgamedir_missing;
1703 }
1704 
FS_ListGameDirs(void)1705 static void FS_ListGameDirs(void)
1706 {
1707 	stringlist_t list, list2;
1708 	int i;
1709 	const char *info;
1710 	char vabuf[1024];
1711 
1712 	fs_all_gamedirs_count = 0;
1713 	if(fs_all_gamedirs)
1714 		Mem_Free(fs_all_gamedirs);
1715 
1716 	stringlistinit(&list);
1717 	listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
1718 	listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
1719 	stringlistsort(&list, false);
1720 
1721 	stringlistinit(&list2);
1722 	for(i = 0; i < list.numstrings; ++i)
1723 	{
1724 		if(i)
1725 			if(!strcmp(list.strings[i-1], list.strings[i]))
1726 				continue;
1727 		info = FS_CheckGameDir(list.strings[i]);
1728 		if(!info)
1729 			continue;
1730 		if(info == fs_checkgamedir_missing)
1731 			continue;
1732 		if(!*info)
1733 			continue;
1734 		stringlistappend(&list2, list.strings[i]);
1735 	}
1736 	stringlistfreecontents(&list);
1737 
1738 	fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1739 	for(i = 0; i < list2.numstrings; ++i)
1740 	{
1741 		info = FS_CheckGameDir(list2.strings[i]);
1742 		// all this cannot happen any more, but better be safe than sorry
1743 		if(!info)
1744 			continue;
1745 		if(info == fs_checkgamedir_missing)
1746 			continue;
1747 		if(!*info)
1748 			continue;
1749 		strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
1750 		strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
1751 		++fs_all_gamedirs_count;
1752 	}
1753 }
1754 
1755 /*
1756 #ifdef WIN32
1757 #pragma comment(lib, "shell32.lib")
1758 #include <ShlObj.h>
1759 #endif
1760 */
1761 
COM_InsertFlags(const char * buf)1762 static void COM_InsertFlags(const char *buf) {
1763 	const char *p;
1764 	char *q;
1765 	const char **new_argv;
1766 	int i = 0;
1767 	int args_left = 256;
1768 	new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2));
1769 	if(com_argc == 0)
1770 		new_argv[0] = "dummy";  // Can't really happen.
1771 	else
1772 		new_argv[0] = com_argv[0];
1773 	++i;
1774 	p = buf;
1775 	while(COM_ParseToken_Console(&p))
1776 	{
1777 		size_t sz = strlen(com_token) + 1; // shut up clang
1778 		if(i > args_left)
1779 			break;
1780 		q = (char *)Mem_Alloc(fs_mempool, sz);
1781 		strlcpy(q, com_token, sz);
1782 		new_argv[i] = q;
1783 		++i;
1784 	}
1785 	// Now: i <= args_left + 1.
1786 	if (com_argc >= 1)
1787 	{
1788 		memcpy((char *)(&new_argv[i]), &com_argv[1], sizeof(*com_argv) * (com_argc - 1));
1789 		i += com_argc - 1;
1790 	}
1791 	// Now: i <= args_left + (com_argc || 1).
1792 	new_argv[i] = NULL;
1793 	com_argv = new_argv;
1794 	com_argc = i;
1795 }
1796 
1797 /*
1798 ================
1799 FS_Init_SelfPack
1800 ================
1801 */
FS_Init_SelfPack(void)1802 void FS_Init_SelfPack (void)
1803 {
1804 	PK3_OpenLibrary ();
1805 	fs_mempool = Mem_AllocPool("file management", 0, NULL);
1806 
1807 	// Load darkplaces.opt from the FS.
1808 	if (!COM_CheckParm("-noopt"))
1809 	{
1810 		char *buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL);
1811 		if(buf)
1812 			COM_InsertFlags(buf);
1813 		Mem_Free(buf);
1814 	}
1815 
1816 #ifndef USE_RWOPS
1817 	// Provide the SelfPack.
1818 	if (!COM_CheckParm("-noselfpack"))
1819 	{
1820 		if (com_selffd >= 0)
1821 		{
1822 			fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true);
1823 			if(fs_selfpack)
1824 			{
1825 				FS_AddSelfPack();
1826 				if (!COM_CheckParm("-noopt"))
1827 				{
1828 					char *buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
1829 					if(buf)
1830 						COM_InsertFlags(buf);
1831 					Mem_Free(buf);
1832 				}
1833 			}
1834 		}
1835 	}
1836 #endif
1837 }
1838 
FS_ChooseUserDir(userdirmode_t userdirmode,char * userdir,size_t userdirsize)1839 static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
1840 {
1841 #if defined(__IPHONEOS__)
1842 	if (userdirmode == USERDIRMODE_HOME)
1843 	{
1844 		// fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1845 		// fs_userdir stores configurations to the Documents folder of the app
1846 		strlcpy(userdir, "../Documents/", MAX_OSPATH);
1847 		return 1;
1848 	}
1849 	return -1;
1850 
1851 #elif defined(WIN32)
1852 	char *homedir;
1853 #if _MSC_VER >= 1400
1854 	size_t homedirlen;
1855 #endif
1856 	TCHAR mydocsdir[MAX_PATH + 1];
1857 	wchar_t *savedgamesdirw;
1858 	char savedgamesdir[MAX_OSPATH];
1859 	int fd;
1860 	char vabuf[1024];
1861 
1862 	userdir[0] = 0;
1863 	switch(userdirmode)
1864 	{
1865 	default:
1866 		return -1;
1867 	case USERDIRMODE_NOHOME:
1868 		strlcpy(userdir, fs_basedir, userdirsize);
1869 		break;
1870 	case USERDIRMODE_MYGAMES:
1871 		if (!shfolder_dll)
1872 			Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs);
1873 		mydocsdir[0] = 0;
1874 		if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK)
1875 		{
1876 			dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
1877 			break;
1878 		}
1879 #if _MSC_VER >= 1400
1880 		_dupenv_s(&homedir, &homedirlen, "USERPROFILE");
1881 		if(homedir)
1882 		{
1883 			dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1884 			free(homedir);
1885 			break;
1886 		}
1887 #else
1888 		homedir = getenv("USERPROFILE");
1889 		if(homedir)
1890 		{
1891 			dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1892 			break;
1893 		}
1894 #endif
1895 		return -1;
1896 	case USERDIRMODE_SAVEDGAMES:
1897 		if (!shell32_dll)
1898 			Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs);
1899 		if (!ole32_dll)
1900 			Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs);
1901 		if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
1902 		{
1903 			savedgamesdir[0] = 0;
1904 			qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
1905 /*
1906 #ifdef __cplusplus
1907 			if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1908 #else
1909 			if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1910 #endif
1911 */
1912 			if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1913 			{
1914 				memset(savedgamesdir, 0, sizeof(savedgamesdir));
1915 #if _MSC_VER >= 1400
1916 				wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1);
1917 #else
1918 				wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1);
1919 #endif
1920 				qCoTaskMemFree(savedgamesdirw);
1921 			}
1922 			qCoUninitialize();
1923 			if (savedgamesdir[0])
1924 			{
1925 				dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
1926 				break;
1927 			}
1928 		}
1929 		return -1;
1930 	}
1931 #else
1932 	int fd;
1933 	char *homedir;
1934 	char vabuf[1024];
1935 	userdir[0] = 0;
1936 	switch(userdirmode)
1937 	{
1938 	default:
1939 		return -1;
1940 	case USERDIRMODE_NOHOME:
1941 		strlcpy(userdir, fs_basedir, userdirsize);
1942 		break;
1943 	case USERDIRMODE_HOME:
1944 		homedir = getenv("HOME");
1945 		if(homedir)
1946 		{
1947 			dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1948 			break;
1949 		}
1950 		return -1;
1951 	case USERDIRMODE_SAVEDGAMES:
1952 		homedir = getenv("HOME");
1953 		if(homedir)
1954 		{
1955 #ifdef MACOSX
1956 			dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
1957 #else
1958 			// the XDG say some files would need to go in:
1959 			// XDG_CONFIG_HOME (or ~/.config/%s/)
1960 			// XDG_DATA_HOME (or ~/.local/share/%s/)
1961 			// XDG_CACHE_HOME (or ~/.cache/%s/)
1962 			// and also search the following global locations if defined:
1963 			// XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
1964 			// XDG_DATA_DIRS (normally /usr/share/%s/)
1965 			// this would be too complicated...
1966 			return -1;
1967 #endif
1968 			break;
1969 		}
1970 		return -1;
1971 	}
1972 #endif
1973 
1974 
1975 #if !defined(__IPHONEOS__)
1976 
1977 #ifdef WIN32
1978 	// historical behavior...
1979 	if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
1980 		return 0; // don't bother checking if the basedir folder is writable, it's annoying...  unless it is Quake on Windows where NOHOME is the default preferred and we have to check for an error case
1981 #endif
1982 
1983 	// see if we can write to this path (note: won't create path)
1984 #ifdef WIN32
1985 	// no access() here, we must try to open the file for appending
1986 	fd = FS_SysOpenFiledesc(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
1987 	if(fd >= 0)
1988 		FILEDESC_CLOSE(fd);
1989 #else
1990 	// on Unix, we don't need to ACTUALLY attempt to open the file
1991 	if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
1992 		fd = 1;
1993 	else
1994 		fd = -1;
1995 #endif
1996 	if(fd >= 0)
1997 	{
1998 		return 1; // good choice - the path exists and is writable
1999 	}
2000 	else
2001 	{
2002 		if (userdirmode == USERDIRMODE_NOHOME)
2003 			return -1; // path usually already exists, we lack permissions
2004 		else
2005 			return 0; // probably good - failed to write but maybe we need to create path
2006 	}
2007 #endif
2008 }
2009 
2010 /*
2011 ================
2012 FS_Init
2013 ================
2014 */
FS_Init(void)2015 void FS_Init (void)
2016 {
2017 	const char *p;
2018 	int i;
2019 
2020 	*fs_basedir = 0;
2021 	*fs_userdir = 0;
2022 	*fs_gamedir = 0;
2023 
2024 	// -basedir <path>
2025 	// Overrides the system supplied base directory (under GAMENAME)
2026 // COMMANDLINEOPTION: Filesystem: -basedir <path> chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1)
2027 	i = COM_CheckParm ("-basedir");
2028 	if (i && i < com_argc-1)
2029 	{
2030 		strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
2031 		i = (int)strlen (fs_basedir);
2032 		if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
2033 			fs_basedir[i-1] = 0;
2034 	}
2035 	else
2036 	{
2037 // If the base directory is explicitly defined by the compilation process
2038 #ifdef DP_FS_BASEDIR
2039 		strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
2040 #elif defined(__ANDROID__)
2041 		dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
2042 #elif defined(MACOSX)
2043 		// FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
2044 		if (strstr(com_argv[0], ".app/"))
2045 		{
2046 			char *split;
2047 			strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
2048 			split = strstr(fs_basedir, ".app/");
2049 			if (split)
2050 			{
2051 				struct stat statresult;
2052 				char vabuf[1024];
2053 				// truncate to just after the .app/
2054 				split[5] = 0;
2055 				// see if gamedir exists in Resources
2056 				if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
2057 				{
2058 					// found gamedir inside Resources, use it
2059 					strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
2060 				}
2061 				else
2062 				{
2063 					// no gamedir found in Resources, gamedir is probably
2064 					// outside the .app, remove .app part of path
2065 					while (split > fs_basedir && *split != '/')
2066 						split--;
2067 					*split = 0;
2068 				}
2069 			}
2070 		}
2071 #endif
2072 	}
2073 
2074 	// make sure the appending of a path separator won't create an unterminated string
2075 	memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
2076 	// add a path separator to the end of the basedir if it lacks one
2077 	if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
2078 		strlcat(fs_basedir, "/", sizeof(fs_basedir));
2079 
2080 	// Add the personal game directory
2081 	if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
2082 		dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
2083 	else if (COM_CheckParm("-nohome"))
2084 		*fs_userdir = 0; // user wants roaming installation, no userdir
2085 	else
2086 	{
2087 #ifdef DP_FS_USERDIR
2088 		strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir));
2089 #else
2090 		int dirmode;
2091 		int highestuserdirmode = USERDIRMODE_COUNT - 1;
2092 		int preferreduserdirmode = USERDIRMODE_COUNT - 1;
2093 		int userdirstatus[USERDIRMODE_COUNT];
2094 # ifdef WIN32
2095 		// historical behavior...
2096 		if (!strcmp(gamedirname1, "id1"))
2097 			preferreduserdirmode = USERDIRMODE_NOHOME;
2098 # endif
2099 		// check what limitations the user wants to impose
2100 		if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
2101 		if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
2102 		if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
2103 		// gather the status of the possible userdirs
2104 		for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
2105 		{
2106 			userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2107 			if (userdirstatus[dirmode] == 1)
2108 				Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
2109 			else if (userdirstatus[dirmode] == 0)
2110 				Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
2111 			else
2112 				Con_DPrintf("userdir %i (not applicable)\n", dirmode);
2113 		}
2114 		// some games may prefer writing to basedir, but if write fails we
2115 		// have to search for a real userdir...
2116 		if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
2117 			preferreduserdirmode = highestuserdirmode;
2118 		// check for an existing userdir and continue using it if possible...
2119 		for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
2120 			if (userdirstatus[dirmode] == 1)
2121 				break;
2122 		// if no existing userdir found, make a new one...
2123 		if (dirmode == 0 && preferreduserdirmode > 0)
2124 			for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
2125 				if (userdirstatus[dirmode] >= 0)
2126 					break;
2127 		// and finally, we picked one...
2128 		FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2129 		Con_DPrintf("userdir %i is the winner\n", dirmode);
2130 #endif
2131 	}
2132 
2133 	// if userdir equal to basedir, clear it to avoid confusion later
2134 	if (!strcmp(fs_basedir, fs_userdir))
2135 		fs_userdir[0] = 0;
2136 
2137 	FS_ListGameDirs();
2138 
2139 	p = FS_CheckGameDir(gamedirname1);
2140 	if(!p || p == fs_checkgamedir_missing)
2141 		Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
2142 
2143 	if(gamedirname2)
2144 	{
2145 		p = FS_CheckGameDir(gamedirname2);
2146 		if(!p || p == fs_checkgamedir_missing)
2147 			Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
2148 	}
2149 
2150 	// -game <gamedir>
2151 	// Adds basedir/gamedir as an override game
2152 	// LordHavoc: now supports multiple -game directories
2153 	for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
2154 	{
2155 		if (!com_argv[i])
2156 			continue;
2157 		if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
2158 		{
2159 			i++;
2160 			p = FS_CheckGameDir(com_argv[i]);
2161 			if(!p)
2162 				Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
2163 			if(p == fs_checkgamedir_missing)
2164 				Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
2165 			// add the gamedir to the list of active gamedirs
2166 			strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
2167 			fs_numgamedirs++;
2168 		}
2169 	}
2170 
2171 	// generate the searchpath
2172 	FS_Rescan();
2173 
2174 	if (Thread_HasThreads())
2175 		fs_mutex = Thread_CreateMutex();
2176 }
2177 
FS_Init_Commands(void)2178 void FS_Init_Commands(void)
2179 {
2180 	Cvar_RegisterVariable (&scr_screenshot_name);
2181 	Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2182 	Cvar_RegisterVariable (&cvar_fs_gamedir);
2183 
2184 	Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
2185 	Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2186 	Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
2187 	Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2188 	Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2189 	Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2190 }
2191 
2192 /*
2193 ================
2194 FS_Shutdown
2195 ================
2196 */
FS_Shutdown(void)2197 void FS_Shutdown (void)
2198 {
2199 	// close all pack files and such
2200 	// (hopefully there aren't any other open files, but they'll be cleaned up
2201 	//  by the OS anyway)
2202 	FS_ClearSearchPath();
2203 	Mem_FreePool (&fs_mempool);
2204 	PK3_CloseLibrary ();
2205 
2206 #ifdef WIN32
2207 	Sys_UnloadLibrary (&shfolder_dll);
2208 	Sys_UnloadLibrary (&shell32_dll);
2209 	Sys_UnloadLibrary (&ole32_dll);
2210 #endif
2211 
2212 	if (fs_mutex)
2213 		Thread_DestroyMutex(fs_mutex);
2214 }
2215 
FS_SysOpenFiledesc(const char * filepath,const char * mode,qboolean nonblocking)2216 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking)
2217 {
2218 	filedesc_t handle = FILEDESC_INVALID;
2219 	int mod, opt;
2220 	unsigned int ind;
2221 	qboolean dolock = false;
2222 
2223 	// Parse the mode string
2224 	switch (mode[0])
2225 	{
2226 		case 'r':
2227 			mod = O_RDONLY;
2228 			opt = 0;
2229 			break;
2230 		case 'w':
2231 			mod = O_WRONLY;
2232 			opt = O_CREAT | O_TRUNC;
2233 			break;
2234 		case 'a':
2235 			mod = O_WRONLY;
2236 			opt = O_CREAT | O_APPEND;
2237 			break;
2238 		default:
2239 			Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2240 			return FILEDESC_INVALID;
2241 	}
2242 	for (ind = 1; mode[ind] != '\0'; ind++)
2243 	{
2244 		switch (mode[ind])
2245 		{
2246 			case '+':
2247 				mod = O_RDWR;
2248 				break;
2249 			case 'b':
2250 				opt |= O_BINARY;
2251 				break;
2252 			case 'l':
2253 				dolock = true;
2254 				break;
2255 			default:
2256 				Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2257 							filepath, mode, mode[ind]);
2258 		}
2259 	}
2260 
2261 	if (nonblocking)
2262 		opt |= O_NONBLOCK;
2263 
2264 	if(COM_CheckParm("-readonly") && mod != O_RDONLY)
2265 		return FILEDESC_INVALID;
2266 
2267 #if USE_RWOPS
2268 	if (dolock)
2269 		return FILEDESC_INVALID;
2270 	handle = SDL_RWFromFile(filepath, mode);
2271 #else
2272 # ifdef WIN32
2273 #  if _MSC_VER >= 1400
2274 	_sopen_s(&handle, filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2275 #  else
2276 	handle = _sopen (filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2277 #  endif
2278 # else
2279 	handle = open (filepath, mod | opt, 0666);
2280 	if(handle >= 0 && dolock)
2281 	{
2282 		struct flock l;
2283 		l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
2284 		l.l_whence = SEEK_SET;
2285 		l.l_start = 0;
2286 		l.l_len = 0;
2287 		if(fcntl(handle, F_SETLK, &l) == -1)
2288 		{
2289 			FILEDESC_CLOSE(handle);
2290 			handle = -1;
2291 		}
2292 	}
2293 # endif
2294 #endif
2295 
2296 	return handle;
2297 }
2298 
FS_SysOpenFD(const char * filepath,const char * mode,qboolean nonblocking)2299 int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
2300 {
2301 #ifdef USE_RWOPS
2302 	return -1;
2303 #else
2304 	return FS_SysOpenFiledesc(filepath, mode, nonblocking);
2305 #endif
2306 }
2307 
2308 /*
2309 ====================
2310 FS_SysOpen
2311 
2312 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2313 ====================
2314 */
FS_SysOpen(const char * filepath,const char * mode,qboolean nonblocking)2315 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
2316 {
2317 	qfile_t* file;
2318 
2319 	file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2320 	file->ungetc = EOF;
2321 	file->handle = FS_SysOpenFiledesc(filepath, mode, nonblocking);
2322 	if (!FILEDESC_ISVALID(file->handle))
2323 	{
2324 		Mem_Free (file);
2325 		return NULL;
2326 	}
2327 
2328 	file->filename = Mem_strdup(fs_mempool, filepath);
2329 
2330 	file->real_length = FILEDESC_SEEK (file->handle, 0, SEEK_END);
2331 
2332 	// For files opened in append mode, we start at the end of the file
2333 	if (mode[0] == 'a')
2334 		file->position = file->real_length;
2335 	else
2336 		FILEDESC_SEEK (file->handle, 0, SEEK_SET);
2337 
2338 	return file;
2339 }
2340 
2341 
2342 /*
2343 ===========
2344 FS_OpenPackedFile
2345 
2346 Open a packed file using its package file descriptor
2347 ===========
2348 */
FS_OpenPackedFile(pack_t * pack,int pack_ind)2349 static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2350 {
2351 	packfile_t *pfile;
2352 	filedesc_t dup_handle;
2353 	qfile_t* file;
2354 
2355 	pfile = &pack->files[pack_ind];
2356 
2357 	// If we don't have the true offset, get it now
2358 	if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2359 		if (!PK3_GetTrueFileOffset (pfile, pack))
2360 			return NULL;
2361 
2362 #ifndef LINK_TO_ZLIB
2363 	// No Zlib DLL = no compressed files
2364 	if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2365 	{
2366 		Con_Printf("WARNING: can't open the compressed file %s\n"
2367 					"You need the Zlib DLL to use compressed files\n",
2368 					pfile->name);
2369 		return NULL;
2370 	}
2371 #endif
2372 
2373 	// LordHavoc: FILEDESC_SEEK affects all duplicates of a handle so we do it before
2374 	// the dup() call to avoid having to close the dup_handle on error here
2375 	if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
2376 	{
2377 		Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2378 					pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2379 		return NULL;
2380 	}
2381 
2382 	dup_handle = FILEDESC_DUP (pack->filename, pack->handle);
2383 	if (!FILEDESC_ISVALID(dup_handle))
2384 	{
2385 		Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2386 		return NULL;
2387 	}
2388 
2389 	file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2390 	memset (file, 0, sizeof (*file));
2391 	file->handle = dup_handle;
2392 	file->flags = QFILE_FLAG_PACKED;
2393 	file->real_length = pfile->realsize;
2394 	file->offset = pfile->offset;
2395 	file->position = 0;
2396 	file->ungetc = EOF;
2397 
2398 	if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2399 	{
2400 		ztoolkit_t *ztk;
2401 
2402 		file->flags |= QFILE_FLAG_DEFLATED;
2403 
2404 		// We need some more variables
2405 		ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2406 
2407 		ztk->comp_length = pfile->packsize;
2408 
2409 		// Initialize zlib stream
2410 		ztk->zstream.next_in = ztk->input;
2411 		ztk->zstream.avail_in = 0;
2412 
2413 		/* From Zlib's "unzip.c":
2414 		 *
2415 		 * windowBits is passed < 0 to tell that there is no zlib header.
2416 		 * Note that in this case inflate *requires* an extra "dummy" byte
2417 		 * after the compressed stream in order to complete decompression and
2418 		 * return Z_STREAM_END.
2419 		 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2420 		 * size of both compressed and uncompressed data
2421 		 */
2422 		if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2423 		{
2424 			Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2425 			FILEDESC_CLOSE(dup_handle);
2426 			Mem_Free(file);
2427 			return NULL;
2428 		}
2429 
2430 		ztk->zstream.next_out = file->buff;
2431 		ztk->zstream.avail_out = sizeof (file->buff);
2432 
2433 		file->ztk = ztk;
2434 	}
2435 
2436 	return file;
2437 }
2438 
2439 /*
2440 ====================
2441 FS_CheckNastyPath
2442 
2443 Return true if the path should be rejected due to one of the following:
2444 1: path elements that are non-portable
2445 2: path elements that would allow access to files outside the game directory,
2446    or are just not a good idea for a mod to be using.
2447 ====================
2448 */
FS_CheckNastyPath(const char * path,qboolean isgamedir)2449 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
2450 {
2451 	// all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2452 	if (!path[0])
2453 		return 2;
2454 
2455 	// Windows: don't allow \ in filenames (windows-only), period.
2456 	// (on Windows \ is a directory separator, but / is also supported)
2457 	if (strstr(path, "\\"))
2458 		return 1; // non-portable
2459 
2460 	// Mac: don't allow Mac-only filenames - : is a directory separator
2461 	// instead of /, but we rely on / working already, so there's no reason to
2462 	// support a Mac-only path
2463 	// Amiga and Windows: : tries to go to root of drive
2464 	if (strstr(path, ":"))
2465 		return 1; // non-portable attempt to go to root of drive
2466 
2467 	// Amiga: // is parent directory
2468 	if (strstr(path, "//"))
2469 		return 1; // non-portable attempt to go to parent directory
2470 
2471 	// all: don't allow going to parent directory (../ or /../)
2472 	if (strstr(path, ".."))
2473 		return 2; // attempt to go outside the game directory
2474 
2475 	// Windows and UNIXes: don't allow absolute paths
2476 	if (path[0] == '/')
2477 		return 2; // attempt to go outside the game directory
2478 
2479 	// all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc
2480 	if (strstr(path, "./"))
2481 		return 2; // possible attempt to go outside the game directory
2482 
2483 	// all: forbid trailing slash on gamedir
2484 	if (isgamedir && path[strlen(path)-1] == '/')
2485 		return 2;
2486 
2487 	// all: forbid leading dot on any filename for any reason
2488 	if (strstr(path, "/."))
2489 		return 2; // attempt to go outside the game directory
2490 
2491 	// after all these checks we're pretty sure it's a / separated filename
2492 	// and won't do much if any harm
2493 	return false;
2494 }
2495 
2496 
2497 /*
2498 ====================
2499 FS_FindFile
2500 
2501 Look for a file in the packages and in the filesystem
2502 
2503 Return the searchpath where the file was found (or NULL)
2504 and the file index in the package if relevant
2505 ====================
2506 */
FS_FindFile(const char * name,int * index,qboolean quiet)2507 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
2508 {
2509 	searchpath_t *search;
2510 	pack_t *pak;
2511 
2512 	// search through the path, one element at a time
2513 	for (search = fs_searchpaths;search;search = search->next)
2514 	{
2515 		// is the element a pak file?
2516 		if (search->pack && !search->pack->vpack)
2517 		{
2518 			int (*strcmp_funct) (const char* str1, const char* str2);
2519 			int left, right, middle;
2520 
2521 			pak = search->pack;
2522 			strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2523 
2524 			// Look for the file (binary search)
2525 			left = 0;
2526 			right = pak->numfiles - 1;
2527 			while (left <= right)
2528 			{
2529 				int diff;
2530 
2531 				middle = (left + right) / 2;
2532 				diff = strcmp_funct (pak->files[middle].name, name);
2533 
2534 				// Found it
2535 				if (!diff)
2536 				{
2537 					if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2538 					{
2539 						// yes, but the first one is empty so we treat it as not being there
2540 						if (!quiet && developer_extra.integer)
2541 							Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2542 
2543 						if (index != NULL)
2544 							*index = -1;
2545 						return NULL;
2546 					}
2547 
2548 					if (!quiet && developer_extra.integer)
2549 						Con_DPrintf("FS_FindFile: %s in %s\n",
2550 									pak->files[middle].name, pak->filename);
2551 
2552 					if (index != NULL)
2553 						*index = middle;
2554 					return search;
2555 				}
2556 
2557 				// If we're too far in the list
2558 				if (diff > 0)
2559 					right = middle - 1;
2560 				else
2561 					left = middle + 1;
2562 			}
2563 		}
2564 		else
2565 		{
2566 			char netpath[MAX_OSPATH];
2567 			dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2568 			if (FS_SysFileExists (netpath))
2569 			{
2570 				if (!quiet && developer_extra.integer)
2571 					Con_DPrintf("FS_FindFile: %s\n", netpath);
2572 
2573 				if (index != NULL)
2574 					*index = -1;
2575 				return search;
2576 			}
2577 		}
2578 	}
2579 
2580 	if (!quiet && developer_extra.integer)
2581 		Con_DPrintf("FS_FindFile: can't find %s\n", name);
2582 
2583 	if (index != NULL)
2584 		*index = -1;
2585 	return NULL;
2586 }
2587 
2588 
2589 /*
2590 ===========
2591 FS_OpenReadFile
2592 
2593 Look for a file in the search paths and open it in read-only mode
2594 ===========
2595 */
FS_OpenReadFile(const char * filename,qboolean quiet,qboolean nonblocking,int symlinkLevels)2596 static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2597 {
2598 	searchpath_t *search;
2599 	int pack_ind;
2600 
2601 	search = FS_FindFile (filename, &pack_ind, quiet);
2602 
2603 	// Not found?
2604 	if (search == NULL)
2605 		return NULL;
2606 
2607 	// Found in the filesystem?
2608 	if (pack_ind < 0)
2609 	{
2610 		// this works with vpacks, so we are fine
2611 		char path [MAX_OSPATH];
2612 		dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2613 		return FS_SysOpen (path, "rb", nonblocking);
2614 	}
2615 
2616 	// So, we found it in a package...
2617 
2618 	// Is it a PK3 symlink?
2619 	// TODO also handle directory symlinks by parsing the whole structure...
2620 	// but heck, file symlinks are good enough for now
2621 	if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2622 	{
2623 		if(symlinkLevels <= 0)
2624 		{
2625 			Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2626 			return NULL;
2627 		}
2628 		else
2629 		{
2630 			char linkbuf[MAX_QPATH];
2631 			fs_offset_t count;
2632 			qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2633 			const char *mergeslash;
2634 			char *mergestart;
2635 
2636 			if(!linkfile)
2637 				return NULL;
2638 			count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2639 			FS_Close(linkfile);
2640 			if(count < 0)
2641 				return NULL;
2642 			linkbuf[count] = 0;
2643 
2644 			// Now combine the paths...
2645 			mergeslash = strrchr(filename, '/');
2646 			mergestart = linkbuf;
2647 			if(!mergeslash)
2648 				mergeslash = filename;
2649 			while(!strncmp(mergestart, "../", 3))
2650 			{
2651 				mergestart += 3;
2652 				while(mergeslash > filename)
2653 				{
2654 					--mergeslash;
2655 					if(*mergeslash == '/')
2656 						break;
2657 				}
2658 			}
2659 			// Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2660 			if(mergeslash == filename)
2661 			{
2662 				// Either mergeslash == filename, then we just replace the name (done below)
2663 			}
2664 			else
2665 			{
2666 				// Or, we append the name after mergeslash;
2667 				// or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2668 				int spaceNeeded = mergeslash - filename + 1;
2669 				int spaceRemoved = mergestart - linkbuf;
2670 				if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2671 				{
2672 					Con_DPrintf("symlink: too long path rejected\n");
2673 					return NULL;
2674 				}
2675 				memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2676 				memcpy(linkbuf, filename, spaceNeeded);
2677 				linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2678 				mergestart = linkbuf;
2679 			}
2680 			if (!quiet && developer_loading.integer)
2681 				Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2682 			if(FS_CheckNastyPath (mergestart, false))
2683 			{
2684 				Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2685 				return NULL;
2686 			}
2687 			return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2688 		}
2689 	}
2690 
2691 	return FS_OpenPackedFile (search->pack, pack_ind);
2692 }
2693 
2694 
2695 /*
2696 =============================================================================
2697 
2698 MAIN PUBLIC FUNCTIONS
2699 
2700 =============================================================================
2701 */
2702 
2703 /*
2704 ====================
2705 FS_OpenRealFile
2706 
2707 Open a file in the userpath. The syntax is the same as fopen
2708 Used for savegame scanning in menu, and all file writing.
2709 ====================
2710 */
FS_OpenRealFile(const char * filepath,const char * mode,qboolean quiet)2711 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2712 {
2713 	char real_path [MAX_OSPATH];
2714 
2715 	if (FS_CheckNastyPath(filepath, false))
2716 	{
2717 		Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2718 		return NULL;
2719 	}
2720 
2721 	dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2722 
2723 	// If the file is opened in "write", "append", or "read/write" mode,
2724 	// create directories up to the file.
2725 	if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2726 		FS_CreatePath (real_path);
2727 	return FS_SysOpen (real_path, mode, false);
2728 }
2729 
2730 
2731 /*
2732 ====================
2733 FS_OpenVirtualFile
2734 
2735 Open a file. The syntax is the same as fopen
2736 ====================
2737 */
FS_OpenVirtualFile(const char * filepath,qboolean quiet)2738 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2739 {
2740 	qfile_t *result = NULL;
2741 	if (FS_CheckNastyPath(filepath, false))
2742 	{
2743 		Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2744 		return NULL;
2745 	}
2746 
2747 	if (fs_mutex) Thread_LockMutex(fs_mutex);
2748 	result = FS_OpenReadFile (filepath, quiet, false, 16);
2749 	if (fs_mutex) Thread_UnlockMutex(fs_mutex);
2750 	return result;
2751 }
2752 
2753 
2754 /*
2755 ====================
2756 FS_FileFromData
2757 
2758 Open a file. The syntax is the same as fopen
2759 ====================
2760 */
FS_FileFromData(const unsigned char * data,const size_t size,qboolean quiet)2761 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2762 {
2763 	qfile_t* file;
2764 	file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2765 	memset (file, 0, sizeof (*file));
2766 	file->flags = QFILE_FLAG_DATA;
2767 	file->ungetc = EOF;
2768 	file->real_length = size;
2769 	file->data = data;
2770 	return file;
2771 }
2772 
2773 /*
2774 ====================
2775 FS_Close
2776 
2777 Close a file
2778 ====================
2779 */
FS_Close(qfile_t * file)2780 int FS_Close (qfile_t* file)
2781 {
2782 	if(file->flags & QFILE_FLAG_DATA)
2783 	{
2784 		Mem_Free(file);
2785 		return 0;
2786 	}
2787 
2788 	if (FILEDESC_CLOSE (file->handle))
2789 		return EOF;
2790 
2791 	if (file->filename)
2792 	{
2793 		if (file->flags & QFILE_FLAG_REMOVE)
2794 		{
2795 			if (remove(file->filename) == -1)
2796 			{
2797 				// No need to report this. If removing a just
2798 				// written file failed, this most likely means
2799 				// someone else deleted it first - which we
2800 				// like.
2801 			}
2802 		}
2803 
2804 		Mem_Free((void *) file->filename);
2805 	}
2806 
2807 	if (file->ztk)
2808 	{
2809 		qz_inflateEnd (&file->ztk->zstream);
2810 		Mem_Free (file->ztk);
2811 	}
2812 
2813 	Mem_Free (file);
2814 	return 0;
2815 }
2816 
FS_RemoveOnClose(qfile_t * file)2817 void FS_RemoveOnClose(qfile_t* file)
2818 {
2819 	file->flags |= QFILE_FLAG_REMOVE;
2820 }
2821 
2822 /*
2823 ====================
2824 FS_Write
2825 
2826 Write "datasize" bytes into a file
2827 ====================
2828 */
FS_Write(qfile_t * file,const void * data,size_t datasize)2829 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2830 {
2831 	fs_offset_t written = 0;
2832 
2833 	// If necessary, seek to the exact file position we're supposed to be
2834 	if (file->buff_ind != file->buff_len)
2835 	{
2836 		if (FILEDESC_SEEK (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1)
2837 		{
2838 			Con_Printf("WARNING: could not seek in %s.\n", file->filename);
2839 		}
2840 	}
2841 
2842 	// Purge cached data
2843 	FS_Purge (file);
2844 
2845 	// Write the buffer and update the position
2846 	// LordHavoc: to hush a warning about passing size_t to an unsigned int parameter on Win64 we do this as multiple writes if the size would be too big for an integer (we never write that big in one go, but it's a theory)
2847 	while (written < (fs_offset_t)datasize)
2848 	{
2849 		// figure out how much to write in one chunk
2850 		fs_offset_t maxchunk = 1<<30; // 1 GiB
2851 		int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
2852 		int result = (int)FILEDESC_WRITE (file->handle, (const unsigned char *)data + written, chunk);
2853 		// if at least some was written, add it to our accumulator
2854 		if (result > 0)
2855 			written += result;
2856 		// if the result is not what we expected, consider the write to be incomplete
2857 		if (result != chunk)
2858 			break;
2859 	}
2860 	file->position = FILEDESC_SEEK (file->handle, 0, SEEK_CUR);
2861 	if (file->real_length < file->position)
2862 		file->real_length = file->position;
2863 
2864 	// note that this will never be less than 0 even if the write failed
2865 	return written;
2866 }
2867 
2868 
2869 /*
2870 ====================
2871 FS_Read
2872 
2873 Read up to "buffersize" bytes from a file
2874 ====================
2875 */
FS_Read(qfile_t * file,void * buffer,size_t buffersize)2876 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2877 {
2878 	fs_offset_t count, done;
2879 
2880 	if (buffersize == 0)
2881 		return 0;
2882 
2883 	// Get rid of the ungetc character
2884 	if (file->ungetc != EOF)
2885 	{
2886 		((char*)buffer)[0] = file->ungetc;
2887 		buffersize--;
2888 		file->ungetc = EOF;
2889 		done = 1;
2890 	}
2891 	else
2892 		done = 0;
2893 
2894 	if(file->flags & QFILE_FLAG_DATA)
2895 	{
2896 		size_t left = file->real_length - file->position;
2897 		if(buffersize > left)
2898 			buffersize = left;
2899 		memcpy(buffer, file->data + file->position, buffersize);
2900 		file->position += buffersize;
2901 		return buffersize;
2902 	}
2903 
2904 	// First, we copy as many bytes as we can from "buff"
2905 	if (file->buff_ind < file->buff_len)
2906 	{
2907 		count = file->buff_len - file->buff_ind;
2908 		count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2909 		done += count;
2910 		memcpy (buffer, &file->buff[file->buff_ind], count);
2911 		file->buff_ind += count;
2912 
2913 		buffersize -= count;
2914 		if (buffersize == 0)
2915 			return done;
2916 	}
2917 
2918 	// NOTE: at this point, the read buffer is always empty
2919 
2920 	// If the file isn't compressed
2921 	if (! (file->flags & QFILE_FLAG_DEFLATED))
2922 	{
2923 		fs_offset_t nb;
2924 
2925 		// We must take care to not read after the end of the file
2926 		count = file->real_length - file->position;
2927 
2928 		// If we have a lot of data to get, put them directly into "buffer"
2929 		if (buffersize > sizeof (file->buff) / 2)
2930 		{
2931 			if (count > (fs_offset_t)buffersize)
2932 				count = (fs_offset_t)buffersize;
2933 			if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
2934 			{
2935 				// Seek failed. When reading from a pipe, and
2936 				// the caller never called FS_Seek, this still
2937 				// works fine.  So no reporting this error.
2938 			}
2939 			nb = FILEDESC_READ (file->handle, &((unsigned char*)buffer)[done], count);
2940 			if (nb > 0)
2941 			{
2942 				done += nb;
2943 				file->position += nb;
2944 
2945 				// Purge cached data
2946 				FS_Purge (file);
2947 			}
2948 		}
2949 		else
2950 		{
2951 			if (count > (fs_offset_t)sizeof (file->buff))
2952 				count = (fs_offset_t)sizeof (file->buff);
2953 			if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
2954 			{
2955 				// Seek failed. When reading from a pipe, and
2956 				// the caller never called FS_Seek, this still
2957 				// works fine.  So no reporting this error.
2958 			}
2959 			nb = FILEDESC_READ (file->handle, file->buff, count);
2960 			if (nb > 0)
2961 			{
2962 				file->buff_len = nb;
2963 				file->position += nb;
2964 
2965 				// Copy the requested data in "buffer" (as much as we can)
2966 				count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2967 				memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2968 				file->buff_ind = count;
2969 				done += count;
2970 			}
2971 		}
2972 
2973 		return done;
2974 	}
2975 
2976 	// If the file is compressed, it's more complicated...
2977 	// We cycle through a few operations until we have read enough data
2978 	while (buffersize > 0)
2979 	{
2980 		ztoolkit_t *ztk = file->ztk;
2981 		int error;
2982 
2983 		// NOTE: at this point, the read buffer is always empty
2984 
2985 		// If "input" is also empty, we need to refill it
2986 		if (ztk->in_ind == ztk->in_len)
2987 		{
2988 			// If we are at the end of the file
2989 			if (file->position == file->real_length)
2990 				return done;
2991 
2992 			count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2993 			if (count > (fs_offset_t)sizeof (ztk->input))
2994 				count = (fs_offset_t)sizeof (ztk->input);
2995 			FILEDESC_SEEK (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2996 			if (FILEDESC_READ (file->handle, ztk->input, count) != count)
2997 			{
2998 				Con_Printf ("FS_Read: unexpected end of file\n");
2999 				break;
3000 			}
3001 
3002 			ztk->in_ind = 0;
3003 			ztk->in_len = count;
3004 			ztk->in_position += count;
3005 		}
3006 
3007 		ztk->zstream.next_in = &ztk->input[ztk->in_ind];
3008 		ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
3009 
3010 		// Now that we are sure we have compressed data available, we need to determine
3011 		// if it's better to inflate it in "file->buff" or directly in "buffer"
3012 
3013 		// Inflate the data in "file->buff"
3014 		if (buffersize < sizeof (file->buff) / 2)
3015 		{
3016 			ztk->zstream.next_out = file->buff;
3017 			ztk->zstream.avail_out = sizeof (file->buff);
3018 			error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3019 			if (error != Z_OK && error != Z_STREAM_END)
3020 			{
3021 				Con_Printf ("FS_Read: Can't inflate file\n");
3022 				break;
3023 			}
3024 			ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3025 
3026 			file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
3027 			file->position += file->buff_len;
3028 
3029 			// Copy the requested data in "buffer" (as much as we can)
3030 			count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3031 			memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3032 			file->buff_ind = count;
3033 		}
3034 
3035 		// Else, we inflate directly in "buffer"
3036 		else
3037 		{
3038 			ztk->zstream.next_out = &((unsigned char*)buffer)[done];
3039 			ztk->zstream.avail_out = (unsigned int)buffersize;
3040 			error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3041 			if (error != Z_OK && error != Z_STREAM_END)
3042 			{
3043 				Con_Printf ("FS_Read: Can't inflate file\n");
3044 				break;
3045 			}
3046 			ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3047 
3048 			// How much data did it inflate?
3049 			count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
3050 			file->position += count;
3051 
3052 			// Purge cached data
3053 			FS_Purge (file);
3054 		}
3055 
3056 		done += count;
3057 		buffersize -= count;
3058 	}
3059 
3060 	return done;
3061 }
3062 
3063 
3064 /*
3065 ====================
3066 FS_Print
3067 
3068 Print a string into a file
3069 ====================
3070 */
FS_Print(qfile_t * file,const char * msg)3071 int FS_Print (qfile_t* file, const char *msg)
3072 {
3073 	return (int)FS_Write (file, msg, strlen (msg));
3074 }
3075 
3076 /*
3077 ====================
3078 FS_Printf
3079 
3080 Print a string into a file
3081 ====================
3082 */
FS_Printf(qfile_t * file,const char * format,...)3083 int FS_Printf(qfile_t* file, const char* format, ...)
3084 {
3085 	int result;
3086 	va_list args;
3087 
3088 	va_start (args, format);
3089 	result = FS_VPrintf (file, format, args);
3090 	va_end (args);
3091 
3092 	return result;
3093 }
3094 
3095 
3096 /*
3097 ====================
3098 FS_VPrintf
3099 
3100 Print a string into a file
3101 ====================
3102 */
FS_VPrintf(qfile_t * file,const char * format,va_list ap)3103 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
3104 {
3105 	int len;
3106 	fs_offset_t buff_size = MAX_INPUTLINE;
3107 	char *tempbuff;
3108 
3109 	for (;;)
3110 	{
3111 		tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
3112 		len = dpvsnprintf (tempbuff, buff_size, format, ap);
3113 		if (len >= 0 && len < buff_size)
3114 			break;
3115 		Mem_Free (tempbuff);
3116 		buff_size *= 2;
3117 	}
3118 
3119 	len = FILEDESC_WRITE (file->handle, tempbuff, len);
3120 	Mem_Free (tempbuff);
3121 
3122 	return len;
3123 }
3124 
3125 
3126 /*
3127 ====================
3128 FS_Getc
3129 
3130 Get the next character of a file
3131 ====================
3132 */
FS_Getc(qfile_t * file)3133 int FS_Getc (qfile_t* file)
3134 {
3135 	unsigned char c;
3136 
3137 	if (FS_Read (file, &c, 1) != 1)
3138 		return EOF;
3139 
3140 	return c;
3141 }
3142 
3143 
3144 /*
3145 ====================
3146 FS_UnGetc
3147 
3148 Put a character back into the read buffer (only supports one character!)
3149 ====================
3150 */
FS_UnGetc(qfile_t * file,unsigned char c)3151 int FS_UnGetc (qfile_t* file, unsigned char c)
3152 {
3153 	// If there's already a character waiting to be read
3154 	if (file->ungetc != EOF)
3155 		return EOF;
3156 
3157 	file->ungetc = c;
3158 	return c;
3159 }
3160 
3161 
3162 /*
3163 ====================
3164 FS_Seek
3165 
3166 Move the position index in a file
3167 ====================
3168 */
FS_Seek(qfile_t * file,fs_offset_t offset,int whence)3169 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3170 {
3171 	ztoolkit_t *ztk;
3172 	unsigned char* buffer;
3173 	fs_offset_t buffersize;
3174 
3175 	// Compute the file offset
3176 	switch (whence)
3177 	{
3178 		case SEEK_CUR:
3179 			offset += file->position - file->buff_len + file->buff_ind;
3180 			break;
3181 
3182 		case SEEK_SET:
3183 			break;
3184 
3185 		case SEEK_END:
3186 			offset += file->real_length;
3187 			break;
3188 
3189 		default:
3190 			return -1;
3191 	}
3192 	if (offset < 0 || offset > file->real_length)
3193 		return -1;
3194 
3195 	if(file->flags & QFILE_FLAG_DATA)
3196 	{
3197 		file->position = offset;
3198 		return 0;
3199 	}
3200 
3201 	// If we have the data in our read buffer, we don't need to actually seek
3202 	if (file->position - file->buff_len <= offset && offset <= file->position)
3203 	{
3204 		file->buff_ind = offset + file->buff_len - file->position;
3205 		return 0;
3206 	}
3207 
3208 	// Purge cached data
3209 	FS_Purge (file);
3210 
3211 	// Unpacked or uncompressed files can seek directly
3212 	if (! (file->flags & QFILE_FLAG_DEFLATED))
3213 	{
3214 		if (FILEDESC_SEEK (file->handle, file->offset + offset, SEEK_SET) == -1)
3215 			return -1;
3216 		file->position = offset;
3217 		return 0;
3218 	}
3219 
3220 	// Seeking in compressed files is more a hack than anything else,
3221 	// but we need to support it, so here we go.
3222 	ztk = file->ztk;
3223 
3224 	// If we have to go back in the file, we need to restart from the beginning
3225 	if (offset <= file->position)
3226 	{
3227 		ztk->in_ind = 0;
3228 		ztk->in_len = 0;
3229 		ztk->in_position = 0;
3230 		file->position = 0;
3231 		if (FILEDESC_SEEK (file->handle, file->offset, SEEK_SET) == -1)
3232 			Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n");
3233 
3234 		// Reset the Zlib stream
3235 		ztk->zstream.next_in = ztk->input;
3236 		ztk->zstream.avail_in = 0;
3237 		qz_inflateReset (&ztk->zstream);
3238 	}
3239 
3240 	// We need a big buffer to force inflating into it directly
3241 	buffersize = 2 * sizeof (file->buff);
3242 	buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3243 
3244 	// Skip all data until we reach the requested offset
3245 	while (offset > file->position)
3246 	{
3247 		fs_offset_t diff = offset - file->position;
3248 		fs_offset_t count, len;
3249 
3250 		count = (diff > buffersize) ? buffersize : diff;
3251 		len = FS_Read (file, buffer, count);
3252 		if (len != count)
3253 		{
3254 			Mem_Free (buffer);
3255 			return -1;
3256 		}
3257 	}
3258 
3259 	Mem_Free (buffer);
3260 	return 0;
3261 }
3262 
3263 
3264 /*
3265 ====================
3266 FS_Tell
3267 
3268 Give the current position in a file
3269 ====================
3270 */
FS_Tell(qfile_t * file)3271 fs_offset_t FS_Tell (qfile_t* file)
3272 {
3273 	return file->position - file->buff_len + file->buff_ind;
3274 }
3275 
3276 
3277 /*
3278 ====================
3279 FS_FileSize
3280 
3281 Give the total size of a file
3282 ====================
3283 */
FS_FileSize(qfile_t * file)3284 fs_offset_t FS_FileSize (qfile_t* file)
3285 {
3286 	return file->real_length;
3287 }
3288 
3289 
3290 /*
3291 ====================
3292 FS_Purge
3293 
3294 Erases any buffered input or output data
3295 ====================
3296 */
FS_Purge(qfile_t * file)3297 void FS_Purge (qfile_t* file)
3298 {
3299 	file->buff_len = 0;
3300 	file->buff_ind = 0;
3301 	file->ungetc = EOF;
3302 }
3303 
3304 
3305 /*
3306 ============
3307 FS_LoadAndCloseQFile
3308 
3309 Loads full content of a qfile_t and closes it.
3310 Always appends a 0 byte.
3311 ============
3312 */
FS_LoadAndCloseQFile(qfile_t * file,const char * path,mempool_t * pool,qboolean quiet,fs_offset_t * filesizepointer)3313 static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3314 {
3315 	unsigned char *buf = NULL;
3316 	fs_offset_t filesize = 0;
3317 
3318 	if (file)
3319 	{
3320 		filesize = file->real_length;
3321 		if(filesize < 0)
3322 		{
3323 			Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3324 			FS_Close(file);
3325 			return NULL;
3326 		}
3327 
3328 		buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3329 		buf[filesize] = '\0';
3330 		FS_Read (file, buf, filesize);
3331 		FS_Close (file);
3332 		if (developer_loadfile.integer)
3333 			Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3334 	}
3335 
3336 	if (filesizepointer)
3337 		*filesizepointer = filesize;
3338 	return buf;
3339 }
3340 
3341 
3342 /*
3343 ============
3344 FS_LoadFile
3345 
3346 Filename are relative to the quake directory.
3347 Always appends a 0 byte.
3348 ============
3349 */
FS_LoadFile(const char * path,mempool_t * pool,qboolean quiet,fs_offset_t * filesizepointer)3350 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3351 {
3352 	qfile_t *file = FS_OpenVirtualFile(path, quiet);
3353 	return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3354 }
3355 
3356 
3357 /*
3358 ============
3359 FS_SysLoadFile
3360 
3361 Filename are OS paths.
3362 Always appends a 0 byte.
3363 ============
3364 */
FS_SysLoadFile(const char * path,mempool_t * pool,qboolean quiet,fs_offset_t * filesizepointer)3365 unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3366 {
3367 	qfile_t *file = FS_SysOpen(path, "rb", false);
3368 	return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3369 }
3370 
3371 
3372 /*
3373 ============
3374 FS_WriteFile
3375 
3376 The filename will be prefixed by the current game directory
3377 ============
3378 */
FS_WriteFileInBlocks(const char * filename,const void * const * data,const fs_offset_t * len,size_t count)3379 qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3380 {
3381 	qfile_t *file;
3382 	size_t i;
3383 	fs_offset_t lentotal;
3384 
3385 	file = FS_OpenRealFile(filename, "wb", false);
3386 	if (!file)
3387 	{
3388 		Con_Printf("FS_WriteFile: failed on %s\n", filename);
3389 		return false;
3390 	}
3391 
3392 	lentotal = 0;
3393 	for(i = 0; i < count; ++i)
3394 		lentotal += len[i];
3395 	Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3396 	for(i = 0; i < count; ++i)
3397 		FS_Write (file, data[i], len[i]);
3398 	FS_Close (file);
3399 	return true;
3400 }
3401 
FS_WriteFile(const char * filename,const void * data,fs_offset_t len)3402 qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3403 {
3404 	return FS_WriteFileInBlocks(filename, &data, &len, 1);
3405 }
3406 
3407 
3408 /*
3409 =============================================================================
3410 
3411 OTHERS PUBLIC FUNCTIONS
3412 
3413 =============================================================================
3414 */
3415 
3416 /*
3417 ============
3418 FS_StripExtension
3419 ============
3420 */
FS_StripExtension(const char * in,char * out,size_t size_out)3421 void FS_StripExtension (const char *in, char *out, size_t size_out)
3422 {
3423 	char *last = NULL;
3424 	char currentchar;
3425 
3426 	if (size_out == 0)
3427 		return;
3428 
3429 	while ((currentchar = *in) && size_out > 1)
3430 	{
3431 		if (currentchar == '.')
3432 			last = out;
3433 		else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3434 			last = NULL;
3435 		*out++ = currentchar;
3436 		in++;
3437 		size_out--;
3438 	}
3439 	if (last)
3440 		*last = 0;
3441 	else
3442 		*out = 0;
3443 }
3444 
3445 
3446 /*
3447 ==================
3448 FS_DefaultExtension
3449 ==================
3450 */
FS_DefaultExtension(char * path,const char * extension,size_t size_path)3451 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3452 {
3453 	const char *src;
3454 
3455 	// if path doesn't have a .EXT, append extension
3456 	// (extension should include the .)
3457 	src = path + strlen(path);
3458 
3459 	while (*src != '/' && src != path)
3460 	{
3461 		if (*src == '.')
3462 			return;                 // it has an extension
3463 		src--;
3464 	}
3465 
3466 	strlcat (path, extension, size_path);
3467 }
3468 
3469 
3470 /*
3471 ==================
3472 FS_FileType
3473 
3474 Look for a file in the packages and in the filesystem
3475 ==================
3476 */
FS_FileType(const char * filename)3477 int FS_FileType (const char *filename)
3478 {
3479 	searchpath_t *search;
3480 	char fullpath[MAX_OSPATH];
3481 
3482 	search = FS_FindFile (filename, NULL, true);
3483 	if(!search)
3484 		return FS_FILETYPE_NONE;
3485 
3486 	if(search->pack && !search->pack->vpack)
3487 		return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3488 
3489 	dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3490 	return FS_SysFileType(fullpath);
3491 }
3492 
3493 
3494 /*
3495 ==================
3496 FS_FileExists
3497 
3498 Look for a file in the packages and in the filesystem
3499 ==================
3500 */
FS_FileExists(const char * filename)3501 qboolean FS_FileExists (const char *filename)
3502 {
3503 	return (FS_FindFile (filename, NULL, true) != NULL);
3504 }
3505 
3506 
3507 /*
3508 ==================
3509 FS_SysFileExists
3510 
3511 Look for a file in the filesystem only
3512 ==================
3513 */
FS_SysFileType(const char * path)3514 int FS_SysFileType (const char *path)
3515 {
3516 #if WIN32
3517 // Sajt - some older sdks are missing this define
3518 # ifndef INVALID_FILE_ATTRIBUTES
3519 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3520 # endif
3521 
3522 	DWORD result = GetFileAttributes(path);
3523 
3524 	if(result == INVALID_FILE_ATTRIBUTES)
3525 		return FS_FILETYPE_NONE;
3526 
3527 	if(result & FILE_ATTRIBUTE_DIRECTORY)
3528 		return FS_FILETYPE_DIRECTORY;
3529 
3530 	return FS_FILETYPE_FILE;
3531 #else
3532 	struct stat buf;
3533 
3534 	if (stat (path,&buf) == -1)
3535 		return FS_FILETYPE_NONE;
3536 
3537 #ifndef S_ISDIR
3538 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3539 #endif
3540 	if(S_ISDIR(buf.st_mode))
3541 		return FS_FILETYPE_DIRECTORY;
3542 
3543 	return FS_FILETYPE_FILE;
3544 #endif
3545 }
3546 
FS_SysFileExists(const char * path)3547 qboolean FS_SysFileExists (const char *path)
3548 {
3549 	return FS_SysFileType (path) != FS_FILETYPE_NONE;
3550 }
3551 
3552 /*
3553 ===========
3554 FS_Search
3555 
3556 Allocate and fill a search structure with information on matching filenames.
3557 ===========
3558 */
FS_Search(const char * pattern,int caseinsensitive,int quiet)3559 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
3560 {
3561 	fssearch_t *search;
3562 	searchpath_t *searchpath;
3563 	pack_t *pak;
3564 	int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3565 	stringlist_t resultlist;
3566 	stringlist_t dirlist;
3567 	const char *slash, *backslash, *colon, *separator;
3568 	char *basepath;
3569 
3570 	for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3571 		;
3572 
3573 	if (i > 0)
3574 	{
3575 		Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3576 		return NULL;
3577 	}
3578 
3579 	stringlistinit(&resultlist);
3580 	stringlistinit(&dirlist);
3581 	search = NULL;
3582 	slash = strrchr(pattern, '/');
3583 	backslash = strrchr(pattern, '\\');
3584 	colon = strrchr(pattern, ':');
3585 	separator = max(slash, backslash);
3586 	separator = max(separator, colon);
3587 	basepathlength = separator ? (separator + 1 - pattern) : 0;
3588 	basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3589 	if (basepathlength)
3590 		memcpy(basepath, pattern, basepathlength);
3591 	basepath[basepathlength] = 0;
3592 
3593 	// search through the path, one element at a time
3594 	for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3595 	{
3596 		// is the element a pak file?
3597 		if (searchpath->pack && !searchpath->pack->vpack)
3598 		{
3599 			// look through all the pak file elements
3600 			pak = searchpath->pack;
3601 			for (i = 0;i < pak->numfiles;i++)
3602 			{
3603 				char temp[MAX_OSPATH];
3604 				strlcpy(temp, pak->files[i].name, sizeof(temp));
3605 				while (temp[0])
3606 				{
3607 					if (matchpattern(temp, (char *)pattern, true))
3608 					{
3609 						for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3610 							if (!strcmp(resultlist.strings[resultlistindex], temp))
3611 								break;
3612 						if (resultlistindex == resultlist.numstrings)
3613 						{
3614 							stringlistappend(&resultlist, temp);
3615 							if (!quiet && developer_loading.integer)
3616 								Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3617 						}
3618 					}
3619 					// strip off one path element at a time until empty
3620 					// this way directories are added to the listing if they match the pattern
3621 					slash = strrchr(temp, '/');
3622 					backslash = strrchr(temp, '\\');
3623 					colon = strrchr(temp, ':');
3624 					separator = temp;
3625 					if (separator < slash)
3626 						separator = slash;
3627 					if (separator < backslash)
3628 						separator = backslash;
3629 					if (separator < colon)
3630 						separator = colon;
3631 					*((char *)separator) = 0;
3632 				}
3633 			}
3634 		}
3635 		else
3636 		{
3637 			stringlist_t matchedSet, foundSet;
3638 			const char *start = pattern;
3639 
3640 			stringlistinit(&matchedSet);
3641 			stringlistinit(&foundSet);
3642 			// add a first entry to the set
3643 			stringlistappend(&matchedSet, "");
3644 			// iterate through pattern's path
3645 			while (*start)
3646 			{
3647 				const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3648 				char subpath[MAX_OSPATH];
3649 				char subpattern[MAX_OSPATH];
3650 
3651 				// find the next wildcard
3652 				wildcard = strchr(start, '?');
3653 				asterisk = strchr(start, '*');
3654 				if (asterisk && (!wildcard || asterisk < wildcard))
3655 				{
3656 					wildcard = asterisk;
3657 				}
3658 
3659 				if (wildcard)
3660 				{
3661 					nextseparator = strchr( wildcard, '/' );
3662 				}
3663 				else
3664 				{
3665 					nextseparator = NULL;
3666 				}
3667 
3668 				if( !nextseparator ) {
3669 					nextseparator = start + strlen( start );
3670 				}
3671 
3672 				// prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3673 				// copy everything up except nextseperator
3674 				strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3675 				// find the last '/' before the wildcard
3676 				prevseparator = strrchr( subpattern, '/' );
3677 				if (!prevseparator)
3678 					prevseparator = subpattern;
3679 				else
3680 					prevseparator++;
3681 				// copy everything from start to the previous including the '/' (before the wildcard)
3682 				// everything up to start is already included in the path of matchedSet's entries
3683 				strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3684 
3685 				// for each entry in matchedSet try to open the subdirectories specified in subpath
3686 				for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3687 					char temp[MAX_OSPATH];
3688 					strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3689 					strlcat( temp, subpath, sizeof(temp) );
3690 					listdirectory( &foundSet, searchpath->filename, temp );
3691 				}
3692 				if( dirlistindex == 0 ) {
3693 					break;
3694 				}
3695 				// reset the current result set
3696 				stringlistfreecontents( &matchedSet );
3697 				// match against the pattern
3698 				for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3699 					const char *direntry = foundSet.strings[ dirlistindex ];
3700 					if (matchpattern(direntry, subpattern, true)) {
3701 						stringlistappend( &matchedSet, direntry );
3702 					}
3703 				}
3704 				stringlistfreecontents( &foundSet );
3705 
3706 				start = nextseparator;
3707 			}
3708 
3709 			for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3710 			{
3711 				const char *matchtemp = matchedSet.strings[dirlistindex];
3712 				if (matchpattern(matchtemp, (char *)pattern, true))
3713 				{
3714 					for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3715 						if (!strcmp(resultlist.strings[resultlistindex], matchtemp))
3716 							break;
3717 					if (resultlistindex == resultlist.numstrings)
3718 					{
3719 						stringlistappend(&resultlist, matchtemp);
3720 						if (!quiet && developer_loading.integer)
3721 							Con_Printf("SearchDirFile: %s\n", matchtemp);
3722 					}
3723 				}
3724 			}
3725 			stringlistfreecontents( &matchedSet );
3726 		}
3727 	}
3728 
3729 	if (resultlist.numstrings)
3730 	{
3731 		stringlistsort(&resultlist, true);
3732 		numfiles = resultlist.numstrings;
3733 		numchars = 0;
3734 		for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3735 			numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3736 		search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3737 		search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3738 		search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3739 		search->numfilenames = (int)numfiles;
3740 		numfiles = 0;
3741 		numchars = 0;
3742 		for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3743 		{
3744 			size_t textlen;
3745 			search->filenames[numfiles] = search->filenamesbuffer + numchars;
3746 			textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3747 			memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3748 			numfiles++;
3749 			numchars += (int)textlen;
3750 		}
3751 	}
3752 	stringlistfreecontents(&resultlist);
3753 
3754 	Mem_Free(basepath);
3755 	return search;
3756 }
3757 
FS_FreeSearch(fssearch_t * search)3758 void FS_FreeSearch(fssearch_t *search)
3759 {
3760 	Z_Free(search);
3761 }
3762 
3763 extern int con_linewidth;
FS_ListDirectory(const char * pattern,int oneperline)3764 static int FS_ListDirectory(const char *pattern, int oneperline)
3765 {
3766 	int numfiles;
3767 	int numcolumns;
3768 	int numlines;
3769 	int columnwidth;
3770 	int linebufpos;
3771 	int i, j, k, l;
3772 	const char *name;
3773 	char linebuf[MAX_INPUTLINE];
3774 	fssearch_t *search;
3775 	search = FS_Search(pattern, true, true);
3776 	if (!search)
3777 		return 0;
3778 	numfiles = search->numfilenames;
3779 	if (!oneperline)
3780 	{
3781 		// FIXME: the names could be added to one column list and then
3782 		// gradually shifted into the next column if they fit, and then the
3783 		// next to make a compact variable width listing but it's a lot more
3784 		// complicated...
3785 		// find width for columns
3786 		columnwidth = 0;
3787 		for (i = 0;i < numfiles;i++)
3788 		{
3789 			l = (int)strlen(search->filenames[i]);
3790 			if (columnwidth < l)
3791 				columnwidth = l;
3792 		}
3793 		// count the spacing character
3794 		columnwidth++;
3795 		// calculate number of columns
3796 		numcolumns = con_linewidth / columnwidth;
3797 		// don't bother with the column printing if it's only one column
3798 		if (numcolumns >= 2)
3799 		{
3800 			numlines = (numfiles + numcolumns - 1) / numcolumns;
3801 			for (i = 0;i < numlines;i++)
3802 			{
3803 				linebufpos = 0;
3804 				for (k = 0;k < numcolumns;k++)
3805 				{
3806 					l = i * numcolumns + k;
3807 					if (l < numfiles)
3808 					{
3809 						name = search->filenames[l];
3810 						for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3811 							linebuf[linebufpos++] = name[j];
3812 						// space out name unless it's the last on the line
3813 						if (k + 1 < numcolumns && l + 1 < numfiles)
3814 							for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3815 								linebuf[linebufpos++] = ' ';
3816 					}
3817 				}
3818 				linebuf[linebufpos] = 0;
3819 				Con_Printf("%s\n", linebuf);
3820 			}
3821 		}
3822 		else
3823 			oneperline = true;
3824 	}
3825 	if (oneperline)
3826 		for (i = 0;i < numfiles;i++)
3827 			Con_Printf("%s\n", search->filenames[i]);
3828 	FS_FreeSearch(search);
3829 	return (int)numfiles;
3830 }
3831 
FS_ListDirectoryCmd(const char * cmdname,int oneperline)3832 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3833 {
3834 	const char *pattern;
3835 	if (Cmd_Argc() >= 3)
3836 	{
3837 		Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3838 		return;
3839 	}
3840 	if (Cmd_Argc() == 2)
3841 		pattern = Cmd_Argv(1);
3842 	else
3843 		pattern = "*";
3844 	if (!FS_ListDirectory(pattern, oneperline))
3845 		Con_Print("No files found.\n");
3846 }
3847 
FS_Dir_f(void)3848 void FS_Dir_f(void)
3849 {
3850 	FS_ListDirectoryCmd("dir", true);
3851 }
3852 
FS_Ls_f(void)3853 void FS_Ls_f(void)
3854 {
3855 	FS_ListDirectoryCmd("ls", false);
3856 }
3857 
FS_Which_f(void)3858 void FS_Which_f(void)
3859 {
3860 	const char *filename;
3861 	int index;
3862 	searchpath_t *sp;
3863 	if (Cmd_Argc() != 2)
3864 	{
3865 		Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3866 		return;
3867 	}
3868 	filename = Cmd_Argv(1);
3869 	sp = FS_FindFile(filename, &index, true);
3870 	if (!sp) {
3871 		Con_Printf("%s isn't anywhere\n", filename);
3872 		return;
3873 	}
3874 	if (sp->pack)
3875 	{
3876 		if(sp->pack->vpack)
3877 			Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
3878 		else
3879 			Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3880 	}
3881 	else
3882 		Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3883 }
3884 
3885 
FS_WhichPack(const char * filename)3886 const char *FS_WhichPack(const char *filename)
3887 {
3888 	int index;
3889 	searchpath_t *sp = FS_FindFile(filename, &index, true);
3890 	if(sp && sp->pack)
3891 		return sp->pack->shortname;
3892 	else if(sp)
3893 		return "";
3894 	else
3895 		return 0;
3896 }
3897 
3898 /*
3899 ====================
3900 FS_IsRegisteredQuakePack
3901 
3902 Look for a proof of purchase file file in the requested package
3903 
3904 If it is found, this file should NOT be downloaded.
3905 ====================
3906 */
FS_IsRegisteredQuakePack(const char * name)3907 qboolean FS_IsRegisteredQuakePack(const char *name)
3908 {
3909 	searchpath_t *search;
3910 	pack_t *pak;
3911 
3912 	// search through the path, one element at a time
3913 	for (search = fs_searchpaths;search;search = search->next)
3914 	{
3915 		if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3916 			// TODO do we want to support vpacks in here too?
3917 		{
3918 			int (*strcmp_funct) (const char* str1, const char* str2);
3919 			int left, right, middle;
3920 
3921 			pak = search->pack;
3922 			strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3923 
3924 			// Look for the file (binary search)
3925 			left = 0;
3926 			right = pak->numfiles - 1;
3927 			while (left <= right)
3928 			{
3929 				int diff;
3930 
3931 				middle = (left + right) / 2;
3932 				diff = strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3933 
3934 				// Found it
3935 				if (!diff)
3936 					return true;
3937 
3938 				// If we're too far in the list
3939 				if (diff > 0)
3940 					right = middle - 1;
3941 				else
3942 					left = middle + 1;
3943 			}
3944 
3945 			// we found the requested pack but it is not registered quake
3946 			return false;
3947 		}
3948 	}
3949 
3950 	return false;
3951 }
3952 
FS_CRCFile(const char * filename,size_t * filesizepointer)3953 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3954 {
3955 	int crc = -1;
3956 	unsigned char *filedata;
3957 	fs_offset_t filesize;
3958 	if (filesizepointer)
3959 		*filesizepointer = 0;
3960 	if (!filename || !*filename)
3961 		return crc;
3962 	filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3963 	if (filedata)
3964 	{
3965 		if (filesizepointer)
3966 			*filesizepointer = filesize;
3967 		crc = CRC_Block(filedata, filesize);
3968 		Mem_Free(filedata);
3969 	}
3970 	return crc;
3971 }
3972 
FS_Deflate(const unsigned char * data,size_t size,size_t * deflated_size,int level,mempool_t * mempool)3973 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3974 {
3975 	z_stream strm;
3976 	unsigned char *out = NULL;
3977 	unsigned char *tmp;
3978 
3979 	*deflated_size = 0;
3980 #ifndef LINK_TO_ZLIB
3981 	if(!zlib_dll)
3982 		return NULL;
3983 #endif
3984 
3985 	memset(&strm, 0, sizeof(strm));
3986 	strm.zalloc = Z_NULL;
3987 	strm.zfree = Z_NULL;
3988 	strm.opaque = Z_NULL;
3989 
3990 	if(level < 0)
3991 		level = Z_DEFAULT_COMPRESSION;
3992 
3993 	if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3994 	{
3995 		Con_Printf("FS_Deflate: deflate init error!\n");
3996 		return NULL;
3997 	}
3998 
3999 	strm.next_in = (unsigned char*)data;
4000 	strm.avail_in = (unsigned int)size;
4001 
4002 	tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
4003 	if(!tmp)
4004 	{
4005 		Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
4006 		qz_deflateEnd(&strm);
4007 		return NULL;
4008 	}
4009 
4010 	strm.next_out = tmp;
4011 	strm.avail_out = (unsigned int)size;
4012 
4013 	if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
4014 	{
4015 		Con_Printf("FS_Deflate: deflate failed!\n");
4016 		qz_deflateEnd(&strm);
4017 		Mem_Free(tmp);
4018 		return NULL;
4019 	}
4020 
4021 	if(qz_deflateEnd(&strm) != Z_OK)
4022 	{
4023 		Con_Printf("FS_Deflate: deflateEnd failed\n");
4024 		Mem_Free(tmp);
4025 		return NULL;
4026 	}
4027 
4028 	if(strm.total_out >= size)
4029 	{
4030 		Con_Printf("FS_Deflate: deflate is useless on this data!\n");
4031 		Mem_Free(tmp);
4032 		return NULL;
4033 	}
4034 
4035 	out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
4036 	if(!out)
4037 	{
4038 		Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
4039 		Mem_Free(tmp);
4040 		return NULL;
4041 	}
4042 
4043 	*deflated_size = (size_t)strm.total_out;
4044 
4045 	memcpy(out, tmp, strm.total_out);
4046 	Mem_Free(tmp);
4047 
4048 	return out;
4049 }
4050 
AssertBufsize(sizebuf_t * buf,int length)4051 static void AssertBufsize(sizebuf_t *buf, int length)
4052 {
4053 	if(buf->cursize + length > buf->maxsize)
4054 	{
4055 		int oldsize = buf->maxsize;
4056 		unsigned char *olddata;
4057 		olddata = buf->data;
4058 		buf->maxsize += length;
4059 		buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
4060 		if(olddata)
4061 		{
4062 			memcpy(buf->data, olddata, oldsize);
4063 			Mem_Free(olddata);
4064 		}
4065 	}
4066 }
4067 
FS_Inflate(const unsigned char * data,size_t size,size_t * inflated_size,mempool_t * mempool)4068 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
4069 {
4070 	int ret;
4071 	z_stream strm;
4072 	unsigned char *out = NULL;
4073 	unsigned char tmp[2048];
4074 	unsigned int have;
4075 	sizebuf_t outbuf;
4076 
4077 	*inflated_size = 0;
4078 #ifndef LINK_TO_ZLIB
4079 	if(!zlib_dll)
4080 		return NULL;
4081 #endif
4082 
4083 	memset(&outbuf, 0, sizeof(outbuf));
4084 	outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
4085 	outbuf.maxsize = sizeof(tmp);
4086 
4087 	memset(&strm, 0, sizeof(strm));
4088 	strm.zalloc = Z_NULL;
4089 	strm.zfree = Z_NULL;
4090 	strm.opaque = Z_NULL;
4091 
4092 	if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
4093 	{
4094 		Con_Printf("FS_Inflate: inflate init error!\n");
4095 		Mem_Free(outbuf.data);
4096 		return NULL;
4097 	}
4098 
4099 	strm.next_in = (unsigned char*)data;
4100 	strm.avail_in = (unsigned int)size;
4101 
4102 	do
4103 	{
4104 		strm.next_out = tmp;
4105 		strm.avail_out = sizeof(tmp);
4106 		ret = qz_inflate(&strm, Z_NO_FLUSH);
4107 		// it either returns Z_OK on progress, Z_STREAM_END on end
4108 		// or an error code
4109 		switch(ret)
4110 		{
4111 			case Z_STREAM_END:
4112 			case Z_OK:
4113 				break;
4114 
4115 			case Z_STREAM_ERROR:
4116 				Con_Print("FS_Inflate: stream error!\n");
4117 				break;
4118 			case Z_DATA_ERROR:
4119 				Con_Print("FS_Inflate: data error!\n");
4120 				break;
4121 			case Z_MEM_ERROR:
4122 				Con_Print("FS_Inflate: mem error!\n");
4123 				break;
4124 			case Z_BUF_ERROR:
4125 				Con_Print("FS_Inflate: buf error!\n");
4126 				break;
4127 			default:
4128 				Con_Print("FS_Inflate: unknown error!\n");
4129 				break;
4130 
4131 		}
4132 		if(ret != Z_OK && ret != Z_STREAM_END)
4133 		{
4134 			Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
4135 			Mem_Free(outbuf.data);
4136 			qz_inflateEnd(&strm);
4137 			return NULL;
4138 		}
4139 		have = sizeof(tmp) - strm.avail_out;
4140 		AssertBufsize(&outbuf, max(have, sizeof(tmp)));
4141 		SZ_Write(&outbuf, tmp, have);
4142 	} while(ret != Z_STREAM_END);
4143 
4144 	qz_inflateEnd(&strm);
4145 
4146 	out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4147 	if(!out)
4148 	{
4149 		Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4150 		Mem_Free(outbuf.data);
4151 		return NULL;
4152 	}
4153 
4154 	memcpy(out, outbuf.data, outbuf.cursize);
4155 	Mem_Free(outbuf.data);
4156 
4157 	*inflated_size = (size_t)outbuf.cursize;
4158 
4159 	return out;
4160 }
4161