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