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