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