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