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