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