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