]> icculus.org git repositories - divverent/darkplaces.git/blob - fs.c
doxygen comments and config file
[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 #include "quakedef.h"
26
27 #include <limits.h>
28 #include <fcntl.h>
29
30 #ifdef WIN32
31 # include <direct.h>
32 # include <io.h>
33 # include <shlobj.h>
34 #else
35 # include <pwd.h>
36 # include <sys/stat.h>
37 # include <unistd.h>
38 #endif
39
40 #include "fs.h"
41 #include "wad.h"
42
43 // Win32 requires us to add O_BINARY, but the other OSes don't have it
44 #ifndef O_BINARY
45 # define O_BINARY 0
46 #endif
47
48 // In case the system doesn't support the O_NONBLOCK flag
49 #ifndef O_NONBLOCK
50 # define O_NONBLOCK 0
51 #endif
52
53 // largefile support for Win32
54 #ifdef WIN32
55 # define lseek _lseeki64
56 #endif
57
58 #if _MSC_VER >= 1400
59 // suppress deprecated warnings
60 # include <sys/stat.h>
61 # include <share.h>
62 # define read _read
63 # define write _write
64 # define close _close
65 # define unlink _unlink
66 # define dup _dup
67 #endif
68
69 /** \page fs File System
70
71 All of Quake's data access is through a hierchal file system, but the contents
72 of the file system can be transparently merged from several sources.
73
74 The "base directory" is the path to the directory holding the quake.exe and
75 all game directories.  The sys_* files pass this to host_init in
76 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
77 line parm to allow code debugging in a different directory.  The base
78 directory is only used during filesystem initialization.
79
80 The "game directory" is the first tree on the search path and directory that
81 all generated files (savegames, screenshots, demos, config files) will be
82 saved to.  This can be overridden with the "-game" command line parameter.
83 The game directory can never be changed while quake is executing.  This is a
84 precaution against having a malicious server instruct clients to write files
85 over areas they shouldn't.
86
87 */
88
89
90 /*
91 =============================================================================
92
93 CONSTANTS
94
95 =============================================================================
96 */
97
98 // Magic numbers of a ZIP file (big-endian format)
99 #define ZIP_DATA_HEADER 0x504B0304  // "PK\3\4"
100 #define ZIP_CDIR_HEADER 0x504B0102  // "PK\1\2"
101 #define ZIP_END_HEADER  0x504B0506  // "PK\5\6"
102
103 // Other constants for ZIP files
104 #define ZIP_MAX_COMMENTS_SIZE           ((unsigned short)0xFFFF)
105 #define ZIP_END_CDIR_SIZE                       22
106 #define ZIP_CDIR_CHUNK_BASE_SIZE        46
107 #define ZIP_LOCAL_CHUNK_BASE_SIZE       30
108
109 // Zlib constants (from zlib.h)
110 #define Z_SYNC_FLUSH    2
111 #define MAX_WBITS               15
112 #define Z_OK                    0
113 #define Z_STREAM_END    1
114 #define Z_STREAM_ERROR  (-2)
115 #define Z_DATA_ERROR    (-3)
116 #define Z_MEM_ERROR     (-4)
117 #define Z_BUF_ERROR     (-5)
118 #define ZLIB_VERSION    "1.2.3"
119
120 #define Z_BINARY 0
121 #define Z_DEFLATED 8
122 #define Z_MEMLEVEL_DEFAULT 8
123
124 #define Z_NULL 0
125 #define Z_DEFAULT_COMPRESSION (-1)
126 #define Z_NO_FLUSH 0
127 #define Z_SYNC_FLUSH 2
128 #define Z_FULL_FLUSH 3
129 #define Z_FINISH 4
130
131 // Uncomment the following line if the zlib DLL you have still uses
132 // the 1.1.x series calling convention on Win32 (WINAPI)
133 //#define ZLIB_USES_WINAPI
134
135
136 /*
137 =============================================================================
138
139 TYPES
140
141 =============================================================================
142 */
143
144 /*! Zlib stream (from zlib.h)
145  * \warning: some pointers we don't use directly have
146  * been cast to "void*" for a matter of simplicity
147  */
148 typedef struct
149 {
150         unsigned char                   *next_in;       ///< next input byte
151         unsigned int    avail_in;       ///< number of bytes available at next_in
152         unsigned long   total_in;       ///< total nb of input bytes read so far
153
154         unsigned char                   *next_out;      ///< next output byte should be put there
155         unsigned int    avail_out;      ///< remaining free space at next_out
156         unsigned long   total_out;      ///< total nb of bytes output so far
157
158         char                    *msg;           ///< last error message, NULL if no error
159         void                    *state;         ///< not visible by applications
160
161         void                    *zalloc;        ///< used to allocate the internal state
162         void                    *zfree;         ///< used to free the internal state
163         void                    *opaque;        ///< private data object passed to zalloc and zfree
164
165         int                             data_type;      ///< best guess about the data type: ascii or binary
166         unsigned long   adler;          ///< adler32 value of the uncompressed data
167         unsigned long   reserved;       ///< reserved for future use
168 } z_stream;
169
170
171 /// inside a package (PAK or PK3)
172 #define QFILE_FLAG_PACKED (1 << 0)
173 /// file is compressed using the deflate algorithm (PK3 only)
174 #define QFILE_FLAG_DEFLATED (1 << 1)
175 /// file is actually already loaded data
176 #define QFILE_FLAG_DATA (1 << 2)
177
178 #define FILE_BUFF_SIZE 2048
179 typedef struct
180 {
181         z_stream        zstream;
182         size_t          comp_length;                    ///< length of the compressed file
183         size_t          in_ind, in_len;                 ///< input buffer current index and length
184         size_t          in_position;                    ///< position in the compressed file
185         unsigned char           input [FILE_BUFF_SIZE];
186 } ztoolkit_t;
187
188 struct qfile_s
189 {
190         int                             flags;
191         int                             handle;                                 ///< file descriptor
192         fs_offset_t             real_length;                    ///< uncompressed file size (for files opened in "read" mode)
193         fs_offset_t             position;                               ///< current position in the file
194         fs_offset_t             offset;                                 ///< offset into the package (0 if external file)
195         int                             ungetc;                                 ///< single stored character from ungetc, cleared to EOF when read
196
197         // Contents buffer
198         fs_offset_t             buff_ind, buff_len;             ///< buffer current index and length
199         unsigned char                   buff [FILE_BUFF_SIZE];
200
201         ztoolkit_t*             ztk;    ///< For zipped files.
202
203         const unsigned char *data;      ///< For data files.
204 };
205
206
207 // ------ PK3 files on disk ------ //
208
209 // You can get the complete ZIP format description from PKWARE website
210
211 typedef struct pk3_endOfCentralDir_s
212 {
213         unsigned int signature;
214         unsigned short disknum;
215         unsigned short cdir_disknum;    ///< number of the disk with the start of the central directory
216         unsigned short localentries;    ///< number of entries in the central directory on this disk
217         unsigned short nbentries;               ///< total number of entries in the central directory on this disk
218         unsigned int cdir_size;                 ///< size of the central directory
219         unsigned int cdir_offset;               ///< with respect to the starting disk number
220         unsigned short comment_size;
221 } pk3_endOfCentralDir_t;
222
223
224 // ------ PAK files on disk ------ //
225 typedef struct dpackfile_s
226 {
227         char name[56];
228         int filepos, filelen;
229 } dpackfile_t;
230
231 typedef struct dpackheader_s
232 {
233         char id[4];
234         int dirofs;
235         int dirlen;
236 } dpackheader_t;
237
238
239 /*! \name Packages in memory
240  * @{
241  */
242 /// the offset in packfile_t is the true contents offset
243 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
244 /// file compressed using the deflate algorithm
245 #define PACKFILE_FLAG_DEFLATED (1 << 1)
246 /// file is a symbolic link
247 #define PACKFILE_FLAG_SYMLINK (1 << 2)
248
249 typedef struct packfile_s
250 {
251         char name [MAX_QPATH];
252         int flags;
253         fs_offset_t offset;
254         fs_offset_t packsize;   ///< size in the package
255         fs_offset_t realsize;   ///< real file size (uncompressed)
256 } packfile_t;
257
258 typedef struct pack_s
259 {
260         char filename [MAX_OSPATH];
261         char shortname [MAX_QPATH];
262         int handle;
263         int ignorecase;  ///< PK3 ignores case
264         int numfiles;
265         packfile_t *files;
266 } pack_t;
267 //@}
268
269 /// Search paths for files (including packages)
270 typedef struct searchpath_s
271 {
272         // only one of filename / pack will be used
273         char filename[MAX_OSPATH];
274         pack_t *pack;
275         struct searchpath_s *next;
276 } searchpath_t;
277
278
279 /*
280 =============================================================================
281
282 FUNCTION PROTOTYPES
283
284 =============================================================================
285 */
286
287 void FS_Dir_f(void);
288 void FS_Ls_f(void);
289 void FS_Which_f(void);
290
291 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
292 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
293                                                                         fs_offset_t offset, fs_offset_t packsize,
294                                                                         fs_offset_t realsize, int flags);
295
296
297 /*
298 =============================================================================
299
300 VARIABLES
301
302 =============================================================================
303 */
304
305 mempool_t *fs_mempool;
306
307 searchpath_t *fs_searchpaths = NULL;
308
309 #define MAX_FILES_IN_PACK       65536
310
311 char fs_gamedir[MAX_OSPATH];
312 char fs_basedir[MAX_OSPATH];
313
314 // list of active game directories (empty if not running a mod)
315 int fs_numgamedirs = 0;
316 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
317
318 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; the date is encoded using strftime escapes)"};
319 cvar_t fs_empty_files_in_pack_mark_deletions = {0, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"};
320
321
322 /*
323 =============================================================================
324
325 PRIVATE FUNCTIONS - PK3 HANDLING
326
327 =============================================================================
328 */
329
330 // Functions exported from zlib
331 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
332 # define ZEXPORT WINAPI
333 #else
334 # define ZEXPORT
335 #endif
336
337 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
338 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
339 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
340 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
341 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
342 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
343 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
344
345 #define qz_inflateInit2(strm, windowBits) \
346         qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
347 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
348         qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
349
350 //        qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
351
352 static dllfunction_t zlibfuncs[] =
353 {
354         {"inflate",                     (void **) &qz_inflate},
355         {"inflateEnd",          (void **) &qz_inflateEnd},
356         {"inflateInit2_",       (void **) &qz_inflateInit2_},
357         {"inflateReset",        (void **) &qz_inflateReset},
358         {"deflateInit2_",   (void **) &qz_deflateInit2_},
359         {"deflateEnd",      (void **) &qz_deflateEnd},
360         {"deflate",         (void **) &qz_deflate},
361         {NULL, NULL}
362 };
363
364 /// Handle for Zlib DLL
365 static dllhandle_t zlib_dll = NULL;
366
367 #ifdef WIN32
368 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
369 static dllfunction_t shfolderfuncs[] =
370 {
371         {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
372         {NULL, NULL}
373 };
374 static dllhandle_t shfolder_dll = NULL;
375 #endif
376
377 /*
378 ====================
379 PK3_CloseLibrary
380
381 Unload the Zlib DLL
382 ====================
383 */
384 void PK3_CloseLibrary (void)
385 {
386         Sys_UnloadLibrary (&zlib_dll);
387 }
388
389
390 /*
391 ====================
392 PK3_OpenLibrary
393
394 Try to load the Zlib DLL
395 ====================
396 */
397 qboolean PK3_OpenLibrary (void)
398 {
399         const char* dllnames [] =
400         {
401 #if defined(WIN64)
402                 "zlib64.dll",
403 #elif defined(WIN32)
404 # ifdef ZLIB_USES_WINAPI
405                 "zlibwapi.dll",
406                 "zlib.dll",
407 # else
408                 "zlib1.dll",
409 # endif
410 #elif defined(MACOSX)
411                 "libz.dylib",
412 #else
413                 "libz.so.1",
414                 "libz.so",
415 #endif
416                 NULL
417         };
418
419         // Already loaded?
420         if (zlib_dll)
421                 return true;
422
423         // Load the DLL
424         return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
425 }
426
427 /*
428 ====================
429 FS_HasZlib
430
431 See if zlib is available
432 ====================
433 */
434 qboolean FS_HasZlib(void)
435 {
436         PK3_OpenLibrary(); // to be safe
437         return (zlib_dll != 0);
438 }
439
440 /*
441 ====================
442 PK3_GetEndOfCentralDir
443
444 Extract the end of the central directory from a PK3 package
445 ====================
446 */
447 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
448 {
449         fs_offset_t filesize, maxsize;
450         unsigned char *buffer, *ptr;
451         int ind;
452
453         // Get the package size
454         filesize = lseek (packhandle, 0, SEEK_END);
455         if (filesize < ZIP_END_CDIR_SIZE)
456                 return false;
457
458         // Load the end of the file in memory
459         if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
460                 maxsize = filesize;
461         else
462                 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
463         buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
464         lseek (packhandle, filesize - maxsize, SEEK_SET);
465         if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
466         {
467                 Mem_Free (buffer);
468                 return false;
469         }
470
471         // Look for the end of central dir signature around the end of the file
472         maxsize -= ZIP_END_CDIR_SIZE;
473         ptr = &buffer[maxsize];
474         ind = 0;
475         while (BuffBigLong (ptr) != ZIP_END_HEADER)
476         {
477                 if (ind == maxsize)
478                 {
479                         Mem_Free (buffer);
480                         return false;
481                 }
482
483                 ind++;
484                 ptr--;
485         }
486
487         memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
488         eocd->signature = LittleLong (eocd->signature);
489         eocd->disknum = LittleShort (eocd->disknum);
490         eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
491         eocd->localentries = LittleShort (eocd->localentries);
492         eocd->nbentries = LittleShort (eocd->nbentries);
493         eocd->cdir_size = LittleLong (eocd->cdir_size);
494         eocd->cdir_offset = LittleLong (eocd->cdir_offset);
495         eocd->comment_size = LittleShort (eocd->comment_size);
496
497         Mem_Free (buffer);
498
499         return true;
500 }
501
502
503 /*
504 ====================
505 PK3_BuildFileList
506
507 Extract the file list from a PK3 file
508 ====================
509 */
510 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
511 {
512         unsigned char *central_dir, *ptr;
513         unsigned int ind;
514         fs_offset_t remaining;
515
516         // Load the central directory in memory
517         central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
518         lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
519         if(read (pack->handle, central_dir, eocd->cdir_size) != (ssize_t) eocd->cdir_size)
520         {
521                 Mem_Free (central_dir);
522                 return -1;
523         }
524
525         // Extract the files properties
526         // The parsing is done "by hand" because some fields have variable sizes and
527         // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
528         remaining = eocd->cdir_size;
529         pack->numfiles = 0;
530         ptr = central_dir;
531         for (ind = 0; ind < eocd->nbentries; ind++)
532         {
533                 fs_offset_t namesize, count;
534
535                 // Checking the remaining size
536                 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
537                 {
538                         Mem_Free (central_dir);
539                         return -1;
540                 }
541                 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
542
543                 // Check header
544                 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
545                 {
546                         Mem_Free (central_dir);
547                         return -1;
548                 }
549
550                 namesize = BuffLittleShort (&ptr[28]);  // filename length
551
552                 // Check encryption, compression, and attributes
553                 // 1st uint8  : general purpose bit flag
554                 //    Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
555                 //
556                 // LordHavoc: bit 3 would be a problem if we were scanning the archive
557                 // but is not a problem in the central directory where the values are
558                 // always real.
559                 //
560                 // bit 3 seems to always be set by the standard Mac OSX zip maker
561                 //
562                 // 2nd uint8 : external file attributes
563                 //    Check bits 3 (file is a directory) and 5 (file is a volume (?))
564                 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
565                 {
566                         // Still enough bytes for the name?
567                         if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
568                         {
569                                 Mem_Free (central_dir);
570                                 return -1;
571                         }
572
573                         // WinZip doesn't use the "directory" attribute, so we need to check the name directly
574                         if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
575                         {
576                                 char filename [sizeof (pack->files[0].name)];
577                                 fs_offset_t offset, packsize, realsize;
578                                 int flags;
579
580                                 // Extract the name (strip it if necessary)
581                                 namesize = min(namesize, (int)sizeof (filename) - 1);
582                                 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
583                                 filename[namesize] = '\0';
584
585                                 if (BuffLittleShort (&ptr[10]))
586                                         flags = PACKFILE_FLAG_DEFLATED;
587                                 else
588                                         flags = 0;
589                                 offset = BuffLittleLong (&ptr[42]);
590                                 packsize = BuffLittleLong (&ptr[20]);
591                                 realsize = BuffLittleLong (&ptr[24]);
592
593                                 switch(ptr[5]) // C_VERSION_MADE_BY_1
594                                 {
595                                         case 3: // UNIX_
596                                         case 2: // VMS_
597                                         case 16: // BEOS_
598                                                 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
599                                                         // can't use S_ISLNK here, as this has to compile on non-UNIX too
600                                                         flags |= PACKFILE_FLAG_SYMLINK;
601                                                 break;
602                                 }
603
604                                 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
605                         }
606                 }
607
608                 // Skip the name, additionnal field, and comment
609                 // 1er uint16 : extra field length
610                 // 2eme uint16 : file comment length
611                 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
612                 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
613                 remaining -= count;
614         }
615
616         // If the package is empty, central_dir is NULL here
617         if (central_dir != NULL)
618                 Mem_Free (central_dir);
619         return pack->numfiles;
620 }
621
622
623 /*
624 ====================
625 FS_LoadPackPK3
626
627 Create a package entry associated with a PK3 file
628 ====================
629 */
630 pack_t *FS_LoadPackPK3 (const char *packfile)
631 {
632         int packhandle;
633         pk3_endOfCentralDir_t eocd;
634         pack_t *pack;
635         int real_nb_files;
636
637 #if _MSC_VER >= 1400
638         _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
639 #else
640         packhandle = open (packfile, O_RDONLY | O_BINARY);
641 #endif
642         if (packhandle < 0)
643                 return NULL;
644
645         if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
646         {
647                 Con_Printf ("%s is not a PK3 file\n", packfile);
648                 close(packhandle);
649                 return NULL;
650         }
651
652         // Multi-volume ZIP archives are NOT allowed
653         if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
654         {
655                 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
656                 close(packhandle);
657                 return NULL;
658         }
659
660         // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
661         // since eocd.nbentries is an unsigned 16 bits integer
662 #if MAX_FILES_IN_PACK < 65535
663         if (eocd.nbentries > MAX_FILES_IN_PACK)
664         {
665                 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
666                 close(packhandle);
667                 return NULL;
668         }
669 #endif
670
671         // Create a package structure in memory
672         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
673         pack->ignorecase = true; // PK3 ignores case
674         strlcpy (pack->filename, packfile, sizeof (pack->filename));
675         pack->handle = packhandle;
676         pack->numfiles = eocd.nbentries;
677         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
678
679         real_nb_files = PK3_BuildFileList (pack, &eocd);
680         if (real_nb_files < 0)
681         {
682                 Con_Printf ("%s is not a valid PK3 file\n", packfile);
683                 close(pack->handle);
684                 Mem_Free(pack);
685                 return NULL;
686         }
687
688         Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
689         return pack;
690 }
691
692
693 /*
694 ====================
695 PK3_GetTrueFileOffset
696
697 Find where the true file data offset is
698 ====================
699 */
700 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
701 {
702         unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
703         fs_offset_t count;
704
705         // Already found?
706         if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
707                 return true;
708
709         // Load the local file description
710         lseek (pack->handle, pfile->offset, SEEK_SET);
711         count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
712         if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
713         {
714                 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
715                 return false;
716         }
717
718         // Skip name and extra field
719         pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
720
721         pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
722         return true;
723 }
724
725
726 /*
727 =============================================================================
728
729 OTHER PRIVATE FUNCTIONS
730
731 =============================================================================
732 */
733
734
735 /*
736 ====================
737 FS_AddFileToPack
738
739 Add a file to the list of files contained into a package
740 ====================
741 */
742 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
743                                                                          fs_offset_t offset, fs_offset_t packsize,
744                                                                          fs_offset_t realsize, int flags)
745 {
746         int (*strcmp_funct) (const char* str1, const char* str2);
747         int left, right, middle;
748         packfile_t *pfile;
749
750         strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
751
752         // Look for the slot we should put that file into (binary search)
753         left = 0;
754         right = pack->numfiles - 1;
755         while (left <= right)
756         {
757                 int diff;
758
759                 middle = (left + right) / 2;
760                 diff = strcmp_funct (pack->files[middle].name, name);
761
762                 // If we found the file, there's a problem
763                 if (!diff)
764                         Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
765
766                 // If we're too far in the list
767                 if (diff > 0)
768                         right = middle - 1;
769                 else
770                         left = middle + 1;
771         }
772
773         // We have to move the right of the list by one slot to free the one we need
774         pfile = &pack->files[left];
775         memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
776         pack->numfiles++;
777
778         strlcpy (pfile->name, name, sizeof (pfile->name));
779         pfile->offset = offset;
780         pfile->packsize = packsize;
781         pfile->realsize = realsize;
782         pfile->flags = flags;
783
784         return pfile;
785 }
786
787
788 /*
789 ============
790 FS_CreatePath
791
792 Only used for FS_OpenRealFile.
793 ============
794 */
795 void FS_CreatePath (char *path)
796 {
797         char *ofs, save;
798
799         for (ofs = path+1 ; *ofs ; ofs++)
800         {
801                 if (*ofs == '/' || *ofs == '\\')
802                 {
803                         // create the directory
804                         save = *ofs;
805                         *ofs = 0;
806                         FS_mkdir (path);
807                         *ofs = save;
808                 }
809         }
810 }
811
812
813 /*
814 ============
815 FS_Path_f
816
817 ============
818 */
819 void FS_Path_f (void)
820 {
821         searchpath_t *s;
822
823         Con_Print("Current search path:\n");
824         for (s=fs_searchpaths ; s ; s=s->next)
825         {
826                 if (s->pack)
827                         Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
828                 else
829                         Con_Printf("%s\n", s->filename);
830         }
831 }
832
833
834 /*
835 =================
836 FS_LoadPackPAK
837 =================
838 */
839 /*! Takes an explicit (not game tree related) path to a pak file.
840  *Loads the header and directory, adding the files at the beginning
841  *of the list so they override previous pack files.
842  */
843 pack_t *FS_LoadPackPAK (const char *packfile)
844 {
845         dpackheader_t header;
846         int i, numpackfiles;
847         int packhandle;
848         pack_t *pack;
849         dpackfile_t *info;
850
851 #if _MSC_VER >= 1400
852         _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
853 #else
854         packhandle = open (packfile, O_RDONLY | O_BINARY);
855 #endif
856         if (packhandle < 0)
857                 return NULL;
858         if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
859         {
860                 Con_Printf ("%s is not a packfile\n", packfile);
861                 close(packhandle);
862                 return NULL;
863         }
864         if (memcmp(header.id, "PACK", 4))
865         {
866                 Con_Printf ("%s is not a packfile\n", packfile);
867                 close(packhandle);
868                 return NULL;
869         }
870         header.dirofs = LittleLong (header.dirofs);
871         header.dirlen = LittleLong (header.dirlen);
872
873         if (header.dirlen % sizeof(dpackfile_t))
874         {
875                 Con_Printf ("%s has an invalid directory size\n", packfile);
876                 close(packhandle);
877                 return NULL;
878         }
879
880         numpackfiles = header.dirlen / sizeof(dpackfile_t);
881
882         if (numpackfiles > MAX_FILES_IN_PACK)
883         {
884                 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
885                 close(packhandle);
886                 return NULL;
887         }
888
889         info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
890         lseek (packhandle, header.dirofs, SEEK_SET);
891         if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
892         {
893                 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
894                 Mem_Free(info);
895                 close(packhandle);
896                 return NULL;
897         }
898
899         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
900         pack->ignorecase = false; // PAK is case sensitive
901         strlcpy (pack->filename, packfile, sizeof (pack->filename));
902         pack->handle = packhandle;
903         pack->numfiles = 0;
904         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
905
906         // parse the directory
907         for (i = 0;i < numpackfiles;i++)
908         {
909                 fs_offset_t offset = LittleLong (info[i].filepos);
910                 fs_offset_t size = LittleLong (info[i].filelen);
911
912                 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
913         }
914
915         Mem_Free(info);
916
917         Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
918         return pack;
919 }
920
921 /*
922 ================
923 FS_AddPack_Fullpath
924 ================
925 */
926 /*! Adds the given pack to the search path.
927  * The pack type is autodetected by the file extension.
928  *
929  * Returns true if the file was successfully added to the
930  * search path or if it was already included.
931  *
932  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
933  * plain directories.
934  *
935  */
936 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
937 {
938         searchpath_t *search;
939         pack_t *pak = NULL;
940         const char *ext = FS_FileExtension(pakfile);
941
942         for(search = fs_searchpaths; search; search = search->next)
943         {
944                 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
945                 {
946                         if(already_loaded)
947                                 *already_loaded = true;
948                         return true; // already loaded
949                 }
950         }
951
952         if(already_loaded)
953                 *already_loaded = false;
954
955         if(!strcasecmp(ext, "pak"))
956                 pak = FS_LoadPackPAK (pakfile);
957         else if(!strcasecmp(ext, "pk3"))
958                 pak = FS_LoadPackPK3 (pakfile);
959         else
960                 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
961
962         if (pak)
963         {
964                 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
965                 //Con_DPrintf("  Registered pack with short name %s\n", shortname);
966                 if(keep_plain_dirs)
967                 {
968                         // find the first item whose next one is a pack or NULL
969                         searchpath_t *insertion_point = 0;
970                         if(fs_searchpaths && !fs_searchpaths->pack)
971                         {
972                                 insertion_point = fs_searchpaths;
973                                 for(;;)
974                                 {
975                                         if(!insertion_point->next)
976                                                 break;
977                                         if(insertion_point->next->pack)
978                                                 break;
979                                         insertion_point = insertion_point->next;
980                                 }
981                         }
982                         // If insertion_point is NULL, this means that either there is no
983                         // item in the list yet, or that the very first item is a pack. In
984                         // that case, we want to insert at the beginning...
985                         if(!insertion_point)
986                         {
987                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
988                                 search->pack = pak;
989                                 search->next = fs_searchpaths;
990                                 fs_searchpaths = search;
991                         }
992                         else
993                         // otherwise we want to append directly after insertion_point.
994                         {
995                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
996                                 search->pack = pak;
997                                 search->next = insertion_point->next;
998                                 insertion_point->next = search;
999                         }
1000                 }
1001                 else
1002                 {
1003                         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1004                         search->pack = pak;
1005                         search->next = fs_searchpaths;
1006                         fs_searchpaths = search;
1007                 }
1008                 return true;
1009         }
1010         else
1011         {
1012                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1013                 return false;
1014         }
1015 }
1016
1017
1018 /*
1019 ================
1020 FS_AddPack
1021 ================
1022 */
1023 /*! Adds the given pack to the search path and searches for it in the game path.
1024  * The pack type is autodetected by the file extension.
1025  *
1026  * Returns true if the file was successfully added to the
1027  * search path or if it was already included.
1028  *
1029  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1030  * plain directories.
1031  */
1032 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1033 {
1034         char fullpath[MAX_QPATH];
1035         int index;
1036         searchpath_t *search;
1037
1038         if(already_loaded)
1039                 *already_loaded = false;
1040
1041         // then find the real name...
1042         search = FS_FindFile(pakfile, &index, true);
1043         if(!search || search->pack)
1044         {
1045                 Con_Printf("could not find pak \"%s\"\n", pakfile);
1046                 return false;
1047         }
1048
1049         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1050
1051         return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1052 }
1053
1054
1055 /*
1056 ================
1057 FS_AddGameDirectory
1058
1059 Sets fs_gamedir, adds the directory to the head of the path,
1060 then loads and adds pak1.pak pak2.pak ...
1061 ================
1062 */
1063 void FS_AddGameDirectory (const char *dir)
1064 {
1065         int i;
1066         stringlist_t list;
1067         searchpath_t *search;
1068
1069         strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1070
1071         stringlistinit(&list);
1072         listdirectory(&list, "", dir);
1073         stringlistsort(&list);
1074
1075         // add any PAK package in the directory
1076         for (i = 0;i < list.numstrings;i++)
1077         {
1078                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1079                 {
1080                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1081                 }
1082         }
1083
1084         // add any PK3 package in the directory
1085         for (i = 0;i < list.numstrings;i++)
1086         {
1087                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
1088                 {
1089                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1090                 }
1091         }
1092
1093         stringlistfreecontents(&list);
1094
1095         // Add the directory to the search path
1096         // (unpacked files have the priority over packed files)
1097         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1098         strlcpy (search->filename, dir, sizeof (search->filename));
1099         search->next = fs_searchpaths;
1100         fs_searchpaths = search;
1101 }
1102
1103
1104 /*
1105 ================
1106 FS_AddGameHierarchy
1107 ================
1108 */
1109 void FS_AddGameHierarchy (const char *dir)
1110 {
1111         int i;
1112         char userdir[MAX_QPATH];
1113 #ifdef WIN32
1114         TCHAR mydocsdir[MAX_PATH + 1];
1115 #if _MSC_VER >= 1400
1116         size_t homedirlen;
1117 #endif
1118 #endif
1119         char *homedir;
1120
1121         // Add the common game directory
1122         FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1123
1124         *userdir = 0;
1125
1126         // Add the personal game directory
1127 #ifdef WIN32
1128         if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
1129         {
1130                 dpsnprintf(userdir, sizeof(userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
1131                 Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", userdir);
1132         }
1133         else
1134         {
1135                 // use the environment
1136 #if _MSC_VER >= 1400
1137                 _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
1138 #else
1139                 homedir = getenv("USERPROFILE");
1140 #endif
1141
1142                 if(homedir)
1143                 {
1144                         dpsnprintf(userdir, sizeof(userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
1145 #if _MSC_VER >= 1400
1146                         free(homedir);
1147 #endif
1148                         Con_DPrintf("Obtained personal directory %s from environment\n", userdir);
1149                 }
1150                 else
1151                         *userdir = 0; // just to make sure it hasn't been written to by SHGetFolderPath returning failure
1152         }
1153
1154         if(!*userdir)
1155                 Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
1156 #else
1157         homedir = getenv ("HOME");
1158         if(homedir)
1159                 dpsnprintf(userdir, sizeof(userdir), "%s/.%s/", homedir, gameuserdirname);
1160
1161         if(!*userdir)
1162                 Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
1163 #endif
1164
1165
1166 #ifdef WIN32
1167         if(!COM_CheckParm("-mygames"))
1168         {
1169 #if _MSC_VER >= 1400
1170                 int fd;
1171                 _sopen_s(&fd, va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); // note: no O_TRUNC here!
1172 #else
1173                 int fd = open (va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1174 #endif
1175                 if(fd >= 0)
1176                 {
1177                         close(fd);
1178                         *userdir = 0; // we have write access to the game dir, so let's use it
1179                 }
1180         }
1181 #endif
1182
1183         if(COM_CheckParm("-nohome"))
1184                 *userdir = 0;
1185
1186         if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1187                 dpsnprintf(userdir, sizeof(userdir), "%s/", com_argv[i+1]);
1188
1189         if (*userdir)
1190                 FS_AddGameDirectory(va("%s%s/", userdir, dir));
1191 }
1192
1193
1194 /*
1195 ============
1196 FS_FileExtension
1197 ============
1198 */
1199 const char *FS_FileExtension (const char *in)
1200 {
1201         const char *separator, *backslash, *colon, *dot;
1202
1203         separator = strrchr(in, '/');
1204         backslash = strrchr(in, '\\');
1205         if (!separator || separator < backslash)
1206                 separator = backslash;
1207         colon = strrchr(in, ':');
1208         if (!separator || separator < colon)
1209                 separator = colon;
1210
1211         dot = strrchr(in, '.');
1212         if (dot == NULL || (separator && (dot < separator)))
1213                 return "";
1214
1215         return dot + 1;
1216 }
1217
1218
1219 /*
1220 ============
1221 FS_FileWithoutPath
1222 ============
1223 */
1224 const char *FS_FileWithoutPath (const char *in)
1225 {
1226         const char *separator, *backslash, *colon;
1227
1228         separator = strrchr(in, '/');
1229         backslash = strrchr(in, '\\');
1230         if (!separator || separator < backslash)
1231                 separator = backslash;
1232         colon = strrchr(in, ':');
1233         if (!separator || separator < colon)
1234                 separator = colon;
1235         return separator ? separator + 1 : in;
1236 }
1237
1238
1239 /*
1240 ================
1241 FS_ClearSearchPath
1242 ================
1243 */
1244 void FS_ClearSearchPath (void)
1245 {
1246         // unload all packs and directory information, close all pack files
1247         // (if a qfile is still reading a pack it won't be harmed because it used
1248         //  dup() to get its own handle already)
1249         while (fs_searchpaths)
1250         {
1251                 searchpath_t *search = fs_searchpaths;
1252                 fs_searchpaths = search->next;
1253                 if (search->pack)
1254                 {
1255                         // close the file
1256                         close(search->pack->handle);
1257                         // free any memory associated with it
1258                         if (search->pack->files)
1259                                 Mem_Free(search->pack->files);
1260                         Mem_Free(search->pack);
1261                 }
1262                 Mem_Free(search);
1263         }
1264 }
1265
1266
1267 /*
1268 ================
1269 FS_Rescan
1270 ================
1271 */
1272 void FS_Rescan (void)
1273 {
1274         int i;
1275         qboolean fs_modified = false;
1276
1277         FS_ClearSearchPath();
1278
1279         // add the game-specific paths
1280         // gamedirname1 (typically id1)
1281         FS_AddGameHierarchy (gamedirname1);
1282         // update the com_modname (used for server info)
1283         strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1284
1285         // add the game-specific path, if any
1286         // (only used for mission packs and the like, which should set fs_modified)
1287         if (gamedirname2)
1288         {
1289                 fs_modified = true;
1290                 FS_AddGameHierarchy (gamedirname2);
1291         }
1292
1293         // -game <gamedir>
1294         // Adds basedir/gamedir as an override game
1295         // LordHavoc: now supports multiple -game directories
1296         // set the com_modname (reported in server info)
1297         for (i = 0;i < fs_numgamedirs;i++)
1298         {
1299                 fs_modified = true;
1300                 FS_AddGameHierarchy (fs_gamedirs[i]);
1301                 // update the com_modname (used server info)
1302                 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1303         }
1304
1305         // set the default screenshot name to either the mod name or the
1306         // gamemode screenshot name
1307         if (strcmp(com_modname, gamedirname1))
1308                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1309         else
1310                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1311
1312         // If "-condebug" is in the command line, remove the previous log file
1313         if (COM_CheckParm ("-condebug") != 0)
1314                 unlink (va("%s/qconsole.log", fs_gamedir));
1315
1316         // look for the pop.lmp file and set registered to true if it is found
1317         if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
1318         {
1319                 if (fs_modified)
1320                         Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1321                 else
1322                         Con_Print("Playing shareware version.\n");
1323         }
1324         else
1325         {
1326                 Cvar_Set ("registered", "1");
1327                 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1328                         Con_Print("Playing registered version.\n");
1329         }
1330
1331         // unload all wads so that future queries will return the new data
1332         W_UnloadAll();
1333 }
1334
1335 void FS_Rescan_f(void)
1336 {
1337         FS_Rescan();
1338 }
1339
1340 /*
1341 ================
1342 FS_ChangeGameDirs
1343 ================
1344 */
1345 extern void Host_SaveConfig (void);
1346 extern void Host_LoadConfig_f (void);
1347 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1348 {
1349         int i;
1350
1351         if (fs_numgamedirs == numgamedirs)
1352         {
1353                 for (i = 0;i < numgamedirs;i++)
1354                         if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1355                                 break;
1356                 if (i == numgamedirs)
1357                         return true; // already using this set of gamedirs, do nothing
1358         }
1359
1360         if (numgamedirs > MAX_GAMEDIRS)
1361         {
1362                 if (complain)
1363                         Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1364                 return false; // too many gamedirs
1365         }
1366
1367         for (i = 0;i < numgamedirs;i++)
1368         {
1369                 // if string is nasty, reject it
1370                 if(FS_CheckNastyPath(gamedirs[i], true))
1371                 {
1372                         if (complain)
1373                                 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1374                         return false; // nasty gamedirs
1375                 }
1376         }
1377
1378         for (i = 0;i < numgamedirs;i++)
1379         {
1380                 if (!FS_CheckGameDir(gamedirs[i]) && failmissing)
1381                 {
1382                         if (complain)
1383                                 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1384                         return false; // missing gamedirs
1385                 }
1386         }
1387
1388         Host_SaveConfig();
1389
1390         fs_numgamedirs = numgamedirs;
1391         for (i = 0;i < fs_numgamedirs;i++)
1392                 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1393
1394         // reinitialize filesystem to detect the new paks
1395         FS_Rescan();
1396
1397         // exec the new config
1398         Host_LoadConfig_f();
1399
1400         // unload all sounds so they will be reloaded from the new files as needed
1401         S_UnloadAllSounds_f();
1402
1403         // reinitialize renderer (this reloads hud/console background/etc)
1404         R_Modules_Restart();
1405
1406         return true;
1407 }
1408
1409 /*
1410 ================
1411 FS_GameDir_f
1412 ================
1413 */
1414 void FS_GameDir_f (void)
1415 {
1416         int i;
1417         int numgamedirs;
1418         char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1419
1420         if (Cmd_Argc() < 2)
1421         {
1422                 Con_Printf("gamedirs active:");
1423                 for (i = 0;i < fs_numgamedirs;i++)
1424                         Con_Printf(" %s", fs_gamedirs[i]);
1425                 Con_Printf("\n");
1426                 return;
1427         }
1428
1429         numgamedirs = Cmd_Argc() - 1;
1430         if (numgamedirs > MAX_GAMEDIRS)
1431         {
1432                 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1433                 return;
1434         }
1435
1436         for (i = 0;i < numgamedirs;i++)
1437                 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1438
1439         if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1440         {
1441                 // actually, changing during game would work fine, but would be stupid
1442                 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1443                 return;
1444         }
1445
1446         // halt demo playback to close the file
1447         CL_Disconnect();
1448
1449         FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1450 }
1451
1452
1453 /*
1454 ================
1455 FS_CheckGameDir
1456 ================
1457 */
1458 qboolean FS_CheckGameDir(const char *gamedir)
1459 {
1460         qboolean success;
1461         stringlist_t list;
1462         stringlistinit(&list);
1463         listdirectory(&list, va("%s%s/", fs_basedir, gamedir), "");
1464         success = list.numstrings > 0;
1465         stringlistfreecontents(&list);
1466         return success;
1467 }
1468
1469
1470 /*
1471 ================
1472 FS_Init
1473 ================
1474 */
1475 void FS_Init (void)
1476 {
1477         int i;
1478
1479 #ifdef WIN32
1480         const char* dllnames [] =
1481         {
1482                 "shfolder.dll",  // IE 4, or Win NT and higher
1483                 NULL
1484         };
1485         Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
1486         // don't care for the result; if it fails, %USERPROFILE% will be used instead
1487 #endif
1488
1489         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1490
1491         strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1492
1493 // If the base directory is explicitly defined by the compilation process
1494 #ifdef DP_FS_BASEDIR
1495         strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1496 #else
1497         strlcpy(fs_basedir, "", sizeof(fs_basedir));
1498
1499 #ifdef MACOSX
1500         // FIXME: is there a better way to find the directory outside the .app?
1501         if (strstr(com_argv[0], ".app/"))
1502         {
1503                 char *split;
1504
1505                 split = strstr(com_argv[0], ".app/");
1506                 while (split > com_argv[0] && *split != '/')
1507                         split--;
1508                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1509                 fs_basedir[split - com_argv[0]] = 0;
1510         }
1511 #endif
1512 #endif
1513
1514         PK3_OpenLibrary ();
1515
1516         // -basedir <path>
1517         // Overrides the system supplied base directory (under GAMENAME)
1518 // 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)
1519         i = COM_CheckParm ("-basedir");
1520         if (i && i < com_argc-1)
1521         {
1522                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1523                 i = (int)strlen (fs_basedir);
1524                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1525                         fs_basedir[i-1] = 0;
1526         }
1527
1528         // add a path separator to the end of the basedir if it lacks one
1529         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1530                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1531
1532         if (!FS_CheckGameDir(gamedirname1))
1533                 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1534
1535         if (gamedirname2 && !FS_CheckGameDir(gamedirname2))
1536                 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1537
1538         // -game <gamedir>
1539         // Adds basedir/gamedir as an override game
1540         // LordHavoc: now supports multiple -game directories
1541         for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1542         {
1543                 if (!com_argv[i])
1544                         continue;
1545                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1546                 {
1547                         i++;
1548                         if (FS_CheckNastyPath(com_argv[i], true))
1549                                 Sys_Error("-game %s%s/ is a dangerous/non-portable path\n", fs_basedir, com_argv[i]);
1550                         if (!FS_CheckGameDir(com_argv[i]))
1551                                 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1552                         // add the gamedir to the list of active gamedirs
1553                         strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1554                         fs_numgamedirs++;
1555                 }
1556         }
1557
1558         // generate the searchpath
1559         FS_Rescan();
1560 }
1561
1562 void FS_Init_Commands(void)
1563 {
1564         Cvar_RegisterVariable (&scr_screenshot_name);
1565         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1566
1567         Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1568         Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1569         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1570         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1571         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1572         Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
1573 }
1574
1575 /*
1576 ================
1577 FS_Shutdown
1578 ================
1579 */
1580 void FS_Shutdown (void)
1581 {
1582         // close all pack files and such
1583         // (hopefully there aren't any other open files, but they'll be cleaned up
1584         //  by the OS anyway)
1585         FS_ClearSearchPath();
1586         Mem_FreePool (&fs_mempool);
1587
1588 #ifdef WIN32
1589         Sys_UnloadLibrary (&shfolder_dll);
1590 #endif
1591 }
1592
1593 /*
1594 ====================
1595 FS_SysOpen
1596
1597 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1598 ====================
1599 */
1600 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1601 {
1602         qfile_t* file;
1603         int mod, opt;
1604         unsigned int ind;
1605
1606         // Parse the mode string
1607         switch (mode[0])
1608         {
1609                 case 'r':
1610                         mod = O_RDONLY;
1611                         opt = 0;
1612                         break;
1613                 case 'w':
1614                         mod = O_WRONLY;
1615                         opt = O_CREAT | O_TRUNC;
1616                         break;
1617                 case 'a':
1618                         mod = O_WRONLY;
1619                         opt = O_CREAT | O_APPEND;
1620                         break;
1621                 default:
1622                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1623                         return NULL;
1624         }
1625         for (ind = 1; mode[ind] != '\0'; ind++)
1626         {
1627                 switch (mode[ind])
1628                 {
1629                         case '+':
1630                                 mod = O_RDWR;
1631                                 break;
1632                         case 'b':
1633                                 opt |= O_BINARY;
1634                                 break;
1635                         default:
1636                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1637                                                         filepath, mode, mode[ind]);
1638                 }
1639         }
1640
1641         if (nonblocking)
1642                 opt |= O_NONBLOCK;
1643
1644         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1645         memset (file, 0, sizeof (*file));
1646         file->ungetc = EOF;
1647
1648 #if _MSC_VER >= 1400
1649         _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
1650 #else
1651         file->handle = open (filepath, mod | opt, 0666);
1652 #endif
1653         if (file->handle < 0)
1654         {
1655                 Mem_Free (file);
1656                 return NULL;
1657         }
1658
1659         file->real_length = lseek (file->handle, 0, SEEK_END);
1660
1661         // For files opened in append mode, we start at the end of the file
1662         if (mod & O_APPEND)
1663                 file->position = file->real_length;
1664         else
1665                 lseek (file->handle, 0, SEEK_SET);
1666
1667         return file;
1668 }
1669
1670
1671 /*
1672 ===========
1673 FS_OpenPackedFile
1674
1675 Open a packed file using its package file descriptor
1676 ===========
1677 */
1678 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1679 {
1680         packfile_t *pfile;
1681         int dup_handle;
1682         qfile_t* file;
1683
1684         pfile = &pack->files[pack_ind];
1685
1686         // If we don't have the true offset, get it now
1687         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1688                 if (!PK3_GetTrueFileOffset (pfile, pack))
1689                         return NULL;
1690
1691         // No Zlib DLL = no compressed files
1692         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1693         {
1694                 Con_Printf("WARNING: can't open the compressed file %s\n"
1695                                         "You need the Zlib DLL to use compressed files\n",
1696                                         pfile->name);
1697                 return NULL;
1698         }
1699
1700         // LordHavoc: lseek affects all duplicates of a handle so we do it before
1701         // the dup() call to avoid having to close the dup_handle on error here
1702         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1703         {
1704                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1705                                         pfile->name, pack->filename, (int) pfile->offset);
1706                 return NULL;
1707         }
1708
1709         dup_handle = dup (pack->handle);
1710         if (dup_handle < 0)
1711         {
1712                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1713                 return NULL;
1714         }
1715
1716         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1717         memset (file, 0, sizeof (*file));
1718         file->handle = dup_handle;
1719         file->flags = QFILE_FLAG_PACKED;
1720         file->real_length = pfile->realsize;
1721         file->offset = pfile->offset;
1722         file->position = 0;
1723         file->ungetc = EOF;
1724
1725         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1726         {
1727                 ztoolkit_t *ztk;
1728
1729                 file->flags |= QFILE_FLAG_DEFLATED;
1730
1731                 // We need some more variables
1732                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1733
1734                 ztk->comp_length = pfile->packsize;
1735
1736                 // Initialize zlib stream
1737                 ztk->zstream.next_in = ztk->input;
1738                 ztk->zstream.avail_in = 0;
1739
1740                 /* From Zlib's "unzip.c":
1741                  *
1742                  * windowBits is passed < 0 to tell that there is no zlib header.
1743                  * Note that in this case inflate *requires* an extra "dummy" byte
1744                  * after the compressed stream in order to complete decompression and
1745                  * return Z_STREAM_END.
1746                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1747                  * size of both compressed and uncompressed data
1748                  */
1749                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1750                 {
1751                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1752                         close(dup_handle);
1753                         Mem_Free(file);
1754                         return NULL;
1755                 }
1756
1757                 ztk->zstream.next_out = file->buff;
1758                 ztk->zstream.avail_out = sizeof (file->buff);
1759
1760                 file->ztk = ztk;
1761         }
1762
1763         return file;
1764 }
1765
1766 /*
1767 ====================
1768 FS_CheckNastyPath
1769
1770 Return true if the path should be rejected due to one of the following:
1771 1: path elements that are non-portable
1772 2: path elements that would allow access to files outside the game directory,
1773    or are just not a good idea for a mod to be using.
1774 ====================
1775 */
1776 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
1777 {
1778         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
1779         if (!path[0])
1780                 return 2;
1781
1782         // Windows: don't allow \ in filenames (windows-only), period.
1783         // (on Windows \ is a directory separator, but / is also supported)
1784         if (strstr(path, "\\"))
1785                 return 1; // non-portable
1786
1787         // Mac: don't allow Mac-only filenames - : is a directory separator
1788         // instead of /, but we rely on / working already, so there's no reason to
1789         // support a Mac-only path
1790         // Amiga and Windows: : tries to go to root of drive
1791         if (strstr(path, ":"))
1792                 return 1; // non-portable attempt to go to root of drive
1793
1794         // Amiga: // is parent directory
1795         if (strstr(path, "//"))
1796                 return 1; // non-portable attempt to go to parent directory
1797
1798         // all: don't allow going to parent directory (../ or /../)
1799         if (strstr(path, ".."))
1800                 return 2; // attempt to go outside the game directory
1801
1802         // Windows and UNIXes: don't allow absolute paths
1803         if (path[0] == '/')
1804                 return 2; // attempt to go outside the game directory
1805
1806         // 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
1807         if (strchr(path, '.'))
1808         {
1809                 if (isgamedir)
1810                 {
1811                         // gamedir is entirely path elements, so simply forbid . entirely
1812                         return 2;
1813                 }
1814                 if (strchr(path, '.') < strrchr(path, '/'))
1815                         return 2; // possible attempt to go outside the game directory
1816         }
1817
1818         // all: forbid trailing slash on gamedir
1819         if (isgamedir && path[strlen(path)-1] == '/')
1820                 return 2;
1821
1822         // all: forbid leading dot on any filename for any reason
1823         if (strstr(path, "/."))
1824                 return 2; // attempt to go outside the game directory
1825
1826         // after all these checks we're pretty sure it's a / separated filename
1827         // and won't do much if any harm
1828         return false;
1829 }
1830
1831
1832 /*
1833 ====================
1834 FS_FindFile
1835
1836 Look for a file in the packages and in the filesystem
1837
1838 Return the searchpath where the file was found (or NULL)
1839 and the file index in the package if relevant
1840 ====================
1841 */
1842 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1843 {
1844         searchpath_t *search;
1845         pack_t *pak;
1846
1847         // search through the path, one element at a time
1848         for (search = fs_searchpaths;search;search = search->next)
1849         {
1850                 // is the element a pak file?
1851                 if (search->pack)
1852                 {
1853                         int (*strcmp_funct) (const char* str1, const char* str2);
1854                         int left, right, middle;
1855
1856                         pak = search->pack;
1857                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1858
1859                         // Look for the file (binary search)
1860                         left = 0;
1861                         right = pak->numfiles - 1;
1862                         while (left <= right)
1863                         {
1864                                 int diff;
1865
1866                                 middle = (left + right) / 2;
1867                                 diff = strcmp_funct (pak->files[middle].name, name);
1868
1869                                 // Found it
1870                                 if (!diff)
1871                                 {
1872                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
1873                                         {
1874                                                 // yes, but the first one is empty so we treat it as not being there
1875                                                 if (!quiet && developer.integer >= 10)
1876                                                         Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
1877
1878                                                 if (index != NULL)
1879                                                         *index = -1;
1880                                                 return NULL;
1881                                         }
1882
1883                                         if (!quiet && developer.integer >= 10)
1884                                                 Con_Printf("FS_FindFile: %s in %s\n",
1885                                                                         pak->files[middle].name, pak->filename);
1886
1887                                         if (index != NULL)
1888                                                 *index = middle;
1889                                         return search;
1890                                 }
1891
1892                                 // If we're too far in the list
1893                                 if (diff > 0)
1894                                         right = middle - 1;
1895                                 else
1896                                         left = middle + 1;
1897                         }
1898                 }
1899                 else
1900                 {
1901                         char netpath[MAX_OSPATH];
1902                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1903                         if (FS_SysFileExists (netpath))
1904                         {
1905                                 if (!quiet && developer.integer >= 10)
1906                                         Con_Printf("FS_FindFile: %s\n", netpath);
1907
1908                                 if (index != NULL)
1909                                         *index = -1;
1910                                 return search;
1911                         }
1912                 }
1913         }
1914
1915         if (!quiet && developer.integer >= 10)
1916                 Con_Printf("FS_FindFile: can't find %s\n", name);
1917
1918         if (index != NULL)
1919                 *index = -1;
1920         return NULL;
1921 }
1922
1923
1924 /*
1925 ===========
1926 FS_OpenReadFile
1927
1928 Look for a file in the search paths and open it in read-only mode
1929 ===========
1930 */
1931 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
1932 {
1933         searchpath_t *search;
1934         int pack_ind;
1935
1936         search = FS_FindFile (filename, &pack_ind, quiet);
1937
1938         // Not found?
1939         if (search == NULL)
1940                 return NULL;
1941
1942         // Found in the filesystem?
1943         if (pack_ind < 0)
1944         {
1945                 char path [MAX_OSPATH];
1946                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1947                 return FS_SysOpen (path, "rb", nonblocking);
1948         }
1949
1950         // So, we found it in a package...
1951
1952         // Is it a PK3 symlink?
1953         // TODO also handle directory symlinks by parsing the whole structure...
1954         // but heck, file symlinks are good enough for now
1955         if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
1956         {
1957                 if(symlinkLevels <= 0)
1958                 {
1959                         Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
1960                         return NULL;
1961                 }
1962                 else
1963                 {
1964                         char linkbuf[MAX_QPATH];
1965                         fs_offset_t count;
1966                         qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
1967                         const char *mergeslash;
1968                         char *mergestart;
1969
1970                         if(!linkfile)
1971                                 return NULL;
1972                         count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
1973                         FS_Close(linkfile);
1974                         if(count < 0)
1975                                 return NULL;
1976                         linkbuf[count] = 0;
1977                         
1978                         // Now combine the paths...
1979                         mergeslash = strrchr(filename, '/');
1980                         mergestart = linkbuf;
1981                         if(!mergeslash)
1982                                 mergeslash = filename;
1983                         while(!strncmp(mergestart, "../", 3))
1984                         {
1985                                 mergestart += 3;
1986                                 while(mergeslash > filename)
1987                                 {
1988                                         --mergeslash;
1989                                         if(*mergeslash == '/')
1990                                                 break;
1991                                 }
1992                         }
1993                         // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
1994                         if(mergeslash == filename)
1995                         {
1996                                 // Either mergeslash == filename, then we just replace the name (done below)
1997                         }
1998                         else
1999                         {
2000                                 // Or, we append the name after mergeslash;
2001                                 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2002                                 int spaceNeeded = mergeslash - filename + 1;
2003                                 int spaceRemoved = mergestart - linkbuf;
2004                                 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2005                                 {
2006                                         Con_DPrintf("symlink: too long path rejected\n");
2007                                         return NULL;
2008                                 }
2009                                 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2010                                 memcpy(linkbuf, filename, spaceNeeded);
2011                                 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2012                                 mergestart = linkbuf;
2013                         }
2014                         if (!quiet && developer_loading.integer)
2015                                 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2016                         if(FS_CheckNastyPath (mergestart, false))
2017                         {
2018                                 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2019                                 return NULL;
2020                         }
2021                         return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2022                 }
2023         }
2024
2025         return FS_OpenPackedFile (search->pack, pack_ind);
2026 }
2027
2028
2029 /*
2030 =============================================================================
2031
2032 MAIN PUBLIC FUNCTIONS
2033
2034 =============================================================================
2035 */
2036
2037 /*
2038 ====================
2039 FS_OpenRealFile
2040
2041 Open a file in the userpath. The syntax is the same as fopen
2042 Used for savegame scanning in menu, and all file writing.
2043 ====================
2044 */
2045 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2046 {
2047         char real_path [MAX_OSPATH];
2048
2049         if (FS_CheckNastyPath(filepath, false))
2050         {
2051                 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2052                 return NULL;
2053         }
2054
2055         dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
2056
2057         // If the file is opened in "write", "append", or "read/write" mode,
2058         // create directories up to the file.
2059         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2060                 FS_CreatePath (real_path);
2061         return FS_SysOpen (real_path, mode, false);
2062 }
2063
2064
2065 /*
2066 ====================
2067 FS_OpenVirtualFile
2068
2069 Open a file. The syntax is the same as fopen
2070 ====================
2071 */
2072 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2073 {
2074         if (FS_CheckNastyPath(filepath, false))
2075         {
2076                 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2077                 return NULL;
2078         }
2079
2080         return FS_OpenReadFile (filepath, quiet, false, 16);
2081 }
2082
2083
2084 /*
2085 ====================
2086 FS_FileFromData
2087
2088 Open a file. The syntax is the same as fopen
2089 ====================
2090 */
2091 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2092 {
2093         qfile_t* file;
2094         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2095         memset (file, 0, sizeof (*file));
2096         file->flags = QFILE_FLAG_DATA;
2097         file->ungetc = EOF;
2098         file->real_length = size;
2099         file->data = data;
2100         return file;
2101 }
2102
2103 /*
2104 ====================
2105 FS_Close
2106
2107 Close a file
2108 ====================
2109 */
2110 int FS_Close (qfile_t* file)
2111 {
2112         if(file->flags & QFILE_FLAG_DATA)
2113         {
2114                 Mem_Free(file);
2115                 return 0;
2116         }
2117
2118         if (close (file->handle))
2119                 return EOF;
2120
2121         if (file->ztk)
2122         {
2123                 qz_inflateEnd (&file->ztk->zstream);
2124                 Mem_Free (file->ztk);
2125         }
2126
2127         Mem_Free (file);
2128         return 0;
2129 }
2130
2131
2132 /*
2133 ====================
2134 FS_Write
2135
2136 Write "datasize" bytes into a file
2137 ====================
2138 */
2139 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2140 {
2141         fs_offset_t result;
2142
2143         // If necessary, seek to the exact file position we're supposed to be
2144         if (file->buff_ind != file->buff_len)
2145                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2146
2147         // Purge cached data
2148         FS_Purge (file);
2149
2150         // Write the buffer and update the position
2151         result = write (file->handle, data, (fs_offset_t)datasize);
2152         file->position = lseek (file->handle, 0, SEEK_CUR);
2153         if (file->real_length < file->position)
2154                 file->real_length = file->position;
2155
2156         if (result < 0)
2157                 return 0;
2158
2159         return result;
2160 }
2161
2162
2163 /*
2164 ====================
2165 FS_Read
2166
2167 Read up to "buffersize" bytes from a file
2168 ====================
2169 */
2170 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2171 {
2172         fs_offset_t count, done;
2173
2174         if (buffersize == 0)
2175                 return 0;
2176
2177         // Get rid of the ungetc character
2178         if (file->ungetc != EOF)
2179         {
2180                 ((char*)buffer)[0] = file->ungetc;
2181                 buffersize--;
2182                 file->ungetc = EOF;
2183                 done = 1;
2184         }
2185         else
2186                 done = 0;
2187
2188         if(file->flags & QFILE_FLAG_DATA)
2189         {
2190                 size_t left = file->real_length - file->position;
2191                 if(buffersize > left)
2192                         buffersize = left;
2193                 memcpy(buffer, file->data + file->position, buffersize);
2194                 file->position += buffersize;
2195                 return buffersize;
2196         }
2197
2198         // First, we copy as many bytes as we can from "buff"
2199         if (file->buff_ind < file->buff_len)
2200         {
2201                 count = file->buff_len - file->buff_ind;
2202                 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2203                 done += count;
2204                 memcpy (buffer, &file->buff[file->buff_ind], count);
2205                 file->buff_ind += count;
2206
2207                 buffersize -= count;
2208                 if (buffersize == 0)
2209                         return done;
2210         }
2211
2212         // NOTE: at this point, the read buffer is always empty
2213
2214         // If the file isn't compressed
2215         if (! (file->flags & QFILE_FLAG_DEFLATED))
2216         {
2217                 fs_offset_t nb;
2218
2219                 // We must take care to not read after the end of the file
2220                 count = file->real_length - file->position;
2221
2222                 // If we have a lot of data to get, put them directly into "buffer"
2223                 if (buffersize > sizeof (file->buff) / 2)
2224                 {
2225                         if (count > (fs_offset_t)buffersize)
2226                                 count = (fs_offset_t)buffersize;
2227                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2228                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2229                         if (nb > 0)
2230                         {
2231                                 done += nb;
2232                                 file->position += nb;
2233
2234                                 // Purge cached data
2235                                 FS_Purge (file);
2236                         }
2237                 }
2238                 else
2239                 {
2240                         if (count > (fs_offset_t)sizeof (file->buff))
2241                                 count = (fs_offset_t)sizeof (file->buff);
2242                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2243                         nb = read (file->handle, file->buff, count);
2244                         if (nb > 0)
2245                         {
2246                                 file->buff_len = nb;
2247                                 file->position += nb;
2248
2249                                 // Copy the requested data in "buffer" (as much as we can)
2250                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2251                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2252                                 file->buff_ind = count;
2253                                 done += count;
2254                         }
2255                 }
2256
2257                 return done;
2258         }
2259
2260         // If the file is compressed, it's more complicated...
2261         // We cycle through a few operations until we have read enough data
2262         while (buffersize > 0)
2263         {
2264                 ztoolkit_t *ztk = file->ztk;
2265                 int error;
2266
2267                 // NOTE: at this point, the read buffer is always empty
2268
2269                 // If "input" is also empty, we need to refill it
2270                 if (ztk->in_ind == ztk->in_len)
2271                 {
2272                         // If we are at the end of the file
2273                         if (file->position == file->real_length)
2274                                 return done;
2275
2276                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2277                         if (count > (fs_offset_t)sizeof (ztk->input))
2278                                 count = (fs_offset_t)sizeof (ztk->input);
2279                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2280                         if (read (file->handle, ztk->input, count) != count)
2281                         {
2282                                 Con_Printf ("FS_Read: unexpected end of file\n");
2283                                 break;
2284                         }
2285
2286                         ztk->in_ind = 0;
2287                         ztk->in_len = count;
2288                         ztk->in_position += count;
2289                 }
2290
2291                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2292                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2293
2294                 // Now that we are sure we have compressed data available, we need to determine
2295                 // if it's better to inflate it in "file->buff" or directly in "buffer"
2296
2297                 // Inflate the data in "file->buff"
2298                 if (buffersize < sizeof (file->buff) / 2)
2299                 {
2300                         ztk->zstream.next_out = file->buff;
2301                         ztk->zstream.avail_out = sizeof (file->buff);
2302                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2303                         if (error != Z_OK && error != Z_STREAM_END)
2304                         {
2305                                 Con_Printf ("FS_Read: Can't inflate file\n");
2306                                 break;
2307                         }
2308                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2309
2310                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2311                         file->position += file->buff_len;
2312
2313                         // Copy the requested data in "buffer" (as much as we can)
2314                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2315                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2316                         file->buff_ind = count;
2317                 }
2318
2319                 // Else, we inflate directly in "buffer"
2320                 else
2321                 {
2322                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2323                         ztk->zstream.avail_out = (unsigned int)buffersize;
2324                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2325                         if (error != Z_OK && error != Z_STREAM_END)
2326                         {
2327                                 Con_Printf ("FS_Read: Can't inflate file\n");
2328                                 break;
2329                         }
2330                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2331
2332                         // How much data did it inflate?
2333                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2334                         file->position += count;
2335
2336                         // Purge cached data
2337                         FS_Purge (file);
2338                 }
2339
2340                 done += count;
2341                 buffersize -= count;
2342         }
2343
2344         return done;
2345 }
2346
2347
2348 /*
2349 ====================
2350 FS_Print
2351
2352 Print a string into a file
2353 ====================
2354 */
2355 int FS_Print (qfile_t* file, const char *msg)
2356 {
2357         return (int)FS_Write (file, msg, strlen (msg));
2358 }
2359
2360 /*
2361 ====================
2362 FS_Printf
2363
2364 Print a string into a file
2365 ====================
2366 */
2367 int FS_Printf(qfile_t* file, const char* format, ...)
2368 {
2369         int result;
2370         va_list args;
2371
2372         va_start (args, format);
2373         result = FS_VPrintf (file, format, args);
2374         va_end (args);
2375
2376         return result;
2377 }
2378
2379
2380 /*
2381 ====================
2382 FS_VPrintf
2383
2384 Print a string into a file
2385 ====================
2386 */
2387 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2388 {
2389         int len;
2390         fs_offset_t buff_size = MAX_INPUTLINE;
2391         char *tempbuff;
2392
2393         for (;;)
2394         {
2395                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2396                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2397                 if (len >= 0 && len < buff_size)
2398                         break;
2399                 Mem_Free (tempbuff);
2400                 buff_size *= 2;
2401         }
2402
2403         len = write (file->handle, tempbuff, len);
2404         Mem_Free (tempbuff);
2405
2406         return len;
2407 }
2408
2409
2410 /*
2411 ====================
2412 FS_Getc
2413
2414 Get the next character of a file
2415 ====================
2416 */
2417 int FS_Getc (qfile_t* file)
2418 {
2419         unsigned char c;
2420
2421         if (FS_Read (file, &c, 1) != 1)
2422                 return EOF;
2423
2424         return c;
2425 }
2426
2427
2428 /*
2429 ====================
2430 FS_UnGetc
2431
2432 Put a character back into the read buffer (only supports one character!)
2433 ====================
2434 */
2435 int FS_UnGetc (qfile_t* file, unsigned char c)
2436 {
2437         // If there's already a character waiting to be read
2438         if (file->ungetc != EOF)
2439                 return EOF;
2440
2441         file->ungetc = c;
2442         return c;
2443 }
2444
2445
2446 /*
2447 ====================
2448 FS_Seek
2449
2450 Move the position index in a file
2451 ====================
2452 */
2453 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2454 {
2455         ztoolkit_t *ztk;
2456         unsigned char* buffer;
2457         fs_offset_t buffersize;
2458
2459         // Compute the file offset
2460         switch (whence)
2461         {
2462                 case SEEK_CUR:
2463                         offset += file->position - file->buff_len + file->buff_ind;
2464                         break;
2465
2466                 case SEEK_SET:
2467                         break;
2468
2469                 case SEEK_END:
2470                         offset += file->real_length;
2471                         break;
2472
2473                 default:
2474                         return -1;
2475         }
2476         if (offset < 0 || offset > file->real_length)
2477                 return -1;
2478
2479         if(file->flags & QFILE_FLAG_DATA)
2480         {
2481                 file->position = offset;
2482                 return 0;
2483         }
2484
2485         // If we have the data in our read buffer, we don't need to actually seek
2486         if (file->position - file->buff_len <= offset && offset <= file->position)
2487         {
2488                 file->buff_ind = offset + file->buff_len - file->position;
2489                 return 0;
2490         }
2491
2492         // Purge cached data
2493         FS_Purge (file);
2494
2495         // Unpacked or uncompressed files can seek directly
2496         if (! (file->flags & QFILE_FLAG_DEFLATED))
2497         {
2498                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2499                         return -1;
2500                 file->position = offset;
2501                 return 0;
2502         }
2503
2504         // Seeking in compressed files is more a hack than anything else,
2505         // but we need to support it, so here we go.
2506         ztk = file->ztk;
2507
2508         // If we have to go back in the file, we need to restart from the beginning
2509         if (offset <= file->position)
2510         {
2511                 ztk->in_ind = 0;
2512                 ztk->in_len = 0;
2513                 ztk->in_position = 0;
2514                 file->position = 0;
2515                 lseek (file->handle, file->offset, SEEK_SET);
2516
2517                 // Reset the Zlib stream
2518                 ztk->zstream.next_in = ztk->input;
2519                 ztk->zstream.avail_in = 0;
2520                 qz_inflateReset (&ztk->zstream);
2521         }
2522
2523         // We need a big buffer to force inflating into it directly
2524         buffersize = 2 * sizeof (file->buff);
2525         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2526
2527         // Skip all data until we reach the requested offset
2528         while (offset > file->position)
2529         {
2530                 fs_offset_t diff = offset - file->position;
2531                 fs_offset_t count, len;
2532
2533                 count = (diff > buffersize) ? buffersize : diff;
2534                 len = FS_Read (file, buffer, count);
2535                 if (len != count)
2536                 {
2537                         Mem_Free (buffer);
2538                         return -1;
2539                 }
2540         }
2541
2542         Mem_Free (buffer);
2543         return 0;
2544 }
2545
2546
2547 /*
2548 ====================
2549 FS_Tell
2550
2551 Give the current position in a file
2552 ====================
2553 */
2554 fs_offset_t FS_Tell (qfile_t* file)
2555 {
2556         return file->position - file->buff_len + file->buff_ind;
2557 }
2558
2559
2560 /*
2561 ====================
2562 FS_FileSize
2563
2564 Give the total size of a file
2565 ====================
2566 */
2567 fs_offset_t FS_FileSize (qfile_t* file)
2568 {
2569         return file->real_length;
2570 }
2571
2572
2573 /*
2574 ====================
2575 FS_Purge
2576
2577 Erases any buffered input or output data
2578 ====================
2579 */
2580 void FS_Purge (qfile_t* file)
2581 {
2582         file->buff_len = 0;
2583         file->buff_ind = 0;
2584         file->ungetc = EOF;
2585 }
2586
2587
2588 /*
2589 ============
2590 FS_LoadFile
2591
2592 Filename are relative to the quake directory.
2593 Always appends a 0 byte.
2594 ============
2595 */
2596 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2597 {
2598         qfile_t *file;
2599         unsigned char *buf = NULL;
2600         fs_offset_t filesize = 0;
2601
2602         file = FS_OpenVirtualFile(path, quiet);
2603         if (file)
2604         {
2605                 filesize = file->real_length;
2606                 if(filesize < 0)
2607                 {
2608                         Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
2609                         FS_Close(file);
2610                         return NULL;
2611                 }
2612
2613                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2614                 buf[filesize] = '\0';
2615                 FS_Read (file, buf, filesize);
2616                 FS_Close (file);
2617                 if (developer_loadfile.integer)
2618                         Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
2619         }
2620
2621         if (filesizepointer)
2622                 *filesizepointer = filesize;
2623         return buf;
2624 }
2625
2626
2627 /*
2628 ============
2629 FS_WriteFile
2630
2631 The filename will be prefixed by the current game directory
2632 ============
2633 */
2634 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2635 {
2636         qfile_t *file;
2637
2638         file = FS_OpenRealFile(filename, "wb", false);
2639         if (!file)
2640         {
2641                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2642                 return false;
2643         }
2644
2645         Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)len);
2646         FS_Write (file, data, len);
2647         FS_Close (file);
2648         return true;
2649 }
2650
2651
2652 /*
2653 =============================================================================
2654
2655 OTHERS PUBLIC FUNCTIONS
2656
2657 =============================================================================
2658 */
2659
2660 /*
2661 ============
2662 FS_StripExtension
2663 ============
2664 */
2665 void FS_StripExtension (const char *in, char *out, size_t size_out)
2666 {
2667         char *last = NULL;
2668         char currentchar;
2669
2670         if (size_out == 0)
2671                 return;
2672
2673         while ((currentchar = *in) && size_out > 1)
2674         {
2675                 if (currentchar == '.')
2676                         last = out;
2677                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2678                         last = NULL;
2679                 *out++ = currentchar;
2680                 in++;
2681                 size_out--;
2682         }
2683         if (last)
2684                 *last = 0;
2685         else
2686                 *out = 0;
2687 }
2688
2689
2690 /*
2691 ==================
2692 FS_DefaultExtension
2693 ==================
2694 */
2695 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2696 {
2697         const char *src;
2698
2699         // if path doesn't have a .EXT, append extension
2700         // (extension should include the .)
2701         src = path + strlen(path) - 1;
2702
2703         while (*src != '/' && src != path)
2704         {
2705                 if (*src == '.')
2706                         return;                 // it has an extension
2707                 src--;
2708         }
2709
2710         strlcat (path, extension, size_path);
2711 }
2712
2713
2714 /*
2715 ==================
2716 FS_FileType
2717
2718 Look for a file in the packages and in the filesystem
2719 ==================
2720 */
2721 int FS_FileType (const char *filename)
2722 {
2723         searchpath_t *search;
2724         char fullpath[MAX_QPATH];
2725
2726         search = FS_FindFile (filename, NULL, true);
2727         if(!search)
2728                 return FS_FILETYPE_NONE;
2729
2730         if(search->pack)
2731                 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
2732
2733         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
2734         return FS_SysFileType(fullpath);
2735 }
2736
2737
2738 /*
2739 ==================
2740 FS_FileExists
2741
2742 Look for a file in the packages and in the filesystem
2743 ==================
2744 */
2745 qboolean FS_FileExists (const char *filename)
2746 {
2747         return (FS_FindFile (filename, NULL, true) != NULL);
2748 }
2749
2750
2751 /*
2752 ==================
2753 FS_SysFileExists
2754
2755 Look for a file in the filesystem only
2756 ==================
2757 */
2758 int FS_SysFileType (const char *path)
2759 {
2760 #if WIN32
2761 // Sajt - some older sdks are missing this define
2762 # ifndef INVALID_FILE_ATTRIBUTES
2763 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
2764 # endif
2765
2766         DWORD result = GetFileAttributes(path);
2767
2768         if(result == INVALID_FILE_ATTRIBUTES)
2769                 return FS_FILETYPE_NONE;
2770
2771         if(result & FILE_ATTRIBUTE_DIRECTORY)
2772                 return FS_FILETYPE_DIRECTORY;
2773
2774         return FS_FILETYPE_FILE;
2775 #else
2776         struct stat buf;
2777
2778         if (stat (path,&buf) == -1)
2779                 return FS_FILETYPE_NONE;
2780
2781         if(S_ISDIR(buf.st_mode))
2782                 return FS_FILETYPE_DIRECTORY;
2783
2784         return FS_FILETYPE_FILE;
2785 #endif
2786 }
2787
2788 qboolean FS_SysFileExists (const char *path)
2789 {
2790         return FS_SysFileType (path) != FS_FILETYPE_NONE;
2791 }
2792
2793 void FS_mkdir (const char *path)
2794 {
2795 #if WIN32
2796         _mkdir (path);
2797 #else
2798         mkdir (path, 0777);
2799 #endif
2800 }
2801
2802 /*
2803 ===========
2804 FS_Search
2805
2806 Allocate and fill a search structure with information on matching filenames.
2807 ===========
2808 */
2809 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2810 {
2811         fssearch_t *search;
2812         searchpath_t *searchpath;
2813         pack_t *pak;
2814         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
2815         stringlist_t resultlist;
2816         stringlist_t dirlist;
2817         const char *slash, *backslash, *colon, *separator;
2818         char *basepath;
2819         char temp[MAX_OSPATH];
2820
2821         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2822                 ;
2823
2824         if (i > 0)
2825         {
2826                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2827                 return NULL;
2828         }
2829
2830         stringlistinit(&resultlist);
2831         stringlistinit(&dirlist);
2832         search = NULL;
2833         slash = strrchr(pattern, '/');
2834         backslash = strrchr(pattern, '\\');
2835         colon = strrchr(pattern, ':');
2836         separator = max(slash, backslash);
2837         separator = max(separator, colon);
2838         basepathlength = separator ? (separator + 1 - pattern) : 0;
2839         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2840         if (basepathlength)
2841                 memcpy(basepath, pattern, basepathlength);
2842         basepath[basepathlength] = 0;
2843
2844         // search through the path, one element at a time
2845         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2846         {
2847                 // is the element a pak file?
2848                 if (searchpath->pack)
2849                 {
2850                         // look through all the pak file elements
2851                         pak = searchpath->pack;
2852                         for (i = 0;i < pak->numfiles;i++)
2853                         {
2854                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
2855                                 while (temp[0])
2856                                 {
2857                                         if (matchpattern(temp, (char *)pattern, true))
2858                                         {
2859                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2860                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
2861                                                                 break;
2862                                                 if (resultlistindex == resultlist.numstrings)
2863                                                 {
2864                                                         stringlistappend(&resultlist, temp);
2865                                                         if (!quiet && developer_loading.integer)
2866                                                                 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
2867                                                 }
2868                                         }
2869                                         // strip off one path element at a time until empty
2870                                         // this way directories are added to the listing if they match the pattern
2871                                         slash = strrchr(temp, '/');
2872                                         backslash = strrchr(temp, '\\');
2873                                         colon = strrchr(temp, ':');
2874                                         separator = temp;
2875                                         if (separator < slash)
2876                                                 separator = slash;
2877                                         if (separator < backslash)
2878                                                 separator = backslash;
2879                                         if (separator < colon)
2880                                                 separator = colon;
2881                                         *((char *)separator) = 0;
2882                                 }
2883                         }
2884                 }
2885                 else
2886                 {
2887                         stringlist_t matchedSet, foundSet;
2888                         const char *start = pattern;
2889
2890                         stringlistinit(&matchedSet);
2891                         stringlistinit(&foundSet);
2892                         // add a first entry to the set
2893                         stringlistappend(&matchedSet, "");
2894                         // iterate through pattern's path
2895                         while (*start)
2896                         {
2897                                 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
2898                                 char subpath[MAX_OSPATH];
2899                                 char subpattern[MAX_OSPATH];
2900
2901                                 // find the next wildcard
2902                                 wildcard = strchr(start, '?');
2903                                 asterisk = strchr(start, '*');
2904                                 if (asterisk && (!wildcard || asterisk < wildcard))
2905                                 {
2906                                         wildcard = asterisk;
2907                                 }
2908
2909                                 if (wildcard)
2910                                 {
2911                                         nextseparator = strchr( wildcard, '/' );
2912                                 }
2913                                 else
2914                                 {
2915                                         nextseparator = NULL;
2916                                 }
2917
2918                                 if( !nextseparator ) {
2919                                         nextseparator = start + strlen( start );
2920                                 }
2921
2922                                 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
2923                                 // copy everything up except nextseperator
2924                                 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
2925                                 // find the last '/' before the wildcard
2926                                 prevseparator = strrchr( subpattern, '/' );
2927                                 if (!prevseparator)
2928                                         prevseparator = subpattern;
2929                                 else
2930                                         prevseparator++;
2931                                 // copy everything from start to the previous including the '/' (before the wildcard)
2932                                 // everything up to start is already included in the path of matchedSet's entries
2933                                 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
2934
2935                                 // for each entry in matchedSet try to open the subdirectories specified in subpath
2936                                 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
2937                                         strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
2938                                         strlcat( temp, subpath, sizeof(temp) );
2939                                         listdirectory( &foundSet, searchpath->filename, temp );
2940                                 }
2941                                 if( dirlistindex == 0 ) {
2942                                         break;
2943                                 }
2944                                 // reset the current result set
2945                                 stringlistfreecontents( &matchedSet );
2946                                 // match against the pattern
2947                                 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
2948                                         const char *direntry = foundSet.strings[ dirlistindex ];
2949                                         if (matchpattern(direntry, subpattern, true)) {
2950                                                 stringlistappend( &matchedSet, direntry );
2951                                         }
2952                                 }
2953                                 stringlistfreecontents( &foundSet );
2954
2955                                 start = nextseparator;
2956                         }
2957
2958                         for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
2959                         {
2960                                 const char *temp = matchedSet.strings[dirlistindex];
2961                                 if (matchpattern(temp, (char *)pattern, true))
2962                                 {
2963                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2964                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
2965                                                         break;
2966                                         if (resultlistindex == resultlist.numstrings)
2967                                         {
2968                                                 stringlistappend(&resultlist, temp);
2969                                                 if (!quiet && developer_loading.integer)
2970                                                         Con_Printf("SearchDirFile: %s\n", temp);
2971                                         }
2972                                 }
2973                         }
2974                         stringlistfreecontents( &matchedSet );
2975                 }
2976         }
2977
2978         if (resultlist.numstrings)
2979         {
2980                 stringlistsort(&resultlist);
2981                 numfiles = resultlist.numstrings;
2982                 numchars = 0;
2983                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2984                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
2985                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2986                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2987                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2988                 search->numfilenames = (int)numfiles;
2989                 numfiles = 0;
2990                 numchars = 0;
2991                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2992                 {
2993                         size_t textlen;
2994                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
2995                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
2996                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
2997                         numfiles++;
2998                         numchars += (int)textlen;
2999                 }
3000         }
3001         stringlistfreecontents(&resultlist);
3002
3003         Mem_Free(basepath);
3004         return search;
3005 }
3006
3007 void FS_FreeSearch(fssearch_t *search)
3008 {
3009         Z_Free(search);
3010 }
3011
3012 extern int con_linewidth;
3013 int FS_ListDirectory(const char *pattern, int oneperline)
3014 {
3015         int numfiles;
3016         int numcolumns;
3017         int numlines;
3018         int columnwidth;
3019         int linebufpos;
3020         int i, j, k, l;
3021         const char *name;
3022         char linebuf[MAX_INPUTLINE];
3023         fssearch_t *search;
3024         search = FS_Search(pattern, true, true);
3025         if (!search)
3026                 return 0;
3027         numfiles = search->numfilenames;
3028         if (!oneperline)
3029         {
3030                 // FIXME: the names could be added to one column list and then
3031                 // gradually shifted into the next column if they fit, and then the
3032                 // next to make a compact variable width listing but it's a lot more
3033                 // complicated...
3034                 // find width for columns
3035                 columnwidth = 0;
3036                 for (i = 0;i < numfiles;i++)
3037                 {
3038                         l = (int)strlen(search->filenames[i]);
3039                         if (columnwidth < l)
3040                                 columnwidth = l;
3041                 }
3042                 // count the spacing character
3043                 columnwidth++;
3044                 // calculate number of columns
3045                 numcolumns = con_linewidth / columnwidth;
3046                 // don't bother with the column printing if it's only one column
3047                 if (numcolumns >= 2)
3048                 {
3049                         numlines = (numfiles + numcolumns - 1) / numcolumns;
3050                         for (i = 0;i < numlines;i++)
3051                         {
3052                                 linebufpos = 0;
3053                                 for (k = 0;k < numcolumns;k++)
3054                                 {
3055                                         l = i * numcolumns + k;
3056                                         if (l < numfiles)
3057                                         {
3058                                                 name = search->filenames[l];
3059                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3060                                                         linebuf[linebufpos++] = name[j];
3061                                                 // space out name unless it's the last on the line
3062                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
3063                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3064                                                                 linebuf[linebufpos++] = ' ';
3065                                         }
3066                                 }
3067                                 linebuf[linebufpos] = 0;
3068                                 Con_Printf("%s\n", linebuf);
3069                         }
3070                 }
3071                 else
3072                         oneperline = true;
3073         }
3074         if (oneperline)
3075                 for (i = 0;i < numfiles;i++)
3076                         Con_Printf("%s\n", search->filenames[i]);
3077         FS_FreeSearch(search);
3078         return (int)numfiles;
3079 }
3080
3081 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3082 {
3083         const char *pattern;
3084         if (Cmd_Argc() > 3)
3085         {
3086                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3087                 return;
3088         }
3089         if (Cmd_Argc() == 2)
3090                 pattern = Cmd_Argv(1);
3091         else
3092                 pattern = "*";
3093         if (!FS_ListDirectory(pattern, oneperline))
3094                 Con_Print("No files found.\n");
3095 }
3096
3097 void FS_Dir_f(void)
3098 {
3099         FS_ListDirectoryCmd("dir", true);
3100 }
3101
3102 void FS_Ls_f(void)
3103 {
3104         FS_ListDirectoryCmd("ls", false);
3105 }
3106
3107 void FS_Which_f(void)
3108 {
3109         const char *filename;
3110         int index;
3111         searchpath_t *sp;
3112         if (Cmd_Argc() != 2)
3113         {
3114                 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3115                 return;
3116         }  
3117         filename = Cmd_Argv(1);
3118         sp = FS_FindFile(filename, &index, true);
3119         if (!sp) {
3120                 Con_Printf("%s isn't anywhere\n", filename);
3121                 return;
3122         }
3123         if (sp->pack)
3124                 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3125         else
3126                 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3127 }
3128
3129
3130 const char *FS_WhichPack(const char *filename)
3131 {
3132         int index;
3133         searchpath_t *sp = FS_FindFile(filename, &index, true);
3134         if(sp && sp->pack)
3135                 return sp->pack->shortname;
3136         else
3137                 return 0;
3138 }
3139
3140 /*
3141 ====================
3142 FS_IsRegisteredQuakePack
3143
3144 Look for a proof of purchase file file in the requested package
3145
3146 If it is found, this file should NOT be downloaded.
3147 ====================
3148 */
3149 qboolean FS_IsRegisteredQuakePack(const char *name)
3150 {
3151         searchpath_t *search;
3152         pack_t *pak;
3153
3154         // search through the path, one element at a time
3155         for (search = fs_searchpaths;search;search = search->next)
3156         {
3157                 if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3158                 {
3159                         int (*strcmp_funct) (const char* str1, const char* str2);
3160                         int left, right, middle;
3161
3162                         pak = search->pack;
3163                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3164
3165                         // Look for the file (binary search)
3166                         left = 0;
3167                         right = pak->numfiles - 1;
3168                         while (left <= right)
3169                         {
3170                                 int diff;
3171
3172                                 middle = (left + right) / 2;
3173                                 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3174
3175                                 // Found it
3176                                 if (!diff)
3177                                         return true;
3178
3179                                 // If we're too far in the list
3180                                 if (diff > 0)
3181                                         right = middle - 1;
3182                                 else
3183                                         left = middle + 1;
3184                         }
3185
3186                         // we found the requested pack but it is not registered quake
3187                         return false;
3188                 }
3189         }
3190
3191         return false;
3192 }
3193
3194 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3195 {
3196         int crc = -1;
3197         unsigned char *filedata;
3198         fs_offset_t filesize;
3199         if (filesizepointer)
3200                 *filesizepointer = 0;
3201         if (!filename || !*filename)
3202                 return crc;
3203         filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3204         if (filedata)
3205         {
3206                 if (filesizepointer)
3207                         *filesizepointer = filesize;
3208                 crc = CRC_Block(filedata, filesize);
3209                 Mem_Free(filedata);
3210         }
3211         return crc;
3212 }
3213
3214 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3215 {
3216         z_stream strm;
3217         unsigned char *out = NULL;
3218         unsigned char *tmp;
3219
3220         memset(&strm, 0, sizeof(strm));
3221         strm.zalloc = Z_NULL;
3222         strm.zfree = Z_NULL;
3223         strm.opaque = Z_NULL;
3224
3225         if(level < 0)
3226                 level = Z_DEFAULT_COMPRESSION;
3227
3228         if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3229         {
3230                 Con_Printf("FS_Deflate: deflate init error!\n");
3231                 return NULL;
3232         }
3233
3234         strm.next_in = (unsigned char*)data;
3235         strm.avail_in = size;
3236
3237         tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
3238         if(!tmp)
3239         {
3240                 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
3241                 qz_deflateEnd(&strm);
3242                 return NULL;
3243         }
3244
3245         strm.next_out = tmp;
3246         strm.avail_out = size;
3247
3248         if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
3249         {
3250                 Con_Printf("FS_Deflate: deflate failed!\n");
3251                 qz_deflateEnd(&strm);
3252                 Mem_Free(tmp);
3253                 return NULL;
3254         }
3255         
3256         if(qz_deflateEnd(&strm) != Z_OK)
3257         {
3258                 Con_Printf("FS_Deflate: deflateEnd failed\n");
3259                 Mem_Free(tmp);
3260                 return NULL;
3261         }
3262
3263         if(strm.total_out >= size)
3264         {
3265                 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
3266                 Mem_Free(tmp);
3267                 return NULL;
3268         }
3269
3270         out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
3271         if(!out)
3272         {
3273                 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
3274                 Mem_Free(tmp);
3275                 return NULL;
3276         }
3277
3278         if(deflated_size)
3279                 *deflated_size = (size_t)strm.total_out;
3280
3281         memcpy(out, tmp, strm.total_out);
3282         Mem_Free(tmp);
3283         
3284         return out;
3285 }
3286
3287 static void AssertBufsize(sizebuf_t *buf, int length)
3288 {
3289         if(buf->cursize + length > buf->maxsize)
3290         {
3291                 int oldsize = buf->maxsize;
3292                 unsigned char *olddata;
3293                 olddata = buf->data;
3294                 buf->maxsize += length;
3295                 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
3296                 if(olddata)
3297                 {
3298                         memcpy(buf->data, olddata, oldsize);
3299                         Mem_Free(olddata);
3300                 }
3301         }
3302 }
3303
3304 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
3305 {
3306         int ret;
3307         z_stream strm;
3308         unsigned char *out = NULL;
3309         unsigned char tmp[2048];
3310         unsigned int have;
3311         sizebuf_t outbuf;
3312
3313         memset(&outbuf, 0, sizeof(outbuf));
3314         outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
3315         outbuf.maxsize = sizeof(tmp);
3316
3317         memset(&strm, 0, sizeof(strm));
3318         strm.zalloc = Z_NULL;
3319         strm.zfree = Z_NULL;
3320         strm.opaque = Z_NULL;
3321
3322         if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
3323         {
3324                 Con_Printf("FS_Inflate: inflate init error!\n");
3325                 Mem_Free(outbuf.data);
3326                 return NULL;
3327         }
3328
3329         strm.next_in = (unsigned char*)data;
3330         strm.avail_in = size;
3331
3332         do
3333         {
3334                 strm.next_out = tmp;
3335                 strm.avail_out = sizeof(tmp);
3336                 ret = qz_inflate(&strm, Z_NO_FLUSH);
3337                 // it either returns Z_OK on progress, Z_STREAM_END on end
3338                 // or an error code
3339                 switch(ret)
3340                 {
3341                         case Z_STREAM_END:
3342                         case Z_OK:
3343                                 break;
3344                                 
3345                         case Z_STREAM_ERROR:
3346                                 Con_Print("FS_Inflate: stream error!\n");
3347                                 break;
3348                         case Z_DATA_ERROR:
3349                                 Con_Print("FS_Inflate: data error!\n");
3350                                 break;
3351                         case Z_MEM_ERROR:
3352                                 Con_Print("FS_Inflate: mem error!\n");
3353                                 break;
3354                         case Z_BUF_ERROR:
3355                                 Con_Print("FS_Inflate: buf error!\n");
3356                                 break;
3357                         default:
3358                                 Con_Print("FS_Inflate: unknown error!\n");
3359                                 break;
3360                                 
3361                 }
3362                 if(ret != Z_OK && ret != Z_STREAM_END)
3363                 {
3364                         Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
3365                         Mem_Free(outbuf.data);
3366                         qz_inflateEnd(&strm);
3367                         return NULL;
3368                 }
3369                 have = sizeof(tmp) - strm.avail_out;
3370                 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
3371                 SZ_Write(&outbuf, tmp, have);
3372         } while(ret != Z_STREAM_END);
3373
3374         qz_inflateEnd(&strm);
3375
3376         out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
3377         if(!out)
3378         {
3379                 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
3380                 Mem_Free(outbuf.data);
3381                 return NULL;
3382         }
3383
3384         memcpy(out, outbuf.data, outbuf.cursize);
3385         Mem_Free(outbuf.data);
3386
3387         if(inflated_size)
3388                 *inflated_size = (size_t)outbuf.cursize;
3389         
3390         return out;
3391 }