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