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