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