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