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