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