]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - fs.c
-Added the cvar sv_progs, which allows you to set the name of the server
[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 #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 ====================
1047 FS_SysOpen
1048
1049 Internal function used to create a qfile_t and open the relevant file on disk
1050 ====================
1051 */
1052 static qfile_t* FS_SysOpen (const char* filepath, const char* mode)
1053 {
1054         qfile_t* file;
1055
1056         file = Mem_Alloc (fs_mempool, sizeof (*file));
1057         memset (file, 0, sizeof (*file));
1058
1059 #ifdef FS_USESYSCALLS
1060         if (strchr(mode, 'r'))
1061                 file->stream = open (filepath, O_RDONLY | O_BINARY);
1062         else if (strchr(mode, 'w'))
1063                 file->stream = open (filepath, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666);
1064         else if (strchr(mode, 'a'))
1065                 file->stream = open (filepath, O_RDWR | O_BINARY | O_CREAT | O_APPEND, 0666);
1066         else
1067                 file->stream = -1;
1068         if (file->stream < 0)
1069         {
1070                 Mem_Free (file);
1071                 return NULL;
1072         }
1073 #else
1074         file->stream = fopen (filepath, mode);
1075         if (!file->stream)
1076         {
1077                 Mem_Free (file);
1078                 return NULL;
1079         }
1080 #endif
1081
1082         return file;
1083 }
1084
1085
1086 /*
1087 ===========
1088 FS_OpenRead
1089 ===========
1090 */
1091 qfile_t *FS_OpenRead (const char *path, int offs, int len)
1092 {
1093         qfile_t* file;
1094
1095         file = FS_SysOpen (path, "rb");
1096         if (!file)
1097         {
1098                 Sys_Error ("Couldn't open %s", path);
1099                 return NULL;
1100         }
1101
1102         // Normal file
1103         if (offs < 0 || len < 0)
1104         {
1105                 // We set fs_filesize here for normal files
1106 #ifdef FS_USESYSCALLS
1107                 fs_filesize = lseek (file->stream, 0, SEEK_END);
1108                 lseek (file->stream, 0, SEEK_SET);
1109 #else
1110                 fseek (file->stream, 0, SEEK_END);
1111                 fs_filesize = ftell (file->stream);
1112                 fseek (file->stream, 0, SEEK_SET);
1113 #endif
1114         }
1115         // Packed file
1116         else
1117         {
1118 #ifdef FS_USESYSCALLS
1119                 lseek (file->stream, offs, SEEK_SET);
1120 #else
1121                 fseek (file->stream, offs, SEEK_SET);
1122 #endif
1123
1124                 file->flags |= FS_FLAG_PACKED;
1125                 file->length = len;
1126                 file->offset = offs;
1127                 file->position = 0;
1128         }
1129
1130         return file;
1131 }
1132
1133 /*
1134 ====================
1135 FS_CheckNastyPath
1136
1137 Return true if the path should be rejected due to one of the following:
1138 1: path elements that are non-portable
1139 2: path elements that would allow access to files outside the game directory,
1140    or are just not a good idea for a mod to be using.
1141 ====================
1142 */
1143 int FS_CheckNastyPath (const char *path)
1144 {
1145         // Windows: don't allow \ in filenames (windows-only), period.
1146         // (on Windows \ is a directory separator, but / is also supported)
1147         if (strstr(path, "\\"))
1148                 return 1; // non-portable
1149         // Mac: don't allow Mac-only filenames - : is a directory separator
1150         // instead of /, but we rely on / working already, so there's no reason to
1151         // support a Mac-only path
1152         // Amiga and Windows: : tries to go to root of drive
1153         if (strstr(path, ":"))
1154                 return 1; // non-portable attempt to go to root of drive
1155         // Amiga: // is parent directory
1156         if (strstr(path, "//"))
1157                 return 1; // non-portable attempt to go to parent directory
1158         // all: don't allow going to current directory (./) or parent directory (../ or /../)
1159         if (strstr(path, "./"))
1160                 return 2; // attempt to go to parent directory
1161         // after all these checks we're pretty sure it's a / separated filename
1162         // and won't do much if any harm
1163         return false;
1164 }
1165
1166
1167 /*
1168 ====================
1169 FS_FindFile
1170
1171 Look for a file in the packages and in the filesystem
1172
1173 Return the searchpath where the file was found (or NULL)
1174 and the file index in the package if relevant
1175 ====================
1176 */
1177 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1178 {
1179         searchpath_t *search;
1180         pack_t *pak;
1181
1182         // search through the path, one element at a time
1183         for (search = fs_searchpaths;search;search = search->next)
1184         {
1185                 // is the element a pak file?
1186                 if (search->pack)
1187                 {
1188                         int (*strcmp_funct) (const char* str1, const char* str2);
1189                         int left, right, middle;
1190
1191                         pak = search->pack;
1192                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1193
1194                         // Look for the file (binary search)
1195                         left = 0;
1196                         right = pak->numfiles - 1;
1197                         while (left <= right)
1198                         {
1199                                 int diff;
1200
1201                                 middle = (left + right) / 2;
1202                                 diff = strcmp_funct (pak->files[middle].name, name);
1203
1204                                 // Found it
1205                                 if (!diff)
1206                                 {
1207                                         if (!quiet)
1208                                                 Con_DPrintf("FS_FindFile: %s in %s\n",
1209                                                                         pak->files[middle].name, pak->filename);
1210
1211                                         if (index != NULL)
1212                                                 *index = middle;
1213                                         return search;
1214                                 }
1215
1216                                 // If we're too far in the list
1217                                 if (diff > 0)
1218                                         right = middle - 1;
1219                                 else
1220                                         left = middle + 1;
1221                         }
1222                 }
1223                 else
1224                 {
1225                         char netpath[MAX_OSPATH];
1226                         snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, name);
1227                         if (FS_SysFileExists (netpath))
1228                         {
1229                                 if (!quiet)
1230                                         Con_DPrintf("FS_FindFile: %s\n", netpath);
1231
1232                                 if (index != NULL)
1233                                         *index = -1;
1234                                 return search;
1235                         }
1236                 }
1237         }
1238
1239         if (!quiet)
1240                 Con_DPrintf("FS_FindFile: can't find %s\n", name);
1241
1242         if (index != NULL)
1243                 *index = -1;
1244         return NULL;
1245 }
1246
1247
1248 /*
1249 ===========
1250 FS_FOpenFile
1251
1252 If the requested file is inside a packfile, a new qfile_t* will be opened
1253 into the file.
1254
1255 Sets fs_filesize
1256 ===========
1257 */
1258 qfile_t *FS_FOpenFile (const char *filename, qboolean quiet)
1259 {
1260         searchpath_t *search;
1261         packfile_t *packfile;
1262         int i;
1263         qfile_t *file;
1264
1265         search = FS_FindFile (filename, &i, quiet);
1266
1267         // Not found?
1268         if (search == NULL)
1269         {
1270                 fs_filesize = -1;
1271                 return NULL;
1272         }
1273
1274         // Found in the filesystem?
1275         if (i < 0)
1276         {
1277                 char netpath[MAX_OSPATH];
1278                 snprintf(netpath, sizeof(netpath), "%s/%s", search->filename, filename);
1279                 return FS_OpenRead(netpath, -1, -1);
1280         }
1281
1282         // So, we found it in a package...
1283         packfile = &search->pack->files[i];
1284
1285         // If we don't have the true offset, get it now
1286         if (! (packfile->flags & FILE_FLAG_TRUEOFFS))
1287                 PK3_GetTrueFileOffset (packfile, search->pack);
1288
1289         // No Zlib DLL = no compressed files
1290         if (!zlib_dll && (packfile->flags & FILE_FLAG_DEFLATED))
1291         {
1292                 Con_Printf("WARNING: can't open the compressed file %s\n"
1293                                         "You need the Zlib DLL to use compressed files\n",
1294                                         filename);
1295                 fs_filesize = -1;
1296                 return NULL;
1297         }
1298
1299         // open a new file in the pakfile
1300         file = FS_OpenRead (search->pack->filename, packfile->offset, packfile->packsize);
1301         fs_filesize = packfile->realsize;
1302
1303         if (packfile->flags & FILE_FLAG_DEFLATED)
1304         {
1305                 ztoolkit_t *ztk;
1306
1307                 file->flags |= FS_FLAG_DEFLATED;
1308
1309                 // We need some more variables
1310                 ztk = Mem_Alloc (fs_mempool, sizeof (*file->z));
1311
1312                 ztk->real_length = packfile->realsize;
1313
1314                 // Initialize zlib stream
1315                 ztk->zstream.next_in = ztk->input;
1316                 ztk->zstream.avail_in = 0;
1317
1318                 /* From Zlib's "unzip.c":
1319                  *
1320                  * windowBits is passed < 0 to tell that there is no zlib header.
1321                  * Note that in this case inflate *requires* an extra "dummy" byte
1322                  * after the compressed stream in order to complete decompression and
1323                  * return Z_STREAM_END.
1324                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1325                  * size of both compressed and uncompressed data
1326                  */
1327                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1328                         Sys_Error ("inflate init error (file: %s)", filename);
1329
1330                 ztk->zstream.next_out = ztk->output;
1331                 ztk->zstream.avail_out = sizeof (ztk->output);
1332
1333                 file->z = ztk;
1334         }
1335
1336         return file;
1337 }
1338
1339
1340 /*
1341 =============================================================================
1342
1343 MAIN PUBLIC FUNCTIONS
1344
1345 =============================================================================
1346 */
1347
1348 /*
1349 ====================
1350 FS_Open
1351
1352 Open a file. The syntax is the same as fopen
1353 ====================
1354 */
1355 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet)
1356 {
1357         if (FS_CheckNastyPath(filepath))
1358         {
1359                 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1360                 return NULL;
1361         }
1362
1363         // If the file is opened in "write" or "append" mode
1364         if (strchr (mode, 'w') || strchr (mode, 'a'))
1365         {
1366                 char real_path [MAX_OSPATH];
1367
1368                 // Open the file on disk directly
1369                 snprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1370
1371                 // Create directories up to the file
1372                 FS_CreatePath (real_path);
1373
1374                 return FS_SysOpen (real_path, mode);
1375         }
1376
1377         // Else, we look at the various search paths
1378         return FS_FOpenFile (filepath, quiet);
1379 }
1380
1381
1382 /*
1383 ====================
1384 FS_Close
1385
1386 Close a file
1387 ====================
1388 */
1389 int FS_Close (qfile_t* file)
1390 {
1391 #ifdef FS_USESYSCALLS
1392         if (close (file->stream))
1393 #else
1394         if (fclose (file->stream))
1395 #endif
1396                 return EOF;
1397
1398         if (file->z)
1399         {
1400                 qz_inflateEnd (&file->z->zstream);
1401                 Mem_Free (file->z);
1402         }
1403
1404         Mem_Free (file);
1405         return 0;
1406 }
1407
1408
1409 /*
1410 ====================
1411 FS_Write
1412
1413 Write "datasize" bytes into a file
1414 ====================
1415 */
1416 size_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1417 {
1418 #ifdef FS_USESYSCALLS
1419         return write (file->stream, data, datasize);
1420 #else
1421         return fwrite (data, 1, datasize, file->stream);
1422 #endif
1423 }
1424
1425
1426 /*
1427 ====================
1428 FS_Read
1429
1430 Read up to "buffersize" bytes from a file
1431 ====================
1432 */
1433 size_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1434 {
1435         size_t count, nb;
1436         ztoolkit_t *ztk;
1437
1438         // Quick path for unpacked files
1439         if (! (file->flags & FS_FLAG_PACKED))
1440 #ifdef FS_USESYSCALLS
1441                 return read (file->stream, buffer, buffersize);
1442 #else
1443                 return fread (buffer, 1, buffersize, file->stream);
1444 #endif
1445
1446         // If the file isn't compressed
1447         if (! (file->flags & FS_FLAG_DEFLATED))
1448         {
1449                 // We must take care to not read after the end of the file
1450                 count = file->length - file->position;
1451                 if (buffersize > count)
1452                         buffersize = count;
1453
1454 #ifdef FS_USESYSCALLS
1455                 nb = read (file->stream, buffer, buffersize);
1456 #else
1457                 nb = fread (buffer, 1, buffersize, file->stream);
1458 #endif
1459
1460                 file->position += nb;
1461                 return nb;
1462         }
1463
1464         // If the file is compressed, it's more complicated...
1465         ztk = file->z;
1466
1467         // First, we copy as many bytes as we can from "output"
1468         if (ztk->out_ind < ztk->out_max)
1469         {
1470                 count = ztk->out_max - ztk->out_ind;
1471
1472                 nb = (buffersize > count) ? count : buffersize;
1473                 memcpy (buffer, &ztk->output[ztk->out_ind], nb);
1474                 ztk->out_ind += nb;
1475                 file->position += nb;
1476         }
1477         else
1478                 nb = 0;
1479
1480         // We cycle through a few operations until we have inflated enough data
1481         while (nb < buffersize)
1482         {
1483                 // NOTE: at this point, "output" should always be empty
1484
1485                 // If "input" is also empty, we need to fill it
1486                 if (ztk->in_ind == ztk->in_max)
1487                 {
1488                         size_t remain;
1489
1490                         // If we are at the end of the file
1491                         if (ztk->out_position == ztk->real_length)
1492                                 return nb;
1493
1494                         remain = file->length - ztk->in_position;
1495                         count = (remain > sizeof (ztk->input)) ? sizeof (ztk->input) : remain;
1496 #ifdef FS_USESYSCALLS
1497                         read (file->stream, ztk->input, count);
1498 #else
1499                         fread (ztk->input, 1, count, file->stream);
1500 #endif
1501
1502                         // Update indexes and counters
1503                         ztk->in_ind = 0;
1504                         ztk->in_max = count;
1505                         ztk->in_position += count;
1506                 }
1507
1508                 // Now that we are sure we have compressed data available, we need to determine
1509                 // if it's better to inflate it in "output" or directly in "buffer" (we are in this
1510                 // case if we still need more bytes than "output" can contain)
1511
1512                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1513                 ztk->zstream.avail_in = ztk->in_max - ztk->in_ind;
1514
1515                 // If output will be able to contain at least 1 more byte than the data we need
1516                 if (buffersize - nb < sizeof (ztk->output))
1517                 {
1518                         int error;
1519
1520                         // Inflate the data in "output"
1521                         ztk->zstream.next_out = ztk->output;
1522                         ztk->zstream.avail_out = sizeof (ztk->output);
1523                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1524                         if (error != Z_OK && error != Z_STREAM_END)
1525                                 Sys_Error ("Can't inflate file");
1526                         ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1527                         ztk->out_max = sizeof (ztk->output) - ztk->zstream.avail_out;
1528                         ztk->out_position += ztk->out_max;
1529
1530                         // Copy the requested data in "buffer" (as much as we can)
1531                         count = (buffersize - nb > ztk->out_max) ? ztk->out_max : buffersize - nb;
1532                         memcpy (&((qbyte*)buffer)[nb], ztk->output, count);
1533                         ztk->out_ind = count;
1534                 }
1535
1536                 // Else, we inflate directly in "buffer"
1537                 else
1538                 {
1539                         int error;
1540
1541                         // Inflate the data in "buffer"
1542                         ztk->zstream.next_out = &((qbyte*)buffer)[nb];
1543                         ztk->zstream.avail_out = buffersize - nb;
1544                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1545                         if (error != Z_OK && error != Z_STREAM_END)
1546                                 Sys_Error ("Can't inflate file");
1547                         ztk->in_ind = ztk->in_max - ztk->zstream.avail_in;
1548
1549                         // Invalidate the output data (for FS_Seek)
1550                         ztk->out_max = 0;
1551                         ztk->out_ind = 0;
1552
1553                         // How much data did it inflate?
1554                         count = buffersize - nb - ztk->zstream.avail_out;
1555                         ztk->out_position += count;
1556                 }
1557
1558                 nb += count;
1559                 file->position += count;
1560         }
1561
1562         return nb;
1563 }
1564
1565
1566 /*
1567 ====================
1568 FS_Flush
1569
1570 Flush the file output stream
1571 ====================
1572 */
1573 int FS_Flush (qfile_t* file)
1574 {
1575 #ifdef FS_USESYSCALLS
1576         return 0;
1577 #else
1578         return fflush (file->stream);
1579 #endif
1580 }
1581
1582
1583 /*
1584 ====================
1585 FS_Print
1586
1587 Print a string into a file
1588 ====================
1589 */
1590 int FS_Print(qfile_t* file, const char *msg)
1591 {
1592         return FS_Write(file, msg, strlen(msg));
1593 }
1594
1595 /*
1596 ====================
1597 FS_Printf
1598
1599 Print a string into a file
1600 ====================
1601 */
1602 int FS_Printf(qfile_t* file, const char* format, ...)
1603 {
1604         int result;
1605         va_list args;
1606
1607         va_start (args, format);
1608         result = FS_VPrintf(file, format, args);
1609         va_end (args);
1610
1611         return result;
1612 }
1613
1614
1615 /*
1616 ====================
1617 FS_VPrintf
1618
1619 Print a string into a file
1620 ====================
1621 */
1622 int FS_VPrintf(qfile_t* file, const char* format, va_list ap)
1623 {
1624 #ifdef FS_USESYSCALLS
1625 {
1626         int len;
1627         char tempstring[1024];
1628         len = vsnprintf (tempstring, sizeof(tempstring), format, ap);
1629         if (len >= sizeof(tempstring))
1630         {
1631                 int result;
1632                 char *temp = Mem_Alloc(tempmempool, len + 1);
1633                 len = vsnprintf (temp, len + 1, format, ap);
1634                 result = write (file->stream, temp, len);
1635                 Mem_Free(temp);
1636                 return result;
1637         }
1638         else
1639                 return write (file->stream, tempstring, len);
1640 }
1641 #else
1642         return vfprintf (file->stream, format, ap);
1643 #endif
1644 }
1645
1646
1647 /*
1648 ====================
1649 FS_Getc
1650
1651 Get the next character of a file
1652 ====================
1653 */
1654 int FS_Getc (qfile_t* file)
1655 {
1656         char c;
1657
1658         if (FS_Read (file, &c, 1) != 1)
1659                 return EOF;
1660
1661         return c;
1662 }
1663
1664
1665 /*
1666 ====================
1667 FS_Seek
1668
1669 Move the position index in a file
1670 ====================
1671 */
1672 int FS_Seek (qfile_t* file, long offset, int whence)
1673 {
1674         // Quick path for unpacked files
1675         if (! (file->flags & FS_FLAG_PACKED))
1676 #ifdef FS_USESYSCALLS
1677         {
1678                 if (lseek (file->stream, offset, whence) == -1)
1679                         return -1;
1680                 return 0;
1681         }
1682 #else
1683                 return fseek (file->stream, offset, whence);
1684 #endif
1685
1686         // Seeking in compressed files is more a hack than anything else,
1687         // but we need to support it, so here it is.
1688         if (file->flags & FS_FLAG_DEFLATED)
1689         {
1690                 ztoolkit_t *ztk = file->z;
1691                 qbyte buffer [sizeof (ztk->output)];  // it's big to force inflating into buffer directly
1692
1693                 switch (whence)
1694                 {
1695                         case SEEK_CUR:
1696                                 offset += file->position;
1697                                 break;
1698
1699                         case SEEK_SET:
1700                                 break;
1701
1702                         case SEEK_END:
1703                                 offset += ztk->real_length;
1704                                 break;
1705
1706                         default:
1707                                 return -1;
1708                 }
1709                 if (offset < 0 || offset > (long) ztk->real_length)
1710                         return -1;
1711
1712                 // If we need to go back in the file
1713                 if (offset <= (long) file->position)
1714                 {
1715                         // If we still have the data we need in the output buffer
1716                         if (file->position - offset <= ztk->out_ind)
1717                         {
1718                                 ztk->out_ind -= file->position - offset;
1719                                 file->position = offset;
1720                                 return 0;
1721                         }
1722
1723                         // Else, we restart from the beginning of the file
1724                         ztk->in_ind = 0;
1725                         ztk->in_max = 0;
1726                         ztk->in_position = 0;
1727                         ztk->out_ind = 0;
1728                         ztk->out_max = 0;
1729                         ztk->out_position = 0;
1730                         file->position = 0;
1731 #ifdef FS_USESYSCALLS
1732                         lseek (file->stream, file->offset, SEEK_SET);
1733 #else
1734                         fseek (file->stream, file->offset, SEEK_SET);
1735 #endif
1736
1737                         // Reset the Zlib stream
1738                         ztk->zstream.next_in = ztk->input;
1739                         ztk->zstream.avail_in = 0;
1740                         qz_inflateReset (&ztk->zstream);
1741                 }
1742
1743                 // Skip all data until we reach the requested offset
1744                 while ((long) file->position < offset)
1745                 {
1746                         size_t diff = offset - file->position;
1747                         size_t count, len;
1748
1749                         count = (diff > sizeof (buffer)) ? sizeof (buffer) : diff;
1750                         len = FS_Read (file, buffer, count);
1751                         if (len != count)
1752                                 return -1;
1753                 }
1754
1755                 return 0;
1756         }
1757
1758         // Packed files receive a special treatment too, because
1759         // we need to make sure it doesn't go outside of the file
1760         switch (whence)
1761         {
1762                 case SEEK_CUR:
1763                         offset += file->position;
1764                         break;
1765
1766                 case SEEK_SET:
1767                         break;
1768
1769                 case SEEK_END:
1770                         offset += file->length;
1771                         break;
1772
1773                 default:
1774                         return -1;
1775         }
1776         if (offset < 0 || offset > (long) file->length)
1777                 return -1;
1778
1779 #ifdef FS_USESYSCALLS
1780         if (lseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1781                 return -1;
1782 #else
1783         if (fseek (file->stream, file->offset + offset, SEEK_SET) == -1)
1784                 return -1;
1785 #endif
1786         file->position = offset;
1787         return 0;
1788 }
1789
1790
1791 /*
1792 ====================
1793 FS_Tell
1794
1795 Give the current position in a file
1796 ====================
1797 */
1798 long FS_Tell (qfile_t* file)
1799 {
1800         if (file->flags & FS_FLAG_PACKED)
1801                 return file->position;
1802
1803 #ifdef FS_USESYSCALLS
1804         return lseek (file->stream, 0, SEEK_CUR);
1805 #else
1806         return ftell (file->stream);
1807 #endif
1808 }
1809
1810
1811 /*
1812 ====================
1813 FS_Gets
1814
1815 Extract a line from a file
1816 ====================
1817 */
1818 char* FS_Gets (qfile_t* file, char* buffer, int buffersize)
1819 {
1820         size_t ind;
1821
1822         // Quick path for unpacked files
1823 #ifndef FS_USESYSCALLS
1824         if (! (file->flags & FS_FLAG_PACKED))
1825                 return fgets (buffer, buffersize, file->stream);
1826 #endif
1827
1828         for (ind = 0; ind < (size_t) buffersize - 1; ind++)
1829         {
1830                 int c = FS_Getc (file);
1831                 switch (c)
1832                 {
1833                         // End of file
1834                         case -1:
1835                                 if (!ind)
1836                                         return NULL;
1837
1838                                 buffer[ind] = '\0';
1839                                 return buffer;
1840
1841                         // End of line
1842                         case '\r':
1843                         case '\n':
1844                                 buffer[ind] = '\n';
1845                                 buffer[ind + 1] = '\0';
1846                                 return buffer;
1847
1848                         default:
1849                                 buffer[ind] = c;
1850                 }
1851
1852         }
1853
1854         buffer[buffersize - 1] = '\0';
1855         return buffer;
1856 }
1857
1858
1859 /*
1860 ==========
1861 FS_Getline
1862
1863 Dynamic length version of fgets. DO NOT free the buffer.
1864 ==========
1865 */
1866 char *FS_Getline (qfile_t *file)
1867 {
1868         static int  size = 256;
1869         static char *buf = 0;
1870         char        *t;
1871         int         len;
1872
1873         if (!buf)
1874                 buf = Mem_Alloc (fs_mempool, size);
1875
1876         if (!FS_Gets (file, buf, size))
1877                 return 0;
1878
1879         len = strlen (buf);
1880         while (buf[len - 1] != '\n' && buf[len - 1] != '\r')
1881         {
1882                 t = Mem_Alloc (fs_mempool, size + 256);
1883                 memcpy(t, buf, size);
1884                 Mem_Free(buf);
1885                 size += 256;
1886                 buf = t;
1887                 if (!FS_Gets (file, buf + len, size - len))
1888                         break;
1889                 len = strlen (buf);
1890         }
1891         while ((len = strlen(buf)) && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
1892                 buf[len - 1] = 0;
1893         return buf;
1894 }
1895
1896
1897 /*
1898 ====================
1899 FS_Eof
1900
1901 Extract a line from a file
1902 ====================
1903 */
1904 // FIXME: remove this function?
1905 int FS_Eof (qfile_t* file)
1906 {
1907         if (file->flags & FS_FLAG_PACKED)
1908         {
1909                 if (file->flags & FS_FLAG_DEFLATED)
1910                         return (file->position == file->z->real_length);
1911
1912                 return (file->position == file->length);
1913         }
1914
1915 #ifdef FS_USESYSCALLS
1916         Sys_Error("FS_Eof: not implemented using syscalls\n");
1917         return false;
1918 #else
1919         return feof (file->stream);
1920 #endif
1921 }
1922
1923
1924 /*
1925 ============
1926 FS_LoadFile
1927
1928 Filename are relative to the quake directory.
1929 Always appends a 0 byte.
1930 ============
1931 */
1932 qbyte *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet)
1933 {
1934         qfile_t *h;
1935         qbyte *buf;
1936
1937         // look for it in the filesystem or pack files
1938         h = FS_Open (path, "rb", quiet);
1939         if (!h)
1940                 return NULL;
1941
1942         buf = Mem_Alloc(pool, fs_filesize+1);
1943         if (!buf)
1944                 Sys_Error ("FS_LoadFile: not enough available memory for %s (size %i)", path, fs_filesize);
1945
1946         ((qbyte *)buf)[fs_filesize] = 0;
1947
1948         FS_Read (h, buf, fs_filesize);
1949         FS_Close (h);
1950
1951         return buf;
1952 }
1953
1954
1955 /*
1956 ============
1957 FS_WriteFile
1958
1959 The filename will be prefixed by the current game directory
1960 ============
1961 */
1962 qboolean FS_WriteFile (const char *filename, void *data, int len)
1963 {
1964         qfile_t *handle;
1965
1966         handle = FS_Open (filename, "wb", false);
1967         if (!handle)
1968         {
1969                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
1970                 return false;
1971         }
1972
1973         Con_DPrintf("FS_WriteFile: %s\n", filename);
1974         FS_Write (handle, data, len);
1975         FS_Close (handle);
1976         return true;
1977 }
1978
1979
1980 /*
1981 =============================================================================
1982
1983 OTHERS PUBLIC FUNCTIONS
1984
1985 =============================================================================
1986 */
1987
1988 /*
1989 ============
1990 FS_StripExtension
1991 ============
1992 */
1993 void FS_StripExtension (const char *in, char *out, size_t size_out)
1994 {
1995         char *last = NULL;
1996
1997         if (size_out == 0)
1998                 return;
1999
2000         while (*in && size_out > 1)
2001         {
2002                 if (*in == '.')
2003                         last = out;
2004                 else if (*in == '/' || *in == '\\' || *in == ':')
2005                         last = NULL;
2006                 *out++ = *in++;
2007                 size_out--;
2008         }
2009         if (last)
2010                 *last = 0;
2011         else
2012                 *out = 0;
2013 }
2014
2015
2016 /*
2017 ==================
2018 FS_DefaultExtension
2019 ==================
2020 */
2021 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2022 {
2023         const char *src;
2024
2025         // if path doesn't have a .EXT, append extension
2026         // (extension should include the .)
2027         src = path + strlen(path) - 1;
2028
2029         while (*src != '/' && src != path)
2030         {
2031                 if (*src == '.')
2032                         return;                 // it has an extension
2033                 src--;
2034         }
2035
2036         strlcat (path, extension, size_path);
2037 }
2038
2039
2040 /*
2041 ==================
2042 FS_FileExists
2043
2044 Look for a file in the packages and in the filesystem
2045 ==================
2046 */
2047 qboolean FS_FileExists (const char *filename)
2048 {
2049         return (FS_FindFile (filename, NULL, true) != NULL);
2050 }
2051
2052
2053 /*
2054 ==================
2055 FS_SysFileExists
2056
2057 Look for a file in the filesystem only
2058 ==================
2059 */
2060 qboolean FS_SysFileExists (const char *path)
2061 {
2062 #if WIN32
2063         FILE *f;
2064
2065         f = fopen (path, "rb");
2066         if (f)
2067         {
2068                 fclose (f);
2069                 return true;
2070         }
2071
2072         return false;
2073 #else
2074         struct stat buf;
2075
2076         if (stat (path,&buf) == -1)
2077                 return false;
2078
2079         return true;
2080 #endif
2081 }
2082
2083 void FS_mkdir (const char *path)
2084 {
2085 #if WIN32
2086         _mkdir (path);
2087 #else
2088         mkdir (path, 0777);
2089 #endif
2090 }
2091
2092 /*
2093 ===========
2094 FS_Search
2095
2096 Allocate and fill a search structure with information on matching filenames.
2097 ===========
2098 */
2099 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2100 {
2101         fssearch_t *search;
2102         searchpath_t *searchpath;
2103         pack_t *pak;
2104         int i, basepathlength, numfiles, numchars;
2105         stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2106         const char *slash, *backslash, *colon, *separator;
2107         char *basepath;
2108         char netpath[MAX_OSPATH];
2109         char temp[MAX_OSPATH];
2110
2111         while(!strncmp(pattern, "./", 2))
2112                 pattern += 2;
2113         while(!strncmp(pattern, ".\\", 2))
2114                 pattern += 2;
2115
2116         search = NULL;
2117         liststart = NULL;
2118         listcurrent = NULL;
2119         listtemp = NULL;
2120         slash = strrchr(pattern, '/');
2121         backslash = strrchr(pattern, '\\');
2122         colon = strrchr(pattern, ':');
2123         separator = pattern;
2124         if (separator < slash)
2125                 separator = slash;
2126         if (separator < backslash)
2127                 separator = backslash;
2128         if (separator < colon)
2129                 separator = colon;
2130         basepathlength = separator - pattern;
2131         basepath = Mem_Alloc (tempmempool, basepathlength + 1);
2132         if (basepathlength)
2133                 memcpy(basepath, pattern, basepathlength);
2134         basepath[basepathlength] = 0;
2135
2136         // search through the path, one element at a time
2137         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2138         {
2139                 // is the element a pak file?
2140                 if (searchpath->pack)
2141                 {
2142                         // look through all the pak file elements
2143                         pak = searchpath->pack;
2144                         for (i = 0;i < pak->numfiles;i++)
2145                         {
2146                                 strcpy(temp, pak->files[i].name);
2147                                 while (temp[0])
2148                                 {
2149                                         if (matchpattern(temp, (char *)pattern, true))
2150                                         {
2151                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2152                                                         if (!strcmp(listtemp->text, temp))
2153                                                                 break;
2154                                                 if (listtemp == NULL)
2155                                                 {
2156                                                         listcurrent = stringlistappend(listcurrent, temp);
2157                                                         if (liststart == NULL)
2158                                                                 liststart = listcurrent;
2159                                                         if (!quiet)
2160                                                                 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2161                                                 }
2162                                         }
2163                                         // strip off one path element at a time until empty
2164                                         // this way directories are added to the listing if they match the pattern
2165                                         slash = strrchr(temp, '/');
2166                                         backslash = strrchr(temp, '\\');
2167                                         colon = strrchr(temp, ':');
2168                                         separator = temp;
2169                                         if (separator < slash)
2170                                                 separator = slash;
2171                                         if (separator < backslash)
2172                                                 separator = backslash;
2173                                         if (separator < colon)
2174                                                 separator = colon;
2175                                         *((char *)separator) = 0;
2176                                 }
2177                         }
2178                 }
2179                 else
2180                 {
2181                         // get a directory listing and look at each name
2182                         snprintf(netpath, sizeof (netpath), "%s/%s", searchpath->filename, basepath);
2183                         if ((dir = listdirectory(netpath)))
2184                         {
2185                                 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2186                                 {
2187                                         snprintf(temp, sizeof(temp), "%s/%s", basepath, dirfile->text);
2188                                         if (matchpattern(temp, (char *)pattern, true))
2189                                         {
2190                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2191                                                         if (!strcmp(listtemp->text, temp))
2192                                                                 break;
2193                                                 if (listtemp == NULL)
2194                                                 {
2195                                                         listcurrent = stringlistappend(listcurrent, temp);
2196                                                         if (liststart == NULL)
2197                                                                 liststart = listcurrent;
2198                                                         if (!quiet)
2199                                                                 Con_DPrintf("SearchDirFile: %s\n", temp);
2200                                                 }
2201                                         }
2202                                 }
2203                                 freedirectory(dir);
2204                         }
2205                 }
2206         }
2207
2208         if (liststart)
2209         {
2210                 liststart = stringlistsort(liststart);
2211                 numfiles = 0;
2212                 numchars = 0;
2213                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2214                 {
2215                         numfiles++;
2216                         numchars += strlen(listtemp->text) + 1;
2217                 }
2218                 search = Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2219                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2220                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2221                 search->numfilenames = numfiles;
2222                 numfiles = 0;
2223                 numchars = 0;
2224                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2225                 {
2226                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
2227                         strcpy(search->filenames[numfiles], listtemp->text);
2228                         numfiles++;
2229                         numchars += strlen(listtemp->text) + 1;
2230                 }
2231                 if (liststart)
2232                         stringlistfree(liststart);
2233         }
2234
2235         Mem_Free(basepath);
2236         return search;
2237 }
2238
2239 void FS_FreeSearch(fssearch_t *search)
2240 {
2241         Z_Free(search);
2242 }
2243
2244 extern int con_linewidth;
2245 int FS_ListDirectory(const char *pattern, int oneperline)
2246 {
2247         int numfiles;
2248         int numcolumns;
2249         int numlines;
2250         int columnwidth;
2251         int linebufpos;
2252         int i, j, k, l;
2253         const char *name;
2254         char linebuf[4096];
2255         fssearch_t *search;
2256         search = FS_Search(pattern, true, true);
2257         if (!search)
2258                 return 0;
2259         numfiles = search->numfilenames;
2260         if (!oneperline)
2261         {
2262                 // FIXME: the names could be added to one column list and then
2263                 // gradually shifted into the next column if they fit, and then the
2264                 // next to make a compact variable width listing but it's a lot more
2265                 // complicated...
2266                 // find width for columns
2267                 columnwidth = 0;
2268                 for (i = 0;i < numfiles;i++)
2269                 {
2270                         l = strlen(search->filenames[i]);
2271                         if (columnwidth < l)
2272                                 columnwidth = l;
2273                 }
2274                 // count the spacing character
2275                 columnwidth++;
2276                 // calculate number of columns
2277                 numcolumns = con_linewidth / columnwidth;
2278                 // don't bother with the column printing if it's only one column
2279                 if (numcolumns >= 2)
2280                 {
2281                         numlines = (numfiles + numcolumns - 1) / numcolumns;
2282                         for (i = 0;i < numlines;i++)
2283                         {
2284                                 linebufpos = 0;
2285                                 for (k = 0;k < numcolumns;k++)
2286                                 {
2287                                         l = i * numcolumns + k;
2288                                         if (l < numfiles)
2289                                         {
2290                                                 name = search->filenames[l];
2291                                                 for (j = 0;name[j] && j < (int)sizeof(linebuf) - 1;j++)
2292                                                         linebuf[linebufpos++] = name[j];
2293                                                 // space out name unless it's the last on the line
2294                                                 if (k < (numcolumns - 1) && l < (numfiles - 1))
2295                                                         for (;j < columnwidth && j < (int)sizeof(linebuf) - 1;j++)
2296                                                                 linebuf[linebufpos++] = ' ';
2297                                         }
2298                                 }
2299                                 linebuf[linebufpos] = 0;
2300                                 Con_Printf("%s\n", linebuf);
2301                         }
2302                 }
2303                 else
2304                         oneperline = true;
2305         }
2306         if (oneperline)
2307                 for (i = 0;i < numfiles;i++)
2308                         Con_Printf("%s\n", search->filenames[i]);
2309         FS_FreeSearch(search);
2310         return numfiles;
2311 }
2312
2313 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2314 {
2315         const char *pattern;
2316         if (Cmd_Argc() > 3)
2317         {
2318                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2319                 return;
2320         }
2321         if (Cmd_Argc() == 2)
2322                 pattern = Cmd_Argv(1);
2323         else
2324                 pattern = "*";
2325         if (!FS_ListDirectory(pattern, oneperline))
2326                 Con_Print("No files found.\n");
2327 }
2328
2329 void FS_Dir_f(void)
2330 {
2331         FS_ListDirectoryCmd("dir", true);
2332 }
2333
2334 void FS_Ls_f(void)
2335 {
2336         FS_ListDirectoryCmd("ls", false);
2337 }
2338