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