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