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