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