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