]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - fs.c
3e6b8a821ffe83e25c3404443278024d710a4814
[xonotic/darkplaces.git] / fs.c
1 /*
2         DarkPlaces file system
3
4         Copyright (C) 2003-2006 Mathieu Olivier
5
6         This program is free software; you can redistribute it and/or
7         modify it under the terms of the GNU General Public License
8         as published by the Free Software Foundation; either version 2
9         of the License, or (at your option) any later version.
10
11         This program is distributed in the hope that it will be useful,
12         but WITHOUT ANY WARRANTY; without even the implied warranty of
13         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15         See the GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program; if not, write to:
19
20                 Free Software Foundation, Inc.
21                 59 Temple Place - Suite 330
22                 Boston, MA  02111-1307, USA
23 */
24
25 // on UNIX platforms we need to define this so that video saving does not cause a SIGFSZ (file size) signal when a video clip exceeds 2GB
26 #define _FILE_OFFSET_BITS 64
27
28 #include "quakedef.h"
29
30 #include <limits.h>
31 #include <fcntl.h>
32
33 #ifdef WIN32
34 # include <direct.h>
35 # include <io.h>
36 #else
37 # include <pwd.h>
38 # include <sys/stat.h>
39 # include <unistd.h>
40 #endif
41
42 #include "fs.h"
43
44 // Win32 requires us to add O_BINARY, but the other OSes don't have it
45 #ifndef O_BINARY
46 # define O_BINARY 0
47 #endif
48
49 // In case the system doesn't support the O_NONBLOCK flag
50 #ifndef O_NONBLOCK
51 # define O_NONBLOCK 0
52 #endif
53
54
55 /*
56
57 All of Quake's data access is through a hierchal file system, but the contents
58 of the file system can be transparently merged from several sources.
59
60 The "base directory" is the path to the directory holding the quake.exe and
61 all game directories.  The sys_* files pass this to host_init in
62 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
63 line parm to allow code debugging in a different directory.  The base
64 directory is only used during filesystem initialization.
65
66 The "game directory" is the first tree on the search path and directory that
67 all generated files (savegames, screenshots, demos, config files) will be
68 saved to.  This can be overridden with the "-game" command line parameter.
69 The game directory can never be changed while quake is executing.  This is a
70 precaution against having a malicious server instruct clients to write files
71 over areas they shouldn't.
72
73 */
74
75
76 /*
77 =============================================================================
78
79 CONSTANTS
80
81 =============================================================================
82 */
83
84 // Magic numbers of a ZIP file (big-endian format)
85 #define ZIP_DATA_HEADER 0x504B0304  // "PK\3\4"
86 #define ZIP_CDIR_HEADER 0x504B0102  // "PK\1\2"
87 #define ZIP_END_HEADER  0x504B0506  // "PK\5\6"
88
89 // Other constants for ZIP files
90 #define ZIP_MAX_COMMENTS_SIZE           ((unsigned short)0xFFFF)
91 #define ZIP_END_CDIR_SIZE                       22
92 #define ZIP_CDIR_CHUNK_BASE_SIZE        46
93 #define ZIP_LOCAL_CHUNK_BASE_SIZE       30
94
95 // Zlib constants (from zlib.h)
96 #define Z_SYNC_FLUSH    2
97 #define MAX_WBITS               15
98 #define Z_OK                    0
99 #define Z_STREAM_END    1
100 #define ZLIB_VERSION    "1.2.3"
101
102 // Uncomment the following line if the zlib DLL you have still uses
103 // the 1.1.x series calling convention on Win32 (WINAPI)
104 //#define ZLIB_USES_WINAPI
105
106
107 /*
108 =============================================================================
109
110 TYPES
111
112 =============================================================================
113 */
114
115 // Zlib stream (from zlib.h)
116 // Warning: some pointers we don't use directly have
117 // been cast to "void*" for a matter of simplicity
118 typedef struct
119 {
120         unsigned char                   *next_in;       // next input byte
121         unsigned int    avail_in;       // number of bytes available at next_in
122         unsigned long   total_in;       // total nb of input bytes read so far
123
124         unsigned char                   *next_out;      // next output byte should be put there
125         unsigned int    avail_out;      // remaining free space at next_out
126         unsigned long   total_out;      // total nb of bytes output so far
127
128         char                    *msg;           // last error message, NULL if no error
129         void                    *state;         // not visible by applications
130
131         void                    *zalloc;        // used to allocate the internal state
132         void                    *zfree;         // used to free the internal state
133         void                    *opaque;        // private data object passed to zalloc and zfree
134
135         int                             data_type;      // best guess about the data type: ascii or binary
136         unsigned long   adler;          // adler32 value of the uncompressed data
137         unsigned long   reserved;       // reserved for future use
138 } z_stream;
139
140
141 // inside a package (PAK or PK3)
142 #define QFILE_FLAG_PACKED (1 << 0)
143 // file is compressed using the deflate algorithm (PK3 only)
144 #define QFILE_FLAG_DEFLATED (1 << 1)
145
146 #define FILE_BUFF_SIZE 2048
147 typedef struct
148 {
149         z_stream        zstream;
150         size_t          comp_length;                    // length of the compressed file
151         size_t          in_ind, in_len;                 // input buffer current index and length
152         size_t          in_position;                    // position in the compressed file
153         unsigned char           input [FILE_BUFF_SIZE];
154 } ztoolkit_t;
155
156 struct qfile_s
157 {
158         int                             flags;
159         int                             handle;                                 // file descriptor
160         fs_offset_t             real_length;                    // uncompressed file size (for files opened in "read" mode)
161         fs_offset_t             position;                               // current position in the file
162         fs_offset_t             offset;                                 // offset into the package (0 if external file)
163         int                             ungetc;                                 // single stored character from ungetc, cleared to EOF when read
164
165         // Contents buffer
166         fs_offset_t             buff_ind, buff_len;             // buffer current index and length
167         unsigned char                   buff [FILE_BUFF_SIZE];
168
169         // For zipped files
170         ztoolkit_t*             ztk;
171 };
172
173
174 // ------ PK3 files on disk ------ //
175
176 // You can get the complete ZIP format description from PKWARE website
177
178 typedef struct pk3_endOfCentralDir_s
179 {
180         unsigned int signature;
181         unsigned short disknum;
182         unsigned short cdir_disknum;    // number of the disk with the start of the central directory
183         unsigned short localentries;    // number of entries in the central directory on this disk
184         unsigned short nbentries;               // total number of entries in the central directory on this disk
185         unsigned int cdir_size;                 // size of the central directory
186         unsigned int cdir_offset;               // with respect to the starting disk number
187         unsigned short comment_size;
188 } pk3_endOfCentralDir_t;
189
190
191 // ------ PAK files on disk ------ //
192 typedef struct dpackfile_s
193 {
194         char name[56];
195         int filepos, filelen;
196 } dpackfile_t;
197
198 typedef struct dpackheader_s
199 {
200         char id[4];
201         int dirofs;
202         int dirlen;
203 } dpackheader_t;
204
205
206 // Packages in memory
207 // the offset in packfile_t is the true contents offset
208 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
209 // file compressed using the deflate algorithm
210 #define PACKFILE_FLAG_DEFLATED (1 << 1)
211
212 typedef struct packfile_s
213 {
214         char name [MAX_QPATH];
215         int flags;
216         fs_offset_t offset;
217         fs_offset_t packsize;   // size in the package
218         fs_offset_t realsize;   // real file size (uncompressed)
219 } packfile_t;
220
221 typedef struct pack_s
222 {
223         char filename [MAX_OSPATH];
224         int handle;
225         int ignorecase;  // PK3 ignores case
226         int numfiles;
227         packfile_t *files;
228 } pack_t;
229
230
231 // Search paths for files (including packages)
232 typedef struct searchpath_s
233 {
234         // only one of filename / pack will be used
235         char filename[MAX_OSPATH];
236         pack_t *pack;
237         struct searchpath_s *next;
238 } searchpath_t;
239
240
241 /*
242 =============================================================================
243
244 FUNCTION PROTOTYPES
245
246 =============================================================================
247 */
248
249 void FS_Dir_f(void);
250 void FS_Ls_f(void);
251
252 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
253 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
254                                                                         fs_offset_t offset, fs_offset_t packsize,
255                                                                         fs_offset_t realsize, int flags);
256
257
258 /*
259 =============================================================================
260
261 VARIABLES
262
263 =============================================================================
264 */
265
266 mempool_t *fs_mempool;
267
268 searchpath_t *fs_searchpaths = NULL;
269
270 #define MAX_FILES_IN_PACK       65536
271
272 char fs_gamedir[MAX_OSPATH];
273 char fs_basedir[MAX_OSPATH];
274
275 // list of active game directories (empty if not running a mod)
276 #define MAX_GAMEDIRS 16
277 int fs_numgamedirs = 0;
278 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
279
280 cvar_t scr_screenshot_name = {0, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running)"};
281 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         while (fs_searchpaths)
1083         {
1084                 searchpath_t *search = fs_searchpaths;
1085                 fs_searchpaths = search->next;
1086                 if (search->pack)
1087                 {
1088                         if (search->pack->files)
1089                                 Mem_Free(search->pack->files);
1090                         Mem_Free(search->pack);
1091                 }
1092                 Mem_Free(search);
1093         }
1094 }
1095
1096
1097 /*
1098 ================
1099 FS_Rescan
1100 ================
1101 */
1102 void FS_Rescan (void)
1103 {
1104         int i;
1105         qboolean fs_modified = false;
1106
1107         FS_ClearSearchPath();
1108
1109         // add the game-specific paths
1110         // gamedirname1 (typically id1)
1111         FS_AddGameHierarchy (gamedirname1);
1112         // update the com_modname (used for server info)
1113         strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1114
1115         // add the game-specific path, if any
1116         // (only used for mission packs and the like, which should set fs_modified)
1117         if (gamedirname2)
1118         {
1119                 fs_modified = true;
1120                 FS_AddGameHierarchy (gamedirname2);
1121         }
1122
1123         // -game <gamedir>
1124         // Adds basedir/gamedir as an override game
1125         // LordHavoc: now supports multiple -game directories
1126         // set the com_modname (reported in server info)
1127         for (i = 0;i < fs_numgamedirs;i++)
1128         {
1129                 fs_modified = true;
1130                 FS_AddGameHierarchy (fs_gamedirs[i]);
1131                 // update the com_modname (used server info)
1132                 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1133         }
1134
1135         // set the default screenshot name to either the mod name or the
1136         // gamemode screenshot name
1137         if (strcmp(com_modname, gamedirname1))
1138                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1139         else
1140                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1141
1142         // If "-condebug" is in the command line, remove the previous log file
1143         if (COM_CheckParm ("-condebug") != 0)
1144                 unlink (va("%s/qconsole.log", fs_gamedir));
1145
1146         // look for the pop.lmp file and set registered to true if it is found
1147         if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
1148         {
1149                 if (fs_modified)
1150                         Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1151                 else
1152                         Con_Print("Playing shareware version.\n");
1153         }
1154         else
1155         {
1156                 Cvar_Set ("registered", "1");
1157                 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1158                         Con_Print("Playing registered version.\n");
1159         }
1160 }
1161
1162 void FS_Rescan_f(void)
1163 {
1164         FS_Rescan();
1165 }
1166
1167 /*
1168 ================
1169 FS_ChangeGameDirs
1170 ================
1171 */
1172 extern void Host_SaveConfig_f (void);
1173 extern void Host_LoadConfig_f (void);
1174 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1175 {
1176         int i;
1177
1178         if (fs_numgamedirs == numgamedirs)
1179         {
1180                 for (i = 0;i < numgamedirs;i++)
1181                         if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1182                                 break;
1183                 if (i == numgamedirs)
1184                         return true; // already using this set of gamedirs, do nothing
1185         }
1186
1187         if (numgamedirs > MAX_GAMEDIRS)
1188         {
1189                 if (complain)
1190                         Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1191                 return false; // too many gamedirs
1192         }
1193
1194         for (i = 0;i < numgamedirs;i++)
1195         {
1196                 // if string is nasty, reject it
1197                 if(FS_CheckNastyPath(gamedirs[i], true))
1198                 {
1199                         if (complain)
1200                                 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1201                         return false; // nasty gamedirs
1202                 }
1203         }
1204
1205         for (i = 0;i < numgamedirs;i++)
1206         {
1207                 if (!FS_CheckGameDir(gamedirs[i]) && failmissing)
1208                 {
1209                         if (complain)
1210                                 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1211                         return false; // missing gamedirs
1212                 }
1213         }
1214
1215         Host_SaveConfig_f();
1216
1217         fs_numgamedirs = numgamedirs;
1218         for (i = 0;i < fs_numgamedirs;i++)
1219                 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1220
1221         // reinitialize filesystem to detect the new paks
1222         FS_Rescan();
1223
1224         // exec the new config
1225         Host_LoadConfig_f();
1226
1227         // reinitialize the loaded sounds
1228         S_Reload_f();
1229
1230         // reinitialize renderer (this reloads hud/console background/etc)
1231         R_Modules_Restart();
1232
1233         return true;
1234 }
1235
1236 /*
1237 ================
1238 FS_GameDir_f
1239 ================
1240 */
1241 void FS_GameDir_f (void)
1242 {
1243         int i;
1244         int numgamedirs;
1245         char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1246
1247         if (Cmd_Argc() < 2)
1248         {
1249                 Con_Printf("gamedirs active:");
1250                 for (i = 0;i < fs_numgamedirs;i++)
1251                         Con_Printf(" %s", fs_gamedirs[i]);
1252                 Con_Printf("\n");
1253                 return;
1254         }
1255
1256         numgamedirs = Cmd_Argc() - 1;
1257         if (numgamedirs > MAX_GAMEDIRS)
1258         {
1259                 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1260                 return;
1261         }
1262
1263         for (i = 0;i < numgamedirs;i++)
1264                 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1265
1266         // allow gamedir change during demo loop
1267         if (cls.demoplayback)
1268                 CL_Disconnect();
1269
1270         if (cls.state == ca_connected || sv.active)
1271         {
1272                 // actually, changing during game would work fine, but would be stupid
1273                 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1274                 return;
1275         }
1276
1277         FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1278 }
1279
1280
1281 /*
1282 ================
1283 FS_CheckGameDir
1284 ================
1285 */
1286 qboolean FS_CheckGameDir(const char *gamedir)
1287 {
1288         qboolean success;
1289         stringlist_t list;
1290         stringlistinit(&list);
1291         listdirectory(&list, va("%s%s/", fs_basedir, gamedir));
1292         success = list.numstrings > 0;
1293         stringlistfreecontents(&list);
1294         return success;
1295 }
1296
1297
1298 /*
1299 ================
1300 FS_Init
1301 ================
1302 */
1303 void FS_Init (void)
1304 {
1305         int i;
1306
1307         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1308
1309         strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1310
1311 // If the base directory is explicitly defined by the compilation process
1312 #ifdef DP_FS_BASEDIR
1313         strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1314 #else
1315         strlcpy(fs_basedir, "", sizeof(fs_basedir));
1316
1317 #ifdef MACOSX
1318         // FIXME: is there a better way to find the directory outside the .app?
1319         if (strstr(com_argv[0], ".app/"))
1320         {
1321                 char *split;
1322
1323                 split = strstr(com_argv[0], ".app/");
1324                 while (split > com_argv[0] && *split != '/')
1325                         split--;
1326                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1327                 fs_basedir[split - com_argv[0]] = 0;
1328         }
1329 #endif
1330 #endif
1331
1332         PK3_OpenLibrary ();
1333
1334         // -basedir <path>
1335         // Overrides the system supplied base directory (under GAMENAME)
1336 // COMMANDLINEOPTION: Filesystem: -basedir <path> chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1)
1337         i = COM_CheckParm ("-basedir");
1338         if (i && i < com_argc-1)
1339         {
1340                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1341                 i = (int)strlen (fs_basedir);
1342                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1343                         fs_basedir[i-1] = 0;
1344         }
1345
1346         // add a path separator to the end of the basedir if it lacks one
1347         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1348                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1349
1350         if (!FS_CheckGameDir(gamedirname1))
1351                 Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1352
1353         if (gamedirname2 && !FS_CheckGameDir(gamedirname2))
1354                 Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1355
1356         // -game <gamedir>
1357         // Adds basedir/gamedir as an override game
1358         // LordHavoc: now supports multiple -game directories
1359         for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1360         {
1361                 if (!com_argv[i])
1362                         continue;
1363                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1364                 {
1365                         i++;
1366                         if (FS_CheckNastyPath(com_argv[i], true))
1367                                 Sys_Error("-game %s%s/ is a dangerous/non-portable path\n", fs_basedir, com_argv[i]);
1368                         if (!FS_CheckGameDir(com_argv[i]))
1369                                 Sys_Error("-game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1370                         // add the gamedir to the list of active gamedirs
1371                         strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1372                         fs_numgamedirs++;
1373                 }
1374         }
1375
1376         // generate the searchpath
1377         FS_Rescan();
1378 }
1379
1380 void FS_Init_Commands(void)
1381 {
1382         Cvar_RegisterVariable (&scr_screenshot_name);
1383         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1384
1385         Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1386         Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1387         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1388         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1389         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1390 }
1391
1392 /*
1393 ================
1394 FS_Shutdown
1395 ================
1396 */
1397 void FS_Shutdown (void)
1398 {
1399         Mem_FreePool (&fs_mempool);
1400 }
1401
1402 /*
1403 ====================
1404 FS_SysOpen
1405
1406 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1407 ====================
1408 */
1409 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1410 {
1411         qfile_t* file;
1412         int mod, opt;
1413         unsigned int ind;
1414
1415         // Parse the mode string
1416         switch (mode[0])
1417         {
1418                 case 'r':
1419                         mod = O_RDONLY;
1420                         opt = 0;
1421                         break;
1422                 case 'w':
1423                         mod = O_WRONLY;
1424                         opt = O_CREAT | O_TRUNC;
1425                         break;
1426                 case 'a':
1427                         mod = O_WRONLY;
1428                         opt = O_CREAT | O_APPEND;
1429                         break;
1430                 default:
1431                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1432                         return NULL;
1433         }
1434         for (ind = 1; mode[ind] != '\0'; ind++)
1435         {
1436                 switch (mode[ind])
1437                 {
1438                         case '+':
1439                                 mod = O_RDWR;
1440                                 break;
1441                         case 'b':
1442                                 opt |= O_BINARY;
1443                                 break;
1444                         default:
1445                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1446                                                         filepath, mode, mode[ind]);
1447                 }
1448         }
1449
1450         if (nonblocking)
1451                 opt |= O_NONBLOCK;
1452
1453         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1454         memset (file, 0, sizeof (*file));
1455         file->ungetc = EOF;
1456
1457         file->handle = open (filepath, mod | opt, 0666);
1458         if (file->handle < 0)
1459         {
1460                 Mem_Free (file);
1461                 return NULL;
1462         }
1463
1464         file->real_length = lseek (file->handle, 0, SEEK_END);
1465
1466         // For files opened in append mode, we start at the end of the file
1467         if (mod & O_APPEND)
1468                 file->position = file->real_length;
1469         else
1470                 lseek (file->handle, 0, SEEK_SET);
1471
1472         return file;
1473 }
1474
1475
1476 /*
1477 ===========
1478 FS_OpenPackedFile
1479
1480 Open a packed file using its package file descriptor
1481 ===========
1482 */
1483 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1484 {
1485         packfile_t *pfile;
1486         int dup_handle;
1487         qfile_t* file;
1488
1489         pfile = &pack->files[pack_ind];
1490
1491         // If we don't have the true offset, get it now
1492         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1493                 if (!PK3_GetTrueFileOffset (pfile, pack))
1494                         return NULL;
1495
1496         // No Zlib DLL = no compressed files
1497         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1498         {
1499                 Con_Printf("WARNING: can't open the compressed file %s\n"
1500                                         "You need the Zlib DLL to use compressed files\n",
1501                                         pfile->name);
1502                 return NULL;
1503         }
1504
1505         // LordHavoc: lseek affects all duplicates of a handle so we do it before
1506         // the dup() call to avoid having to close the dup_handle on error here
1507         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1508         {
1509                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1510                                         pfile->name, pack->filename, (int) pfile->offset);
1511                 return NULL;
1512         }
1513
1514         dup_handle = dup (pack->handle);
1515         if (dup_handle < 0)
1516         {
1517                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1518                 return NULL;
1519         }
1520
1521         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1522         memset (file, 0, sizeof (*file));
1523         file->handle = dup_handle;
1524         file->flags = QFILE_FLAG_PACKED;
1525         file->real_length = pfile->realsize;
1526         file->offset = pfile->offset;
1527         file->position = 0;
1528         file->ungetc = EOF;
1529
1530         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1531         {
1532                 ztoolkit_t *ztk;
1533
1534                 file->flags |= QFILE_FLAG_DEFLATED;
1535
1536                 // We need some more variables
1537                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1538
1539                 ztk->comp_length = pfile->packsize;
1540
1541                 // Initialize zlib stream
1542                 ztk->zstream.next_in = ztk->input;
1543                 ztk->zstream.avail_in = 0;
1544
1545                 /* From Zlib's "unzip.c":
1546                  *
1547                  * windowBits is passed < 0 to tell that there is no zlib header.
1548                  * Note that in this case inflate *requires* an extra "dummy" byte
1549                  * after the compressed stream in order to complete decompression and
1550                  * return Z_STREAM_END.
1551                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1552                  * size of both compressed and uncompressed data
1553                  */
1554                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1555                 {
1556                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1557                         close(dup_handle);
1558                         Mem_Free(file);
1559                         return NULL;
1560                 }
1561
1562                 ztk->zstream.next_out = file->buff;
1563                 ztk->zstream.avail_out = sizeof (file->buff);
1564
1565                 file->ztk = ztk;
1566         }
1567
1568         return file;
1569 }
1570
1571 /*
1572 ====================
1573 FS_CheckNastyPath
1574
1575 Return true if the path should be rejected due to one of the following:
1576 1: path elements that are non-portable
1577 2: path elements that would allow access to files outside the game directory,
1578    or are just not a good idea for a mod to be using.
1579 ====================
1580 */
1581 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
1582 {
1583         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
1584         if (!path[0])
1585                 return 2;
1586
1587         // Windows: don't allow \ in filenames (windows-only), period.
1588         // (on Windows \ is a directory separator, but / is also supported)
1589         if (strstr(path, "\\"))
1590                 return 1; // non-portable
1591
1592         // Mac: don't allow Mac-only filenames - : is a directory separator
1593         // instead of /, but we rely on / working already, so there's no reason to
1594         // support a Mac-only path
1595         // Amiga and Windows: : tries to go to root of drive
1596         if (strstr(path, ":"))
1597                 return 1; // non-portable attempt to go to root of drive
1598
1599         // Amiga: // is parent directory
1600         if (strstr(path, "//"))
1601                 return 1; // non-portable attempt to go to parent directory
1602
1603         // all: don't allow going to parent directory (../ or /../)
1604         if (strstr(path, ".."))
1605                 return 2; // attempt to go outside the game directory
1606
1607         // Windows and UNIXes: don't allow absolute paths
1608         if (path[0] == '/')
1609                 return 2; // attempt to go outside the game directory
1610
1611         // 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
1612         if (strchr(path, '.'))
1613         {
1614                 if (isgamedir)
1615                 {
1616                         // gamedir is entirely path elements, so simply forbid . entirely
1617                         return 2;
1618                 }
1619                 if (strchr(path, '.') < strrchr(path, '/'))
1620                         return 2; // possible attempt to go outside the game directory
1621         }
1622
1623         // all: forbid trailing slash on gamedir
1624         if (isgamedir && path[strlen(path)-1] == '/')
1625                 return 2;
1626
1627         // all: forbid leading dot on any filename for any reason
1628         if (strstr(path, "/."))
1629                 return 2; // attempt to go outside the game directory
1630
1631         // after all these checks we're pretty sure it's a / separated filename
1632         // and won't do much if any harm
1633         return false;
1634 }
1635
1636
1637 /*
1638 ====================
1639 FS_FindFile
1640
1641 Look for a file in the packages and in the filesystem
1642
1643 Return the searchpath where the file was found (or NULL)
1644 and the file index in the package if relevant
1645 ====================
1646 */
1647 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1648 {
1649         searchpath_t *search;
1650         pack_t *pak;
1651
1652         // search through the path, one element at a time
1653         for (search = fs_searchpaths;search;search = search->next)
1654         {
1655                 // is the element a pak file?
1656                 if (search->pack)
1657                 {
1658                         int (*strcmp_funct) (const char* str1, const char* str2);
1659                         int left, right, middle;
1660
1661                         pak = search->pack;
1662                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1663
1664                         // Look for the file (binary search)
1665                         left = 0;
1666                         right = pak->numfiles - 1;
1667                         while (left <= right)
1668                         {
1669                                 int diff;
1670
1671                                 middle = (left + right) / 2;
1672                                 diff = strcmp_funct (pak->files[middle].name, name);
1673
1674                                 // Found it
1675                                 if (!diff)
1676                                 {
1677                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
1678                                         {
1679                                                 // yes, but the first one is empty so we treat it as not being there
1680                                                 if (!quiet && developer.integer >= 10)
1681                                                         Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
1682
1683                                                 if (index != NULL)
1684                                                         *index = -1;
1685                                                 return NULL;
1686                                         }
1687
1688                                         if (!quiet && developer.integer >= 10)
1689                                                 Con_Printf("FS_FindFile: %s in %s\n",
1690                                                                         pak->files[middle].name, pak->filename);
1691
1692                                         if (index != NULL)
1693                                                 *index = middle;
1694                                         return search;
1695                                 }
1696
1697                                 // If we're too far in the list
1698                                 if (diff > 0)
1699                                         right = middle - 1;
1700                                 else
1701                                         left = middle + 1;
1702                         }
1703                 }
1704                 else
1705                 {
1706                         char netpath[MAX_OSPATH];
1707                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1708                         if (FS_SysFileExists (netpath))
1709                         {
1710                                 if (!quiet && developer.integer >= 10)
1711                                         Con_Printf("FS_FindFile: %s\n", netpath);
1712
1713                                 if (index != NULL)
1714                                         *index = -1;
1715                                 return search;
1716                         }
1717                 }
1718         }
1719
1720         if (!quiet && developer.integer >= 10)
1721                 Con_Printf("FS_FindFile: can't find %s\n", name);
1722
1723         if (index != NULL)
1724                 *index = -1;
1725         return NULL;
1726 }
1727
1728
1729 /*
1730 ===========
1731 FS_OpenReadFile
1732
1733 Look for a file in the search paths and open it in read-only mode
1734 ===========
1735 */
1736 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1737 {
1738         searchpath_t *search;
1739         int pack_ind;
1740
1741         search = FS_FindFile (filename, &pack_ind, quiet);
1742
1743         // Not found?
1744         if (search == NULL)
1745                 return NULL;
1746
1747         // Found in the filesystem?
1748         if (pack_ind < 0)
1749         {
1750                 char path [MAX_OSPATH];
1751                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1752                 return FS_SysOpen (path, "rb", nonblocking);
1753         }
1754
1755         // So, we found it in a package...
1756         return FS_OpenPackedFile (search->pack, pack_ind);
1757 }
1758
1759
1760 /*
1761 =============================================================================
1762
1763 MAIN PUBLIC FUNCTIONS
1764
1765 =============================================================================
1766 */
1767
1768 /*
1769 ====================
1770 FS_Open
1771
1772 Open a file. The syntax is the same as fopen
1773 ====================
1774 */
1775 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1776 {
1777         if (FS_CheckNastyPath(filepath, false))
1778         {
1779                 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1780                 return NULL;
1781         }
1782
1783         // If the file is opened in "write", "append", or "read/write" mode
1784         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1785         {
1786                 char real_path [MAX_OSPATH];
1787
1788                 // Open the file on disk directly
1789                 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1790
1791                 // Create directories up to the file
1792                 FS_CreatePath (real_path);
1793
1794                 return FS_SysOpen (real_path, mode, nonblocking);
1795         }
1796         // Else, we look at the various search paths and open the file in read-only mode
1797         else
1798                 return FS_OpenReadFile (filepath, quiet, nonblocking);
1799 }
1800
1801
1802 /*
1803 ====================
1804 FS_Close
1805
1806 Close a file
1807 ====================
1808 */
1809 int FS_Close (qfile_t* file)
1810 {
1811         if (close (file->handle))
1812                 return EOF;
1813
1814         if (file->ztk)
1815         {
1816                 qz_inflateEnd (&file->ztk->zstream);
1817                 Mem_Free (file->ztk);
1818         }
1819
1820         Mem_Free (file);
1821         return 0;
1822 }
1823
1824
1825 /*
1826 ====================
1827 FS_Write
1828
1829 Write "datasize" bytes into a file
1830 ====================
1831 */
1832 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1833 {
1834         fs_offset_t result;
1835
1836         // If necessary, seek to the exact file position we're supposed to be
1837         if (file->buff_ind != file->buff_len)
1838                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1839
1840         // Purge cached data
1841         FS_Purge (file);
1842
1843         // Write the buffer and update the position
1844         result = write (file->handle, data, (fs_offset_t)datasize);
1845         file->position = lseek (file->handle, 0, SEEK_CUR);
1846         if (file->real_length < file->position)
1847                 file->real_length = file->position;
1848
1849         if (result < 0)
1850                 return 0;
1851
1852         return result;
1853 }
1854
1855
1856 /*
1857 ====================
1858 FS_Read
1859
1860 Read up to "buffersize" bytes from a file
1861 ====================
1862 */
1863 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1864 {
1865         fs_offset_t count, done;
1866
1867         if (buffersize == 0)
1868                 return 0;
1869
1870         // Get rid of the ungetc character
1871         if (file->ungetc != EOF)
1872         {
1873                 ((char*)buffer)[0] = file->ungetc;
1874                 buffersize--;
1875                 file->ungetc = EOF;
1876                 done = 1;
1877         }
1878         else
1879                 done = 0;
1880
1881         // First, we copy as many bytes as we can from "buff"
1882         if (file->buff_ind < file->buff_len)
1883         {
1884                 count = file->buff_len - file->buff_ind;
1885
1886                 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1887                 memcpy (buffer, &file->buff[file->buff_ind], done);
1888                 file->buff_ind += done;
1889
1890                 buffersize -= done;
1891                 if (buffersize == 0)
1892                         return done;
1893         }
1894
1895         // NOTE: at this point, the read buffer is always empty
1896
1897         // If the file isn't compressed
1898         if (! (file->flags & QFILE_FLAG_DEFLATED))
1899         {
1900                 fs_offset_t nb;
1901
1902                 // We must take care to not read after the end of the file
1903                 count = file->real_length - file->position;
1904
1905                 // If we have a lot of data to get, put them directly into "buffer"
1906                 if (buffersize > sizeof (file->buff) / 2)
1907                 {
1908                         if (count > (fs_offset_t)buffersize)
1909                                 count = (fs_offset_t)buffersize;
1910                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1911                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1912                         if (nb > 0)
1913                         {
1914                                 done += nb;
1915                                 file->position += nb;
1916
1917                                 // Purge cached data
1918                                 FS_Purge (file);
1919                         }
1920                 }
1921                 else
1922                 {
1923                         if (count > (fs_offset_t)sizeof (file->buff))
1924                                 count = (fs_offset_t)sizeof (file->buff);
1925                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1926                         nb = read (file->handle, file->buff, count);
1927                         if (nb > 0)
1928                         {
1929                                 file->buff_len = nb;
1930                                 file->position += nb;
1931
1932                                 // Copy the requested data in "buffer" (as much as we can)
1933                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1934                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1935                                 file->buff_ind = count;
1936                                 done += count;
1937                         }
1938                 }
1939
1940                 return done;
1941         }
1942
1943         // If the file is compressed, it's more complicated...
1944         // We cycle through a few operations until we have read enough data
1945         while (buffersize > 0)
1946         {
1947                 ztoolkit_t *ztk = file->ztk;
1948                 int error;
1949
1950                 // NOTE: at this point, the read buffer is always empty
1951
1952                 // If "input" is also empty, we need to refill it
1953                 if (ztk->in_ind == ztk->in_len)
1954                 {
1955                         // If we are at the end of the file
1956                         if (file->position == file->real_length)
1957                                 return done;
1958
1959                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1960                         if (count > (fs_offset_t)sizeof (ztk->input))
1961                                 count = (fs_offset_t)sizeof (ztk->input);
1962                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1963                         if (read (file->handle, ztk->input, count) != count)
1964                         {
1965                                 Con_Printf ("FS_Read: unexpected end of file\n");
1966                                 break;
1967                         }
1968
1969                         ztk->in_ind = 0;
1970                         ztk->in_len = count;
1971                         ztk->in_position += count;
1972                 }
1973
1974                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1975                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1976
1977                 // Now that we are sure we have compressed data available, we need to determine
1978                 // if it's better to inflate it in "file->buff" or directly in "buffer"
1979
1980                 // Inflate the data in "file->buff"
1981                 if (buffersize < sizeof (file->buff) / 2)
1982                 {
1983                         ztk->zstream.next_out = file->buff;
1984                         ztk->zstream.avail_out = sizeof (file->buff);
1985                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1986                         if (error != Z_OK && error != Z_STREAM_END)
1987                         {
1988                                 Con_Printf ("FS_Read: Can't inflate file\n");
1989                                 break;
1990                         }
1991                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1992
1993                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1994                         file->position += file->buff_len;
1995
1996                         // Copy the requested data in "buffer" (as much as we can)
1997                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1998                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1999                         file->buff_ind = count;
2000                 }
2001
2002                 // Else, we inflate directly in "buffer"
2003                 else
2004                 {
2005                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2006                         ztk->zstream.avail_out = (unsigned int)buffersize;
2007                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2008                         if (error != Z_OK && error != Z_STREAM_END)
2009                         {
2010                                 Con_Printf ("FS_Read: Can't inflate file\n");
2011                                 break;
2012                         }
2013                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2014
2015                         // How much data did it inflate?
2016                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2017                         file->position += count;
2018
2019                         // Purge cached data
2020                         FS_Purge (file);
2021                 }
2022
2023                 done += count;
2024                 buffersize -= count;
2025         }
2026
2027         return done;
2028 }
2029
2030
2031 /*
2032 ====================
2033 FS_Print
2034
2035 Print a string into a file
2036 ====================
2037 */
2038 int FS_Print (qfile_t* file, const char *msg)
2039 {
2040         return (int)FS_Write (file, msg, strlen (msg));
2041 }
2042
2043 /*
2044 ====================
2045 FS_Printf
2046
2047 Print a string into a file
2048 ====================
2049 */
2050 int FS_Printf(qfile_t* file, const char* format, ...)
2051 {
2052         int result;
2053         va_list args;
2054
2055         va_start (args, format);
2056         result = FS_VPrintf (file, format, args);
2057         va_end (args);
2058
2059         return result;
2060 }
2061
2062
2063 /*
2064 ====================
2065 FS_VPrintf
2066
2067 Print a string into a file
2068 ====================
2069 */
2070 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2071 {
2072         int len;
2073         fs_offset_t buff_size = MAX_INPUTLINE;
2074         char *tempbuff;
2075
2076         for (;;)
2077         {
2078                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2079                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2080                 if (len >= 0 && len < buff_size)
2081                         break;
2082                 Mem_Free (tempbuff);
2083                 buff_size *= 2;
2084         }
2085
2086         len = write (file->handle, tempbuff, len);
2087         Mem_Free (tempbuff);
2088
2089         return len;
2090 }
2091
2092
2093 /*
2094 ====================
2095 FS_Getc
2096
2097 Get the next character of a file
2098 ====================
2099 */
2100 int FS_Getc (qfile_t* file)
2101 {
2102         char c;
2103
2104         if (FS_Read (file, &c, 1) != 1)
2105                 return EOF;
2106
2107         return c;
2108 }
2109
2110
2111 /*
2112 ====================
2113 FS_UnGetc
2114
2115 Put a character back into the read buffer (only supports one character!)
2116 ====================
2117 */
2118 int FS_UnGetc (qfile_t* file, unsigned char c)
2119 {
2120         // If there's already a character waiting to be read
2121         if (file->ungetc != EOF)
2122                 return EOF;
2123
2124         file->ungetc = c;
2125         return c;
2126 }
2127
2128
2129 /*
2130 ====================
2131 FS_Seek
2132
2133 Move the position index in a file
2134 ====================
2135 */
2136 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2137 {
2138         ztoolkit_t *ztk;
2139         unsigned char* buffer;
2140         fs_offset_t buffersize;
2141
2142         // Compute the file offset
2143         switch (whence)
2144         {
2145                 case SEEK_CUR:
2146                         offset += file->position - file->buff_len + file->buff_ind;
2147                         break;
2148
2149                 case SEEK_SET:
2150                         break;
2151
2152                 case SEEK_END:
2153                         offset += file->real_length;
2154                         break;
2155
2156                 default:
2157                         return -1;
2158         }
2159         if (offset < 0 || offset > (long) file->real_length)
2160                 return -1;
2161
2162         // If we have the data in our read buffer, we don't need to actually seek
2163         if (file->position - file->buff_len <= offset && offset <= file->position)
2164         {
2165                 file->buff_ind = offset + file->buff_len - file->position;
2166                 return 0;
2167         }
2168
2169         // Purge cached data
2170         FS_Purge (file);
2171
2172         // Unpacked or uncompressed files can seek directly
2173         if (! (file->flags & QFILE_FLAG_DEFLATED))
2174         {
2175                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2176                         return -1;
2177                 file->position = offset;
2178                 return 0;
2179         }
2180
2181         // Seeking in compressed files is more a hack than anything else,
2182         // but we need to support it, so here we go.
2183         ztk = file->ztk;
2184
2185         // If we have to go back in the file, we need to restart from the beginning
2186         if (offset <= file->position)
2187         {
2188                 ztk->in_ind = 0;
2189                 ztk->in_len = 0;
2190                 ztk->in_position = 0;
2191                 file->position = 0;
2192                 lseek (file->handle, file->offset, SEEK_SET);
2193
2194                 // Reset the Zlib stream
2195                 ztk->zstream.next_in = ztk->input;
2196                 ztk->zstream.avail_in = 0;
2197                 qz_inflateReset (&ztk->zstream);
2198         }
2199
2200         // We need a big buffer to force inflating into it directly
2201         buffersize = 2 * sizeof (file->buff);
2202         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2203
2204         // Skip all data until we reach the requested offset
2205         while (offset > file->position)
2206         {
2207                 fs_offset_t diff = offset - file->position;
2208                 fs_offset_t count, len;
2209
2210                 count = (diff > buffersize) ? buffersize : diff;
2211                 len = FS_Read (file, buffer, count);
2212                 if (len != count)
2213                 {
2214                         Mem_Free (buffer);
2215                         return -1;
2216                 }
2217         }
2218
2219         Mem_Free (buffer);
2220         return 0;
2221 }
2222
2223
2224 /*
2225 ====================
2226 FS_Tell
2227
2228 Give the current position in a file
2229 ====================
2230 */
2231 fs_offset_t FS_Tell (qfile_t* file)
2232 {
2233         return file->position - file->buff_len + file->buff_ind;
2234 }
2235
2236
2237 /*
2238 ====================
2239 FS_FileSize
2240
2241 Give the total size of a file
2242 ====================
2243 */
2244 fs_offset_t FS_FileSize (qfile_t* file)
2245 {
2246         return file->real_length;
2247 }
2248
2249
2250 /*
2251 ====================
2252 FS_Purge
2253
2254 Erases any buffered input or output data
2255 ====================
2256 */
2257 void FS_Purge (qfile_t* file)
2258 {
2259         file->buff_len = 0;
2260         file->buff_ind = 0;
2261         file->ungetc = EOF;
2262 }
2263
2264
2265 /*
2266 ============
2267 FS_LoadFile
2268
2269 Filename are relative to the quake directory.
2270 Always appends a 0 byte.
2271 ============
2272 */
2273 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2274 {
2275         qfile_t *file;
2276         unsigned char *buf = NULL;
2277         fs_offset_t filesize = 0;
2278
2279         file = FS_Open (path, "rb", quiet, false);
2280         if (file)
2281         {
2282                 filesize = file->real_length;
2283                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2284                 buf[filesize] = '\0';
2285                 FS_Read (file, buf, filesize);
2286                 FS_Close (file);
2287         }
2288
2289         if (filesizepointer)
2290                 *filesizepointer = filesize;
2291         return buf;
2292 }
2293
2294
2295 /*
2296 ============
2297 FS_WriteFile
2298
2299 The filename will be prefixed by the current game directory
2300 ============
2301 */
2302 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2303 {
2304         qfile_t *file;
2305
2306         file = FS_Open (filename, "wb", false, false);
2307         if (!file)
2308         {
2309                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2310                 return false;
2311         }
2312
2313         Con_DPrintf("FS_WriteFile: %s\n", filename);
2314         FS_Write (file, data, len);
2315         FS_Close (file);
2316         return true;
2317 }
2318
2319
2320 /*
2321 =============================================================================
2322
2323 OTHERS PUBLIC FUNCTIONS
2324
2325 =============================================================================
2326 */
2327
2328 /*
2329 ============
2330 FS_StripExtension
2331 ============
2332 */
2333 void FS_StripExtension (const char *in, char *out, size_t size_out)
2334 {
2335         char *last = NULL;
2336         char currentchar;
2337
2338         if (size_out == 0)
2339                 return;
2340
2341         while ((currentchar = *in) && size_out > 1)
2342         {
2343                 if (currentchar == '.')
2344                         last = out;
2345                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2346                         last = NULL;
2347                 *out++ = currentchar;
2348                 in++;
2349                 size_out--;
2350         }
2351         if (last)
2352                 *last = 0;
2353         else
2354                 *out = 0;
2355 }
2356
2357
2358 /*
2359 ==================
2360 FS_DefaultExtension
2361 ==================
2362 */
2363 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2364 {
2365         const char *src;
2366
2367         // if path doesn't have a .EXT, append extension
2368         // (extension should include the .)
2369         src = path + strlen(path) - 1;
2370
2371         while (*src != '/' && src != path)
2372         {
2373                 if (*src == '.')
2374                         return;                 // it has an extension
2375                 src--;
2376         }
2377
2378         strlcat (path, extension, size_path);
2379 }
2380
2381
2382 /*
2383 ==================
2384 FS_FileExists
2385
2386 Look for a file in the packages and in the filesystem
2387 ==================
2388 */
2389 qboolean FS_FileExists (const char *filename)
2390 {
2391         return (FS_FindFile (filename, NULL, true) != NULL);
2392 }
2393
2394
2395 /*
2396 ==================
2397 FS_SysFileExists
2398
2399 Look for a file in the filesystem only
2400 ==================
2401 */
2402 qboolean FS_SysFileExists (const char *path)
2403 {
2404 #if WIN32
2405         int desc;
2406
2407         // TODO: use another function instead, to avoid opening the file
2408         desc = open (path, O_RDONLY | O_BINARY);
2409         if (desc < 0)
2410                 return false;
2411
2412         close (desc);
2413         return true;
2414 #else
2415         struct stat buf;
2416
2417         if (stat (path,&buf) == -1)
2418                 return false;
2419
2420         return true;
2421 #endif
2422 }
2423
2424 void FS_mkdir (const char *path)
2425 {
2426 #if WIN32
2427         _mkdir (path);
2428 #else
2429         mkdir (path, 0777);
2430 #endif
2431 }
2432
2433 /*
2434 ===========
2435 FS_Search
2436
2437 Allocate and fill a search structure with information on matching filenames.
2438 ===========
2439 */
2440 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2441 {
2442         fssearch_t *search;
2443         searchpath_t *searchpath;
2444         pack_t *pak;
2445         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
2446         stringlist_t resultlist;
2447         stringlist_t dirlist;
2448         const char *slash, *backslash, *colon, *separator;
2449         char *basepath;
2450         char netpath[MAX_OSPATH];
2451         char temp[MAX_OSPATH];
2452
2453         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2454                 ;
2455
2456         if (i > 0)
2457         {
2458                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2459                 return NULL;
2460         }
2461
2462         stringlistinit(&resultlist);
2463         stringlistinit(&dirlist);
2464         search = NULL;
2465         slash = strrchr(pattern, '/');
2466         backslash = strrchr(pattern, '\\');
2467         colon = strrchr(pattern, ':');
2468         separator = max(slash, backslash);
2469         separator = max(separator, colon);
2470         basepathlength = separator ? (separator + 1 - pattern) : 0;
2471         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2472         if (basepathlength)
2473                 memcpy(basepath, pattern, basepathlength);
2474         basepath[basepathlength] = 0;
2475
2476         // search through the path, one element at a time
2477         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2478         {
2479                 // is the element a pak file?
2480                 if (searchpath->pack)
2481                 {
2482                         // look through all the pak file elements
2483                         pak = searchpath->pack;
2484                         for (i = 0;i < pak->numfiles;i++)
2485                         {
2486                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
2487                                 while (temp[0])
2488                                 {
2489                                         if (matchpattern(temp, (char *)pattern, true))
2490                                         {
2491                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2492                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
2493                                                                 break;
2494                                                 if (resultlistindex == resultlist.numstrings)
2495                                                 {
2496                                                         stringlistappend(&resultlist, temp);
2497                                                         if (!quiet)
2498                                                                 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2499                                                 }
2500                                         }
2501                                         // strip off one path element at a time until empty
2502                                         // this way directories are added to the listing if they match the pattern
2503                                         slash = strrchr(temp, '/');
2504                                         backslash = strrchr(temp, '\\');
2505                                         colon = strrchr(temp, ':');
2506                                         separator = temp;
2507                                         if (separator < slash)
2508                                                 separator = slash;
2509                                         if (separator < backslash)
2510                                                 separator = backslash;
2511                                         if (separator < colon)
2512                                                 separator = colon;
2513                                         *((char *)separator) = 0;
2514                                 }
2515                         }
2516                 }
2517                 else
2518                 {
2519                         // get a directory listing and look at each name
2520                         dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2521                         stringlistinit(&dirlist);
2522                         listdirectory(&dirlist, netpath);
2523                         for (dirlistindex = 0;dirlistindex < dirlist.numstrings;dirlistindex++)
2524                         {
2525                                 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirlist.strings[dirlistindex]);
2526                                 if (matchpattern(temp, (char *)pattern, true))
2527                                 {
2528                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2529                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
2530                                                         break;
2531                                         if (resultlistindex == resultlist.numstrings)
2532                                         {
2533                                                 stringlistappend(&resultlist, temp);
2534                                                 if (!quiet)
2535                                                         Con_DPrintf("SearchDirFile: %s\n", temp);
2536                                         }
2537                                 }
2538                         }
2539                         stringlistfreecontents(&dirlist);
2540                 }
2541         }
2542
2543         if (resultlist.numstrings)
2544         {
2545                 stringlistsort(&resultlist);
2546                 numfiles = resultlist.numstrings;
2547                 numchars = 0;
2548                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2549                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
2550                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2551                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2552                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2553                 search->numfilenames = (int)numfiles;
2554                 numfiles = 0;
2555                 numchars = 0;
2556                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2557                 {
2558                         size_t textlen;
2559                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
2560                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
2561                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
2562                         numfiles++;
2563                         numchars += (int)textlen;
2564                 }
2565         }
2566         stringlistfreecontents(&resultlist);
2567
2568         Mem_Free(basepath);
2569         return search;
2570 }
2571
2572 void FS_FreeSearch(fssearch_t *search)
2573 {
2574         Z_Free(search);
2575 }
2576
2577 extern int con_linewidth;
2578 int FS_ListDirectory(const char *pattern, int oneperline)
2579 {
2580         int numfiles;
2581         int numcolumns;
2582         int numlines;
2583         int columnwidth;
2584         int linebufpos;
2585         int i, j, k, l;
2586         const char *name;
2587         char linebuf[MAX_INPUTLINE];
2588         fssearch_t *search;
2589         search = FS_Search(pattern, true, true);
2590         if (!search)
2591                 return 0;
2592         numfiles = search->numfilenames;
2593         if (!oneperline)
2594         {
2595                 // FIXME: the names could be added to one column list and then
2596                 // gradually shifted into the next column if they fit, and then the
2597                 // next to make a compact variable width listing but it's a lot more
2598                 // complicated...
2599                 // find width for columns
2600                 columnwidth = 0;
2601                 for (i = 0;i < numfiles;i++)
2602                 {
2603                         l = (int)strlen(search->filenames[i]);
2604                         if (columnwidth < l)
2605                                 columnwidth = l;
2606                 }
2607                 // count the spacing character
2608                 columnwidth++;
2609                 // calculate number of columns
2610                 numcolumns = con_linewidth / columnwidth;
2611                 // don't bother with the column printing if it's only one column
2612                 if (numcolumns >= 2)
2613                 {
2614                         numlines = (numfiles + numcolumns - 1) / numcolumns;
2615                         for (i = 0;i < numlines;i++)
2616                         {
2617                                 linebufpos = 0;
2618                                 for (k = 0;k < numcolumns;k++)
2619                                 {
2620                                         l = i * numcolumns + k;
2621                                         if (l < numfiles)
2622                                         {
2623                                                 name = search->filenames[l];
2624                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2625                                                         linebuf[linebufpos++] = name[j];
2626                                                 // space out name unless it's the last on the line
2627                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
2628                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2629                                                                 linebuf[linebufpos++] = ' ';
2630                                         }
2631                                 }
2632                                 linebuf[linebufpos] = 0;
2633                                 Con_Printf("%s\n", linebuf);
2634                         }
2635                 }
2636                 else
2637                         oneperline = true;
2638         }
2639         if (oneperline)
2640                 for (i = 0;i < numfiles;i++)
2641                         Con_Printf("%s\n", search->filenames[i]);
2642         FS_FreeSearch(search);
2643         return (int)numfiles;
2644 }
2645
2646 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2647 {
2648         const char *pattern;
2649         if (Cmd_Argc() > 3)
2650         {
2651                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2652                 return;
2653         }
2654         if (Cmd_Argc() == 2)
2655                 pattern = Cmd_Argv(1);
2656         else
2657                 pattern = "*";
2658         if (!FS_ListDirectory(pattern, oneperline))
2659                 Con_Print("No files found.\n");
2660 }
2661
2662 void FS_Dir_f(void)
2663 {
2664         FS_ListDirectoryCmd("dir", true);
2665 }
2666
2667 void FS_Ls_f(void)
2668 {
2669         FS_ListDirectoryCmd("ls", false);
2670 }
2671
2672 const char *FS_WhichPack(const char *filename)
2673 {
2674         int index;
2675         searchpath_t *sp = FS_FindFile(filename, &index, true);
2676         if(sp && sp->pack)
2677                 return sp->pack->filename;
2678         else
2679                 return 0;
2680 }
2681
2682 /*
2683 ====================
2684 FS_IsRegisteredQuakePack
2685
2686 Look for a proof of purchase file file in the requested package
2687
2688 If it is found, this file should NOT be downloaded.
2689 ====================
2690 */
2691 qboolean FS_IsRegisteredQuakePack(const char *name)
2692 {
2693         searchpath_t *search;
2694         pack_t *pak;
2695
2696         // search through the path, one element at a time
2697         for (search = fs_searchpaths;search;search = search->next)
2698         {
2699                 if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
2700                 {
2701                         int (*strcmp_funct) (const char* str1, const char* str2);
2702                         int left, right, middle;
2703
2704                         pak = search->pack;
2705                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2706
2707                         // Look for the file (binary search)
2708                         left = 0;
2709                         right = pak->numfiles - 1;
2710                         while (left <= right)
2711                         {
2712                                 int diff;
2713
2714                                 middle = (left + right) / 2;
2715                                 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
2716
2717                                 // Found it
2718                                 if (!diff)
2719                                         return true;
2720
2721                                 // If we're too far in the list
2722                                 if (diff > 0)
2723                                         right = middle - 1;
2724                                 else
2725                                         left = middle + 1;
2726                         }
2727
2728                         // we found the requested pack but it is not registered quake
2729                         return false;
2730                 }
2731         }
2732
2733         return false;
2734 }
2735
2736 int FS_CRCFile(const char *filename, size_t *filesizepointer)
2737 {
2738         int crc = -1;
2739         unsigned char *filedata;
2740         fs_offset_t filesize;
2741         if (filesizepointer)
2742                 *filesizepointer = 0;
2743         if (!filename || !*filename)
2744                 return crc;
2745         filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
2746         if (filedata)
2747         {
2748                 if (filesizepointer)
2749                         *filesizepointer = filesize;
2750                 crc = CRC_Block(filedata, filesize);
2751                 Mem_Free(filedata);
2752         }
2753         return crc;
2754 }
2755