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