]> icculus.org git repositories - divverent/darkplaces.git/blob - fs.c
Several fixes related to files opened in write or append mode, and to the ungetc...
[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         // Add the personal game directory
864         homedir = getenv ("HOME");
865         if (homedir != NULL && homedir[0] != '\0')
866                 FS_AddGameDirectory (va("%s/.%s/%s", homedir, gameuserdirname, dir));
867 }
868
869
870 /*
871 ============
872 FS_FileExtension
873 ============
874 */
875 static const char *FS_FileExtension (const char *in)
876 {
877         const char *separator, *backslash, *colon, *dot;
878
879         separator = strrchr(in, '/');
880         backslash = strrchr(in, '\\');
881         if (separator < backslash)
882                 separator = backslash;
883         colon = strrchr(in, ':');
884         if (separator < colon)
885                 separator = colon;
886
887         dot = strrchr(in, '.');
888         if (dot == NULL || dot < separator)
889                 return "";
890
891         return dot + 1;
892 }
893
894
895 /*
896 ================
897 FS_Init
898 ================
899 */
900 void FS_Init (void)
901 {
902         int i;
903         searchpath_t *search;
904
905         fs_mempool = Mem_AllocPool("file management", 0, NULL);
906         pak_mempool = Mem_AllocPool("paks", 0, NULL);
907
908         Cvar_RegisterVariable (&scr_screenshot_name);
909
910         Cmd_AddCommand ("path", FS_Path_f);
911         Cmd_AddCommand ("dir", FS_Dir_f);
912         Cmd_AddCommand ("ls", FS_Ls_f);
913
914         strcpy(fs_basedir, ".");
915         strcpy(fs_gamedir, ".");
916
917         PK3_OpenLibrary ();
918
919         // -basedir <path>
920         // Overrides the system supplied base directory (under GAMENAME)
921 // 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)
922         i = COM_CheckParm ("-basedir");
923         if (i && i < com_argc-1)
924         {
925                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
926                 i = strlen (fs_basedir);
927                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
928                         fs_basedir[i-1] = 0;
929         }
930
931         // -path <dir or packfile> [<dir or packfile>] ...
932         // Fully specifies the exact search path, overriding the generated one
933 // 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)
934         i = COM_CheckParm ("-path");
935         if (i)
936         {
937                 fs_modified = true;
938                 while (++i < com_argc)
939                 {
940                         if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
941                                 break;
942
943                         search = Mem_Alloc(pak_mempool, sizeof(searchpath_t));
944                         if (!strcasecmp (FS_FileExtension(com_argv[i]), "pak"))
945                         {
946                                 search->pack = FS_LoadPackPAK (com_argv[i]);
947                                 if (!search->pack)
948                                         Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
949                         }
950                         else if (!strcasecmp (FS_FileExtension (com_argv[i]), "pk3"))
951                         {
952                                 search->pack = FS_LoadPackPK3 (com_argv[i]);
953                                 if (!search->pack)
954                                         Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
955                         }
956                         else
957                                 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
958                         search->next = fs_searchpaths;
959                         fs_searchpaths = search;
960                 }
961                 return;
962         }
963
964         // add the game-specific paths
965         // gamedirname1 (typically id1)
966         FS_AddGameHierarchy (gamedirname1);
967         Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
968
969         // add the game-specific path, if any
970         if (gamedirname2)
971         {
972                 fs_modified = true;
973                 FS_AddGameHierarchy (gamedirname2);
974         }
975
976         // -game <gamedir>
977         // Adds basedir/gamedir as an override game
978         // LordHavoc: now supports multiple -game directories
979         for (i = 1;i < com_argc;i++)
980         {
981                 if (!com_argv[i])
982                         continue;
983                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
984                 {
985                         i++;
986                         fs_modified = true;
987                         FS_AddGameHierarchy (com_argv[i]);
988                         Cvar_SetQuick (&scr_screenshot_name, com_modname);
989                 }
990         }
991
992         // If "-condebug" is in the command line, remove the previous log file
993         if (COM_CheckParm ("-condebug") != 0)
994                 unlink (va("%s/qconsole.log", fs_gamedir));
995 }
996
997 /*
998 ================
999 FS_Shutdown
1000 ================
1001 */
1002 void FS_Shutdown (void)
1003 {
1004         Mem_FreePool (&pak_mempool);
1005         Mem_FreePool (&fs_mempool);
1006 }
1007
1008 /*
1009 ====================
1010 FS_SysOpen
1011
1012 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1013 ====================
1014 */
1015 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1016 {
1017         qfile_t* file;
1018         int mod, opt;
1019         unsigned int ind;
1020
1021         // Parse the mode string
1022         switch (mode[0])
1023         {
1024                 case 'r':
1025                         mod = O_RDONLY;
1026                         opt = 0;
1027                         break;
1028                 case 'w':
1029                         mod = O_WRONLY;
1030                         opt = O_CREAT | O_TRUNC;
1031                         break;
1032                 case 'a':
1033                         mod = O_WRONLY;
1034                         opt = O_CREAT | O_APPEND;
1035                         break;
1036                 default:
1037                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1038                         return NULL;
1039         }
1040         for (ind = 1; mode[ind] != '\0'; ind++)
1041         {
1042                 switch (mode[ind])
1043                 {
1044                         case '+':
1045                                 mod = O_RDWR;
1046                                 break;
1047                         case 'b':
1048                                 opt |= O_BINARY;
1049                                 break;
1050                         default:
1051                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1052                                                         filepath, mode, mode[ind]);
1053                 }
1054         }
1055
1056 #ifndef WIN32
1057         if (nonblocking)
1058                 opt |= O_NONBLOCK;
1059 #endif
1060
1061         file = Mem_Alloc (fs_mempool, sizeof (*file));
1062         memset (file, 0, sizeof (*file));
1063         file->ungetc = EOF;
1064
1065         file->handle = open (filepath, mod | opt, 0666);
1066         if (file->handle < 0)
1067         {
1068                 Mem_Free (file);
1069                 return NULL;
1070         }
1071
1072         file->real_length = lseek (file->handle, 0, SEEK_END);
1073
1074         // For files opened in append mode, we start at the end of the file
1075         if (mod & O_APPEND)
1076                 file->position = file->real_length;
1077         else
1078                 lseek (file->handle, 0, SEEK_SET);
1079
1080         return file;
1081 }
1082
1083
1084 /*
1085 ===========
1086 FS_OpenPackedFile
1087
1088 Open a packed file using its package file descriptor
1089 ===========
1090 */
1091 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1092 {
1093         packfile_t *pfile;
1094         int dup_handle;
1095         qfile_t* file;
1096
1097         pfile = &pack->files[pack_ind];
1098
1099         // If we don't have the true offset, get it now
1100         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1101                 PK3_GetTrueFileOffset (pfile, pack);
1102
1103         // No Zlib DLL = no compressed files
1104         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1105         {
1106                 Con_Printf("WARNING: can't open the compressed file %s\n"
1107                                         "You need the Zlib DLL to use compressed files\n",
1108                                         pfile->name);
1109                 fs_filesize = -1;
1110                 return NULL;
1111         }
1112
1113         dup_handle = dup (pack->handle);
1114         if (dup_handle < 0)
1115                 Sys_Error ("FS_OpenPackedFile: can't dup package's handle (pack: %s)", pack->filename);
1116
1117         file = Mem_Alloc (fs_mempool, sizeof (*file));
1118         memset (file, 0, sizeof (*file));
1119         file->handle = dup_handle;
1120         file->flags = QFILE_FLAG_PACKED;
1121         file->real_length = pfile->realsize;
1122         file->offset = pfile->offset;
1123         file->position = 0;
1124         file->ungetc = EOF;
1125
1126         if (lseek (file->handle, file->offset, SEEK_SET) == -1)
1127                 Sys_Error ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)",
1128                                         pfile->name, pack->filename, file->offset);
1129
1130         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1131         {
1132                 ztoolkit_t *ztk;
1133
1134                 file->flags |= QFILE_FLAG_DEFLATED;
1135
1136                 // We need some more variables
1137                 ztk = Mem_Alloc (fs_mempool, sizeof (*ztk));
1138
1139                 ztk->comp_length = pfile->packsize;
1140
1141                 // Initialize zlib stream
1142                 ztk->zstream.next_in = ztk->input;
1143                 ztk->zstream.avail_in = 0;
1144
1145                 /* From Zlib's "unzip.c":
1146                  *
1147                  * windowBits is passed < 0 to tell that there is no zlib header.
1148                  * Note that in this case inflate *requires* an extra "dummy" byte
1149                  * after the compressed stream in order to complete decompression and
1150                  * return Z_STREAM_END.
1151                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1152                  * size of both compressed and uncompressed data
1153                  */
1154                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1155                         Sys_Error ("FS_OpenPackedFile: inflate init error (file: %s)", pfile->name);
1156
1157                 ztk->zstream.next_out = file->buff;
1158                 ztk->zstream.avail_out = sizeof (file->buff);
1159
1160                 file->ztk = ztk;
1161         }
1162
1163         fs_filesize = pfile->realsize;
1164
1165         return file;
1166 }
1167
1168 /*
1169 ====================
1170 FS_CheckNastyPath
1171
1172 Return true if the path should be rejected due to one of the following:
1173 1: path elements that are non-portable
1174 2: path elements that would allow access to files outside the game directory,
1175    or are just not a good idea for a mod to be using.
1176 ====================
1177 */
1178 int FS_CheckNastyPath (const char *path)
1179 {
1180         // Windows: don't allow \ in filenames (windows-only), period.
1181         // (on Windows \ is a directory separator, but / is also supported)
1182         if (strstr(path, "\\"))
1183                 return 1; // non-portable
1184
1185         // Mac: don't allow Mac-only filenames - : is a directory separator
1186         // instead of /, but we rely on / working already, so there's no reason to
1187         // support a Mac-only path
1188         // Amiga and Windows: : tries to go to root of drive
1189         if (strstr(path, ":"))
1190                 return 1; // non-portable attempt to go to root of drive
1191
1192         // Amiga: // is parent directory
1193         if (strstr(path, "//"))
1194                 return 1; // non-portable attempt to go to parent directory
1195
1196         // all: don't allow going to current directory (./) or parent directory (../ or /../)
1197         if (strstr(path, "./"))
1198                 return 2; // attempt to go outside the game directory
1199
1200         // Windows and UNIXes: don't allow absolute paths
1201         if (path[0] == '/')
1202                 return 2; // attempt to go outside the game directory
1203
1204         // after all these checks we're pretty sure it's a / separated filename
1205         // and won't do much if any harm
1206         return false;
1207 }
1208
1209
1210 /*
1211 ====================
1212 FS_FindFile
1213
1214 Look for a file in the packages and in the filesystem
1215
1216 Return the searchpath where the file was found (or NULL)
1217 and the file index in the package if relevant
1218 ====================
1219 */
1220 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1221 {
1222         searchpath_t *search;
1223         pack_t *pak;
1224
1225         // search through the path, one element at a time
1226         for (search = fs_searchpaths;search;search = search->next)
1227         {
1228                 // is the element a pak file?
1229                 if (search->pack)
1230                 {
1231                         int (*strcmp_funct) (const char* str1, const char* str2);
1232                         int left, right, middle;
1233
1234                         pak = search->pack;
1235                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1236
1237                         // Look for the file (binary search)
1238                         left = 0;
1239                         right = pak->numfiles - 1;
1240                         while (left <= right)
1241                         {
1242                                 int diff;
1243
1244                                 middle = (left + right) / 2;
1245                                 diff = strcmp_funct (pak->files[middle].name, name);
1246
1247                                 // Found it
1248                                 if (!diff)
1249                                 {
1250                                         if (!quiet)
1251                                                 Con_DPrintf("FS_FindFile: %s in %s\n",
1252                                                                         pak->files[middle].name, pak->filename);
1253
1254                                         if (index != NULL)
1255                                                 *index = middle;
1256                                         return search;
1257                                 }
1258
1259                                 // If we're too far in the list
1260                                 if (diff > 0)
1261                                         right = middle - 1;
1262                                 else
1263                                         left = middle + 1;
1264                         }
1265                 }
1266                 else
1267                 {
1268                         char netpath[MAX_OSPATH];
1269                         dpsnprintf(netpath, sizeof(netpath), "%s/%s", search->filename, name);
1270                         if (FS_SysFileExists (netpath))
1271                         {
1272                                 if (!quiet)
1273                                         Con_DPrintf("FS_FindFile: %s\n", netpath);
1274
1275                                 if (index != NULL)
1276                                         *index = -1;
1277                                 return search;
1278                         }
1279                 }
1280         }
1281
1282         if (!quiet)
1283                 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1284
1285         if (index != NULL)
1286                 *index = -1;
1287         return NULL;
1288 }
1289
1290
1291 /*
1292 ===========
1293 FS_OpenReadFile
1294
1295 Look for a file in the search paths and open it in read-only mode
1296
1297 Sets fs_filesize
1298 ===========
1299 */
1300 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1301 {
1302         searchpath_t *search;
1303         int pack_ind;
1304
1305         search = FS_FindFile (filename, &pack_ind, quiet);
1306
1307         // Not found?
1308         if (search == NULL)
1309         {
1310                 fs_filesize = -1;
1311                 return NULL;
1312         }
1313
1314         // Found in the filesystem?
1315         if (pack_ind < 0)
1316         {
1317                 char path [MAX_OSPATH];
1318                 dpsnprintf (path, sizeof (path), "%s/%s", search->filename, filename);
1319                 return FS_SysOpen (path, "rb", nonblocking);
1320         }
1321
1322         // So, we found it in a package...
1323         return FS_OpenPackedFile (search->pack, pack_ind);
1324 }
1325
1326
1327 /*
1328 =============================================================================
1329
1330 MAIN PUBLIC FUNCTIONS
1331
1332 =============================================================================
1333 */
1334
1335 /*
1336 ====================
1337 FS_Open
1338
1339 Open a file. The syntax is the same as fopen
1340 ====================
1341 */
1342 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1343 {
1344         qfile_t* file;
1345
1346         if (FS_CheckNastyPath(filepath))
1347         {
1348                 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1349                 return NULL;
1350         }
1351
1352         // If the file is opened in "write", "append", or "read/write" mode
1353         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1354         {
1355                 char real_path [MAX_OSPATH];
1356
1357                 // Open the file on disk directly
1358                 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1359
1360                 // Create directories up to the file
1361                 FS_CreatePath (real_path);
1362
1363                 return FS_SysOpen (real_path, mode, nonblocking);
1364         }
1365
1366         // Else, we look at the various search paths and open the file in read-only mode
1367         file = FS_OpenReadFile (filepath, quiet, nonblocking);
1368         if (file != NULL)
1369                 fs_filesize = file->real_length;
1370
1371         return file;
1372 }
1373
1374
1375 /*
1376 ====================
1377 FS_Close
1378
1379 Close a file
1380 ====================
1381 */
1382 int FS_Close (qfile_t* file)
1383 {
1384         if (close (file->handle))
1385                 return EOF;
1386
1387         if (file->ztk)
1388         {
1389                 qz_inflateEnd (&file->ztk->zstream);
1390                 Mem_Free (file->ztk);
1391         }
1392
1393         Mem_Free (file);
1394         return 0;
1395 }
1396
1397
1398 /*
1399 ====================
1400 FS_Write
1401
1402 Write "datasize" bytes into a file
1403 ====================
1404 */
1405 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1406 {
1407         ssize_t result;
1408
1409         // If necessary, seek to the exact file position we're supposed to be
1410         if (file->buff_ind != file->buff_len)
1411                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1412
1413         // Purge cached data
1414         FS_Purge (file);
1415
1416         // Write the buffer and update the position
1417         result = write (file->handle, data, datasize);
1418         file->position = lseek (file->handle, 0, SEEK_CUR);
1419         if (file->real_length < file->position)
1420                 file->real_length = file->position;
1421
1422         if (result < 0)
1423                 return 0;
1424
1425         return result;
1426 }
1427
1428
1429 /*
1430 ====================
1431 FS_Read
1432
1433 Read up to "buffersize" bytes from a file
1434 ====================
1435 */
1436 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1437 {
1438         size_t count, done;
1439
1440         if (buffersize == 0)
1441                 return 0;
1442
1443         // Get rid of the ungetc character
1444         if (file->ungetc != EOF)
1445         {
1446                 ((char*)buffer)[0] = file->ungetc;
1447                 buffersize--;
1448                 file->ungetc = EOF;
1449                 done = 1;
1450         }
1451         else
1452                 done = 0;
1453
1454         // First, we copy as many bytes as we can from "buff"
1455         if (file->buff_ind < file->buff_len)
1456         {
1457                 count = file->buff_len - file->buff_ind;
1458
1459                 done += (buffersize > count) ? count : buffersize;
1460                 memcpy (buffer, &file->buff[file->buff_ind], done);
1461                 file->buff_ind += done;
1462
1463                 buffersize -= done;
1464                 if (buffersize == 0)
1465                         return done;
1466         }
1467
1468         // NOTE: at this point, the read buffer is always empty
1469
1470         // If the file isn't compressed
1471         if (! (file->flags & QFILE_FLAG_DEFLATED))
1472         {
1473                 int nb;
1474
1475                 // We must take care to not read after the end of the file
1476                 count = file->real_length - file->position;
1477
1478                 // If we have a lot of data to get, put them directly into "buffer"
1479                 if (buffersize > sizeof (file->buff) / 2)
1480                 {
1481                         if (count > buffersize)
1482                                 count = buffersize;
1483                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1484                         nb = read (file->handle, &((qbyte*)buffer)[done], count);
1485                         if (nb > 0)
1486                         {
1487                                 done += nb;
1488                                 file->position += nb;
1489
1490                                 // Purge cached data
1491                                 FS_Purge (file);
1492                         }
1493                 }
1494                 else
1495                 {
1496                         if (count > sizeof (file->buff))
1497                                 count = sizeof (file->buff);
1498                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1499                         nb = read (file->handle, file->buff, count);
1500                         if (nb > 0)
1501                         {
1502                                 file->buff_len = nb;
1503                                 file->position += nb;
1504
1505                                 // Copy the requested data in "buffer" (as much as we can)
1506                                 count = (buffersize > file->buff_len) ? file->buff_len : buffersize;
1507                                 memcpy (&((qbyte*)buffer)[done], file->buff, count);
1508                                 file->buff_ind = count;
1509                                 done += count;
1510                         }
1511                 }
1512
1513                 return done;
1514         }
1515
1516         // If the file is compressed, it's more complicated...
1517         // We cycle through a few operations until we have read enough data
1518         while (buffersize > 0)
1519         {
1520                 ztoolkit_t *ztk = file->ztk;
1521                 int error;
1522
1523                 // NOTE: at this point, the read buffer is always empty
1524
1525                 // If "input" is also empty, we need to refill it
1526                 if (ztk->in_ind == ztk->in_len)
1527                 {
1528                         // If we are at the end of the file
1529                         if (file->position == file->real_length)
1530                                 return done;
1531
1532                         count = ztk->comp_length - ztk->in_position;
1533                         if (count > sizeof (ztk->input))
1534                                 count = sizeof (ztk->input);
1535                         lseek (file->handle, file->offset + ztk->in_position, SEEK_SET);
1536                         if (read (file->handle, ztk->input, count) != (ssize_t)count)
1537                                 Sys_Error ("FS_Read: unexpected end of file");
1538
1539                         ztk->in_ind = 0;
1540                         ztk->in_len = count;
1541                         ztk->in_position += count;
1542                 }
1543
1544                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1545                 ztk->zstream.avail_in = ztk->in_len - ztk->in_ind;
1546
1547                 // Now that we are sure we have compressed data available, we need to determine
1548                 // if it's better to inflate it in "file->buff" or directly in "buffer"
1549
1550                 // Inflate the data in "file->buff"
1551                 if (buffersize < sizeof (file->buff) / 2)
1552                 {
1553                         ztk->zstream.next_out = file->buff;
1554                         ztk->zstream.avail_out = sizeof (file->buff);
1555                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1556                         if (error != Z_OK && error != Z_STREAM_END)
1557                                 Sys_Error ("Can't inflate file");
1558                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1559
1560                         file->buff_len = sizeof (file->buff) - ztk->zstream.avail_out;
1561                         file->position += file->buff_len;
1562
1563                         // Copy the requested data in "buffer" (as much as we can)
1564                         count = (buffersize > file->buff_len) ? file->buff_len : buffersize;
1565                         memcpy (&((qbyte*)buffer)[done], file->buff, count);
1566                         file->buff_ind = count;
1567                 }
1568
1569                 // Else, we inflate directly in "buffer"
1570                 else
1571                 {
1572                         ztk->zstream.next_out = &((qbyte*)buffer)[done];
1573                         ztk->zstream.avail_out = buffersize;
1574                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1575                         if (error != Z_OK && error != Z_STREAM_END)
1576                                 Sys_Error ("Can't inflate file");
1577                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1578
1579                         // How much data did it inflate?
1580                         count = buffersize - ztk->zstream.avail_out;
1581                         file->position += count;
1582
1583                         // Purge cached data
1584                         FS_Purge (file);
1585                 }
1586
1587                 done += count;
1588                 buffersize -= count;
1589         }
1590
1591         return done;
1592 }
1593
1594
1595 /*
1596 ====================
1597 FS_Print
1598
1599 Print a string into a file
1600 ====================
1601 */
1602 int FS_Print (qfile_t* file, const char *msg)
1603 {
1604         return FS_Write (file, msg, strlen (msg));
1605 }
1606
1607 /*
1608 ====================
1609 FS_Printf
1610
1611 Print a string into a file
1612 ====================
1613 */
1614 int FS_Printf(qfile_t* file, const char* format, ...)
1615 {
1616         int result;
1617         va_list args;
1618
1619         va_start (args, format);
1620         result = FS_VPrintf (file, format, args);
1621         va_end (args);
1622
1623         return result;
1624 }
1625
1626
1627 /*
1628 ====================
1629 FS_VPrintf
1630
1631 Print a string into a file
1632 ====================
1633 */
1634 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1635 {
1636         int len;
1637         size_t buff_size;
1638         char *tempbuff = NULL;
1639
1640         buff_size = 1024;
1641         tempbuff = Mem_Alloc (tempmempool, buff_size);
1642         len = dpvsnprintf (tempbuff, buff_size, format, ap);
1643         while (len < 0)
1644         {
1645                 Mem_Free (tempbuff);
1646                 buff_size *= 2;
1647                 tempbuff = Mem_Alloc (tempmempool, buff_size);
1648                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1649         }
1650
1651         len = write (file->handle, tempbuff, len);
1652         Mem_Free (tempbuff);
1653
1654         return len;
1655 }
1656
1657
1658 /*
1659 ====================
1660 FS_Getc
1661
1662 Get the next character of a file
1663 ====================
1664 */
1665 int FS_Getc (qfile_t* file)
1666 {
1667         char c;
1668
1669         if (FS_Read (file, &c, 1) != 1)
1670                 return EOF;
1671
1672         return c;
1673 }
1674
1675
1676 /*
1677 ====================
1678 FS_UnGetc
1679
1680 Put a character back into the read buffer (only supports one character!)
1681 ====================
1682 */
1683 int FS_UnGetc (qfile_t* file, unsigned char c)
1684 {
1685         // If there's already a character waiting to be read
1686         if (file->ungetc != EOF)
1687                 return EOF;
1688
1689         file->ungetc = c;
1690         return c;
1691 }
1692
1693
1694 /*
1695 ====================
1696 FS_Seek
1697
1698 Move the position index in a file
1699 ====================
1700 */
1701 int FS_Seek (qfile_t* file, long offset, int whence)
1702 {
1703         ztoolkit_t *ztk;
1704         qbyte* buffer;
1705         size_t buffersize;
1706
1707         // Compute the file offset
1708         switch (whence)
1709         {
1710                 case SEEK_CUR:
1711                         offset += file->position - file->buff_len + file->buff_ind;
1712                         break;
1713
1714                 case SEEK_SET:
1715                         break;
1716
1717                 case SEEK_END:
1718                         offset += file->real_length;
1719                         break;
1720
1721                 default:
1722                         return -1;
1723         }
1724         if (offset < 0 || offset > (long) file->real_length)
1725                 return -1;
1726
1727         // If we have the data in our read buffer, we don't need to actually seek
1728         if (file->position - file->buff_len <= (size_t)offset &&
1729                 (size_t)offset <= file->position)
1730         {
1731                 file->buff_ind = offset + file->buff_len - file->position;
1732                 return 0;
1733         }
1734
1735         // Purge cached data
1736         FS_Purge (file);
1737
1738         // Unpacked or uncompressed files can seek directly
1739         if (! (file->flags & QFILE_FLAG_DEFLATED))
1740         {
1741                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1742                         return -1;
1743                 file->position = offset;
1744                 return 0;
1745         }
1746
1747         // Seeking in compressed files is more a hack than anything else,
1748         // but we need to support it, so here we go.
1749         ztk = file->ztk;
1750
1751         // If we have to go back in the file, we need to restart from the beginning
1752         if ((size_t)offset <= file->position)
1753         {
1754                 ztk->in_ind = 0;
1755                 ztk->in_len = 0;
1756                 ztk->in_position = 0;
1757                 file->position = 0;
1758                 lseek (file->handle, file->offset, SEEK_SET);
1759
1760                 // Reset the Zlib stream
1761                 ztk->zstream.next_in = ztk->input;
1762                 ztk->zstream.avail_in = 0;
1763                 qz_inflateReset (&ztk->zstream);
1764         }
1765
1766         // We need a big buffer to force inflating into it directly
1767         buffersize = 2 * sizeof (file->buff);
1768         buffer = Mem_Alloc (tempmempool, buffersize);
1769
1770         // Skip all data until we reach the requested offset
1771         while ((size_t)offset > file->position)
1772         {
1773                 size_t diff = offset - file->position;
1774                 size_t count, len;
1775
1776                 count = (diff > buffersize) ? buffersize : diff;
1777                 len = FS_Read (file, buffer, count);
1778                 if (len != count)
1779                 {
1780                         Mem_Free (buffer);
1781                         return -1;
1782                 }
1783         }
1784
1785         Mem_Free (buffer);
1786         return 0;
1787 }
1788
1789
1790 /*
1791 ====================
1792 FS_Tell
1793
1794 Give the current position in a file
1795 ====================
1796 */
1797 long FS_Tell (qfile_t* file)
1798 {
1799         return file->position - file->buff_len + file->buff_ind;
1800 }
1801
1802
1803 /*
1804 ====================
1805 FS_Purge
1806
1807 Erases any buffered input or output data
1808 ====================
1809 */
1810 void FS_Purge (qfile_t* file)
1811 {
1812         file->buff_len = 0;
1813         file->buff_ind = 0;
1814         file->ungetc = EOF;
1815 }
1816
1817
1818 /*
1819 ============
1820 FS_LoadFile
1821
1822 Filename are relative to the quake directory.
1823 Always appends a 0 byte.
1824 ============
1825 */
1826 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1827 {
1828         qfile_t *file;
1829         qbyte *buf;
1830
1831         file = FS_Open (path, "rb", quiet, false);
1832         if (!file)
1833                 return NULL;
1834
1835         buf = Mem_Alloc (pool, fs_filesize + 1);
1836         buf[fs_filesize] = '\0';
1837
1838         FS_Read (file, buf, fs_filesize);
1839         FS_Close (file);
1840
1841         return buf;
1842 }
1843
1844
1845 /*
1846 ============
1847 FS_WriteFile
1848
1849 The filename will be prefixed by the current game directory
1850 ============
1851 */
1852 qboolean FS_WriteFile (const char *filename, void *data, int len)
1853 {
1854         qfile_t *file;
1855
1856         file = FS_Open (filename, "wb", false, false);
1857         if (!file)
1858         {
1859                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1860                 return false;
1861         }
1862
1863         Con_DPrintf("FS_WriteFile: %s\n", filename);
1864         FS_Write (file, data, len);
1865         FS_Close (file);
1866         return true;
1867 }
1868
1869
1870 /*
1871 =============================================================================
1872
1873 OTHERS PUBLIC FUNCTIONS
1874
1875 =============================================================================
1876 */
1877
1878 /*
1879 ============
1880 FS_StripExtension
1881 ============
1882 */
1883 void FS_StripExtension (const char *in, char *out, size_t size_out)
1884 {
1885         char *last = NULL;
1886
1887         if (size_out == 0)
1888                 return;
1889
1890         while (*in && size_out > 1)
1891         {
1892                 if (*in == '.')
1893                         last = out;
1894                 else if (*in == '/' || *in == '\\' || *in == ':')
1895                         last = NULL;
1896                 *out++ = *in++;
1897                 size_out--;
1898         }
1899         if (last)
1900                 *last = 0;
1901         else
1902                 *out = 0;
1903 }
1904
1905
1906 /*
1907 ==================
1908 FS_DefaultExtension
1909 ==================
1910 */
1911 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
1912 {
1913         const char *src;
1914
1915         // if path doesn't have a .EXT, append extension
1916         // (extension should include the .)
1917         src = path + strlen(path) - 1;
1918
1919         while (*src != '/' && src != path)
1920         {
1921                 if (*src == '.')
1922                         return;                 // it has an extension
1923                 src--;
1924         }
1925
1926         strlcat (path, extension, size_path);
1927 }
1928
1929
1930 /*
1931 ==================
1932 FS_FileExists
1933
1934 Look for a file in the packages and in the filesystem
1935 ==================
1936 */
1937 qboolean FS_FileExists (const char *filename)
1938 {
1939         return (FS_FindFile (filename, NULL, true) != NULL);
1940 }
1941
1942
1943 /*
1944 ==================
1945 FS_SysFileExists
1946
1947 Look for a file in the filesystem only
1948 ==================
1949 */
1950 qboolean FS_SysFileExists (const char *path)
1951 {
1952 #if WIN32
1953         int desc;
1954
1955         // TODO: use another function instead, to avoid opening the file
1956         desc = open (path, O_RDONLY | O_BINARY);
1957         if (desc < 0)
1958                 return false;
1959
1960         close (desc);
1961         return true;
1962 #else
1963         struct stat buf;
1964
1965         if (stat (path,&buf) == -1)
1966                 return false;
1967
1968         return true;
1969 #endif
1970 }
1971
1972 void FS_mkdir (const char *path)
1973 {
1974 #if WIN32
1975         _mkdir (path);
1976 #else
1977         mkdir (path, 0777);
1978 #endif
1979 }
1980
1981 /*
1982 ===========
1983 FS_Search
1984
1985 Allocate and fill a search structure with information on matching filenames.
1986 ===========
1987 */
1988 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
1989 {
1990         fssearch_t *search;
1991         searchpath_t *searchpath;
1992         pack_t *pak;
1993         int i, basepathlength, numfiles, numchars;
1994         stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
1995         const char *slash, *backslash, *colon, *separator;
1996         char *basepath;
1997         char netpath[MAX_OSPATH];
1998         char temp[MAX_OSPATH];
1999
2000         while(!strncmp(pattern, "./", 2))
2001                 pattern += 2;
2002         while(!strncmp(pattern, ".\\", 2))
2003                 pattern += 2;
2004
2005         search = NULL;
2006         liststart = NULL;
2007         listcurrent = NULL;
2008         listtemp = NULL;
2009         slash = strrchr(pattern, '/');
2010         backslash = strrchr(pattern, '\\');
2011         colon = strrchr(pattern, ':');
2012         separator = pattern;
2013         if (separator < slash)
2014                 separator = slash;
2015         if (separator < backslash)
2016                 separator = backslash;
2017         if (separator < colon)
2018                 separator = colon;
2019         basepathlength = separator - pattern;
2020         basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2021         if (basepathlength)
2022                 memcpy(basepath, pattern, basepathlength);
2023         basepath[basepathlength] = 0;
2024
2025         // search through the path, one element at a time
2026         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2027         {
2028                 // is the element a pak file?
2029                 if (searchpath->pack)
2030                 {
2031                         // look through all the pak file elements
2032                         pak = searchpath->pack;
2033                         for (i = 0;i < pak->numfiles;i++)
2034                         {
2035                                 strcpy(temp, pak->files[i].name);
2036                                 while (temp[0])
2037                                 {
2038                                         if (matchpattern(temp, (char *)pattern, true))
2039                                         {
2040                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2041                                                         if (!strcmp(listtemp->text, temp))
2042                                                                 break;
2043                                                 if (listtemp == NULL)
2044                                                 {
2045                                                         listcurrent = stringlistappend(listcurrent, temp);
2046                                                         if (liststart == NULL)
2047                                                                 liststart = listcurrent;
2048                                                         if (!quiet)
2049                                                                 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2050                                                 }
2051                                         }
2052                                         // strip off one path element at a time until empty
2053                                         // this way directories are added to the listing if they match the pattern
2054                                         slash = strrchr(temp, '/');
2055                                         backslash = strrchr(temp, '\\');
2056                                         colon = strrchr(temp, ':');
2057                                         separator = temp;
2058                                         if (separator < slash)
2059                                                 separator = slash;
2060                                         if (separator < backslash)
2061                                                 separator = backslash;
2062                                         if (separator < colon)
2063                                                 separator = colon;
2064                                         *((char *)separator) = 0;
2065                                 }
2066                         }
2067                 }
2068                 else
2069                 {
2070                         // get a directory listing and look at each name
2071                         dpsnprintf(netpath, sizeof (netpath), "%s/%s", searchpath->filename, basepath);
2072                         if ((dir = listdirectory(netpath)))
2073                         {
2074                                 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2075                                 {
2076                                         dpsnprintf(temp, sizeof(temp), "%s/%s", basepath, dirfile->text);
2077                                         if (matchpattern(temp, (char *)pattern, true))
2078                                         {
2079                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2080                                                         if (!strcmp(listtemp->text, temp))
2081                                                                 break;
2082                                                 if (listtemp == NULL)
2083                                                 {
2084                                                         listcurrent = stringlistappend(listcurrent, temp);
2085                                                         if (liststart == NULL)
2086                                                                 liststart = listcurrent;
2087                                                         if (!quiet)
2088                                                                 Con_DPrintf("SearchDirFile: %s\n", temp);
2089                                                 }
2090                                         }
2091                                 }
2092                                 freedirectory(dir);
2093                         }
2094                 }
2095         }
2096
2097         if (liststart)
2098         {
2099                 liststart = stringlistsort(liststart);
2100                 numfiles = 0;
2101                 numchars = 0;
2102                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2103                 {
2104                         numfiles++;
2105                         numchars += strlen(listtemp->text) + 1;
2106                 }
2107                 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2108                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2109                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2110                 search->numfilenames = numfiles;
2111                 numfiles = 0;
2112                 numchars = 0;
2113                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2114                 {
2115                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
2116                         strcpy(search->filenames[numfiles], listtemp->text);
2117                         numfiles++;
2118                         numchars += strlen(listtemp->text) + 1;
2119                 }
2120                 if (liststart)
2121                         stringlistfree(liststart);
2122         }
2123
2124         Mem_Free(basepath);
2125         return search;
2126 }
2127
2128 void FS_FreeSearch(fssearch_t *search)
2129 {
2130         Z_Free(search);
2131 }
2132
2133 extern int con_linewidth;
2134 int FS_ListDirectory(const char *pattern, int oneperline)
2135 {
2136         int numfiles;
2137         int numcolumns;
2138         int numlines;
2139         int columnwidth;
2140         int linebufpos;
2141         int i, j, k, l;
2142         const char *name;
2143         char linebuf[4096];
2144         fssearch_t *search;
2145         search = FS_Search(pattern, true, true);
2146         if (!search)
2147                 return 0;
2148         numfiles = search->numfilenames;
2149         if (!oneperline)
2150         {
2151                 // FIXME: the names could be added to one column list and then
2152                 // gradually shifted into the next column if they fit, and then the
2153                 // next to make a compact variable width listing but it's a lot more
2154                 // complicated...
2155                 // find width for columns
2156                 columnwidth = 0;
2157                 for (i = 0;i < numfiles;i++)
2158                 {
2159                         l = strlen(search->filenames[i]);
2160                         if (columnwidth < l)
2161                                 columnwidth = l;
2162                 }
2163                 // count the spacing character
2164                 columnwidth++;
2165                 // calculate number of columns
2166                 numcolumns = con_linewidth / columnwidth;
2167                 // don't bother with the column printing if it's only one column
2168                 if (numcolumns >= 2)
2169                 {
2170                         numlines = (numfiles + numcolumns - 1) / numcolumns;
2171                         for (i = 0;i < numlines;i++)
2172                         {
2173                                 linebufpos = 0;
2174                                 for (k = 0;k < numcolumns;k++)
2175                                 {
2176                                         l = i * numcolumns + k;
2177                                         if (l < numfiles)
2178                                         {
2179                                                 name = search->filenames[l];
2180                                                 for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++)
2181                                                         linebuf[linebufpos++] = name[j];
2182                                                 // space out name unless it's the last on the line
2183                                                 if (k < (numcolumns - 1) && l < (numfiles - 1))
2184                                                         for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++)
2185                                                                 linebuf[linebufpos++] = ' ';
2186                                         }
2187                                 }
2188                                 linebuf[linebufpos] = 0;
2189                                 Con_Printf("%s\n", linebuf);
2190                         }
2191                 }
2192                 else
2193                         oneperline = true;
2194         }
2195         if (oneperline)
2196                 for (i = 0;i < numfiles;i++)
2197                         Con_Printf("%s\n", search->filenames[i]);
2198         FS_FreeSearch(search);
2199         return numfiles;
2200 }
2201
2202 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2203 {
2204         const char *pattern;
2205         if (Cmd_Argc() > 3)
2206         {
2207                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2208                 return;
2209         }
2210         if (Cmd_Argc() == 2)
2211                 pattern = Cmd_Argv(1);
2212         else
2213                 pattern = "*";
2214         if (!FS_ListDirectory(pattern, oneperline))
2215                 Con_Print("No files found.\n");
2216 }
2217
2218 void FS_Dir_f(void)
2219 {
2220         FS_ListDirectoryCmd("dir", true);
2221 }
2222
2223 void FS_Ls_f(void)
2224 {
2225         FS_ListDirectoryCmd("ls", false);
2226 }
2227