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