]> icculus.org git repositories - divverent/darkplaces.git/blob - fs.c
removed unused packlist variable and pack->next field
[divverent/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 } pack_t;
229
230
231 // Search paths for files (including packages)
232 typedef struct searchpath_s
233 {
234         // only one of filename / pack will be used
235         char filename[MAX_OSPATH];
236         pack_t *pack;
237         struct searchpath_s *next;
238 } searchpath_t;
239
240
241 /*
242 =============================================================================
243
244 FUNCTION PROTOTYPES
245
246 =============================================================================
247 */
248
249 void FS_Dir_f(void);
250 void FS_Ls_f(void);
251
252 static const char *FS_FileExtension (const char *in);
253 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
254 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
255                                                                         fs_offset_t offset, fs_offset_t packsize,
256                                                                         fs_offset_t realsize, int flags);
257
258
259 /*
260 =============================================================================
261
262 VARIABLES
263
264 =============================================================================
265 */
266
267 mempool_t *fs_mempool;
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
587         real_nb_files = PK3_BuildFileList (pack, &eocd);
588         if (real_nb_files < 0)
589         {
590                 Con_Printf ("%s is not a valid PK3 file\n", packfile);
591                 close(pack->handle);
592                 Mem_Free(pack);
593                 return NULL;
594         }
595
596         Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
597         return pack;
598 }
599
600
601 /*
602 ====================
603 PK3_GetTrueFileOffset
604
605 Find where the true file data offset is
606 ====================
607 */
608 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
609 {
610         unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
611         fs_offset_t count;
612
613         // Already found?
614         if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
615                 return true;
616
617         // Load the local file description
618         lseek (pack->handle, pfile->offset, SEEK_SET);
619         count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
620         if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
621         {
622                 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
623                 return false;
624         }
625
626         // Skip name and extra field
627         pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
628
629         pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
630         return true;
631 }
632
633
634 /*
635 =============================================================================
636
637 OTHER PRIVATE FUNCTIONS
638
639 =============================================================================
640 */
641
642
643 /*
644 ====================
645 FS_AddFileToPack
646
647 Add a file to the list of files contained into a package
648 ====================
649 */
650 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
651                                                                          fs_offset_t offset, fs_offset_t packsize,
652                                                                          fs_offset_t realsize, int flags)
653 {
654         int (*strcmp_funct) (const char* str1, const char* str2);
655         int left, right, middle;
656         packfile_t *pfile;
657
658         strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
659
660         // Look for the slot we should put that file into (binary search)
661         left = 0;
662         right = pack->numfiles - 1;
663         while (left <= right)
664         {
665                 int diff;
666
667                 middle = (left + right) / 2;
668                 diff = strcmp_funct (pack->files[middle].name, name);
669
670                 // If we found the file, there's a problem
671                 if (!diff)
672                         Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
673
674                 // If we're too far in the list
675                 if (diff > 0)
676                         right = middle - 1;
677                 else
678                         left = middle + 1;
679         }
680
681         // We have to move the right of the list by one slot to free the one we need
682         pfile = &pack->files[left];
683         memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
684         pack->numfiles++;
685
686         strlcpy (pfile->name, name, sizeof (pfile->name));
687         pfile->offset = offset;
688         pfile->packsize = packsize;
689         pfile->realsize = realsize;
690         pfile->flags = flags;
691
692         return pfile;
693 }
694
695
696 /*
697 ============
698 FS_CreatePath
699
700 Only used for FS_Open.
701 ============
702 */
703 void FS_CreatePath (char *path)
704 {
705         char *ofs, save;
706
707         for (ofs = path+1 ; *ofs ; ofs++)
708         {
709                 if (*ofs == '/' || *ofs == '\\')
710                 {
711                         // create the directory
712                         save = *ofs;
713                         *ofs = 0;
714                         FS_mkdir (path);
715                         *ofs = save;
716                 }
717         }
718 }
719
720
721 /*
722 ============
723 FS_Path_f
724
725 ============
726 */
727 void FS_Path_f (void)
728 {
729         searchpath_t *s;
730
731         Con_Print("Current search path:\n");
732         for (s=fs_searchpaths ; s ; s=s->next)
733         {
734                 if (s->pack)
735                         Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
736                 else
737                         Con_Printf("%s\n", s->filename);
738         }
739 }
740
741
742 /*
743 =================
744 FS_LoadPackPAK
745
746 Takes an explicit (not game tree related) path to a pak file.
747
748 Loads the header and directory, adding the files at the beginning
749 of the list so they override previous pack files.
750 =================
751 */
752 pack_t *FS_LoadPackPAK (const char *packfile)
753 {
754         dpackheader_t header;
755         int i, numpackfiles;
756         int packhandle;
757         pack_t *pack;
758         dpackfile_t *info;
759
760         packhandle = open (packfile, O_RDONLY | O_BINARY);
761         if (packhandle < 0)
762                 return NULL;
763         read (packhandle, (void *)&header, sizeof(header));
764         if (memcmp(header.id, "PACK", 4))
765         {
766                 Con_Printf ("%s is not a packfile\n", packfile);
767                 close(packhandle);
768                 return NULL;
769         }
770         header.dirofs = LittleLong (header.dirofs);
771         header.dirlen = LittleLong (header.dirlen);
772
773         if (header.dirlen % sizeof(dpackfile_t))
774         {
775                 Con_Printf ("%s has an invalid directory size\n", packfile);
776                 close(packhandle);
777                 return NULL;
778         }
779
780         numpackfiles = header.dirlen / sizeof(dpackfile_t);
781
782         if (numpackfiles > MAX_FILES_IN_PACK)
783         {
784                 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
785                 close(packhandle);
786                 return NULL;
787         }
788
789         info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
790         lseek (packhandle, header.dirofs, SEEK_SET);
791         if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
792         {
793                 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
794                 Mem_Free(info);
795                 close(packhandle);
796                 return NULL;
797         }
798
799         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
800         pack->ignorecase = false; // PAK is case sensitive
801         strlcpy (pack->filename, packfile, sizeof (pack->filename));
802         pack->handle = packhandle;
803         pack->numfiles = 0;
804         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
805
806         // parse the directory
807         for (i = 0;i < numpackfiles;i++)
808         {
809                 fs_offset_t offset = LittleLong (info[i].filepos);
810                 fs_offset_t size = LittleLong (info[i].filelen);
811
812                 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
813         }
814
815         Mem_Free(info);
816
817         Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
818         return pack;
819 }
820
821 /*
822 ================
823 FS_AddPack_Fullpath
824
825 Adds the given pack to the search path.
826 The pack type is autodetected by the file extension.
827
828 Returns true if the file was successfully added to the
829 search path or if it was already included.
830
831 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
832 plain directories.
833 ================
834 */
835 static qboolean FS_AddPack_Fullpath(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
836 {
837         searchpath_t *search;
838         pack_t *pak = NULL;
839         const char *ext = FS_FileExtension(pakfile);
840
841         for(search = fs_searchpaths; search; search = search->next)
842         {
843                 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
844                 {
845                         if(already_loaded)
846                                 *already_loaded = true;
847                         return true; // already loaded
848                 }
849         }
850
851         if(already_loaded)
852                 *already_loaded = false;
853
854         if(!strcasecmp(ext, "pak"))
855                 pak = FS_LoadPackPAK (pakfile);
856         else if(!strcasecmp(ext, "pk3"))
857                 pak = FS_LoadPackPK3 (pakfile);
858         else
859                 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
860
861         if (pak)
862         {
863                 if(keep_plain_dirs)
864                 {
865                         // find the first item whose next one is a pack or NULL
866                         searchpath_t *insertion_point = 0;
867                         if(fs_searchpaths && !fs_searchpaths->pack)
868                         {
869                                 insertion_point = fs_searchpaths;
870                                 for(;;)
871                                 {
872                                         if(!insertion_point->next)
873                                                 break;
874                                         if(insertion_point->next->pack)
875                                                 break;
876                                         insertion_point = insertion_point->next;
877                                 }
878                         }
879                         // If insertion_point is NULL, this means that either there is no
880                         // item in the list yet, or that the very first item is a pack. In
881                         // that case, we want to insert at the beginning...
882                         if(!insertion_point)
883                         {
884                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
885                                 search->pack = pak;
886                                 search->next = fs_searchpaths;
887                                 fs_searchpaths = search;
888                         }
889                         else
890                         // otherwise we want to append directly after insertion_point.
891                         {
892                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
893                                 search->pack = pak;
894                                 search->next = insertion_point->next;
895                                 insertion_point->next = search;
896                         }
897                 }
898                 else
899                 {
900                         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
901                         search->pack = pak;
902                         search->next = fs_searchpaths;
903                         fs_searchpaths = search;
904                 }
905                 return true;
906         }
907         else
908         {
909                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
910                 return false;
911         }
912 }
913
914
915 /*
916 ================
917 FS_AddPack
918
919 Adds the given pack to the search path and searches for it in the game path.
920 The pack type is autodetected by the file extension.
921
922 Returns true if the file was successfully added to the
923 search path or if it was already included.
924
925 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
926 plain directories.
927 ================
928 */
929 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
930 {
931         char fullpath[MAX_QPATH];
932         int index;
933         searchpath_t *search;
934
935         if(already_loaded)
936                 *already_loaded = false;
937
938         // then find the real name...
939         search = FS_FindFile(pakfile, &index, true);
940         if(!search || search->pack)
941         {
942                 Con_Printf("could not find pak \"%s\"\n", pakfile);
943                 return false;
944         }
945
946         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
947
948         return FS_AddPack_Fullpath(fullpath, already_loaded, keep_plain_dirs);
949 }
950
951
952 /*
953 ================
954 FS_AddGameDirectory
955
956 Sets fs_gamedir, adds the directory to the head of the path,
957 then loads and adds pak1.pak pak2.pak ...
958 ================
959 */
960 void FS_AddGameDirectory (const char *dir)
961 {
962         stringlist_t *list, *current;
963         searchpath_t *search;
964         char pakfile[MAX_OSPATH];
965
966         strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
967
968         list = listdirectory(dir);
969
970         // add any PAK package in the directory
971         for (current = list;current;current = current->next)
972         {
973                 if (!strcasecmp(FS_FileExtension(current->text), "pak"))
974                 {
975                         dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
976                         FS_AddPack_Fullpath(pakfile, NULL, false);
977                 }
978         }
979
980         // add any PK3 package in the director
981         for (current = list;current;current = current->next)
982         {
983                 if (!strcasecmp(FS_FileExtension(current->text), "pk3"))
984                 {
985                         dpsnprintf (pakfile, sizeof (pakfile), "%s%s", dir, current->text);
986                         FS_AddPack_Fullpath(pakfile, NULL, false);
987                 }
988         }
989         freedirectory(list);
990
991         // Add the directory to the search path
992         // (unpacked files have the priority over packed files)
993         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
994         strlcpy (search->filename, dir, sizeof (search->filename));
995         search->next = fs_searchpaths;
996         fs_searchpaths = search;
997 }
998
999
1000 /*
1001 ================
1002 FS_AddGameHierarchy
1003 ================
1004 */
1005 void FS_AddGameHierarchy (const char *dir)
1006 {
1007 #ifndef WIN32
1008         const char *homedir;
1009 #endif
1010
1011         // Add the common game directory
1012         FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1013
1014 #ifndef WIN32
1015         // Add the personal game directory
1016         homedir = getenv ("HOME");
1017         if (homedir != NULL && homedir[0] != '\0')
1018                 FS_AddGameDirectory (va("%s/.%s/%s/", homedir, gameuserdirname, dir));
1019 #endif
1020 }
1021
1022
1023 /*
1024 ============
1025 FS_FileExtension
1026 ============
1027 */
1028 static const char *FS_FileExtension (const char *in)
1029 {
1030         const char *separator, *backslash, *colon, *dot;
1031
1032         separator = strrchr(in, '/');
1033         backslash = strrchr(in, '\\');
1034         if (!separator || separator < backslash)
1035                 separator = backslash;
1036         colon = strrchr(in, ':');
1037         if (!separator || separator < colon)
1038                 separator = colon;
1039
1040         dot = strrchr(in, '.');
1041         if (dot == NULL || (separator && (dot < separator)))
1042                 return "";
1043
1044         return dot + 1;
1045 }
1046
1047
1048 /*
1049 ================
1050 FS_Init
1051 ================
1052 */
1053 void FS_Init (void)
1054 {
1055         int i;
1056         searchpath_t *search;
1057
1058         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1059
1060         strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1061
1062 // If the base directory is explicitly defined by the compilation process
1063 #ifdef DP_FS_BASEDIR
1064         strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1065 #else
1066         strlcpy(fs_basedir, "", sizeof(fs_basedir));
1067
1068 #ifdef MACOSX
1069         // FIXME: is there a better way to find the directory outside the .app?
1070         if (strstr(com_argv[0], ".app/"))
1071         {
1072                 char *split;
1073
1074                 split = strstr(com_argv[0], ".app/");
1075                 while (split > com_argv[0] && *split != '/')
1076                         split--;
1077                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1078                 fs_basedir[split - com_argv[0]] = 0;
1079         }
1080 #endif
1081 #endif
1082
1083         PK3_OpenLibrary ();
1084
1085         // -basedir <path>
1086         // Overrides the system supplied base directory (under GAMENAME)
1087 // 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)
1088         i = COM_CheckParm ("-basedir");
1089         if (i && i < com_argc-1)
1090         {
1091                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1092                 i = (int)strlen (fs_basedir);
1093                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1094                         fs_basedir[i-1] = 0;
1095         }
1096
1097         // add a path separator to the end of the basedir if it lacks one
1098         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1099                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1100
1101         // -path <dir or packfile> [<dir or packfile>] ...
1102         // Fully specifies the exact search path, overriding the generated one
1103 // 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)
1104         i = COM_CheckParm ("-path");
1105         if (i)
1106         {
1107                 fs_modified = true;
1108                 while (++i < com_argc)
1109                 {
1110                         if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
1111                                 break;
1112
1113                         if(!FS_AddPack_Fullpath(com_argv[i], NULL, false))
1114                         {
1115                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1116                                 strlcpy (search->filename, com_argv[i], sizeof (search->filename));
1117                                 search->next = fs_searchpaths;
1118                                 fs_searchpaths = search;
1119                         }
1120                 }
1121         }
1122         else
1123         {
1124                 // add the game-specific paths
1125                 // gamedirname1 (typically id1)
1126                 FS_AddGameHierarchy (gamedirname1);
1127
1128                 // add the game-specific path, if any
1129                 if (gamedirname2)
1130                 {
1131                         fs_modified = true;
1132                         FS_AddGameHierarchy (gamedirname2);
1133                 }
1134
1135                 // set the com_modname (reported in server info)
1136                 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1137
1138                 // -game <gamedir>
1139                 // Adds basedir/gamedir as an override game
1140                 // LordHavoc: now supports multiple -game directories
1141                 for (i = 1;i < com_argc;i++)
1142                 {
1143                         if (!com_argv[i])
1144                                 continue;
1145                         if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1146                         {
1147                                 i++;
1148                                 fs_modified = true;
1149                                 FS_AddGameHierarchy (com_argv[i]);
1150                                 // update the com_modname
1151                                 strlcpy (com_modname, com_argv[i], sizeof (com_modname));
1152                         }
1153                 }
1154
1155                 // If "-condebug" is in the command line, remove the previous log file
1156                 if (COM_CheckParm ("-condebug") != 0)
1157                         unlink (va("%s/qconsole.log", fs_gamedir));
1158         }
1159
1160         // look for the pop.lmp file and set registered to true if it is found
1161         if (gamemode == GAME_NORMAL && !FS_FileExists("gfx/pop.lmp"))
1162         {
1163                 if (fs_modified)
1164                         Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1165                 else
1166                         Con_Print("Playing shareware version.\n");
1167         }
1168         else
1169         {
1170                 Cvar_Set ("registered", "1");
1171                 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1172                         Con_Print("Playing registered version.\n");
1173         }
1174
1175         // set the default screenshot name to either the mod name or the
1176         // gamemode screenshot name
1177         if (fs_modified)
1178                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1179         else
1180                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1181 }
1182
1183 void FS_Init_Commands(void)
1184 {
1185         Cvar_RegisterVariable (&scr_screenshot_name);
1186
1187         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1188         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1189         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1190 }
1191
1192 /*
1193 ================
1194 FS_Shutdown
1195 ================
1196 */
1197 void FS_Shutdown (void)
1198 {
1199         Mem_FreePool (&fs_mempool);
1200 }
1201
1202 /*
1203 ====================
1204 FS_SysOpen
1205
1206 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1207 ====================
1208 */
1209 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1210 {
1211         qfile_t* file;
1212         int mod, opt;
1213         unsigned int ind;
1214
1215         // Parse the mode string
1216         switch (mode[0])
1217         {
1218                 case 'r':
1219                         mod = O_RDONLY;
1220                         opt = 0;
1221                         break;
1222                 case 'w':
1223                         mod = O_WRONLY;
1224                         opt = O_CREAT | O_TRUNC;
1225                         break;
1226                 case 'a':
1227                         mod = O_WRONLY;
1228                         opt = O_CREAT | O_APPEND;
1229                         break;
1230                 default:
1231                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1232                         return NULL;
1233         }
1234         for (ind = 1; mode[ind] != '\0'; ind++)
1235         {
1236                 switch (mode[ind])
1237                 {
1238                         case '+':
1239                                 mod = O_RDWR;
1240                                 break;
1241                         case 'b':
1242                                 opt |= O_BINARY;
1243                                 break;
1244                         default:
1245                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1246                                                         filepath, mode, mode[ind]);
1247                 }
1248         }
1249
1250         if (nonblocking)
1251                 opt |= O_NONBLOCK;
1252
1253         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1254         memset (file, 0, sizeof (*file));
1255         file->ungetc = EOF;
1256
1257         file->handle = open (filepath, mod | opt, 0666);
1258         if (file->handle < 0)
1259         {
1260                 Mem_Free (file);
1261                 return NULL;
1262         }
1263
1264         file->real_length = lseek (file->handle, 0, SEEK_END);
1265
1266         // For files opened in append mode, we start at the end of the file
1267         if (mod & O_APPEND)
1268                 file->position = file->real_length;
1269         else
1270                 lseek (file->handle, 0, SEEK_SET);
1271
1272         return file;
1273 }
1274
1275
1276 /*
1277 ===========
1278 FS_OpenPackedFile
1279
1280 Open a packed file using its package file descriptor
1281 ===========
1282 */
1283 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1284 {
1285         packfile_t *pfile;
1286         int dup_handle;
1287         qfile_t* file;
1288
1289         pfile = &pack->files[pack_ind];
1290
1291         // If we don't have the true offset, get it now
1292         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1293                 if (!PK3_GetTrueFileOffset (pfile, pack))
1294                         return NULL;
1295
1296         // No Zlib DLL = no compressed files
1297         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1298         {
1299                 Con_Printf("WARNING: can't open the compressed file %s\n"
1300                                         "You need the Zlib DLL to use compressed files\n",
1301                                         pfile->name);
1302                 return NULL;
1303         }
1304
1305         // LordHavoc: lseek affects all duplicates of a handle so we do it before
1306         // the dup() call to avoid having to close the dup_handle on error here
1307         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1308         {
1309                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1310                                         pfile->name, pack->filename, pfile->offset);
1311                 return NULL;
1312         }
1313
1314         dup_handle = dup (pack->handle);
1315         if (dup_handle < 0)
1316         {
1317                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1318                 return NULL;
1319         }
1320
1321         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1322         memset (file, 0, sizeof (*file));
1323         file->handle = dup_handle;
1324         file->flags = QFILE_FLAG_PACKED;
1325         file->real_length = pfile->realsize;
1326         file->offset = pfile->offset;
1327         file->position = 0;
1328         file->ungetc = EOF;
1329
1330         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1331         {
1332                 ztoolkit_t *ztk;
1333
1334                 file->flags |= QFILE_FLAG_DEFLATED;
1335
1336                 // We need some more variables
1337                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1338
1339                 ztk->comp_length = pfile->packsize;
1340
1341                 // Initialize zlib stream
1342                 ztk->zstream.next_in = ztk->input;
1343                 ztk->zstream.avail_in = 0;
1344
1345                 /* From Zlib's "unzip.c":
1346                  *
1347                  * windowBits is passed < 0 to tell that there is no zlib header.
1348                  * Note that in this case inflate *requires* an extra "dummy" byte
1349                  * after the compressed stream in order to complete decompression and
1350                  * return Z_STREAM_END.
1351                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1352                  * size of both compressed and uncompressed data
1353                  */
1354                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1355                 {
1356                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1357                         close(dup_handle);
1358                         Mem_Free(file);
1359                         return NULL;
1360                 }
1361
1362                 ztk->zstream.next_out = file->buff;
1363                 ztk->zstream.avail_out = sizeof (file->buff);
1364
1365                 file->ztk = ztk;
1366         }
1367
1368         return file;
1369 }
1370
1371 /*
1372 ====================
1373 FS_CheckNastyPath
1374
1375 Return true if the path should be rejected due to one of the following:
1376 1: path elements that are non-portable
1377 2: path elements that would allow access to files outside the game directory,
1378    or are just not a good idea for a mod to be using.
1379 ====================
1380 */
1381 int FS_CheckNastyPath (const char *path)
1382 {
1383         // Windows: don't allow \ in filenames (windows-only), period.
1384         // (on Windows \ is a directory separator, but / is also supported)
1385         if (strstr(path, "\\"))
1386                 return 1; // non-portable
1387
1388         // Mac: don't allow Mac-only filenames - : is a directory separator
1389         // instead of /, but we rely on / working already, so there's no reason to
1390         // support a Mac-only path
1391         // Amiga and Windows: : tries to go to root of drive
1392         if (strstr(path, ":"))
1393                 return 1; // non-portable attempt to go to root of drive
1394
1395         // Amiga: // is parent directory
1396         if (strstr(path, "//"))
1397                 return 1; // non-portable attempt to go to parent directory
1398
1399         // all: don't allow going to current directory (./) or parent directory (../ or /../)
1400         if (strstr(path, "./"))
1401                 return 2; // attempt to go outside the game directory
1402
1403         // Windows and UNIXes: don't allow absolute paths
1404         if (path[0] == '/')
1405                 return 2; // attempt to go outside the game directory
1406
1407         // after all these checks we're pretty sure it's a / separated filename
1408         // and won't do much if any harm
1409         return false;
1410 }
1411
1412
1413 /*
1414 ====================
1415 FS_FindFile
1416
1417 Look for a file in the packages and in the filesystem
1418
1419 Return the searchpath where the file was found (or NULL)
1420 and the file index in the package if relevant
1421 ====================
1422 */
1423 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1424 {
1425         searchpath_t *search;
1426         pack_t *pak;
1427
1428         // search through the path, one element at a time
1429         for (search = fs_searchpaths;search;search = search->next)
1430         {
1431                 // is the element a pak file?
1432                 if (search->pack)
1433                 {
1434                         int (*strcmp_funct) (const char* str1, const char* str2);
1435                         int left, right, middle;
1436
1437                         pak = search->pack;
1438                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1439
1440                         // Look for the file (binary search)
1441                         left = 0;
1442                         right = pak->numfiles - 1;
1443                         while (left <= right)
1444                         {
1445                                 int diff;
1446
1447                                 middle = (left + right) / 2;
1448                                 diff = strcmp_funct (pak->files[middle].name, name);
1449
1450                                 // Found it
1451                                 if (!diff)
1452                                 {
1453                                         if (!quiet && developer.integer >= 10)
1454                                                 Con_Printf("FS_FindFile: %s in %s\n",
1455                                                                         pak->files[middle].name, pak->filename);
1456
1457                                         if (index != NULL)
1458                                                 *index = middle;
1459                                         return search;
1460                                 }
1461
1462                                 // If we're too far in the list
1463                                 if (diff > 0)
1464                                         right = middle - 1;
1465                                 else
1466                                         left = middle + 1;
1467                         }
1468                 }
1469                 else
1470                 {
1471                         char netpath[MAX_OSPATH];
1472                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1473                         if (FS_SysFileExists (netpath))
1474                         {
1475                                 if (!quiet && developer.integer >= 10)
1476                                         Con_Printf("FS_FindFile: %s\n", netpath);
1477
1478                                 if (index != NULL)
1479                                         *index = -1;
1480                                 return search;
1481                         }
1482                 }
1483         }
1484
1485         if (!quiet && developer.integer >= 10)
1486                 Con_Printf("FS_FindFile: can't find %s\n", name);
1487
1488         if (index != NULL)
1489                 *index = -1;
1490         return NULL;
1491 }
1492
1493
1494 /*
1495 ===========
1496 FS_OpenReadFile
1497
1498 Look for a file in the search paths and open it in read-only mode
1499 ===========
1500 */
1501 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1502 {
1503         searchpath_t *search;
1504         int pack_ind;
1505
1506         search = FS_FindFile (filename, &pack_ind, quiet);
1507
1508         // Not found?
1509         if (search == NULL)
1510                 return NULL;
1511
1512         // Found in the filesystem?
1513         if (pack_ind < 0)
1514         {
1515                 char path [MAX_OSPATH];
1516                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1517                 return FS_SysOpen (path, "rb", nonblocking);
1518         }
1519
1520         // So, we found it in a package...
1521         return FS_OpenPackedFile (search->pack, pack_ind);
1522 }
1523
1524
1525 /*
1526 =============================================================================
1527
1528 MAIN PUBLIC FUNCTIONS
1529
1530 =============================================================================
1531 */
1532
1533 /*
1534 ====================
1535 FS_Open
1536
1537 Open a file. The syntax is the same as fopen
1538 ====================
1539 */
1540 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1541 {
1542         if (FS_CheckNastyPath(filepath))
1543         {
1544                 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1545                 return NULL;
1546         }
1547
1548         // If the file is opened in "write", "append", or "read/write" mode
1549         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1550         {
1551                 char real_path [MAX_OSPATH];
1552
1553                 // Open the file on disk directly
1554                 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1555
1556                 // Create directories up to the file
1557                 FS_CreatePath (real_path);
1558
1559                 return FS_SysOpen (real_path, mode, nonblocking);
1560         }
1561         // Else, we look at the various search paths and open the file in read-only mode
1562         else
1563                 return FS_OpenReadFile (filepath, quiet, nonblocking);
1564 }
1565
1566
1567 /*
1568 ====================
1569 FS_Close
1570
1571 Close a file
1572 ====================
1573 */
1574 int FS_Close (qfile_t* file)
1575 {
1576         if (close (file->handle))
1577                 return EOF;
1578
1579         if (file->ztk)
1580         {
1581                 qz_inflateEnd (&file->ztk->zstream);
1582                 Mem_Free (file->ztk);
1583         }
1584
1585         Mem_Free (file);
1586         return 0;
1587 }
1588
1589
1590 /*
1591 ====================
1592 FS_Write
1593
1594 Write "datasize" bytes into a file
1595 ====================
1596 */
1597 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1598 {
1599         fs_offset_t result;
1600
1601         // If necessary, seek to the exact file position we're supposed to be
1602         if (file->buff_ind != file->buff_len)
1603                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1604
1605         // Purge cached data
1606         FS_Purge (file);
1607
1608         // Write the buffer and update the position
1609         result = write (file->handle, data, (fs_offset_t)datasize);
1610         file->position = lseek (file->handle, 0, SEEK_CUR);
1611         if (file->real_length < file->position)
1612                 file->real_length = file->position;
1613
1614         if (result < 0)
1615                 return 0;
1616
1617         return result;
1618 }
1619
1620
1621 /*
1622 ====================
1623 FS_Read
1624
1625 Read up to "buffersize" bytes from a file
1626 ====================
1627 */
1628 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1629 {
1630         fs_offset_t count, done;
1631
1632         if (buffersize == 0)
1633                 return 0;
1634
1635         // Get rid of the ungetc character
1636         if (file->ungetc != EOF)
1637         {
1638                 ((char*)buffer)[0] = file->ungetc;
1639                 buffersize--;
1640                 file->ungetc = EOF;
1641                 done = 1;
1642         }
1643         else
1644                 done = 0;
1645
1646         // First, we copy as many bytes as we can from "buff"
1647         if (file->buff_ind < file->buff_len)
1648         {
1649                 count = file->buff_len - file->buff_ind;
1650
1651                 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1652                 memcpy (buffer, &file->buff[file->buff_ind], done);
1653                 file->buff_ind += done;
1654
1655                 buffersize -= done;
1656                 if (buffersize == 0)
1657                         return done;
1658         }
1659
1660         // NOTE: at this point, the read buffer is always empty
1661
1662         // If the file isn't compressed
1663         if (! (file->flags & QFILE_FLAG_DEFLATED))
1664         {
1665                 fs_offset_t nb;
1666
1667                 // We must take care to not read after the end of the file
1668                 count = file->real_length - file->position;
1669
1670                 // If we have a lot of data to get, put them directly into "buffer"
1671                 if (buffersize > sizeof (file->buff) / 2)
1672                 {
1673                         if (count > (fs_offset_t)buffersize)
1674                                 count = (fs_offset_t)buffersize;
1675                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1676                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1677                         if (nb > 0)
1678                         {
1679                                 done += nb;
1680                                 file->position += nb;
1681
1682                                 // Purge cached data
1683                                 FS_Purge (file);
1684                         }
1685                 }
1686                 else
1687                 {
1688                         if (count > (fs_offset_t)sizeof (file->buff))
1689                                 count = (fs_offset_t)sizeof (file->buff);
1690                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1691                         nb = read (file->handle, file->buff, count);
1692                         if (nb > 0)
1693                         {
1694                                 file->buff_len = nb;
1695                                 file->position += nb;
1696
1697                                 // Copy the requested data in "buffer" (as much as we can)
1698                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1699                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1700                                 file->buff_ind = count;
1701                                 done += count;
1702                         }
1703                 }
1704
1705                 return done;
1706         }
1707
1708         // If the file is compressed, it's more complicated...
1709         // We cycle through a few operations until we have read enough data
1710         while (buffersize > 0)
1711         {
1712                 ztoolkit_t *ztk = file->ztk;
1713                 int error;
1714
1715                 // NOTE: at this point, the read buffer is always empty
1716
1717                 // If "input" is also empty, we need to refill it
1718                 if (ztk->in_ind == ztk->in_len)
1719                 {
1720                         // If we are at the end of the file
1721                         if (file->position == file->real_length)
1722                                 return done;
1723
1724                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1725                         if (count > (fs_offset_t)sizeof (ztk->input))
1726                                 count = (fs_offset_t)sizeof (ztk->input);
1727                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1728                         if (read (file->handle, ztk->input, count) != count)
1729                         {
1730                                 Con_Printf ("FS_Read: unexpected end of file\n");
1731                                 break;
1732                         }
1733
1734                         ztk->in_ind = 0;
1735                         ztk->in_len = count;
1736                         ztk->in_position += count;
1737                 }
1738
1739                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1740                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1741
1742                 // Now that we are sure we have compressed data available, we need to determine
1743                 // if it's better to inflate it in "file->buff" or directly in "buffer"
1744
1745                 // Inflate the data in "file->buff"
1746                 if (buffersize < sizeof (file->buff) / 2)
1747                 {
1748                         ztk->zstream.next_out = file->buff;
1749                         ztk->zstream.avail_out = sizeof (file->buff);
1750                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1751                         if (error != Z_OK && error != Z_STREAM_END)
1752                         {
1753                                 Con_Printf ("FS_Read: Can't inflate file\n");
1754                                 break;
1755                         }
1756                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1757
1758                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1759                         file->position += file->buff_len;
1760
1761                         // Copy the requested data in "buffer" (as much as we can)
1762                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1763                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1764                         file->buff_ind = count;
1765                 }
1766
1767                 // Else, we inflate directly in "buffer"
1768                 else
1769                 {
1770                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
1771                         ztk->zstream.avail_out = (unsigned int)buffersize;
1772                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1773                         if (error != Z_OK && error != Z_STREAM_END)
1774                         {
1775                                 Con_Printf ("FS_Read: Can't inflate file\n");
1776                                 break;
1777                         }
1778                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1779
1780                         // How much data did it inflate?
1781                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
1782                         file->position += count;
1783
1784                         // Purge cached data
1785                         FS_Purge (file);
1786                 }
1787
1788                 done += count;
1789                 buffersize -= count;
1790         }
1791
1792         return done;
1793 }
1794
1795
1796 /*
1797 ====================
1798 FS_Print
1799
1800 Print a string into a file
1801 ====================
1802 */
1803 int FS_Print (qfile_t* file, const char *msg)
1804 {
1805         return (int)FS_Write (file, msg, strlen (msg));
1806 }
1807
1808 /*
1809 ====================
1810 FS_Printf
1811
1812 Print a string into a file
1813 ====================
1814 */
1815 int FS_Printf(qfile_t* file, const char* format, ...)
1816 {
1817         int result;
1818         va_list args;
1819
1820         va_start (args, format);
1821         result = FS_VPrintf (file, format, args);
1822         va_end (args);
1823
1824         return result;
1825 }
1826
1827
1828 /*
1829 ====================
1830 FS_VPrintf
1831
1832 Print a string into a file
1833 ====================
1834 */
1835 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
1836 {
1837         int len;
1838         fs_offset_t buff_size = MAX_INPUTLINE;
1839         char *tempbuff;
1840
1841         for (;;)
1842         {
1843                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
1844                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
1845                 if (len >= 0 && len < buff_size)
1846                         break;
1847                 Mem_Free (tempbuff);
1848                 buff_size *= 2;
1849         }
1850
1851         len = write (file->handle, tempbuff, len);
1852         Mem_Free (tempbuff);
1853
1854         return len;
1855 }
1856
1857
1858 /*
1859 ====================
1860 FS_Getc
1861
1862 Get the next character of a file
1863 ====================
1864 */
1865 int FS_Getc (qfile_t* file)
1866 {
1867         char c;
1868
1869         if (FS_Read (file, &c, 1) != 1)
1870                 return EOF;
1871
1872         return c;
1873 }
1874
1875
1876 /*
1877 ====================
1878 FS_UnGetc
1879
1880 Put a character back into the read buffer (only supports one character!)
1881 ====================
1882 */
1883 int FS_UnGetc (qfile_t* file, unsigned char c)
1884 {
1885         // If there's already a character waiting to be read
1886         if (file->ungetc != EOF)
1887                 return EOF;
1888
1889         file->ungetc = c;
1890         return c;
1891 }
1892
1893
1894 /*
1895 ====================
1896 FS_Seek
1897
1898 Move the position index in a file
1899 ====================
1900 */
1901 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
1902 {
1903         ztoolkit_t *ztk;
1904         unsigned char* buffer;
1905         fs_offset_t buffersize;
1906
1907         // Compute the file offset
1908         switch (whence)
1909         {
1910                 case SEEK_CUR:
1911                         offset += file->position - file->buff_len + file->buff_ind;
1912                         break;
1913
1914                 case SEEK_SET:
1915                         break;
1916
1917                 case SEEK_END:
1918                         offset += file->real_length;
1919                         break;
1920
1921                 default:
1922                         return -1;
1923         }
1924         if (offset < 0 || offset > (long) file->real_length)
1925                 return -1;
1926
1927         // If we have the data in our read buffer, we don't need to actually seek
1928         if (file->position - file->buff_len <= offset && offset <= file->position)
1929         {
1930                 file->buff_ind = offset + file->buff_len - file->position;
1931                 return 0;
1932         }
1933
1934         // Purge cached data
1935         FS_Purge (file);
1936
1937         // Unpacked or uncompressed files can seek directly
1938         if (! (file->flags & QFILE_FLAG_DEFLATED))
1939         {
1940                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
1941                         return -1;
1942                 file->position = offset;
1943                 return 0;
1944         }
1945
1946         // Seeking in compressed files is more a hack than anything else,
1947         // but we need to support it, so here we go.
1948         ztk = file->ztk;
1949
1950         // If we have to go back in the file, we need to restart from the beginning
1951         if (offset <= file->position)
1952         {
1953                 ztk->in_ind = 0;
1954                 ztk->in_len = 0;
1955                 ztk->in_position = 0;
1956                 file->position = 0;
1957                 lseek (file->handle, file->offset, SEEK_SET);
1958
1959                 // Reset the Zlib stream
1960                 ztk->zstream.next_in = ztk->input;
1961                 ztk->zstream.avail_in = 0;
1962                 qz_inflateReset (&ztk->zstream);
1963         }
1964
1965         // We need a big buffer to force inflating into it directly
1966         buffersize = 2 * sizeof (file->buff);
1967         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
1968
1969         // Skip all data until we reach the requested offset
1970         while (offset > file->position)
1971         {
1972                 fs_offset_t diff = offset - file->position;
1973                 fs_offset_t count, len;
1974
1975                 count = (diff > buffersize) ? buffersize : diff;
1976                 len = FS_Read (file, buffer, count);
1977                 if (len != count)
1978                 {
1979                         Mem_Free (buffer);
1980                         return -1;
1981                 }
1982         }
1983
1984         Mem_Free (buffer);
1985         return 0;
1986 }
1987
1988
1989 /*
1990 ====================
1991 FS_Tell
1992
1993 Give the current position in a file
1994 ====================
1995 */
1996 fs_offset_t FS_Tell (qfile_t* file)
1997 {
1998         return file->position - file->buff_len + file->buff_ind;
1999 }
2000
2001
2002 /*
2003 ====================
2004 FS_Purge
2005
2006 Erases any buffered input or output data
2007 ====================
2008 */
2009 void FS_Purge (qfile_t* file)
2010 {
2011         file->buff_len = 0;
2012         file->buff_ind = 0;
2013         file->ungetc = EOF;
2014 }
2015
2016
2017 /*
2018 ============
2019 FS_LoadFile
2020
2021 Filename are relative to the quake directory.
2022 Always appends a 0 byte.
2023 ============
2024 */
2025 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2026 {
2027         qfile_t *file;
2028         unsigned char *buf = NULL;
2029         fs_offset_t filesize = 0;
2030
2031         file = FS_Open (path, "rb", quiet, false);
2032         if (file)
2033         {
2034                 filesize = file->real_length;
2035                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2036                 buf[filesize] = '\0';
2037                 FS_Read (file, buf, filesize);
2038                 FS_Close (file);
2039         }
2040
2041         if (filesizepointer)
2042                 *filesizepointer = filesize;
2043         return buf;
2044 }
2045
2046
2047 /*
2048 ============
2049 FS_WriteFile
2050
2051 The filename will be prefixed by the current game directory
2052 ============
2053 */
2054 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2055 {
2056         qfile_t *file;
2057
2058         file = FS_Open (filename, "wb", false, false);
2059         if (!file)
2060         {
2061                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2062                 return false;
2063         }
2064
2065         Con_DPrintf("FS_WriteFile: %s\n", filename);
2066         FS_Write (file, data, len);
2067         FS_Close (file);
2068         return true;
2069 }
2070
2071
2072 /*
2073 =============================================================================
2074
2075 OTHERS PUBLIC FUNCTIONS
2076
2077 =============================================================================
2078 */
2079
2080 /*
2081 ============
2082 FS_StripExtension
2083 ============
2084 */
2085 void FS_StripExtension (const char *in, char *out, size_t size_out)
2086 {
2087         char *last = NULL;
2088         char currentchar;
2089
2090         if (size_out == 0)
2091                 return;
2092
2093         while ((currentchar = *in) && size_out > 1)
2094         {
2095                 if (currentchar == '.')
2096                         last = out;
2097                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2098                         last = NULL;
2099                 *out++ = currentchar;
2100                 in++;
2101                 size_out--;
2102         }
2103         if (last)
2104                 *last = 0;
2105         else
2106                 *out = 0;
2107 }
2108
2109
2110 /*
2111 ==================
2112 FS_DefaultExtension
2113 ==================
2114 */
2115 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2116 {
2117         const char *src;
2118
2119         // if path doesn't have a .EXT, append extension
2120         // (extension should include the .)
2121         src = path + strlen(path) - 1;
2122
2123         while (*src != '/' && src != path)
2124         {
2125                 if (*src == '.')
2126                         return;                 // it has an extension
2127                 src--;
2128         }
2129
2130         strlcat (path, extension, size_path);
2131 }
2132
2133
2134 /*
2135 ==================
2136 FS_FileExists
2137
2138 Look for a file in the packages and in the filesystem
2139 ==================
2140 */
2141 qboolean FS_FileExists (const char *filename)
2142 {
2143         return (FS_FindFile (filename, NULL, true) != NULL);
2144 }
2145
2146
2147 /*
2148 ==================
2149 FS_SysFileExists
2150
2151 Look for a file in the filesystem only
2152 ==================
2153 */
2154 qboolean FS_SysFileExists (const char *path)
2155 {
2156 #if WIN32
2157         int desc;
2158
2159         // TODO: use another function instead, to avoid opening the file
2160         desc = open (path, O_RDONLY | O_BINARY);
2161         if (desc < 0)
2162                 return false;
2163
2164         close (desc);
2165         return true;
2166 #else
2167         struct stat buf;
2168
2169         if (stat (path,&buf) == -1)
2170                 return false;
2171
2172         return true;
2173 #endif
2174 }
2175
2176 void FS_mkdir (const char *path)
2177 {
2178 #if WIN32
2179         _mkdir (path);
2180 #else
2181         mkdir (path, 0777);
2182 #endif
2183 }
2184
2185 /*
2186 ===========
2187 FS_Search
2188
2189 Allocate and fill a search structure with information on matching filenames.
2190 ===========
2191 */
2192 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2193 {
2194         fssearch_t *search;
2195         searchpath_t *searchpath;
2196         pack_t *pak;
2197         int i, basepathlength, numfiles, numchars;
2198         stringlist_t *dir, *dirfile, *liststart, *listcurrent, *listtemp;
2199         const char *slash, *backslash, *colon, *separator;
2200         char *basepath;
2201         char netpath[MAX_OSPATH];
2202         char temp[MAX_OSPATH];
2203
2204         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2205                 ;
2206
2207         if (i > 0)
2208         {
2209                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2210                 return NULL;
2211         }
2212
2213         search = NULL;
2214         liststart = NULL;
2215         listcurrent = NULL;
2216         listtemp = NULL;
2217         slash = strrchr(pattern, '/');
2218         backslash = strrchr(pattern, '\\');
2219         colon = strrchr(pattern, ':');
2220         separator = max(slash, backslash);
2221         separator = max(separator, colon);
2222         basepathlength = separator ? (separator + 1 - pattern) : 0;
2223         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2224         if (basepathlength)
2225                 memcpy(basepath, pattern, basepathlength);
2226         basepath[basepathlength] = 0;
2227
2228         // search through the path, one element at a time
2229         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2230         {
2231                 // is the element a pak file?
2232                 if (searchpath->pack)
2233                 {
2234                         // look through all the pak file elements
2235                         pak = searchpath->pack;
2236                         for (i = 0;i < pak->numfiles;i++)
2237                         {
2238                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
2239                                 while (temp[0])
2240                                 {
2241                                         if (matchpattern(temp, (char *)pattern, true))
2242                                         {
2243                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2244                                                         if (!strcmp(listtemp->text, temp))
2245                                                                 break;
2246                                                 if (listtemp == NULL)
2247                                                 {
2248                                                         listcurrent = stringlistappend(listcurrent, temp);
2249                                                         if (liststart == NULL)
2250                                                                 liststart = listcurrent;
2251                                                         if (!quiet)
2252                                                                 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2253                                                 }
2254                                         }
2255                                         // strip off one path element at a time until empty
2256                                         // this way directories are added to the listing if they match the pattern
2257                                         slash = strrchr(temp, '/');
2258                                         backslash = strrchr(temp, '\\');
2259                                         colon = strrchr(temp, ':');
2260                                         separator = temp;
2261                                         if (separator < slash)
2262                                                 separator = slash;
2263                                         if (separator < backslash)
2264                                                 separator = backslash;
2265                                         if (separator < colon)
2266                                                 separator = colon;
2267                                         *((char *)separator) = 0;
2268                                 }
2269                         }
2270                 }
2271                 else
2272                 {
2273                         // get a directory listing and look at each name
2274                         dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2275                         if ((dir = listdirectory(netpath)))
2276                         {
2277                                 for (dirfile = dir;dirfile;dirfile = dirfile->next)
2278                                 {
2279                                         dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirfile->text);
2280                                         if (matchpattern(temp, (char *)pattern, true))
2281                                         {
2282                                                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2283                                                         if (!strcmp(listtemp->text, temp))
2284                                                                 break;
2285                                                 if (listtemp == NULL)
2286                                                 {
2287                                                         listcurrent = stringlistappend(listcurrent, temp);
2288                                                         if (liststart == NULL)
2289                                                                 liststart = listcurrent;
2290                                                         if (!quiet)
2291                                                                 Con_DPrintf("SearchDirFile: %s\n", temp);
2292                                                 }
2293                                         }
2294                                 }
2295                                 freedirectory(dir);
2296                         }
2297                 }
2298         }
2299
2300         if (liststart)
2301         {
2302                 liststart = stringlistsort(liststart);
2303                 numfiles = 0;
2304                 numchars = 0;
2305                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2306                 {
2307                         numfiles++;
2308                         numchars += (int)strlen(listtemp->text) + 1;
2309                 }
2310                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2311                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2312                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2313                 search->numfilenames = (int)numfiles;
2314                 numfiles = 0;
2315                 numchars = 0;
2316                 for (listtemp = liststart;listtemp;listtemp = listtemp->next)
2317                 {
2318                         size_t textlen;
2319                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
2320                         textlen = strlen(listtemp->text) + 1;
2321                         memcpy(search->filenames[numfiles], listtemp->text, textlen);
2322                         numfiles++;
2323                         numchars += (int)textlen;
2324                 }
2325                 if (liststart)
2326                         stringlistfree(liststart);
2327         }
2328
2329         Mem_Free(basepath);
2330         return search;
2331 }
2332
2333 void FS_FreeSearch(fssearch_t *search)
2334 {
2335         Z_Free(search);
2336 }
2337
2338 extern int con_linewidth;
2339 int FS_ListDirectory(const char *pattern, int oneperline)
2340 {
2341         int numfiles;
2342         int numcolumns;
2343         int numlines;
2344         int columnwidth;
2345         int linebufpos;
2346         int i, j, k, l;
2347         const char *name;
2348         char linebuf[MAX_INPUTLINE];
2349         fssearch_t *search;
2350         search = FS_Search(pattern, true, true);
2351         if (!search)
2352                 return 0;
2353         numfiles = search->numfilenames;
2354         if (!oneperline)
2355         {
2356                 // FIXME: the names could be added to one column list and then
2357                 // gradually shifted into the next column if they fit, and then the
2358                 // next to make a compact variable width listing but it's a lot more
2359                 // complicated...
2360                 // find width for columns
2361                 columnwidth = 0;
2362                 for (i = 0;i < numfiles;i++)
2363                 {
2364                         l = (int)strlen(search->filenames[i]);
2365                         if (columnwidth < l)
2366                                 columnwidth = l;
2367                 }
2368                 // count the spacing character
2369                 columnwidth++;
2370                 // calculate number of columns
2371                 numcolumns = con_linewidth / columnwidth;
2372                 // don't bother with the column printing if it's only one column
2373                 if (numcolumns >= 2)
2374                 {
2375                         numlines = (numfiles + numcolumns - 1) / numcolumns;
2376                         for (i = 0;i < numlines;i++)
2377                         {
2378                                 linebufpos = 0;
2379                                 for (k = 0;k < numcolumns;k++)
2380                                 {
2381                                         l = i * numcolumns + k;
2382                                         if (l < numfiles)
2383                                         {
2384                                                 name = search->filenames[l];
2385                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2386                                                         linebuf[linebufpos++] = name[j];
2387                                                 // space out name unless it's the last on the line
2388                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
2389                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2390                                                                 linebuf[linebufpos++] = ' ';
2391                                         }
2392                                 }
2393                                 linebuf[linebufpos] = 0;
2394                                 Con_Printf("%s\n", linebuf);
2395                         }
2396                 }
2397                 else
2398                         oneperline = true;
2399         }
2400         if (oneperline)
2401                 for (i = 0;i < numfiles;i++)
2402                         Con_Printf("%s\n", search->filenames[i]);
2403         FS_FreeSearch(search);
2404         return (int)numfiles;
2405 }
2406
2407 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2408 {
2409         const char *pattern;
2410         if (Cmd_Argc() > 3)
2411         {
2412                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2413                 return;
2414         }
2415         if (Cmd_Argc() == 2)
2416                 pattern = Cmd_Argv(1);
2417         else
2418                 pattern = "*";
2419         if (!FS_ListDirectory(pattern, oneperline))
2420                 Con_Print("No files found.\n");
2421 }
2422
2423 void FS_Dir_f(void)
2424 {
2425         FS_ListDirectoryCmd("dir", true);
2426 }
2427
2428 void FS_Ls_f(void)
2429 {
2430         FS_ListDirectoryCmd("ls", false);
2431 }
2432
2433 const char *FS_WhichPack(const char *filename)
2434 {
2435         int index;
2436         searchpath_t *sp = FS_FindFile(filename, &index, true);
2437         if(sp && sp->pack)
2438                 return sp->pack->filename;
2439         else
2440                 return 0;
2441 }