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