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