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