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