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