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