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