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