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