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