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