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