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