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