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