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