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