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