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