]> git.xonotic.org Git - xonotic/darkplaces.git/blob - fs.c
server: always use correct case for map names
[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, qbool set_fs_gamedir)
1349 {
1350         int i;
1351         stringlist_t list;
1352         searchpath_t *search;
1353
1354         if (set_fs_gamedir)
1355                 dp_strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1356
1357         stringlistinit(&list);
1358         listdirectory(&list, "", dir);
1359         stringlistsort(&list, false);
1360
1361         // add any PAK package in the directory
1362         for (i = 0;i < list.numstrings;i++)
1363         {
1364                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1365                 {
1366                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false, false);
1367                 }
1368         }
1369
1370         // add any PK3 package in the directory
1371         for (i = 0;i < list.numstrings;i++)
1372         {
1373                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir")
1374                         || !strcasecmp(FS_FileExtension(list.strings[i]), "dpk") || !strcasecmp(FS_FileExtension(list.strings[i]), "dpkdir"))
1375                 {
1376                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false, false);
1377                 }
1378         }
1379
1380         stringlistfreecontents(&list);
1381
1382         // Add the directory to the search path
1383         // (unpacked files have the priority over packed files)
1384         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1385         dp_strlcpy (search->filename, dir, sizeof (search->filename));
1386         search->next = fs_searchpaths;
1387         fs_searchpaths = search;
1388 }
1389
1390
1391 /*
1392 ================
1393 FS_AddGameHierarchy
1394 ================
1395 */
1396 static void FS_AddGameHierarchy (const char *dir, qbool set_fs_gamedir)
1397 {
1398         char vabuf[1024];
1399         // Add the common game directory
1400         FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir), set_fs_gamedir);
1401
1402         if (*fs_userdir)
1403                 FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir), set_fs_gamedir);
1404 }
1405
1406
1407 /*
1408 ============
1409 FS_FileExtension
1410 ============
1411 */
1412 const char *FS_FileExtension (const char *in)
1413 {
1414         const char *separator, *backslash, *colon, *dot;
1415
1416         dot = strrchr(in, '.');
1417         if (dot == NULL)
1418                 return "";
1419
1420         separator = strrchr(in, '/');
1421         backslash = strrchr(in, '\\');
1422         if (!separator || separator < backslash)
1423                 separator = backslash;
1424         colon = strrchr(in, ':');
1425         if (!separator || separator < colon)
1426                 separator = colon;
1427
1428         if (separator && (dot < separator))
1429                 return "";
1430
1431         return dot + 1;
1432 }
1433
1434
1435 /*
1436 ============
1437 FS_FileWithoutPath
1438 ============
1439 */
1440 const char *FS_FileWithoutPath (const char *in)
1441 {
1442         const char *separator, *backslash, *colon;
1443
1444         separator = strrchr(in, '/');
1445         backslash = strrchr(in, '\\');
1446         if (!separator || separator < backslash)
1447                 separator = backslash;
1448         colon = strrchr(in, ':');
1449         if (!separator || separator < colon)
1450                 separator = colon;
1451         return separator ? separator + 1 : in;
1452 }
1453
1454
1455 /*
1456 ================
1457 FS_ClearSearchPath
1458 ================
1459 */
1460 static void FS_ClearSearchPath (void)
1461 {
1462         // unload all packs and directory information, close all pack files
1463         // (if a qfile is still reading a pack it won't be harmed because it used
1464         //  dup() to get its own handle already)
1465         while (fs_searchpaths)
1466         {
1467                 searchpath_t *search = fs_searchpaths;
1468                 fs_searchpaths = search->next;
1469                 if (search->pack && search->pack != fs_selfpack)
1470                 {
1471                         if(!search->pack->vpack)
1472                         {
1473                                 // close the file
1474                                 FILEDESC_CLOSE(search->pack->handle);
1475                                 // free any memory associated with it
1476                                 if (search->pack->files)
1477                                         Mem_Free(search->pack->files);
1478                         }
1479                         Mem_Free(search->pack);
1480                 }
1481                 Mem_Free(search);
1482         }
1483 }
1484
1485 /*
1486 ================
1487 FS_UnloadPacks_dlcache
1488
1489 Like FS_ClearSearchPath() but unloads only the packs loaded from dlcache
1490 so we don't need to use a full FS_Rescan() to prevent
1491 content from the previous server and/or map from interfering with the next
1492 ================
1493 */
1494 void FS_UnloadPacks_dlcache(void)
1495 {
1496         searchpath_t *search = fs_searchpaths, *searchprev = fs_searchpaths, *searchnext;
1497
1498         if (!fs_unload_dlcache.integer)
1499                 return;
1500
1501         while (search)
1502         {
1503                 searchnext = search->next;
1504                 if (search->pack && search->pack->dlcache)
1505                 {
1506                         Con_DPrintf("Unloading pack: %s\n", search->pack->shortname);
1507
1508                         // remove it from the search path list
1509                         if (search == fs_searchpaths)
1510                                 fs_searchpaths = search->next;
1511                         else
1512                                 searchprev->next = search->next;
1513
1514                         // close the file
1515                         FILEDESC_CLOSE(search->pack->handle);
1516                         // free any memory associated with it
1517                         if (search->pack->files)
1518                                 Mem_Free(search->pack->files);
1519                         Mem_Free(search->pack);
1520                         Mem_Free(search);
1521                 }
1522                 else
1523                         searchprev = search;
1524                 search = searchnext;
1525         }
1526 }
1527
1528 static void FS_AddSelfPack(void)
1529 {
1530         if(fs_selfpack)
1531         {
1532                 searchpath_t *search;
1533                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1534                 search->next = fs_searchpaths;
1535                 search->pack = fs_selfpack;
1536                 fs_searchpaths = search;
1537         }
1538 }
1539
1540
1541 /*
1542 ================
1543 FS_Rescan
1544 ================
1545 */
1546 void FS_Rescan (void)
1547 {
1548         int i;
1549         qbool fs_modified = false;
1550         qbool reset = false;
1551         char gamedirbuf[MAX_INPUTLINE];
1552         char vabuf[1024];
1553
1554         if (fs_searchpaths)
1555                 reset = true;
1556         FS_ClearSearchPath();
1557
1558         // automatically activate gamemode for the gamedirs specified
1559         if (reset)
1560                 COM_ChangeGameTypeForGameDirs();
1561
1562         // add the game-specific paths
1563         // gamedirname1 (typically id1)
1564         FS_AddGameHierarchy (gamedirname1, true);
1565         // update the com_modname (used for server info)
1566         if (gamedirname2 && gamedirname2[0])
1567                 dp_strlcpy(com_modname, gamedirname2, sizeof(com_modname));
1568         else
1569                 dp_strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1570
1571         // add the secondary game-specific path, if any
1572         // (only used for mission packs and the like, which should set fs_modified)
1573         if (gamedirname2 && gamedirname2[0])
1574         {
1575                 fs_modified = true;
1576                 FS_AddGameHierarchy (gamedirname2, true);
1577         }
1578
1579         // -game <gamedir>
1580         // Adds basedir/gamedir as an override game
1581         // LadyHavoc: now supports multiple -game directories
1582         // set the com_modname (reported in server info)
1583         // bones_was_here: does NOT set fs_gamedir in FS_AddGameHierarchy()
1584         // so that we still save files in the right place.
1585         *gamedirbuf = 0;
1586         for (i = 0;i < fs_numgamedirs;i++)
1587         {
1588                 fs_modified = true;
1589                 FS_AddGameHierarchy (fs_gamedirs[i], false);
1590                 // update the com_modname (used server info)
1591                 dp_strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1592                 if(i)
1593                         dp_strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1594                 else
1595                         dp_strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1596         }
1597         Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1598
1599         // add back the selfpack as new first item
1600         FS_AddSelfPack();
1601
1602         if (cls.state != ca_dedicated)
1603         {
1604                 // set the default screenshot name to either the mod name or the
1605                 // gamemode screenshot name
1606                 if (strcmp(com_modname, gamedirname1))
1607                         Cvar_SetQuick (&scr_screenshot_name, com_modname);
1608                 else
1609                         Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1610         }
1611
1612         if((i = Sys_CheckParm("-modname")) && i < sys.argc - 1)
1613                 dp_strlcpy(com_modname, sys.argv[i+1], sizeof(com_modname));
1614
1615         // If "-condebug" is in the command line, remove the previous log file
1616         if (Sys_CheckParm ("-condebug") != 0)
1617                 unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
1618
1619         // look for the pop.lmp file and set registered to true if it is found
1620         if (FS_FileExists("gfx/pop.lmp"))
1621                 Cvar_SetValueQuick(&registered, 1);
1622         switch(gamemode)
1623         {
1624         case GAME_NORMAL:
1625         case GAME_HIPNOTIC:
1626         case GAME_ROGUE:
1627                 if (!registered.integer)
1628                 {
1629                         if (fs_modified)
1630                                 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1631                         else
1632                                 Con_Print("Playing shareware version.\n");
1633                 }
1634                 else
1635                         Con_Print("Playing registered version.\n");
1636                 break;
1637         case GAME_STEELSTORM:
1638                 if (registered.integer)
1639                         Con_Print("Playing registered version.\n");
1640                 else
1641                         Con_Print("Playing shareware version.\n");
1642                 break;
1643         default:
1644                 break;
1645         }
1646
1647         // unload all wads so that future queries will return the new data
1648         W_UnloadAll();
1649 }
1650
1651 static void FS_Rescan_f(cmd_state_t *cmd)
1652 {
1653         FS_Rescan();
1654 }
1655
1656 /*
1657 ================
1658 FS_ChangeGameDirs
1659 ================
1660 */
1661 extern qbool vid_opened;
1662 qbool FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qbool complain, qbool failmissing)
1663 {
1664         int i;
1665         const char *p;
1666
1667         if (fs_numgamedirs == numgamedirs)
1668         {
1669                 for (i = 0;i < numgamedirs;i++)
1670                         if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1671                                 break;
1672                 if (i == numgamedirs)
1673                         return true; // already using this set of gamedirs, do nothing
1674         }
1675
1676         if (numgamedirs > MAX_GAMEDIRS)
1677         {
1678                 if (complain)
1679                         Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1680                 return false; // too many gamedirs
1681         }
1682
1683         for (i = 0;i < numgamedirs;i++)
1684         {
1685                 // if string is nasty, reject it
1686                 p = FS_CheckGameDir(gamedirs[i]);
1687                 if(!p)
1688                 {
1689                         if (complain)
1690                                 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1691                         return false; // nasty gamedirs
1692                 }
1693                 if(p == fs_checkgamedir_missing && failmissing)
1694                 {
1695                         if (complain)
1696                                 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1697                         return false; // missing gamedirs
1698                 }
1699         }
1700
1701         Host_SaveConfig(CONFIGFILENAME);
1702
1703         fs_numgamedirs = numgamedirs;
1704         for (i = 0;i < fs_numgamedirs;i++)
1705                 dp_strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1706
1707         // reinitialize filesystem to detect the new paks
1708         FS_Rescan();
1709
1710         if (cls.demoplayback)
1711         {
1712                 CL_Disconnect();
1713                 cls.demonum = 0;
1714         }
1715
1716         // unload all sounds so they will be reloaded from the new files as needed
1717         S_UnloadAllSounds_f(cmd_local);
1718
1719         // reset everything that can be and reload configs
1720         Cbuf_InsertText(cmd_local, "\nloadconfig\n");
1721
1722         return true;
1723 }
1724
1725 /*
1726 ================
1727 FS_GameDir_f
1728 ================
1729 */
1730 static void FS_GameDir_f(cmd_state_t *cmd)
1731 {
1732         int i;
1733         int numgamedirs;
1734         char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1735
1736         if (Cmd_Argc(cmd) < 2)
1737         {
1738                 Con_Printf("gamedirs active:");
1739                 for (i = 0;i < fs_numgamedirs;i++)
1740                         Con_Printf(" %s", fs_gamedirs[i]);
1741                 Con_Printf("\n");
1742                 return;
1743         }
1744
1745         numgamedirs = Cmd_Argc(cmd) - 1;
1746         if (numgamedirs > MAX_GAMEDIRS)
1747         {
1748                 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1749                 return;
1750         }
1751
1752         for (i = 0;i < numgamedirs;i++)
1753                 dp_strlcpy(gamedirs[i], Cmd_Argv(cmd, i+1), sizeof(gamedirs[i]));
1754
1755         if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1756         {
1757                 // actually, changing during game would work fine, but would be stupid
1758                 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1759                 return;
1760         }
1761
1762         // halt demo playback to close the file
1763         CL_Disconnect();
1764
1765         FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1766 }
1767
1768 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
1769 {
1770         qbool success;
1771         qfile_t *f;
1772         stringlist_t list;
1773         fs_offset_t n;
1774         char vabuf[1024];
1775
1776         stringlistinit(&list);
1777         listdirectory(&list, gamedir, "");
1778         success = list.numstrings > 0;
1779         stringlistfreecontents(&list);
1780
1781         if(success)
1782         {
1783                 f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
1784                 if(f)
1785                 {
1786                         n = FS_Read (f, buf, buflength - 1);
1787                         if(n >= 0)
1788                                 buf[n] = 0;
1789                         else
1790                                 *buf = 0;
1791                         FS_Close(f);
1792                 }
1793                 else
1794                         *buf = 0;
1795                 return buf;
1796         }
1797
1798         return NULL;
1799 }
1800
1801 /*
1802 ================
1803 FS_CheckGameDir
1804 ================
1805 */
1806 const char *FS_CheckGameDir(const char *gamedir)
1807 {
1808         const char *ret;
1809         static char buf[8192];
1810         char vabuf[1024];
1811
1812         if (FS_CheckNastyPath(gamedir, true))
1813                 return NULL;
1814
1815         ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
1816         if(ret)
1817         {
1818                 if(!*ret)
1819                 {
1820                         // get description from basedir
1821                         ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1822                         if(ret)
1823                                 return ret;
1824                         return "";
1825                 }
1826                 return ret;
1827         }
1828
1829         ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1830         if(ret)
1831                 return ret;
1832
1833         return fs_checkgamedir_missing;
1834 }
1835
1836 static void FS_ListGameDirs(void)
1837 {
1838         stringlist_t list, list2;
1839         int i;
1840         const char *info;
1841         char vabuf[1024];
1842
1843         fs_all_gamedirs_count = 0;
1844         if(fs_all_gamedirs)
1845                 Mem_Free(fs_all_gamedirs);
1846
1847         stringlistinit(&list);
1848         listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
1849         listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
1850         stringlistsort(&list, false);
1851
1852         stringlistinit(&list2);
1853         for(i = 0; i < list.numstrings; ++i)
1854         {
1855                 if(i)
1856                         if(!strcmp(list.strings[i-1], list.strings[i]))
1857                                 continue;
1858                 info = FS_CheckGameDir(list.strings[i]);
1859                 if(!info)
1860                         continue;
1861                 if(info == fs_checkgamedir_missing)
1862                         continue;
1863                 if(!*info)
1864                         continue;
1865                 stringlistappend(&list2, list.strings[i]);
1866         }
1867         stringlistfreecontents(&list);
1868
1869         fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1870         for(i = 0; i < list2.numstrings; ++i)
1871         {
1872                 info = FS_CheckGameDir(list2.strings[i]);
1873                 // all this cannot happen any more, but better be safe than sorry
1874                 if(!info)
1875                         continue;
1876                 if(info == fs_checkgamedir_missing)
1877                         continue;
1878                 if(!*info)
1879                         continue;
1880                 dp_strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
1881                 dp_strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
1882                 ++fs_all_gamedirs_count;
1883         }
1884 }
1885
1886 /*
1887 #ifdef WIN32
1888 #pragma comment(lib, "shell32.lib")
1889 #include <ShlObj.h>
1890 #endif
1891 */
1892
1893 static void COM_InsertFlags(const char *buf) {
1894         const char *p;
1895         char *q;
1896         const char **new_argv;
1897         int i = 0;
1898         int args_left = 256;
1899         new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*sys.argv) * (sys.argc + args_left + 2));
1900         if(sys.argc == 0)
1901                 new_argv[0] = "dummy";  // Can't really happen.
1902         else
1903                 new_argv[0] = sys.argv[0];
1904         ++i;
1905         p = buf;
1906         while(COM_ParseToken_Console(&p))
1907         {
1908                 size_t sz = strlen(com_token) + 1; // shut up clang
1909                 if(i > args_left)
1910                         break;
1911                 q = (char *)Mem_Alloc(fs_mempool, sz);
1912                 dp_strlcpy(q, com_token, sz);
1913                 new_argv[i] = q;
1914                 ++i;
1915         }
1916         // Now: i <= args_left + 1.
1917         if (sys.argc >= 1)
1918         {
1919                 memcpy((char *)(&new_argv[i]), &sys.argv[1], sizeof(*sys.argv) * (sys.argc - 1));
1920                 i += sys.argc - 1;
1921         }
1922         // Now: i <= args_left + (sys.argc || 1).
1923         new_argv[i] = NULL;
1924         sys.argv = new_argv;
1925         sys.argc = i;
1926 }
1927
1928 static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
1929 {
1930 #if defined(__IPHONEOS__)
1931         if (userdirmode == USERDIRMODE_HOME)
1932         {
1933                 // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1934                 // fs_userdir stores configurations to the Documents folder of the app
1935                 dp_strlcpy(userdir, "../Documents/", MAX_OSPATH);
1936                 return 1;
1937         }
1938         return -1;
1939
1940 #elif defined(WIN32)
1941         char homedir[WSTRBUF];
1942         wchar *homedirw;
1943 #if _MSC_VER >= 1400
1944         size_t homedirwlen;
1945 #endif
1946         wchar_t mydocsdirw[WSTRBUF];
1947         char mydocsdir[WSTRBUF];
1948         wchar_t *savedgamesdirw;
1949         char savedgamesdir[WSTRBUF] = {0};
1950         int fd;
1951         char vabuf[1024];
1952
1953         userdir[0] = 0;
1954         switch(userdirmode)
1955         {
1956         default:
1957                 return -1;
1958         case USERDIRMODE_NOHOME:
1959                 dp_strlcpy(userdir, fs_basedir, userdirsize);
1960                 break;
1961         case USERDIRMODE_MYGAMES:
1962                 if (!shfolder_dll)
1963                         Sys_LoadDependency(shfolderdllnames, &shfolder_dll, shfolderfuncs);
1964                 mydocsdir[0] = 0;
1965                 if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdirw) == S_OK)
1966                 {
1967                         narrow(mydocsdirw, mydocsdir);
1968                         dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
1969                         break;
1970                 }
1971 #if _MSC_VER >= 1400
1972                 _wdupenv_s(&homedirw, &homedirwlen, L"USERPROFILE");
1973                 narrow(homedirw, homedir);
1974                 if(homedir[0])
1975                 {
1976                         dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1977                         free(homedirw);
1978                         break;
1979                 }
1980 #else
1981                 homedirw = _wgetenv(L"USERPROFILE");
1982                 narrow(homedirw, homedir);
1983                 if(homedir[0])
1984                 {
1985                         dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1986                         break;
1987                 }
1988 #endif
1989                 return -1;
1990         case USERDIRMODE_SAVEDGAMES:
1991                 if (!shell32_dll)
1992                         Sys_LoadDependency(shell32dllnames, &shell32_dll, shell32funcs);
1993                 if (!ole32_dll)
1994                         Sys_LoadDependency(ole32dllnames, &ole32_dll, ole32funcs);
1995                 if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
1996                 {
1997                         savedgamesdir[0] = 0;
1998                         qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
1999 /*
2000 #ifdef __cplusplus
2001                         if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
2002 #else
2003                         if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
2004 #endif
2005 */
2006                         if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
2007                         {
2008                                 narrow(savedgamesdirw, savedgamesdir);
2009                                 qCoTaskMemFree(savedgamesdirw);
2010                         }
2011                         qCoUninitialize();
2012                         if (savedgamesdir[0])
2013                         {
2014                                 dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
2015                                 break;
2016                         }
2017                 }
2018                 return -1;
2019         }
2020 #else
2021         int fd;
2022         char *homedir;
2023         char vabuf[1024];
2024         userdir[0] = 0;
2025         switch(userdirmode)
2026         {
2027         default:
2028                 return -1;
2029         case USERDIRMODE_NOHOME:
2030                 dp_strlcpy(userdir, fs_basedir, userdirsize);
2031                 break;
2032         case USERDIRMODE_HOME:
2033                 homedir = getenv("HOME");
2034                 if(homedir)
2035                 {
2036                         dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
2037                         break;
2038                 }
2039                 return -1;
2040         case USERDIRMODE_SAVEDGAMES:
2041                 homedir = getenv("HOME");
2042                 if(homedir)
2043                 {
2044 #ifdef MACOSX
2045                         dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
2046 #else
2047                         // the XDG say some files would need to go in:
2048                         // XDG_CONFIG_HOME (or ~/.config/%s/)
2049                         // XDG_DATA_HOME (or ~/.local/share/%s/)
2050                         // XDG_CACHE_HOME (or ~/.cache/%s/)
2051                         // and also search the following global locations if defined:
2052                         // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
2053                         // XDG_DATA_DIRS (normally /usr/share/%s/)
2054                         // this would be too complicated...
2055                         return -1;
2056 #endif
2057                         break;
2058                 }
2059                 return -1;
2060         }
2061 #endif
2062
2063
2064 #if !defined(__IPHONEOS__)
2065
2066 #ifdef WIN32
2067         // historical behavior...
2068         if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
2069                 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
2070 #endif
2071
2072         // see if we can write to this path (note: won't create path)
2073 #ifdef WIN32
2074         // no access() here, we must try to open the file for appending
2075         fd = FS_SysOpenFiledesc(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
2076         if(fd >= 0)
2077                 FILEDESC_CLOSE(fd);
2078 #else
2079         // on Unix, we don't need to ACTUALLY attempt to open the file
2080         if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
2081                 fd = 1;
2082         else
2083                 fd = -1;
2084 #endif
2085         if(fd >= 0)
2086         {
2087                 return 1; // good choice - the path exists and is writable
2088         }
2089         else
2090         {
2091                 if (userdirmode == USERDIRMODE_NOHOME)
2092                         return -1; // path usually already exists, we lack permissions
2093                 else
2094                         return 0; // probably good - failed to write but maybe we need to create path
2095         }
2096 #endif
2097 }
2098
2099 void FS_Init_Commands(void)
2100 {
2101         Cvar_RegisterVariable (&scr_screenshot_name);
2102         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2103         Cvar_RegisterVariable (&fs_unload_dlcache);
2104         Cvar_RegisterVariable (&cvar_fs_gamedir);
2105
2106         Cmd_AddCommand(CF_SHARED, "gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
2107         Cmd_AddCommand(CF_SHARED, "fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2108         Cmd_AddCommand(CF_SHARED, "path", FS_Path_f, "print searchpath (game directories and archives)");
2109         Cmd_AddCommand(CF_SHARED, "dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2110         Cmd_AddCommand(CF_SHARED, "ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2111         Cmd_AddCommand(CF_SHARED, "which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2112 }
2113
2114 static void FS_Init_Dir (void)
2115 {
2116         const char *p;
2117         int i;
2118
2119         *fs_basedir = 0;
2120         *fs_userdir = 0;
2121         *fs_gamedir = 0;
2122
2123         // -basedir <path>
2124         // Overrides the system supplied base directory (under GAMENAME)
2125 // 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)
2126         i = Sys_CheckParm ("-basedir");
2127         if (i && i < sys.argc-1)
2128         {
2129                 dp_strlcpy (fs_basedir, sys.argv[i+1], sizeof (fs_basedir));
2130                 i = (int)strlen (fs_basedir);
2131                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
2132                         fs_basedir[i-1] = 0;
2133         }
2134         else
2135         {
2136 // If the base directory is explicitly defined by the compilation process
2137 #ifdef DP_FS_BASEDIR
2138                 dp_strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
2139 #elif defined(__ANDROID__)
2140                 dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
2141 #elif defined(MACOSX)
2142                 // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
2143                 if (strstr(sys.argv[0], ".app/"))
2144                 {
2145                         char *split;
2146                         dp_strlcpy(fs_basedir, sys.argv[0], sizeof(fs_basedir));
2147                         split = strstr(fs_basedir, ".app/");
2148                         if (split)
2149                         {
2150                                 struct stat statresult;
2151                                 char vabuf[1024];
2152                                 // truncate to just after the .app/
2153                                 split[5] = 0;
2154                                 // see if gamedir exists in Resources
2155                                 if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
2156                                 {
2157                                         // found gamedir inside Resources, use it
2158                                         dp_strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
2159                                 }
2160                                 else
2161                                 {
2162                                         // no gamedir found in Resources, gamedir is probably
2163                                         // outside the .app, remove .app part of path
2164                                         while (split > fs_basedir && *split != '/')
2165                                                 split--;
2166                                         *split = 0;
2167                                 }
2168                         }
2169                 }
2170 #endif
2171         }
2172
2173         // make sure the appending of a path separator won't create an unterminated string
2174         memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
2175         // add a path separator to the end of the basedir if it lacks one
2176         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
2177                 dp_strlcat(fs_basedir, "/", sizeof(fs_basedir));
2178
2179         // Add the personal game directory
2180         if((i = Sys_CheckParm("-userdir")) && i < sys.argc - 1)
2181                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", sys.argv[i+1]);
2182         else if (Sys_CheckParm("-nohome"))
2183                 *fs_userdir = 0; // user wants roaming installation, no userdir
2184         else
2185         {
2186 #ifdef DP_FS_USERDIR
2187                 dp_strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir));
2188 #else
2189                 int dirmode;
2190                 int highestuserdirmode = USERDIRMODE_COUNT - 1;
2191                 int preferreduserdirmode = USERDIRMODE_COUNT - 1;
2192                 int userdirstatus[USERDIRMODE_COUNT];
2193 # ifdef WIN32
2194                 // historical behavior...
2195                 if (!strcmp(gamedirname1, "id1"))
2196                         preferreduserdirmode = USERDIRMODE_NOHOME;
2197 # endif
2198                 // check what limitations the user wants to impose
2199                 if (Sys_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
2200                 if (Sys_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
2201                 if (Sys_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
2202                 // gather the status of the possible userdirs
2203                 for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
2204                 {
2205                         userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2206                         if (userdirstatus[dirmode] == 1)
2207                                 Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
2208                         else if (userdirstatus[dirmode] == 0)
2209                                 Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
2210                         else
2211                                 Con_DPrintf("userdir %i (not applicable)\n", dirmode);
2212                 }
2213                 // some games may prefer writing to basedir, but if write fails we
2214                 // have to search for a real userdir...
2215                 if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
2216                         preferreduserdirmode = highestuserdirmode;
2217                 // check for an existing userdir and continue using it if possible...
2218                 for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
2219                         if (userdirstatus[dirmode] == 1)
2220                                 break;
2221                 // if no existing userdir found, make a new one...
2222                 if (dirmode == 0 && preferreduserdirmode > 0)
2223                         for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
2224                                 if (userdirstatus[dirmode] >= 0)
2225                                         break;
2226                 // and finally, we picked one...
2227                 FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2228                 Con_DPrintf("userdir %i is the winner\n", dirmode);
2229 #endif
2230         }
2231
2232         // if userdir equal to basedir, clear it to avoid confusion later
2233         if (!strcmp(fs_basedir, fs_userdir))
2234                 fs_userdir[0] = 0;
2235
2236         FS_ListGameDirs();
2237
2238         p = FS_CheckGameDir(gamedirname1);
2239         if(!p || p == fs_checkgamedir_missing)
2240                 Con_Printf(CON_WARN "WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
2241
2242         if(gamedirname2)
2243         {
2244                 p = FS_CheckGameDir(gamedirname2);
2245                 if(!p || p == fs_checkgamedir_missing)
2246                         Con_Printf(CON_WARN "WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
2247         }
2248
2249         // -game <gamedir>
2250         // Adds basedir/gamedir as an override game
2251         // LadyHavoc: now supports multiple -game directories
2252         for (i = 1;i < sys.argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
2253         {
2254                 if (!sys.argv[i])
2255                         continue;
2256                 if (!strcmp (sys.argv[i], "-game") && i < sys.argc-1)
2257                 {
2258                         i++;
2259                         p = FS_CheckGameDir(sys.argv[i]);
2260                         if(!p)
2261                                 Con_Printf("WARNING: Nasty -game name rejected: %s\n", sys.argv[i]);
2262                         if(p == fs_checkgamedir_missing)
2263                                 Con_Printf(CON_WARN "WARNING: -game %s%s/ not found!\n", fs_basedir, sys.argv[i]);
2264                         // add the gamedir to the list of active gamedirs
2265                         dp_strlcpy (fs_gamedirs[fs_numgamedirs], sys.argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
2266                         fs_numgamedirs++;
2267                 }
2268         }
2269
2270         // generate the searchpath
2271         FS_Rescan();
2272
2273         if (Thread_HasThreads())
2274                 fs_mutex = Thread_CreateMutex();
2275 }
2276
2277 /*
2278 ================
2279 FS_Init_SelfPack
2280 ================
2281 */
2282 void FS_Init_SelfPack (void)
2283 {
2284         char *buf;
2285
2286         // Load darkplaces.opt from the FS.
2287         if (!Sys_CheckParm("-noopt"))
2288         {
2289                 buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL);
2290                 if(buf)
2291                 {
2292                         COM_InsertFlags(buf);
2293                         Mem_Free(buf);
2294                 }
2295         }
2296
2297 #ifndef USE_RWOPS
2298         // Provide the SelfPack.
2299         if (!Sys_CheckParm("-noselfpack") && sys.selffd >= 0)
2300         {
2301                 fs_selfpack = FS_LoadPackPK3FromFD(sys.argv[0], sys.selffd, true);
2302                 if(fs_selfpack)
2303                 {
2304                         FS_AddSelfPack();
2305                         if (!Sys_CheckParm("-noopt"))
2306                         {
2307                                 buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
2308                                 if(buf)
2309                                 {
2310                                         COM_InsertFlags(buf);
2311                                         Mem_Free(buf);
2312                                 }
2313                         }
2314                 }
2315         }
2316 #endif
2317 }
2318
2319 /*
2320 ================
2321 FS_Init
2322 ================
2323 */
2324
2325 void FS_Init(void)
2326 {
2327         fs_mempool = Mem_AllocPool("file management", 0, NULL);
2328
2329         FS_Init_Commands();
2330
2331         PK3_OpenLibrary ();
2332
2333         // initialize the self-pack (must be before COM_InitGameType as it may add command line options)
2334         FS_Init_SelfPack();
2335
2336         // detect gamemode from commandline options or executable name
2337         COM_InitGameType();
2338
2339         FS_Init_Dir();
2340 }
2341
2342 /*
2343 ================
2344 FS_Shutdown
2345 ================
2346 */
2347 void FS_Shutdown (void)
2348 {
2349         // close all pack files and such
2350         // (hopefully there aren't any other open files, but they'll be cleaned up
2351         //  by the OS anyway)
2352         FS_ClearSearchPath();
2353         Mem_FreePool (&fs_mempool);
2354         PK3_CloseLibrary ();
2355
2356 #ifdef WIN32
2357         Sys_FreeLibrary (&shfolder_dll);
2358         Sys_FreeLibrary (&shell32_dll);
2359         Sys_FreeLibrary (&ole32_dll);
2360 #endif
2361
2362         if (fs_mutex)
2363                 Thread_DestroyMutex(fs_mutex);
2364 }
2365
2366 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbool nonblocking)
2367 {
2368         filedesc_t handle = FILEDESC_INVALID;
2369         int mod, opt;
2370         unsigned int ind;
2371         qbool dolock = false;
2372         #ifdef WIN32
2373         wchar filepathw[WSTRBUF] = {0};
2374         #endif
2375
2376         // Parse the mode string
2377         switch (mode[0])
2378         {
2379                 case 'r':
2380                         mod = O_RDONLY;
2381                         opt = 0;
2382                         break;
2383                 case 'w':
2384                         mod = O_WRONLY;
2385                         opt = O_CREAT | O_TRUNC;
2386                         break;
2387                 case 'a':
2388                         mod = O_WRONLY;
2389                         opt = O_CREAT | O_APPEND;
2390                         break;
2391                 default:
2392                         Con_Printf(CON_ERROR "FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2393                         return FILEDESC_INVALID;
2394         }
2395         for (ind = 1; mode[ind] != '\0'; ind++)
2396         {
2397                 switch (mode[ind])
2398                 {
2399                         case '+':
2400                                 mod = O_RDWR;
2401                                 break;
2402                         case 'b':
2403                                 opt |= O_BINARY;
2404                                 break;
2405                         case 'l':
2406                                 dolock = true;
2407                                 break;
2408                         default:
2409                                 Con_Printf(CON_ERROR "FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2410                                                         filepath, mode, mode[ind]);
2411                 }
2412         }
2413
2414         if (nonblocking)
2415                 opt |= O_NONBLOCK;
2416
2417         if(Sys_CheckParm("-readonly") && mod != O_RDONLY)
2418                 return FILEDESC_INVALID;
2419
2420 #if USE_RWOPS
2421         if (dolock)
2422                 return FILEDESC_INVALID;
2423         handle = SDL_RWFromFile(filepath, mode);
2424 #else
2425 # ifdef WIN32
2426         widen(filepath, filepathw);
2427 #  if _MSC_VER >= 1400
2428         _wsopen_s(&handle, filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2429 #  else
2430         handle = _wsopen (filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2431 #  endif
2432 # else
2433         handle = open (filepath, mod | opt, 0666);
2434         if(handle >= 0 && dolock)
2435         {
2436                 struct flock l;
2437                 l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
2438                 l.l_whence = SEEK_SET;
2439                 l.l_start = 0;
2440                 l.l_len = 0;
2441                 if(fcntl(handle, F_SETLK, &l) == -1)
2442                 {
2443                         FILEDESC_CLOSE(handle);
2444                         handle = -1;
2445                 }
2446         }
2447 # endif
2448 #endif
2449
2450         return handle;
2451 }
2452
2453 int FS_SysOpenFD(const char *filepath, const char *mode, qbool nonblocking)
2454 {
2455 #ifdef USE_RWOPS
2456         return -1;
2457 #else
2458         return FS_SysOpenFiledesc(filepath, mode, nonblocking);
2459 #endif
2460 }
2461
2462 /*
2463 ====================
2464 FS_SysOpen
2465
2466 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2467 ====================
2468 */
2469 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qbool nonblocking)
2470 {
2471         qfile_t* file;
2472
2473         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2474         file->ungetc = EOF;
2475         file->handle = FS_SysOpenFiledesc(filepath, mode, nonblocking);
2476         if (!FILEDESC_ISVALID(file->handle))
2477         {
2478                 Mem_Free (file);
2479                 return NULL;
2480         }
2481
2482         file->filename = Mem_strdup(fs_mempool, filepath);
2483
2484         file->real_length = FILEDESC_SEEK (file->handle, 0, SEEK_END);
2485
2486         // For files opened in append mode, we start at the end of the file
2487         if (mode[0] == 'a')
2488                 file->position = file->real_length;
2489         else
2490                 FILEDESC_SEEK (file->handle, 0, SEEK_SET);
2491
2492         return file;
2493 }
2494
2495
2496 /*
2497 ===========
2498 FS_OpenPackedFile
2499
2500 Open a packed file using its package file descriptor
2501 ===========
2502 */
2503 static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2504 {
2505         packfile_t *pfile;
2506         filedesc_t dup_handle;
2507         qfile_t* file;
2508
2509         pfile = &pack->files[pack_ind];
2510
2511         // If we don't have the true offset, get it now
2512         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2513                 if (!PK3_GetTrueFileOffset (pfile, pack))
2514                         return NULL;
2515
2516 #ifndef LINK_TO_ZLIB
2517         // No Zlib DLL = no compressed files
2518         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2519         {
2520                 Con_Printf(CON_WARN "WARNING: can't open the compressed file %s\n"
2521                                         "You need the Zlib DLL to use compressed files\n",
2522                                         pfile->name);
2523                 return NULL;
2524         }
2525 #endif
2526
2527         // LadyHavoc: FILEDESC_SEEK affects all duplicates of a handle so we do it before
2528         // the dup() call to avoid having to close the dup_handle on error here
2529         if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
2530         {
2531                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2532                                         pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2533                 return NULL;
2534         }
2535
2536         dup_handle = FILEDESC_DUP (pack->filename, pack->handle);
2537         if (!FILEDESC_ISVALID(dup_handle))
2538         {
2539                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2540                 return NULL;
2541         }
2542
2543         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2544         memset (file, 0, sizeof (*file));
2545         file->handle = dup_handle;
2546         file->flags = QFILE_FLAG_PACKED;
2547         file->real_length = pfile->realsize;
2548         file->offset = pfile->offset;
2549         file->position = 0;
2550         file->ungetc = EOF;
2551
2552         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2553         {
2554                 ztoolkit_t *ztk;
2555
2556                 file->flags |= QFILE_FLAG_DEFLATED;
2557
2558                 // We need some more variables
2559                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2560
2561                 ztk->comp_length = pfile->packsize;
2562
2563                 // Initialize zlib stream
2564                 ztk->zstream.next_in = ztk->input;
2565                 ztk->zstream.avail_in = 0;
2566
2567                 /* From Zlib's "unzip.c":
2568                  *
2569                  * windowBits is passed < 0 to tell that there is no zlib header.
2570                  * Note that in this case inflate *requires* an extra "dummy" byte
2571                  * after the compressed stream in order to complete decompression and
2572                  * return Z_STREAM_END.
2573                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2574                  * size of both compressed and uncompressed data
2575                  */
2576                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2577                 {
2578                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2579                         FILEDESC_CLOSE(dup_handle);
2580                         Mem_Free(file);
2581                         return NULL;
2582                 }
2583
2584                 ztk->zstream.next_out = file->buff;
2585                 ztk->zstream.avail_out = sizeof (file->buff);
2586
2587                 file->ztk = ztk;
2588         }
2589
2590         return file;
2591 }
2592
2593 /*
2594 ====================
2595 FS_CheckNastyPath
2596
2597 Return true if the path should be rejected due to one of the following:
2598 1: path elements that are non-portable
2599 2: path elements that would allow access to files outside the game directory,
2600    or are just not a good idea for a mod to be using.
2601 ====================
2602 */
2603 int FS_CheckNastyPath (const char *path, qbool isgamedir)
2604 {
2605         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2606         if (!path[0])
2607                 return 2;
2608
2609         // Windows: don't allow \ in filenames (windows-only), period.
2610         // (on Windows \ is a directory separator, but / is also supported)
2611         if (strstr(path, "\\"))
2612                 return 1; // non-portable
2613
2614         // Mac: don't allow Mac-only filenames - : is a directory separator
2615         // instead of /, but we rely on / working already, so there's no reason to
2616         // support a Mac-only path
2617         // Amiga and Windows: : tries to go to root of drive
2618         if (strstr(path, ":"))
2619                 return 1; // non-portable attempt to go to root of drive
2620
2621         // Amiga: // is parent directory
2622         if (strstr(path, "//"))
2623                 return 1; // non-portable attempt to go to parent directory
2624
2625         // all: don't allow going to parent directory (../ or /../)
2626         if (strstr(path, ".."))
2627                 return 2; // attempt to go outside the game directory
2628
2629         // Windows and UNIXes: don't allow absolute paths
2630         if (path[0] == '/')
2631                 return 2; // attempt to go outside the game directory
2632
2633         // all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc
2634         if (strstr(path, "./"))
2635                 return 2; // possible attempt to go outside the game directory
2636
2637         // all: forbid trailing slash on gamedir
2638         if (isgamedir && path[strlen(path)-1] == '/')
2639                 return 2;
2640
2641         // all: forbid leading dot on any filename for any reason
2642         if (strstr(path, "/."))
2643                 return 2; // attempt to go outside the game directory
2644
2645         // after all these checks we're pretty sure it's a / separated filename
2646         // and won't do much if any harm
2647         return false;
2648 }
2649
2650 /*
2651 ====================
2652 FS_SanitizePath
2653
2654 Sanitize path (replace non-portable characters
2655 with portable ones in-place, etc)
2656 ====================
2657 */
2658 void FS_SanitizePath(char *path)
2659 {
2660         for (; *path; path++)
2661                 if (*path == '\\')
2662                         *path = '/';
2663 }
2664
2665 /*
2666 ====================
2667 FS_FindFile
2668
2669 Look for a file in the packages and in the filesystem
2670
2671 Return the searchpath where the file was found (or NULL)
2672 and the file index in the package if relevant
2673 ====================
2674 */
2675 static searchpath_t *FS_FindFile (const char *name, int *index, const char **canonicalname, qbool quiet)
2676 {
2677         searchpath_t *search;
2678         pack_t *pak;
2679
2680         // search through the path, one element at a time
2681         for (search = fs_searchpaths;search;search = search->next)
2682         {
2683                 // is the element a pak file?
2684                 if (search->pack && !search->pack->vpack)
2685                 {
2686                         int (*strcmp_funct) (const char* str1, const char* str2);
2687                         int left, right, middle;
2688
2689                         pak = search->pack;
2690                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2691
2692                         // Look for the file (binary search)
2693                         left = 0;
2694                         right = pak->numfiles - 1;
2695                         while (left <= right)
2696                         {
2697                                 int diff;
2698
2699                                 middle = (left + right) / 2;
2700                                 diff = strcmp_funct (pak->files[middle].name, name);
2701
2702                                 // Found it
2703                                 if (!diff)
2704                                 {
2705                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2706                                         {
2707                                                 // yes, but the first one is empty so we treat it as not being there
2708                                                 if (!quiet && developer_extra.integer)
2709                                                         Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2710
2711                                                 if (index != NULL)
2712                                                         *index = -1;
2713                                                 if (canonicalname)
2714                                                         *canonicalname = NULL;
2715                                                 return NULL;
2716                                         }
2717
2718                                         if (!quiet && developer_extra.integer)
2719                                                 Con_DPrintf("FS_FindFile: %s in %s\n", pak->files[middle].name, pak->filename);
2720
2721                                         if (index != NULL)
2722                                                 *index = middle;
2723                                         if (canonicalname)
2724                                                 *canonicalname = pak->files[middle].name;
2725                                         return search;
2726                                 }
2727
2728                                 // If we're too far in the list
2729                                 if (diff > 0)
2730                                         right = middle - 1;
2731                                 else
2732                                         left = middle + 1;
2733                         }
2734                 }
2735                 else
2736                 {
2737                         char netpath[MAX_OSPATH];
2738                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2739                         if (FS_SysFileExists (netpath))
2740                         {
2741                                 if (!quiet && developer_extra.integer)
2742                                         Con_DPrintf("FS_FindFile: %s\n", netpath);
2743
2744                                 if (index != NULL)
2745                                         *index = -1;
2746                                 if (canonicalname)
2747                                         *canonicalname = name;
2748                                 return search;
2749                         }
2750                 }
2751         }
2752
2753         if (!quiet && developer_extra.integer)
2754                 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2755
2756         if (index != NULL)
2757                 *index = -1;
2758         if (canonicalname)
2759                 *canonicalname = NULL;
2760         return NULL;
2761 }
2762
2763
2764 /*
2765 ===========
2766 FS_OpenReadFile
2767
2768 Look for a file in the search paths and open it in read-only mode
2769 ===========
2770 */
2771 static qfile_t *FS_OpenReadFile (const char *filename, qbool quiet, qbool nonblocking, int symlinkLevels)
2772 {
2773         searchpath_t *search;
2774         int pack_ind;
2775
2776         search = FS_FindFile (filename, &pack_ind, NULL, quiet);
2777
2778         // Not found?
2779         if (search == NULL)
2780                 return NULL;
2781
2782         // Found in the filesystem?
2783         if (pack_ind < 0)
2784         {
2785                 // this works with vpacks, so we are fine
2786                 char path [MAX_OSPATH];
2787                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2788                 return FS_SysOpen (path, "rb", nonblocking);
2789         }
2790
2791         // So, we found it in a package...
2792
2793         // Is it a PK3 symlink?
2794         // TODO also handle directory symlinks by parsing the whole structure...
2795         // but heck, file symlinks are good enough for now
2796         if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2797         {
2798                 if(symlinkLevels <= 0)
2799                 {
2800                         Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2801                         return NULL;
2802                 }
2803                 else
2804                 {
2805                         char linkbuf[MAX_QPATH];
2806                         fs_offset_t count;
2807                         qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2808                         const char *mergeslash;
2809                         char *mergestart;
2810
2811                         if(!linkfile)
2812                                 return NULL;
2813                         count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2814                         FS_Close(linkfile);
2815                         if(count < 0)
2816                                 return NULL;
2817                         linkbuf[count] = 0;
2818
2819                         // Now combine the paths...
2820                         mergeslash = strrchr(filename, '/');
2821                         mergestart = linkbuf;
2822                         if(!mergeslash)
2823                                 mergeslash = filename;
2824                         while(!strncmp(mergestart, "../", 3))
2825                         {
2826                                 mergestart += 3;
2827                                 while(mergeslash > filename)
2828                                 {
2829                                         --mergeslash;
2830                                         if(*mergeslash == '/')
2831                                                 break;
2832                                 }
2833                         }
2834                         // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2835                         if(mergeslash == filename)
2836                         {
2837                                 // Either mergeslash == filename, then we just replace the name (done below)
2838                         }
2839                         else
2840                         {
2841                                 // Or, we append the name after mergeslash;
2842                                 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2843                                 int spaceNeeded = mergeslash - filename + 1;
2844                                 int spaceRemoved = mergestart - linkbuf;
2845                                 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2846                                 {
2847                                         Con_DPrintf("symlink: too long path rejected\n");
2848                                         return NULL;
2849                                 }
2850                                 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2851                                 memcpy(linkbuf, filename, spaceNeeded);
2852                                 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2853                                 mergestart = linkbuf;
2854                         }
2855                         if (!quiet && developer_loading.integer)
2856                                 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2857                         if(FS_CheckNastyPath (mergestart, false))
2858                         {
2859                                 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2860                                 return NULL;
2861                         }
2862                         return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2863                 }
2864         }
2865
2866         return FS_OpenPackedFile (search->pack, pack_ind);
2867 }
2868
2869
2870 /*
2871 =============================================================================
2872
2873 MAIN PUBLIC FUNCTIONS
2874
2875 =============================================================================
2876 */
2877
2878 /*
2879 ====================
2880 FS_OpenRealFile
2881
2882 Open a file in the userpath. The syntax is the same as fopen
2883 Used for savegame scanning in menu, and all file writing.
2884 ====================
2885 */
2886 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qbool quiet)
2887 {
2888         char real_path [MAX_OSPATH];
2889
2890         if (FS_CheckNastyPath(filepath, false))
2891         {
2892                 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2893                 return NULL;
2894         }
2895
2896         dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2897
2898         // If the file is opened in "write", "append", or "read/write" mode,
2899         // create directories up to the file.
2900         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2901                 FS_CreatePath (real_path);
2902         return FS_SysOpen (real_path, mode, false);
2903 }
2904
2905
2906 /*
2907 ====================
2908 FS_OpenVirtualFile
2909
2910 Open a file. The syntax is the same as fopen
2911 ====================
2912 */
2913 qfile_t* FS_OpenVirtualFile (const char* filepath, qbool quiet)
2914 {
2915         qfile_t *result = NULL;
2916         if (FS_CheckNastyPath(filepath, false))
2917         {
2918                 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2919                 return NULL;
2920         }
2921
2922         if (fs_mutex) Thread_LockMutex(fs_mutex);
2923         result = FS_OpenReadFile (filepath, quiet, false, 16);
2924         if (fs_mutex) Thread_UnlockMutex(fs_mutex);
2925         return result;
2926 }
2927
2928
2929 /*
2930 ====================
2931 FS_FileFromData
2932
2933 Open a file. The syntax is the same as fopen
2934 ====================
2935 */
2936 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qbool quiet)
2937 {
2938         qfile_t* file;
2939         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2940         memset (file, 0, sizeof (*file));
2941         file->flags = QFILE_FLAG_DATA;
2942         file->ungetc = EOF;
2943         file->real_length = size;
2944         file->data = data;
2945         return file;
2946 }
2947
2948 /*
2949 ====================
2950 FS_Close
2951
2952 Close a file
2953 ====================
2954 */
2955 int FS_Close (qfile_t* file)
2956 {
2957         if(file->flags & QFILE_FLAG_DATA)
2958         {
2959                 Mem_Free(file);
2960                 return 0;
2961         }
2962
2963         if (FILEDESC_CLOSE (file->handle))
2964                 return EOF;
2965
2966         if (file->filename)
2967         {
2968                 if (file->flags & QFILE_FLAG_REMOVE)
2969                 {
2970                         if (remove(file->filename) == -1)
2971                         {
2972                                 // No need to report this. If removing a just
2973                                 // written file failed, this most likely means
2974                                 // someone else deleted it first - which we
2975                                 // like.
2976                         }
2977                 }
2978
2979                 Mem_Free((void *) file->filename);
2980         }
2981
2982         if (file->ztk)
2983         {
2984                 qz_inflateEnd (&file->ztk->zstream);
2985                 Mem_Free (file->ztk);
2986         }
2987
2988         Mem_Free (file);
2989         return 0;
2990 }
2991
2992 void FS_RemoveOnClose(qfile_t* file)
2993 {
2994         file->flags |= QFILE_FLAG_REMOVE;
2995 }
2996
2997 /*
2998 ====================
2999 FS_Write
3000
3001 Write "datasize" bytes into a file
3002 ====================
3003 */
3004 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
3005 {
3006         fs_offset_t written = 0;
3007
3008         // If necessary, seek to the exact file position we're supposed to be
3009         if (file->buff_ind != file->buff_len)
3010         {
3011                 if (FILEDESC_SEEK (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1)
3012                 {
3013                         Con_Printf(CON_WARN "WARNING: could not seek in %s.\n", file->filename);
3014                 }
3015         }
3016
3017         // Purge cached data
3018         FS_Purge (file);
3019
3020         // Write the buffer and update the position
3021         // 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)
3022         while (written < (fs_offset_t)datasize)
3023         {
3024                 // figure out how much to write in one chunk
3025                 fs_offset_t maxchunk = 1<<30; // 1 GiB
3026                 int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
3027                 int result = (int)FILEDESC_WRITE (file->handle, (const unsigned char *)data + written, chunk);
3028                 // if at least some was written, add it to our accumulator
3029                 if (result > 0)
3030                         written += result;
3031                 // if the result is not what we expected, consider the write to be incomplete
3032                 if (result != chunk)
3033                         break;
3034         }
3035         file->position = FILEDESC_SEEK (file->handle, 0, SEEK_CUR);
3036         if (file->real_length < file->position)
3037                 file->real_length = file->position;
3038
3039         // note that this will never be less than 0 even if the write failed
3040         return written;
3041 }
3042
3043
3044 /*
3045 ====================
3046 FS_Read
3047
3048 Read up to "buffersize" bytes from a file
3049 ====================
3050 */
3051 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
3052 {
3053         fs_offset_t count, done;
3054
3055         if (buffersize == 0 || !buffer)
3056                 return 0;
3057
3058         // Get rid of the ungetc character
3059         if (file->ungetc != EOF)
3060         {
3061                 ((char*)buffer)[0] = file->ungetc;
3062                 buffersize--;
3063                 file->ungetc = EOF;
3064                 done = 1;
3065         }
3066         else
3067                 done = 0;
3068
3069         if(file->flags & QFILE_FLAG_DATA)
3070         {
3071                 size_t left = file->real_length - file->position;
3072                 if(buffersize > left)
3073                         buffersize = left;
3074                 memcpy(buffer, file->data + file->position, buffersize);
3075                 file->position += buffersize;
3076                 return buffersize;
3077         }
3078
3079         // First, we copy as many bytes as we can from "buff"
3080         if (file->buff_ind < file->buff_len)
3081         {
3082                 count = file->buff_len - file->buff_ind;
3083                 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
3084                 done += count;
3085                 memcpy (buffer, &file->buff[file->buff_ind], count);
3086                 file->buff_ind += count;
3087
3088                 buffersize -= count;
3089                 if (buffersize == 0)
3090                         return done;
3091         }
3092
3093         // NOTE: at this point, the read buffer is always empty
3094
3095         // If the file isn't compressed
3096         if (! (file->flags & QFILE_FLAG_DEFLATED))
3097         {
3098                 fs_offset_t nb;
3099
3100                 // We must take care to not read after the end of the file
3101                 count = file->real_length - file->position;
3102
3103                 // If we have a lot of data to get, put them directly into "buffer"
3104                 if (buffersize > sizeof (file->buff) / 2)
3105                 {
3106                         if (count > (fs_offset_t)buffersize)
3107                                 count = (fs_offset_t)buffersize;
3108                         if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
3109                         {
3110                                 // Seek failed. When reading from a pipe, and
3111                                 // the caller never called FS_Seek, this still
3112                                 // works fine.  So no reporting this error.
3113                         }
3114                         nb = FILEDESC_READ (file->handle, &((unsigned char*)buffer)[done], count);
3115                         if (nb > 0)
3116                         {
3117                                 done += nb;
3118                                 file->position += nb;
3119
3120                                 // Purge cached data
3121                                 FS_Purge (file);
3122                         }
3123                 }
3124                 else
3125                 {
3126                         if (count > (fs_offset_t)sizeof (file->buff))
3127                                 count = (fs_offset_t)sizeof (file->buff);
3128                         if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
3129                         {
3130                                 // Seek failed. When reading from a pipe, and
3131                                 // the caller never called FS_Seek, this still
3132                                 // works fine.  So no reporting this error.
3133                         }
3134                         nb = FILEDESC_READ (file->handle, file->buff, count);
3135                         if (nb > 0)
3136                         {
3137                                 file->buff_len = nb;
3138                                 file->position += nb;
3139
3140                                 // Copy the requested data in "buffer" (as much as we can)
3141                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3142                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3143                                 file->buff_ind = count;
3144                                 done += count;
3145                         }
3146                 }
3147
3148                 return done;
3149         }
3150
3151         // If the file is compressed, it's more complicated...
3152         // We cycle through a few operations until we have read enough data
3153         while (buffersize > 0)
3154         {
3155                 ztoolkit_t *ztk = file->ztk;
3156                 int error;
3157
3158                 // NOTE: at this point, the read buffer is always empty
3159
3160                 // If "input" is also empty, we need to refill it
3161                 if (ztk->in_ind == ztk->in_len)
3162                 {
3163                         // If we are at the end of the file
3164                         if (file->position == file->real_length)
3165                                 return done;
3166
3167                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
3168                         if (count > (fs_offset_t)sizeof (ztk->input))
3169                                 count = (fs_offset_t)sizeof (ztk->input);
3170                         FILEDESC_SEEK (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
3171                         if (FILEDESC_READ (file->handle, ztk->input, count) != count)
3172                         {
3173                                 Con_Printf ("FS_Read: unexpected end of file\n");
3174                                 break;
3175                         }
3176
3177                         ztk->in_ind = 0;
3178                         ztk->in_len = count;
3179                         ztk->in_position += count;
3180                 }
3181
3182                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
3183                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
3184
3185                 // Now that we are sure we have compressed data available, we need to determine
3186                 // if it's better to inflate it in "file->buff" or directly in "buffer"
3187
3188                 // Inflate the data in "file->buff"
3189                 if (buffersize < sizeof (file->buff) / 2)
3190                 {
3191                         ztk->zstream.next_out = file->buff;
3192                         ztk->zstream.avail_out = sizeof (file->buff);
3193                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3194                         if (error != Z_OK && error != Z_STREAM_END)
3195                         {
3196                                 Con_Printf ("FS_Read: Can't inflate file\n");
3197                                 break;
3198                         }
3199                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3200
3201                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
3202                         file->position += file->buff_len;
3203
3204                         // Copy the requested data in "buffer" (as much as we can)
3205                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3206                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3207                         file->buff_ind = count;
3208                 }
3209
3210                 // Else, we inflate directly in "buffer"
3211                 else
3212                 {
3213                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
3214                         ztk->zstream.avail_out = (unsigned int)buffersize;
3215                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3216                         if (error != Z_OK && error != Z_STREAM_END)
3217                         {
3218                                 Con_Printf ("FS_Read: Can't inflate file\n");
3219                                 break;
3220                         }
3221                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3222
3223                         // How much data did it inflate?
3224                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
3225                         file->position += count;
3226
3227                         // Purge cached data
3228                         FS_Purge (file);
3229                 }
3230
3231                 done += count;
3232                 buffersize -= count;
3233         }
3234
3235         return done;
3236 }
3237
3238
3239 /*
3240 ====================
3241 FS_Print
3242
3243 Print a string into a file
3244 ====================
3245 */
3246 int FS_Print (qfile_t* file, const char *msg)
3247 {
3248         return (int)FS_Write (file, msg, strlen (msg));
3249 }
3250
3251 /*
3252 ====================
3253 FS_Printf
3254
3255 Print a string into a file
3256 ====================
3257 */
3258 int FS_Printf(qfile_t* file, const char* format, ...)
3259 {
3260         int result;
3261         va_list args;
3262
3263         va_start (args, format);
3264         result = FS_VPrintf (file, format, args);
3265         va_end (args);
3266
3267         return result;
3268 }
3269
3270
3271 /*
3272 ====================
3273 FS_VPrintf
3274
3275 Print a string into a file
3276 ====================
3277 */
3278 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
3279 {
3280         int len;
3281         fs_offset_t buff_size = MAX_INPUTLINE;
3282         char *tempbuff;
3283
3284         for (;;)
3285         {
3286                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
3287                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
3288                 if (len >= 0 && len < buff_size)
3289                         break;
3290                 Mem_Free (tempbuff);
3291                 buff_size *= 2;
3292         }
3293
3294         len = FILEDESC_WRITE (file->handle, tempbuff, len);
3295         Mem_Free (tempbuff);
3296
3297         return len;
3298 }
3299
3300
3301 /*
3302 ====================
3303 FS_Getc
3304
3305 Get the next character of a file
3306 ====================
3307 */
3308 int FS_Getc (qfile_t* file)
3309 {
3310         unsigned char c;
3311
3312         if (FS_Read (file, &c, 1) != 1)
3313                 return EOF;
3314
3315         return c;
3316 }
3317
3318
3319 /*
3320 ====================
3321 FS_UnGetc
3322
3323 Put a character back into the read buffer (only supports one character!)
3324 ====================
3325 */
3326 int FS_UnGetc (qfile_t* file, unsigned char c)
3327 {
3328         // If there's already a character waiting to be read
3329         if (file->ungetc != EOF)
3330                 return EOF;
3331
3332         file->ungetc = c;
3333         return c;
3334 }
3335
3336
3337 /*
3338 ====================
3339 FS_Seek
3340
3341 Move the position index in a file
3342 ====================
3343 */
3344 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3345 {
3346         ztoolkit_t *ztk;
3347         unsigned char* buffer;
3348         fs_offset_t buffersize;
3349
3350         // Compute the file offset
3351         switch (whence)
3352         {
3353                 case SEEK_CUR:
3354                         offset += file->position - file->buff_len + file->buff_ind;
3355                         break;
3356
3357                 case SEEK_SET:
3358                         break;
3359
3360                 case SEEK_END:
3361                         offset += file->real_length;
3362                         break;
3363
3364                 default:
3365                         return -1;
3366         }
3367         if (offset < 0 || offset > file->real_length)
3368                 return -1;
3369
3370         if(file->flags & QFILE_FLAG_DATA)
3371         {
3372                 file->position = offset;
3373                 return 0;
3374         }
3375
3376         // If we have the data in our read buffer, we don't need to actually seek
3377         if (file->position - file->buff_len <= offset && offset <= file->position)
3378         {
3379                 file->buff_ind = offset + file->buff_len - file->position;
3380                 return 0;
3381         }
3382
3383         // Purge cached data
3384         FS_Purge (file);
3385
3386         // Unpacked or uncompressed files can seek directly
3387         if (! (file->flags & QFILE_FLAG_DEFLATED))
3388         {
3389                 if (FILEDESC_SEEK (file->handle, file->offset + offset, SEEK_SET) == -1)
3390                         return -1;
3391                 file->position = offset;
3392                 return 0;
3393         }
3394
3395         // Seeking in compressed files is more a hack than anything else,
3396         // but we need to support it, so here we go.
3397         ztk = file->ztk;
3398
3399         // If we have to go back in the file, we need to restart from the beginning
3400         if (offset <= file->position)
3401         {
3402                 ztk->in_ind = 0;
3403                 ztk->in_len = 0;
3404                 ztk->in_position = 0;
3405                 file->position = 0;
3406                 if (FILEDESC_SEEK (file->handle, file->offset, SEEK_SET) == -1)
3407                         Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n");
3408
3409                 // Reset the Zlib stream
3410                 ztk->zstream.next_in = ztk->input;
3411                 ztk->zstream.avail_in = 0;
3412                 qz_inflateReset (&ztk->zstream);
3413         }
3414
3415         // We need a big buffer to force inflating into it directly
3416         buffersize = 2 * sizeof (file->buff);
3417         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3418
3419         // Skip all data until we reach the requested offset
3420         while (offset > (file->position - file->buff_len + file->buff_ind))
3421         {
3422                 fs_offset_t diff = offset - (file->position - file->buff_len + file->buff_ind);
3423                 fs_offset_t count, len;
3424
3425                 count = (diff > buffersize) ? buffersize : diff;
3426                 len = FS_Read (file, buffer, count);
3427                 if (len != count)
3428                 {
3429                         Mem_Free (buffer);
3430                         return -1;
3431                 }
3432         }
3433
3434         Mem_Free (buffer);
3435         return 0;
3436 }
3437
3438
3439 /*
3440 ====================
3441 FS_Tell
3442
3443 Give the current position in a file
3444 ====================
3445 */
3446 fs_offset_t FS_Tell (qfile_t* file)
3447 {
3448         return file->position - file->buff_len + file->buff_ind;
3449 }
3450
3451
3452 /*
3453 ====================
3454 FS_FileSize
3455
3456 Give the total size of a file
3457 ====================
3458 */
3459 fs_offset_t FS_FileSize (qfile_t* file)
3460 {
3461         return file->real_length;
3462 }
3463
3464
3465 /*
3466 ====================
3467 FS_Purge
3468
3469 Erases any buffered input or output data
3470 ====================
3471 */
3472 void FS_Purge (qfile_t* file)
3473 {
3474         file->buff_len = 0;
3475         file->buff_ind = 0;
3476         file->ungetc = EOF;
3477 }
3478
3479
3480 /*
3481 ============
3482 FS_LoadAndCloseQFile
3483
3484 Loads full content of a qfile_t and closes it.
3485 Always appends a 0 byte.
3486 ============
3487 */
3488 static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3489 {
3490         unsigned char *buf = NULL;
3491         fs_offset_t filesize = 0;
3492
3493         if (file)
3494         {
3495                 filesize = file->real_length;
3496                 if(filesize < 0)
3497                 {
3498                         Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3499                         FS_Close(file);
3500                         return NULL;
3501                 }
3502
3503                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3504                 buf[filesize] = '\0';
3505                 FS_Read (file, buf, filesize);
3506                 FS_Close (file);
3507                 if (developer_loadfile.integer)
3508                         Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3509         }
3510
3511         if (filesizepointer)
3512                 *filesizepointer = filesize;
3513         return buf;
3514 }
3515
3516
3517 /*
3518 ============
3519 FS_LoadFile
3520
3521 Filename are relative to the quake directory.
3522 Always appends a 0 byte.
3523 ============
3524 */
3525 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3526 {
3527         qfile_t *file = FS_OpenVirtualFile(path, quiet);
3528         return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3529 }
3530
3531
3532 /*
3533 ============
3534 FS_SysLoadFile
3535
3536 Filename are OS paths.
3537 Always appends a 0 byte.
3538 ============
3539 */
3540 unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3541 {
3542         qfile_t *file = FS_SysOpen(path, "rb", false);
3543         return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3544 }
3545
3546
3547 /*
3548 ============
3549 FS_WriteFile
3550
3551 The filename will be prefixed by the current game directory
3552 ============
3553 */
3554 qbool FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3555 {
3556         qfile_t *file;
3557         size_t i;
3558         fs_offset_t lentotal;
3559
3560         file = FS_OpenRealFile(filename, "wb", false);
3561         if (!file)
3562         {
3563                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3564                 return false;
3565         }
3566
3567         lentotal = 0;
3568         for(i = 0; i < count; ++i)
3569                 lentotal += len[i];
3570         Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3571         for(i = 0; i < count; ++i)
3572                 FS_Write (file, data[i], len[i]);
3573         FS_Close (file);
3574         return true;
3575 }
3576
3577 qbool FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3578 {
3579         return FS_WriteFileInBlocks(filename, &data, &len, 1);
3580 }
3581
3582
3583 /*
3584 =============================================================================
3585
3586 OTHERS PUBLIC FUNCTIONS
3587
3588 =============================================================================
3589 */
3590
3591 /*
3592 ============
3593 FS_StripExtension
3594 ============
3595 */
3596 void FS_StripExtension (const char *in, char *out, size_t size_out)
3597 {
3598         char *last = NULL;
3599         char currentchar;
3600
3601         if (size_out == 0)
3602                 return;
3603
3604         while ((currentchar = *in) && size_out > 1)
3605         {
3606                 if (currentchar == '.')
3607                         last = out;
3608                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3609                         last = NULL;
3610                 *out++ = currentchar;
3611                 in++;
3612                 size_out--;
3613         }
3614         if (last)
3615                 *last = 0;
3616         else
3617                 *out = 0;
3618 }
3619
3620
3621 /*
3622 ==================
3623 FS_DefaultExtension
3624 ==================
3625 */
3626 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3627 {
3628         const char *src;
3629
3630         // if path doesn't have a .EXT, append extension
3631         // (extension should include the .)
3632         src = path + strlen(path);
3633
3634         while (*src != '/' && src != path)
3635         {
3636                 if (*src == '.')
3637                         return;                 // it has an extension
3638                 src--;
3639         }
3640
3641         dp_strlcat (path, extension, size_path);
3642 }
3643
3644
3645 /*
3646 ==================
3647 FS_FileType
3648
3649 Look for a file in the packages and in the filesystem
3650 ==================
3651 */
3652 int FS_FileType (const char *filename)
3653 {
3654         searchpath_t *search;
3655         char fullpath[MAX_OSPATH];
3656
3657         search = FS_FindFile (filename, NULL, NULL, true);
3658         if(!search)
3659                 return FS_FILETYPE_NONE;
3660
3661         if(search->pack && !search->pack->vpack)
3662                 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3663
3664         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3665         return FS_SysFileType(fullpath);
3666 }
3667
3668
3669 /*
3670 ==================
3671 FS_FileExists
3672
3673 Look for a file in the packages and in the filesystem
3674 Returns its canonical name (VFS path with correct capitalisation) if found, else NULL.
3675 If the file is found outside a pak, this will be the same pointer as passed in.
3676 ==================
3677 */
3678 const char *FS_FileExists (const char *filename)
3679 {
3680         const char *canonicalname;
3681
3682         return FS_FindFile(filename, NULL, &canonicalname, true) ? canonicalname : NULL;
3683 }
3684
3685
3686 /*
3687 ==================
3688 FS_SysFileExists
3689
3690 Look for a file in the filesystem only
3691 ==================
3692 */
3693 int FS_SysFileType (const char *path)
3694 {
3695 #if WIN32
3696 // Sajt - some older sdks are missing this define
3697 # ifndef INVALID_FILE_ATTRIBUTES
3698 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3699 # endif
3700         wchar pathw[WSTRBUF] = {0};
3701         DWORD result;
3702         widen(path, pathw);
3703         result = GetFileAttributesW(pathw);
3704
3705         if(result == INVALID_FILE_ATTRIBUTES)
3706                 return FS_FILETYPE_NONE;
3707
3708         if(result & FILE_ATTRIBUTE_DIRECTORY)
3709                 return FS_FILETYPE_DIRECTORY;
3710
3711         return FS_FILETYPE_FILE;
3712 #else
3713         struct stat buf;
3714
3715         if (stat (path,&buf) == -1)
3716                 return FS_FILETYPE_NONE;
3717
3718 #ifndef S_ISDIR
3719 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3720 #endif
3721         if(S_ISDIR(buf.st_mode))
3722                 return FS_FILETYPE_DIRECTORY;
3723
3724         return FS_FILETYPE_FILE;
3725 #endif
3726 }
3727
3728 qbool FS_SysFileExists (const char *path)
3729 {
3730         return FS_SysFileType (path) != FS_FILETYPE_NONE;
3731 }
3732
3733 /*
3734 ===========
3735 FS_Search
3736
3737 Allocate and fill a search structure with information on matching filenames.
3738 ===========
3739 */
3740 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet, const char *packfile)
3741 {
3742         fssearch_t *search;
3743         searchpath_t *searchpath;
3744         pack_t *pak;
3745         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3746         stringlist_t resultlist;
3747         stringlist_t dirlist;
3748         stringlist_t matchedSet, foundSet;
3749         const char *start, *slash, *backslash, *colon, *separator;
3750         char *basepath;
3751
3752         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3753                 ;
3754
3755         if (i > 0)
3756         {
3757                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3758                 return NULL;
3759         }
3760
3761         stringlistinit(&resultlist);
3762         stringlistinit(&dirlist);
3763         search = NULL;
3764         slash = strrchr(pattern, '/');
3765         backslash = strrchr(pattern, '\\');
3766         colon = strrchr(pattern, ':');
3767         separator = max(slash, backslash);
3768         separator = max(separator, colon);
3769         basepathlength = separator ? (separator + 1 - pattern) : 0;
3770         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3771         if (basepathlength)
3772                 memcpy(basepath, pattern, basepathlength);
3773         basepath[basepathlength] = 0;
3774
3775         // search through the path, one element at a time
3776         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3777         {
3778                 // is the element a pak file?
3779                 if (searchpath->pack && !searchpath->pack->vpack)
3780                 {
3781                         // look through all the pak file elements
3782                         pak = searchpath->pack;
3783                         if(packfile)
3784                         {
3785                                 if(strcmp(packfile, pak->shortname))
3786                                         continue;
3787                         }
3788                         for (i = 0;i < pak->numfiles;i++)
3789                         {
3790                                 char temp[MAX_OSPATH];
3791                                 dp_strlcpy(temp, pak->files[i].name, sizeof(temp));
3792                                 while (temp[0])
3793                                 {
3794                                         if (matchpattern(temp, (char *)pattern, true))
3795                                         {
3796                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3797                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
3798                                                                 break;
3799                                                 if (resultlistindex == resultlist.numstrings)
3800                                                 {
3801                                                         stringlistappend(&resultlist, temp);
3802                                                         if (!quiet && developer_loading.integer)
3803                                                                 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3804                                                 }
3805                                         }
3806                                         // strip off one path element at a time until empty
3807                                         // this way directories are added to the listing if they match the pattern
3808                                         slash = strrchr(temp, '/');
3809                                         backslash = strrchr(temp, '\\');
3810                                         colon = strrchr(temp, ':');
3811                                         separator = temp;
3812                                         if (separator < slash)
3813                                                 separator = slash;
3814                                         if (separator < backslash)
3815                                                 separator = backslash;
3816                                         if (separator < colon)
3817                                                 separator = colon;
3818                                         *((char *)separator) = 0;
3819                                 }
3820                         }
3821                 }
3822                 else
3823                 {
3824                         if(packfile)
3825                                 continue;
3826
3827                         start = pattern;
3828
3829                         stringlistinit(&matchedSet);
3830                         stringlistinit(&foundSet);
3831                         // add a first entry to the set
3832                         stringlistappend(&matchedSet, "");
3833                         // iterate through pattern's path
3834                         while (*start)
3835                         {
3836                                 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3837                                 char subpath[MAX_OSPATH];
3838                                 char subpattern[MAX_OSPATH];
3839
3840                                 // find the next wildcard
3841                                 wildcard = strchr(start, '?');
3842                                 asterisk = strchr(start, '*');
3843                                 if (asterisk && (!wildcard || asterisk < wildcard))
3844                                 {
3845                                         wildcard = asterisk;
3846                                 }
3847
3848                                 if (wildcard)
3849                                 {
3850                                         nextseparator = strchr( wildcard, '/' );
3851                                 }
3852                                 else
3853                                 {
3854                                         nextseparator = NULL;
3855                                 }
3856
3857                                 if( !nextseparator ) {
3858                                         nextseparator = start + strlen( start );
3859                                 }
3860
3861                                 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3862                                 // copy everything up except nextseperator
3863                                 dp_ustr2stp(subpattern, sizeof(subpattern), pattern, nextseparator - pattern);
3864                                 // find the last '/' before the wildcard
3865                                 prevseparator = strrchr( subpattern, '/' );
3866                                 if (!prevseparator)
3867                                         prevseparator = subpattern;
3868                                 else
3869                                         prevseparator++;
3870                                 // copy everything from start to the previous including the '/' (before the wildcard)
3871                                 // everything up to start is already included in the path of matchedSet's entries
3872                                 dp_ustr2stp(subpath, sizeof(subpath), start, (prevseparator - subpattern) - (start - pattern));
3873
3874                                 // for each entry in matchedSet try to open the subdirectories specified in subpath
3875                                 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3876                                         char temp[MAX_OSPATH];
3877                                         dp_strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3878                                         dp_strlcat( temp, subpath, sizeof(temp) );
3879                                         listdirectory( &foundSet, searchpath->filename, temp );
3880                                 }
3881                                 if( dirlistindex == 0 ) {
3882                                         break;
3883                                 }
3884                                 // reset the current result set
3885                                 stringlistfreecontents( &matchedSet );
3886                                 // match against the pattern
3887                                 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3888                                         const char *direntry = foundSet.strings[ dirlistindex ];
3889                                         if (matchpattern(direntry, subpattern, true)) {
3890                                                 stringlistappend( &matchedSet, direntry );
3891                                         }
3892                                 }
3893                                 stringlistfreecontents( &foundSet );
3894
3895                                 start = nextseparator;
3896                         }
3897
3898                         for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3899                         {
3900                                 const char *matchtemp = matchedSet.strings[dirlistindex];
3901                                 if (matchpattern(matchtemp, (char *)pattern, true))
3902                                 {
3903                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3904                                                 if (!strcmp(resultlist.strings[resultlistindex], matchtemp))
3905                                                         break;
3906                                         if (resultlistindex == resultlist.numstrings)
3907                                         {
3908                                                 stringlistappend(&resultlist, matchtemp);
3909                                                 if (!quiet && developer_loading.integer)
3910                                                         Con_Printf("SearchDirFile: %s\n", matchtemp);
3911                                         }
3912                                 }
3913                         }
3914                         stringlistfreecontents( &matchedSet );
3915                 }
3916         }
3917
3918         if (resultlist.numstrings)
3919         {
3920                 stringlistsort(&resultlist, true);
3921                 numfiles = resultlist.numstrings;
3922                 numchars = 0;
3923                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3924                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3925                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3926                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3927                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3928                 search->numfilenames = (int)numfiles;
3929                 numfiles = 0;
3930                 numchars = 0;
3931                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3932                 {
3933                         size_t textlen;
3934                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
3935                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3936                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3937                         numfiles++;
3938                         numchars += (int)textlen;
3939                 }
3940         }
3941         stringlistfreecontents(&resultlist);
3942
3943         Mem_Free(basepath);
3944         return search;
3945 }
3946
3947 void FS_FreeSearch(fssearch_t *search)
3948 {
3949         Z_Free(search);
3950 }
3951
3952 extern int con_linewidth;
3953 static int FS_ListDirectory(const char *pattern, int oneperline)
3954 {
3955         int numfiles;
3956         int numcolumns;
3957         int numlines;
3958         int columnwidth;
3959         int linebufpos;
3960         int i, j, k, l;
3961         const char *name;
3962         char linebuf[MAX_INPUTLINE];
3963         fssearch_t *search;
3964         search = FS_Search(pattern, true, true, NULL);
3965         if (!search)
3966                 return 0;
3967         numfiles = search->numfilenames;
3968         if (!oneperline)
3969         {
3970                 // FIXME: the names could be added to one column list and then
3971                 // gradually shifted into the next column if they fit, and then the
3972                 // next to make a compact variable width listing but it's a lot more
3973                 // complicated...
3974                 // find width for columns
3975                 columnwidth = 0;
3976                 for (i = 0;i < numfiles;i++)
3977                 {
3978                         l = (int)strlen(search->filenames[i]);
3979                         if (columnwidth < l)
3980                                 columnwidth = l;
3981                 }
3982                 // count the spacing character
3983                 columnwidth++;
3984                 // calculate number of columns
3985                 numcolumns = con_linewidth / columnwidth;
3986                 // don't bother with the column printing if it's only one column
3987                 if (numcolumns >= 2)
3988                 {
3989                         numlines = (numfiles + numcolumns - 1) / numcolumns;
3990                         for (i = 0;i < numlines;i++)
3991                         {
3992                                 linebufpos = 0;
3993                                 for (k = 0;k < numcolumns;k++)
3994                                 {
3995                                         l = i * numcolumns + k;
3996                                         if (l < numfiles)
3997                                         {
3998                                                 name = search->filenames[l];
3999                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
4000                                                         linebuf[linebufpos++] = name[j];
4001                                                 // space out name unless it's the last on the line
4002                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
4003                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
4004                                                                 linebuf[linebufpos++] = ' ';
4005                                         }
4006                                 }
4007                                 linebuf[linebufpos] = 0;
4008                                 Con_Printf("%s\n", linebuf);
4009                         }
4010                 }
4011                 else
4012                         oneperline = true;
4013         }
4014         if (oneperline)
4015                 for (i = 0;i < numfiles;i++)
4016                         Con_Printf("%s\n", search->filenames[i]);
4017         FS_FreeSearch(search);
4018         return (int)numfiles;
4019 }
4020
4021 static void FS_ListDirectoryCmd (cmd_state_t *cmd, const char* cmdname, int oneperline)
4022 {
4023         const char *pattern;
4024         if (Cmd_Argc(cmd) >= 3)
4025         {
4026                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
4027                 return;
4028         }
4029         if (Cmd_Argc(cmd) == 2)
4030                 pattern = Cmd_Argv(cmd, 1);
4031         else
4032                 pattern = "*";
4033         if (!FS_ListDirectory(pattern, oneperline))
4034                 Con_Print("No files found.\n");
4035 }
4036
4037 void FS_Dir_f(cmd_state_t *cmd)
4038 {
4039         FS_ListDirectoryCmd(cmd, "dir", true);
4040 }
4041
4042 void FS_Ls_f(cmd_state_t *cmd)
4043 {
4044         FS_ListDirectoryCmd(cmd, "ls", false);
4045 }
4046
4047 void FS_Which_f(cmd_state_t *cmd)
4048 {
4049         const char *filename;
4050         int index;
4051         searchpath_t *sp;
4052         if (Cmd_Argc(cmd) != 2)
4053         {
4054                 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(cmd, 0));
4055                 return;
4056         }
4057         filename = Cmd_Argv(cmd, 1);
4058         sp = FS_FindFile(filename, &index, NULL, true);
4059         if (!sp) {
4060                 Con_Printf("%s isn't anywhere\n", filename);
4061                 return;
4062         }
4063         if (sp->pack)
4064         {
4065                 if(sp->pack->vpack)
4066                         Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
4067                 else
4068                         Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
4069         }
4070         else
4071                 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
4072 }
4073
4074
4075 const char *FS_WhichPack(const char *filename)
4076 {
4077         int index;
4078         searchpath_t *sp = FS_FindFile(filename, &index, NULL, true);
4079         if(sp && sp->pack)
4080                 return sp->pack->shortname;
4081         else if(sp)
4082                 return "";
4083         else
4084                 return 0;
4085 }
4086
4087 /*
4088 ====================
4089 FS_IsRegisteredQuakePack
4090
4091 Look for a proof of purchase file file in the requested package
4092
4093 If it is found, this file should NOT be downloaded.
4094 ====================
4095 */
4096 qbool FS_IsRegisteredQuakePack(const char *name)
4097 {
4098         searchpath_t *search;
4099         pack_t *pak;
4100
4101         // search through the path, one element at a time
4102         for (search = fs_searchpaths;search;search = search->next)
4103         {
4104                 if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
4105                         // TODO do we want to support vpacks in here too?
4106                 {
4107                         int (*strcmp_funct) (const char* str1, const char* str2);
4108                         int left, right, middle;
4109
4110                         pak = search->pack;
4111                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
4112
4113                         // Look for the file (binary search)
4114                         left = 0;
4115                         right = pak->numfiles - 1;
4116                         while (left <= right)
4117                         {
4118                                 int diff;
4119
4120                                 middle = (left + right) / 2;
4121                                 diff = strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
4122
4123                                 // Found it
4124                                 if (!diff)
4125                                         return true;
4126
4127                                 // If we're too far in the list
4128                                 if (diff > 0)
4129                                         right = middle - 1;
4130                                 else
4131                                         left = middle + 1;
4132                         }
4133
4134                         // we found the requested pack but it is not registered quake
4135                         return false;
4136                 }
4137         }
4138
4139         return false;
4140 }
4141
4142 int FS_CRCFile(const char *filename, size_t *filesizepointer)
4143 {
4144         int crc = -1;
4145         unsigned char *filedata;
4146         fs_offset_t filesize;
4147         if (filesizepointer)
4148                 *filesizepointer = 0;
4149         if (!filename || !*filename)
4150                 return crc;
4151         filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
4152         if (filedata)
4153         {
4154                 if (filesizepointer)
4155                         *filesizepointer = filesize;
4156                 crc = CRC_Block(filedata, filesize);
4157                 Mem_Free(filedata);
4158         }
4159         return crc;
4160 }
4161
4162 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
4163 {
4164         z_stream strm;
4165         unsigned char *out = NULL;
4166         unsigned char *tmp;
4167
4168         *deflated_size = 0;
4169 #ifndef LINK_TO_ZLIB
4170         if(!zlib_dll)
4171                 return NULL;
4172 #endif
4173
4174         memset(&strm, 0, sizeof(strm));
4175         strm.zalloc = Z_NULL;
4176         strm.zfree = Z_NULL;
4177         strm.opaque = Z_NULL;
4178
4179         if(level < 0)
4180                 level = Z_DEFAULT_COMPRESSION;
4181
4182         if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
4183         {
4184                 Con_Printf("FS_Deflate: deflate init error!\n");
4185                 return NULL;
4186         }
4187
4188         strm.next_in = (unsigned char*)data;
4189         strm.avail_in = (unsigned int)size;
4190
4191         tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
4192         if(!tmp)
4193         {
4194                 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
4195                 qz_deflateEnd(&strm);
4196                 return NULL;
4197         }
4198
4199         strm.next_out = tmp;
4200         strm.avail_out = (unsigned int)size;
4201
4202         if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
4203         {
4204                 Con_Printf("FS_Deflate: deflate failed!\n");
4205                 qz_deflateEnd(&strm);
4206                 Mem_Free(tmp);
4207                 return NULL;
4208         }
4209
4210         if(qz_deflateEnd(&strm) != Z_OK)
4211         {
4212                 Con_Printf("FS_Deflate: deflateEnd failed\n");
4213                 Mem_Free(tmp);
4214                 return NULL;
4215         }
4216
4217         if(strm.total_out >= size)
4218         {
4219                 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
4220                 Mem_Free(tmp);
4221                 return NULL;
4222         }
4223
4224         out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
4225         if(!out)
4226         {
4227                 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
4228                 Mem_Free(tmp);
4229                 return NULL;
4230         }
4231
4232         *deflated_size = (size_t)strm.total_out;
4233
4234         memcpy(out, tmp, strm.total_out);
4235         Mem_Free(tmp);
4236
4237         return out;
4238 }
4239
4240 static void AssertBufsize(sizebuf_t *buf, int length)
4241 {
4242         if(buf->cursize + length > buf->maxsize)
4243         {
4244                 int oldsize = buf->maxsize;
4245                 unsigned char *olddata;
4246                 olddata = buf->data;
4247                 buf->maxsize += length;
4248                 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
4249                 if(olddata)
4250                 {
4251                         memcpy(buf->data, olddata, oldsize);
4252                         Mem_Free(olddata);
4253                 }
4254         }
4255 }
4256
4257 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
4258 {
4259         int ret;
4260         z_stream strm;
4261         unsigned char *out = NULL;
4262         unsigned char tmp[2048];
4263         unsigned int have;
4264         sizebuf_t outbuf;
4265
4266         *inflated_size = 0;
4267 #ifndef LINK_TO_ZLIB
4268         if(!zlib_dll)
4269                 return NULL;
4270 #endif
4271
4272         memset(&outbuf, 0, sizeof(outbuf));
4273         outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
4274         outbuf.maxsize = sizeof(tmp);
4275
4276         memset(&strm, 0, sizeof(strm));
4277         strm.zalloc = Z_NULL;
4278         strm.zfree = Z_NULL;
4279         strm.opaque = Z_NULL;
4280
4281         if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
4282         {
4283                 Con_Printf("FS_Inflate: inflate init error!\n");
4284                 Mem_Free(outbuf.data);
4285                 return NULL;
4286         }
4287
4288         strm.next_in = (unsigned char*)data;
4289         strm.avail_in = (unsigned int)size;
4290
4291         do
4292         {
4293                 strm.next_out = tmp;
4294                 strm.avail_out = sizeof(tmp);
4295                 ret = qz_inflate(&strm, Z_NO_FLUSH);
4296                 // it either returns Z_OK on progress, Z_STREAM_END on end
4297                 // or an error code
4298                 switch(ret)
4299                 {
4300                         case Z_STREAM_END:
4301                         case Z_OK:
4302                                 break;
4303
4304                         case Z_STREAM_ERROR:
4305                                 Con_Print("FS_Inflate: stream error!\n");
4306                                 break;
4307                         case Z_DATA_ERROR:
4308                                 Con_Print("FS_Inflate: data error!\n");
4309                                 break;
4310                         case Z_MEM_ERROR:
4311                                 Con_Print("FS_Inflate: mem error!\n");
4312                                 break;
4313                         case Z_BUF_ERROR:
4314                                 Con_Print("FS_Inflate: buf error!\n");
4315                                 break;
4316                         default:
4317                                 Con_Print("FS_Inflate: unknown error!\n");
4318                                 break;
4319
4320                 }
4321                 if(ret != Z_OK && ret != Z_STREAM_END)
4322                 {
4323                         Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
4324                         Mem_Free(outbuf.data);
4325                         qz_inflateEnd(&strm);
4326                         return NULL;
4327                 }
4328                 have = sizeof(tmp) - strm.avail_out;
4329                 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
4330                 SZ_Write(&outbuf, tmp, have);
4331         } while(ret != Z_STREAM_END);
4332
4333         qz_inflateEnd(&strm);
4334
4335         out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4336         if(!out)
4337         {
4338                 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4339                 Mem_Free(outbuf.data);
4340                 return NULL;
4341         }
4342
4343         memcpy(out, outbuf.data, outbuf.cursize);
4344         Mem_Free(outbuf.data);
4345
4346         *inflated_size = (size_t)outbuf.cursize;
4347
4348         return out;
4349 }