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