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