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