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