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