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