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