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