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