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