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