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