]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/framework/FileSystem.cpp
Various Mac OS X tweaks to get this to build. Probably breaking things.
[icculus/iodoom3.git] / neo / framework / FileSystem.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 #include "../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "Unzip.h"
33
34 #ifdef WIN32
35         #include <io.h> // for _read
36 #else
37         #if !__MACH__ && __MWERKS__
38                 #include <types.h>
39                 #include <stat.h>
40         #else
41                 #include <sys/types.h>
42                 #include <sys/stat.h>
43         #endif
44         #include <unistd.h>
45 #endif
46
47 #if ID_ENABLE_CURL
48         #include "../curl/include/curl/curl.h"
49 #endif
50
51 /*
52 =============================================================================
53
54 DOOM FILESYSTEM
55
56 All of Doom's data access is through a hierarchical file system, but the contents of 
57 the file system can be transparently merged from several sources.
58
59 A "relativePath" is a reference to game file data, which must include a terminating zero.
60 "..", "\\", and ":" are explicitly illegal in qpaths to prevent any references
61 outside the Doom directory system.
62
63 The "base path" is the path to the directory holding all the game directories and
64 usually the executable. It defaults to the current directory, but can be overridden
65 with "+set fs_basepath c:\doom" on the command line. The base path cannot be modified
66 at all after startup.
67
68 The "save path" is the path to the directory where game files will be saved. It defaults
69 to the base path, but can be overridden with a "+set fs_savepath c:\doom" on the
70 command line. Any files that are created during the game (demos, screenshots, etc.) will
71 be created reletive to the save path.
72
73 The "cd path" is the path to an alternate hierarchy that will be searched if a file
74 is not located in the base path. A user can do a partial install that copies some
75 data to a base path created on their hard drive and leave the rest on the cd. It defaults
76 to the current directory, but it can be overridden with "+set fs_cdpath g:\doom" on the
77 command line.
78
79 The "dev path" is the path to an alternate hierarchy where the editors and tools used
80 during development (Radiant, AF editor, dmap, runAAS) will write files to. It defaults to
81 the cd path, but can be overridden with a "+set fs_devpath c:\doom" on the command line.
82
83 If a user runs the game directly from a CD, the base path would be on the CD. This
84 should still function correctly, but all file writes will fail (harmlessly).
85
86 The "base game" is the directory under the paths where data comes from by default, and
87 can be either "base" or "demo".
88
89 The "current game" may be the same as the base game, or it may be the name of another
90 directory under the paths that should be searched for files before looking in the base
91 game. The game directory is set with "+set fs_game myaddon" on the command line. This is
92 the basis for addons.
93
94 No other directories outside of the base game and current game will ever be referenced by
95 filesystem functions.
96
97 To save disk space and speed up file loading, directory trees can be collapsed into zip
98 files. The files use a ".pk4" extension to prevent users from unzipping them accidentally,
99 but otherwise they are simply normal zip files. A game directory can have multiple zip
100 files of the form "pak0.pk4", "pak1.pk4", etc. Zip files are searched in decending order
101 from the highest number to the lowest, and will always take precedence over the filesystem.
102 This allows a pk4 distributed as a patch to override all existing data.
103
104 Because we will have updated executables freely available online, there is no point to
105 trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
106 should be exactly the same executables as release versions, but with different data that
107 automatically restricts where game media can come from to prevent add-ons from working.
108
109 After the paths are initialized, Doom will look for the product.txt file. If not found
110 and verified, the game will run in restricted mode. In restricted mode, only files
111 contained in demo/pak0.pk4 will be available for loading, and only if the zip header is
112 verified to not have been modified. A single exception is made for DoomConfig.cfg. Files
113 can still be written out in restricted mode, so screenshots and demos are allowed.
114 Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
115 if there is a valid product.txt under the basepath or cdpath.
116
117 If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
118 path, it will be copied over to the save path. This is a development aid to help build
119 test releases and to copy working sets of files.
120
121 If the "fs_copyfiles" cvar is set to 2, any file found in fs_cdpath that is newer than
122 it's fs_savepath version will be copied to fs_savepath (in addition to the fs_copyfiles 1
123 behaviour).
124
125 If the "fs_copyfiles" cvar is set to 3, files from both basepath and cdpath will be copied
126 over to the save path. This is useful when copying working sets of files mainly from base
127 path with an additional cd path (which can be a slower network drive for instance).
128
129 If the "fs_copyfiles" cvar is set to 4, files that exist in the cd path but NOT the base path
130 will be copied to the save path
131
132 NOTE: fs_copyfiles and case sensitivity. On fs_caseSensitiveOS 0 filesystems ( win32 ), the
133 copied files may change casing when copied over.
134
135 The relative path "sound/newstuff/test.wav" would be searched for in the following places:
136
137 for save path, dev path, base path, cd path:
138         for current game, base game:
139                 search directory
140                 search zip files
141
142 downloaded files, to be written to save path + current game's directory
143
144 The filesystem can be safely shutdown and reinitialized with different
145 basedir / cddir / game combinations, but all other subsystems that rely on it
146 (sound, video) must also be forced to restart.
147
148
149 "fs_caseSensitiveOS":
150 This cvar is set on operating systems that use case sensitive filesystems (Linux and OSX)
151 It is a common situation to have the media reference filenames, whereas the file on disc 
152 only matches in a case-insensitive way. When "fs_caseSensitiveOS" is set, the filesystem
153 will always do a case insensitive search.
154 IMPORTANT: This only applies to files, and not to directories. There is no case-insensitive
155 matching of directories. All directory names should be lowercase, when "com_developer" is 1,
156 the filesystem will warn when it catches bad directory situations (regardless of the
157 "fs_caseSensitiveOS" setting)
158 When bad casing in directories happen and "fs_caseSensitiveOS" is set, BuildOSPath will
159 attempt to correct the situation by forcing the path to lowercase. This assumes the media
160 is stored all lowercase.
161
162 "additional mod path search":
163 fs_game_base can be used to set an additional search path
164 in search order, fs_game, fs_game_base, BASEGAME
165 for instance to base a mod of D3 + D3XP assets, fs_game mymod, fs_game_base d3xp
166
167 =============================================================================
168 */
169
170
171
172 // define to fix special-cases for GetPackStatus so that files that shipped in 
173 // the wrong place for Doom 3 don't break pure servers.
174 #define DOOM3_PURE_SPECIAL_CASES        
175
176 typedef bool (*pureExclusionFunc_t)( const struct pureExclusion_s &excl, int l, const idStr &name );
177
178 typedef struct pureExclusion_s {
179         int                                     nameLen;
180         int                                     extLen;
181         const char *            name;
182         const char *            ext;
183         pureExclusionFunc_t     func;
184 } pureExclusion_t;
185
186 bool excludeExtension( const pureExclusion_t &excl, int l, const idStr &name ) {
187         if ( l > excl.extLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) ) {
188                 return true;
189         }
190         return false;
191 }
192
193 bool excludePathPrefixAndExtension( const pureExclusion_t &excl, int l, const idStr &name ) {
194         if ( l > excl.nameLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) && !name.IcmpPrefixPath( excl.name ) ) {
195                 return true;
196         }
197         return false;
198 }
199
200 bool excludeFullName( const pureExclusion_t &excl, int l, const idStr &name ) {
201         if ( l == excl.nameLen && !name.Icmp( excl.name ) ) {
202                 return true;
203         }
204         return false;
205 }
206
207 static pureExclusion_t pureExclusions[] = {
208         { 0,    0,      NULL,                                                                                   "/",            excludeExtension },
209         { 0,    0,      NULL,                                                                                   "\\",           excludeExtension },
210         { 0,    0,      NULL,                                                                                   ".pda",         excludeExtension },
211         { 0,    0,      NULL,                                                                                   ".gui",         excludeExtension },
212         { 0,    0,      NULL,                                                                                   ".pd",          excludeExtension },
213         { 0,    0,      NULL,                                                                                   ".lang",        excludeExtension },
214         { 0,    0,      "sound/VO",                                                                             ".ogg",         excludePathPrefixAndExtension },
215         { 0,    0,      "sound/VO",                                                                             ".wav",         excludePathPrefixAndExtension },
216 #if     defined DOOM3_PURE_SPECIAL_CASES        
217         // add any special-case files or paths for pure servers here
218         { 0,    0,      "sound/ed/marscity/vo_intro_cutscene.ogg",              NULL,           excludeFullName },
219         { 0,    0,      "sound/weapons/soulcube/energize_01.ogg",               NULL,           excludeFullName },
220         { 0,    0,      "sound/xian/creepy/vocal_fx",                                   ".ogg",         excludePathPrefixAndExtension },
221         { 0,    0,      "sound/xian/creepy/vocal_fx",                                   ".wav",         excludePathPrefixAndExtension },
222         { 0,    0,      "sound/feedback",                                                               ".ogg",         excludePathPrefixAndExtension },
223         { 0,    0,      "sound/feedback",                                                               ".wav",         excludePathPrefixAndExtension },
224         { 0,    0,      "guis/assets/mainmenu/chnote.tga",                              NULL,           excludeFullName },
225         { 0,    0,      "sound/levels/alphalabs2/uac_better_place.ogg", NULL,           excludeFullName },
226         { 0,    0,      "textures/bigchars.tga",                                                NULL,           excludeFullName },
227         { 0,    0,      "dds/textures/bigchars.dds",                                    NULL,           excludeFullName },
228         { 0,    0,      "fonts",                                                                                ".tga",         excludePathPrefixAndExtension },
229         { 0,    0,      "dds/fonts",                                                                    ".dds",         excludePathPrefixAndExtension },
230         { 0,    0,      "default.cfg",                                                                  NULL,           excludeFullName },
231         // russian zpak001.pk4
232         { 0,    0,  "fonts",                                                                            ".dat",         excludePathPrefixAndExtension },
233         { 0,    0,      "guis/temp.guied",                                                              NULL,           excludeFullName },
234 #endif
235         { 0,    0,      NULL,                                                                                   NULL,           NULL }
236 };
237
238 // ensures that lengths for pure exclusions are correct
239 class idInitExclusions {
240 public:
241         idInitExclusions() {
242                 for ( int i = 0; pureExclusions[i].func != NULL; i++ ) {
243                         if ( pureExclusions[i].name ) {
244                                 pureExclusions[i].nameLen = idStr::Length( pureExclusions[i].name );
245                         }
246                         if ( pureExclusions[i].ext ) {
247                                 pureExclusions[i].extLen = idStr::Length( pureExclusions[i].ext );
248                         }
249                 }
250         }
251 };
252
253 static idInitExclusions initExclusions;
254
255 #define MAX_ZIPPED_FILE_NAME    2048
256 #define FILE_HASH_SIZE                  1024
257
258 typedef struct fileInPack_s {
259         idStr                           name;                                           // name of the file
260         unsigned long           pos;                                            // file info position in zip
261         struct fileInPack_s * next;                                             // next file in the hash
262 } fileInPack_t;
263
264 typedef enum {
265         BINARY_UNKNOWN = 0,
266         BINARY_YES,
267         BINARY_NO
268 } binaryStatus_t;
269
270 typedef enum {
271         PURE_UNKNOWN = 0,       // need to run the pak through GetPackStatus
272         PURE_NEUTRAL,   // neutral regarding pureness. gets in the pure list if referenced
273         PURE_ALWAYS,    // always referenced - for pak* named files, unless NEVER
274         PURE_NEVER              // VO paks. may be referenced, won't be in the pure lists
275 } pureStatus_t;
276
277 typedef struct {
278         idList<int>                     depends;
279         idList<idDict *>        mapDecls;
280 } addonInfo_t;
281
282 typedef struct {
283         idStr                           pakFilename;                            // c:\doom\base\pak0.pk4
284         unzFile                         handle;
285         int                                     checksum;
286         int                                     numfiles;
287         int                                     length;
288         bool                            referenced;
289         binaryStatus_t          binary;
290         bool                            addon;                                          // this is an addon pack - addon_search tells if it's 'active'
291         bool                            addon_search;                           // is in the search list
292         addonInfo_t                     *addon_info;
293         pureStatus_t            pureStatus;
294         bool                            isNew;                                          // for downloaded paks
295         fileInPack_t            *hashTable[FILE_HASH_SIZE];
296         fileInPack_t            *buildBuffer;
297 } pack_t;
298
299 typedef struct {
300         idStr                           path;                                           // c:\doom
301         idStr                           gamedir;                                        // base
302 } directory_t;
303
304 typedef struct searchpath_s {
305         pack_t *                        pack;                                           // only one of pack / dir will be non NULL
306         directory_t *           dir;
307         struct searchpath_s *next;
308 } searchpath_t;
309
310 // search flags when opening a file
311 #define FSFLAG_SEARCH_DIRS              ( 1 << 0 )
312 #define FSFLAG_SEARCH_PAKS              ( 1 << 1 )
313 #define FSFLAG_PURE_NOREF               ( 1 << 2 )
314 #define FSFLAG_BINARY_ONLY              ( 1 << 3 )
315 #define FSFLAG_SEARCH_ADDONS    ( 1 << 4 )
316
317 // 3 search path (fs_savepath fs_basepath fs_cdpath)
318 // + .jpg and .tga
319 #define MAX_CACHED_DIRS 6
320
321 // how many OSes to handle game paks for ( we don't have to know them precisely )
322 #define MAX_GAME_OS     6
323 #define BINARY_CONFIG "binary.conf"
324 #define ADDON_CONFIG "addon.conf"
325
326 class idDEntry : public idStrList {
327 public:
328                                                 idDEntry() {}
329         virtual                         ~idDEntry() {}
330
331         bool                            Matches( const char *directory, const char *extension ) const;
332         void                            Init( const char *directory, const char *extension, const idStrList &list );
333         void                            Clear( void );
334
335 private:
336         idStr                           directory;
337         idStr                           extension;
338 };
339
340 class idFileSystemLocal : public idFileSystem {
341 public:
342                                                         idFileSystemLocal( void );
343
344         virtual void                    Init( void );
345         virtual void                    StartBackgroundDownloadThread( void );
346         virtual void                    Restart( void );
347         virtual void                    Shutdown( bool reloading );
348         virtual bool                    IsInitialized( void ) const;
349         virtual bool                    PerformingCopyFiles( void ) const;
350         virtual idModList *             ListMods( void );
351         virtual void                    FreeModList( idModList *modList );
352         virtual idFileList *    ListFiles( const char *relativePath, const char *extension, bool sort = false, bool fullRelativePath = false, const char* gamedir = NULL );
353         virtual idFileList *    ListFilesTree( const char *relativePath, const char *extension, bool sort = false, const char* gamedir = NULL );
354         virtual void                    FreeFileList( idFileList *fileList );
355         virtual const char *    OSPathToRelativePath( const char *OSPath );
356         virtual const char *    RelativePathToOSPath( const char *relativePath, const char *basePath );
357         virtual const char *    BuildOSPath( const char *base, const char *game, const char *relativePath );
358         virtual void                    CreateOSPath( const char *OSPath );
359         virtual bool                    FileIsInPAK( const char *relativePath );
360         virtual void                    UpdatePureServerChecksums( void );
361         virtual bool                    UpdateGamePakChecksums( void );
362         virtual fsPureReply_t   SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum );
363         virtual void                    GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int *gamePakChecksum );
364         virtual void                    SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum );
365         virtual void                    ClearPureChecksums( void );
366         virtual int                             GetOSMask( void );
367         virtual int                             ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp );
368         virtual void                    FreeFile( void *buffer );
369         virtual int                             WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" );
370         virtual void                    RemoveFile( const char *relativePath ); 
371         virtual idFile *                OpenFileReadFlags( const char *relativePath, int searchFlags, pack_t **foundInPak = NULL, bool allowCopyFiles = true, const char* gamedir = NULL );
372         virtual idFile *                OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL );
373         virtual idFile *                OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath" );
374         virtual idFile *                OpenFileAppend( const char *relativePath, bool sync = false, const char *basePath = "fs_basepath"   );
375         virtual idFile *                OpenFileByMode( const char *relativePath, fsMode_t mode );
376         virtual idFile *                OpenExplicitFileRead( const char *OSPath );
377         virtual idFile *                OpenExplicitFileWrite( const char *OSPath );
378         virtual void                    CloseFile( idFile *f );
379         virtual void                    BackgroundDownload( backgroundDownload_t *bgl );
380         virtual void                    ResetReadCount( void ) { readCount = 0; }
381         virtual void                    AddToReadCount( int c ) { readCount += c; }
382         virtual int                             GetReadCount( void ) { return readCount; }
383         virtual void                    FindDLL( const char *basename, char dllPath[ MAX_OSPATH ], bool updateChecksum );
384         virtual void                    ClearDirCache( void );
385         virtual bool                    HasD3XP( void );
386         virtual bool                    RunningD3XP( void );
387         virtual void                    CopyFile( const char *fromOSPath, const char *toOSPath );
388         virtual int                             ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isBinary );
389         virtual idFile *                MakeTemporaryFile( void );
390         virtual int                             AddZipFile( const char *path );
391         virtual findFile_t              FindFile( const char *path, bool scheduleAddons );
392         virtual int                             GetNumMaps();
393         virtual const idDict *  GetMapDecl( int i );
394         virtual void                    FindMapScreenshot( const char *path, char *buf, int len );
395         virtual bool                    FilenameCompare( const char *s1, const char *s2 ) const;
396
397         static void                             Dir_f( const idCmdArgs &args );
398         static void                             DirTree_f( const idCmdArgs &args );
399         static void                             Path_f( const idCmdArgs &args );
400         static void                             TouchFile_f( const idCmdArgs &args );
401         static void                             TouchFileList_f( const idCmdArgs &args );
402
403 private:
404         friend dword                    BackgroundDownloadThread( void *parms );
405
406         searchpath_t *                  searchPaths;
407         int                                             readCount;                      // total bytes read
408         int                                             loadCount;                      // total files read
409         int                                             loadStack;                      // total files in memory
410         idStr                                   gameFolder;                     // this will be a single name without separators
411
412         searchpath_t                    *addonPaks;                     // not loaded up, but we saw them
413
414         idDict                                  mapDict;                        // for GetMapDecl
415
416         static idCVar                   fs_debug;
417         static idCVar                   fs_restrict;
418         static idCVar                   fs_copyfiles;
419         static idCVar                   fs_basepath;
420         static idCVar                   fs_savepath;
421         static idCVar                   fs_cdpath;
422         static idCVar                   fs_devpath;
423         static idCVar                   fs_game;
424         static idCVar                   fs_game_base;
425         static idCVar                   fs_caseSensitiveOS;
426         static idCVar                   fs_searchAddons;
427
428         backgroundDownload_t *  backgroundDownloads;
429         backgroundDownload_t    defaultBackgroundDownload;
430         xthreadInfo                             backgroundThread;
431
432         idList<pack_t *>                serverPaks;
433         bool                                    loadedFileFromDir;              // set to true once a file was loaded from a directory - can't switch to pure anymore
434         idList<int>                             restartChecksums;               // used during a restart to set things in right order
435         idList<int>                             addonChecksums;                 // list of checksums that should go to the search list directly ( for restarts )
436         int                                             restartGamePakChecksum;
437         int                                             gameDLLChecksum;                // the checksum of the last loaded game DLL
438         int                                             gamePakChecksum;                // the checksum of the pak holding the loaded game DLL
439
440         int                                             gamePakForOS[ MAX_GAME_OS ];
441
442         idDEntry                                dir_cache[ MAX_CACHED_DIRS ]; // fifo
443         int                                             dir_cache_index;
444         int                                             dir_cache_count;
445
446         int                                             d3xp;   // 0: didn't check, -1: not installed, 1: installed
447
448 private:
449         void                                    ReplaceSeparators( idStr &path, char sep = PATHSEPERATOR_CHAR );
450         long                                    HashFileName( const char *fname ) const;
451         int                                             ListOSFiles( const char *directory, const char *extension, idStrList &list );
452         FILE *                                  OpenOSFile( const char *name, const char *mode, idStr *caseSensitiveName = NULL );
453         FILE *                                  OpenOSFileCorrectName( idStr &path, const char *mode );
454         int                                             DirectFileLength( FILE *o );
455         void                                    CopyFile( idFile *src, const char *toOSPath );
456         int                                             AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const;
457         void                                    GetExtensionList( const char *extension, idStrList &extensionList ) const;
458         int                                             GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir = NULL );
459
460         int                                             GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir = NULL );
461         pack_t *                                LoadZipFile( const char *zipfile );
462         void                                    AddGameDirectory( const char *path, const char *dir );
463         void                                    SetupGameDirectories( const char *gameName );
464         void                                    Startup( void );
465         void                                    SetRestrictions( void );
466                                                         // some files can be obtained from directories without compromising si_pure
467         bool                                    FileAllowedFromDir( const char *path );
468                                                         // searches all the paks, no pure check
469         pack_t *                                GetPackForChecksum( int checksum, bool searchAddons = false );
470                                                         // searches all the paks, no pure check
471         pack_t *                                FindPakForFileChecksum( const char *relativePath, int fileChecksum, bool bReference );
472         idFile_InZip *                  ReadFileFromZip( pack_t *pak, fileInPack_t *pakFile, const char *relativePath );
473         int                                             GetFileChecksum( idFile *file );
474         pureStatus_t                    GetPackStatus( pack_t *pak );
475         addonInfo_t *                   ParseAddonDef( const char *buf, const int len );
476         void                                    FollowAddonDependencies( pack_t *pak );
477
478         static size_t                   CurlWriteFunction( void *ptr, size_t size, size_t nmemb, void *stream );
479                                                         // curl_progress_callback in curl.h
480         static int                              CurlProgressFunction( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow );
481 };
482
483 idCVar  idFileSystemLocal::fs_restrict( "fs_restrict", "", CVAR_SYSTEM | CVAR_INIT | CVAR_BOOL, "" );
484 idCVar  idFileSystemLocal::fs_debug( "fs_debug", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
485 idCVar  idFileSystemLocal::fs_copyfiles( "fs_copyfiles", "0", CVAR_SYSTEM | CVAR_INIT | CVAR_INTEGER, "", 0, 4, idCmdSystem::ArgCompletion_Integer<0,3> );
486 idCVar  idFileSystemLocal::fs_basepath( "fs_basepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
487 idCVar  idFileSystemLocal::fs_savepath( "fs_savepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
488 idCVar  idFileSystemLocal::fs_cdpath( "fs_cdpath", "", CVAR_SYSTEM | CVAR_INIT, "" );
489 idCVar  idFileSystemLocal::fs_devpath( "fs_devpath", "", CVAR_SYSTEM | CVAR_INIT, "" );
490 idCVar  idFileSystemLocal::fs_game( "fs_game", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "mod path" );
491 idCVar  idFileSystemLocal::fs_game_base( "fs_game_base", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "alternate mod path, searched after the main fs_game path, before the basedir" );
492 #ifdef WIN32
493 idCVar  idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
494 #else
495 idCVar  idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "1", CVAR_SYSTEM | CVAR_BOOL, "" );
496 #endif
497 idCVar  idFileSystemLocal::fs_searchAddons( "fs_searchAddons", "0", CVAR_SYSTEM | CVAR_BOOL, "search all addon pk4s ( disables addon functionality )" );
498
499 idFileSystemLocal       fileSystemLocal;
500 idFileSystem *          fileSystem = &fileSystemLocal;
501
502 /*
503 ================
504 idFileSystemLocal::idFileSystemLocal
505 ================
506 */
507 idFileSystemLocal::idFileSystemLocal( void ) {
508         searchPaths = NULL;
509         readCount = 0;
510         loadCount = 0;
511         loadStack = 0;
512         dir_cache_index = 0;
513         dir_cache_count = 0;
514         d3xp = 0;
515         loadedFileFromDir = false;
516         restartGamePakChecksum = 0;
517         memset( &backgroundThread, 0, sizeof( backgroundThread ) );
518         addonPaks = NULL;
519 }
520
521 /*
522 ================
523 idFileSystemLocal::HashFileName
524
525 return a hash value for the filename
526 ================
527 */
528 long idFileSystemLocal::HashFileName( const char *fname ) const {
529         int             i;
530         long    hash;
531         char    letter;
532
533         hash = 0;
534         i = 0;
535         while( fname[i] != '\0' ) {
536                 letter = idStr::ToLower( fname[i] );
537                 if ( letter == '.' ) {
538                         break;                          // don't include extension
539                 }
540                 if ( letter == '\\' ) {
541                         letter = '/';           // damn path names
542                 }
543                 hash += (long)(letter) * (i+119);
544                 i++;
545         }
546         hash &= (FILE_HASH_SIZE-1);
547         return hash;
548 }
549
550 /*
551 ===========
552 idFileSystemLocal::FilenameCompare
553
554 Ignore case and separator char distinctions
555 ===========
556 */
557 bool idFileSystemLocal::FilenameCompare( const char *s1, const char *s2 ) const {
558         int             c1, c2;
559         
560         do {
561                 c1 = *s1++;
562                 c2 = *s2++;
563
564                 if ( c1 >= 'a' && c1 <= 'z' ) {
565                         c1 -= ('a' - 'A');
566                 }
567                 if ( c2 >= 'a' && c2 <= 'z' ) {
568                         c2 -= ('a' - 'A');
569                 }
570
571                 if ( c1 == '\\' || c1 == ':' ) {
572                         c1 = '/';
573                 }
574                 if ( c2 == '\\' || c2 == ':' ) {
575                         c2 = '/';
576                 }
577                 
578                 if ( c1 != c2 ) {
579                         return true;            // strings not equal
580                 }
581         } while( c1 );
582         
583         return false;           // strings are equal
584 }
585
586 /*
587 ================
588 idFileSystemLocal::OpenOSFile
589 optional caseSensitiveName is set to case sensitive file name as found on disc (fs_caseSensitiveOS only)
590 ================
591 */
592 FILE *idFileSystemLocal::OpenOSFile( const char *fileName, const char *mode, idStr *caseSensitiveName ) {
593         int i;
594         FILE *fp;
595         idStr fpath, entry;
596         idStrList list;
597
598 #ifndef __MWERKS__
599 #ifndef WIN32 
600         // some systems will let you fopen a directory
601         struct stat buf;
602         if ( stat( fileName, &buf ) != -1 && !S_ISREG(buf.st_mode) ) {
603                 return NULL;
604         }
605 #endif
606 #endif
607         fp = fopen( fileName, mode );
608         if ( !fp && fs_caseSensitiveOS.GetBool() ) {
609                 fpath = fileName;
610                 fpath.StripFilename();
611                 fpath.StripTrailing( PATHSEPERATOR_CHAR );
612                 if ( ListOSFiles( fpath, NULL, list ) == -1 ) {
613                         return NULL;
614                 }
615                 
616                 for ( i = 0; i < list.Num(); i++ ) {
617                         entry = fpath + PATHSEPERATOR_CHAR + list[i];
618                         if ( !entry.Icmp( fileName ) ) {
619                                 fp = fopen( entry, mode );
620                                 if ( fp ) {
621                                         if ( caseSensitiveName ) {
622                                                 *caseSensitiveName = entry;
623                                                 caseSensitiveName->StripPath();
624                                         }
625                                         if ( fs_debug.GetInteger() ) {
626                                                 common->Printf( "idFileSystemLocal::OpenFileRead: changed %s to %s\n", fileName, entry.c_str() );
627                                         }
628                                         break;
629                                 } else {
630                                         // not supposed to happen if ListOSFiles is doing it's job correctly
631                                         common->Warning( "idFileSystemLocal::OpenFileRead: fs_caseSensitiveOS 1 could not open %s", entry.c_str() );
632                                 }
633                         }
634                 }
635         } else if ( caseSensitiveName ) {
636                 *caseSensitiveName = fileName;
637                 caseSensitiveName->StripPath();
638         }
639         return fp;
640 }
641
642 /*
643 ================
644 idFileSystemLocal::OpenOSFileCorrectName
645 ================
646 */
647 FILE *idFileSystemLocal::OpenOSFileCorrectName( idStr &path, const char *mode ) {
648         idStr caseName;
649         FILE *f = OpenOSFile( path.c_str(), mode, &caseName );
650         if ( f ) {
651                 path.StripFilename();
652                 path += PATHSEPERATOR_STR;
653                 path += caseName;
654         }
655         return f;
656 }
657
658 /*
659 ================
660 idFileSystemLocal::DirectFileLength
661 ================
662 */
663 int idFileSystemLocal::DirectFileLength( FILE *o ) {
664         int             pos;
665         int             end;
666
667         pos = ftell( o );
668         fseek( o, 0, SEEK_END );
669         end = ftell( o );
670         fseek( o, pos, SEEK_SET );
671         return end;
672 }
673
674 /*
675 ============
676 idFileSystemLocal::CreateOSPath
677
678 Creates any directories needed to store the given filename
679 ============
680 */
681 void idFileSystemLocal::CreateOSPath( const char *OSPath ) {
682         char    *ofs;
683         
684         // make absolutely sure that it can't back up the path
685         // FIXME: what about c: ?
686         if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
687 #ifdef _DEBUG           
688                 common->DPrintf( "refusing to create relative path \"%s\"\n", OSPath );
689 #endif
690                 return;
691         }
692
693         idStr path( OSPath );
694         for( ofs = &path[ 1 ]; *ofs ; ofs++ ) {
695                 if ( *ofs == PATHSEPERATOR_CHAR ) {     
696                         // create the directory
697                         *ofs = 0;
698                         Sys_Mkdir( path );
699                         *ofs = PATHSEPERATOR_CHAR;
700                 }
701         }
702 }
703
704 /*
705 =================
706 idFileSystemLocal::CopyFile
707
708 Copy a fully specified file from one place to another
709 =================
710 */
711 void idFileSystemLocal::CopyFile( const char *fromOSPath, const char *toOSPath ) {
712         FILE    *f;
713         int             len;
714         byte    *buf;
715
716         common->Printf( "copy %s to %s\n", fromOSPath, toOSPath );
717         f = OpenOSFile( fromOSPath, "rb" );
718         if ( !f ) {
719                 return;
720         }
721         fseek( f, 0, SEEK_END );
722         len = ftell( f );
723         fseek( f, 0, SEEK_SET );
724
725         buf = (byte *)Mem_Alloc( len );
726         if ( fread( buf, 1, len, f ) != (unsigned int)len ) {
727                 common->FatalError( "short read in idFileSystemLocal::CopyFile()\n" );
728         }
729         fclose( f );
730
731         CreateOSPath( toOSPath );
732         f = OpenOSFile( toOSPath, "wb" );
733         if ( !f ) {
734                 common->Printf( "could not create destination file\n" );
735                 Mem_Free( buf );
736                 return;
737         }
738         if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) {
739                 common->FatalError( "short write in idFileSystemLocal::CopyFile()\n" );
740         }
741         fclose( f );
742         Mem_Free( buf );
743 }
744
745 /*
746 =================
747 idFileSystemLocal::CopyFile
748 =================
749 */
750 void idFileSystemLocal::CopyFile( idFile *src, const char *toOSPath ) {
751         FILE    *f;
752         int             len;
753         byte    *buf;
754
755         common->Printf( "copy %s to %s\n", src->GetName(), toOSPath );
756         src->Seek( 0, FS_SEEK_END );
757         len = src->Tell();
758         src->Seek( 0, FS_SEEK_SET );
759
760         buf = (byte *)Mem_Alloc( len );
761         if ( src->Read( buf, len ) != len ) {
762                 common->FatalError( "Short read in idFileSystemLocal::CopyFile()\n" );
763         }
764
765         CreateOSPath( toOSPath );
766         f = OpenOSFile( toOSPath, "wb" );
767         if ( !f ) {
768                 common->Printf( "could not create destination file\n" );
769                 Mem_Free( buf );
770                 return;
771         }
772         if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) {
773                 common->FatalError( "Short write in idFileSystemLocal::CopyFile()\n" );
774         }
775         fclose( f );
776         Mem_Free( buf );
777 }
778
779 /*
780 ====================
781 idFileSystemLocal::ReplaceSeparators
782
783 Fix things up differently for win/unix/mac
784 ====================
785 */
786 void idFileSystemLocal::ReplaceSeparators( idStr &path, char sep ) {
787         char *s;
788
789         for( s = &path[ 0 ]; *s ; s++ ) {
790                 if ( *s == '/' || *s == '\\' ) {
791                         *s = sep;
792                 }
793         }
794 }
795
796 /*
797 ===================
798 idFileSystemLocal::BuildOSPath
799 ===================
800 */
801 const char *idFileSystemLocal::BuildOSPath( const char *base, const char *game, const char *relativePath ) {
802         static char OSPath[MAX_STRING_CHARS];
803         idStr newPath;
804
805         if ( fs_caseSensitiveOS.GetBool() || com_developer.GetBool() ) {
806                 // extract the path, make sure it's all lowercase
807                 idStr testPath, fileName;
808
809                 sprintf( testPath, "%s/%s", game , relativePath );
810                 testPath.StripFilename();
811
812                 if ( testPath.HasUpper() ) {
813
814                         common->Warning( "Non-portable: path contains uppercase characters: %s", testPath.c_str() );
815
816                         // attempt a fixup on the fly
817                         if ( fs_caseSensitiveOS.GetBool() ) {
818                                 testPath.ToLower();
819                                 fileName = relativePath;
820                                 fileName.StripPath();
821                                 sprintf( newPath, "%s/%s/%s", base, testPath.c_str(), fileName.c_str() );
822                                 ReplaceSeparators( newPath );
823                                 common->DPrintf( "Fixed up to %s\n", newPath.c_str() );
824                                 idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
825                                 return OSPath;
826                         }
827                 }
828         }
829
830         idStr strBase = base;
831         strBase.StripTrailing( '/' );
832         strBase.StripTrailing( '\\' );
833         sprintf( newPath, "%s/%s/%s", strBase.c_str(), game, relativePath );
834         ReplaceSeparators( newPath );
835         idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
836         return OSPath;
837 }
838
839 /*
840 ================
841 idFileSystemLocal::OSPathToRelativePath
842
843 takes a full OS path, as might be found in data from a media creation
844 program, and converts it to a relativePath by stripping off directories
845
846 Returns false if the osPath tree doesn't match any of the existing
847 search paths.
848
849 ================
850 */
851 const char *idFileSystemLocal::OSPathToRelativePath( const char *OSPath ) {
852         static char relativePath[MAX_STRING_CHARS];
853         char *s, *base;
854
855         // skip a drive letter?
856
857         // search for anything with "base" in it
858         // Ase files from max may have the form of:
859         // "//Purgatory/purgatory/doom/base/models/mapobjects/bitch/hologirl.tga"
860         // which won't match any of our drive letter based search paths
861         bool ignoreWarning = false;
862 #ifdef ID_DEMO_BUILD
863         base = strstr( OSPath, BASE_GAMEDIR );  
864         idStr tempStr = OSPath;
865         tempStr.ToLower();
866         if ( ( strstr( tempStr, "//" ) || strstr( tempStr, "w:" ) ) && strstr( tempStr, "/doom/base/") ) {
867                 // will cause a warning but will load the file. ase models have
868                 // hard coded doom/base/ in the material names
869                 base = strstr( OSPath, "base" );
870                 ignoreWarning = true;
871         }
872 #else
873         // look for the first complete directory name
874         base = (char *)strstr( OSPath, BASE_GAMEDIR );
875         while ( base ) {
876                 char c1 = '\0', c2;
877                 if ( base > OSPath ) {
878                         c1 = *(base - 1);
879                 }
880                 c2 = *( base + strlen( BASE_GAMEDIR ) );
881                 if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) {
882                         break;
883                 }
884                 base = strstr( base + 1, BASE_GAMEDIR );
885         }
886 #endif
887         // fs_game and fs_game_base support - look for first complete name with a mod path
888         // ( fs_game searched before fs_game_base )
889         const char *fsgame = NULL;
890         int igame = 0;
891         for ( igame = 0; igame < 2; igame++ ) {
892                 if ( igame == 0 ) {
893                         fsgame = fs_game.GetString();
894                 } else if ( igame == 1 ) {
895                         fsgame = fs_game_base.GetString();
896                 }
897                 if ( base == NULL && fsgame && strlen( fsgame ) ) {
898                         base = (char *)strstr( OSPath, fsgame );
899                         while ( base ) {
900                                 char c1 = '\0', c2;
901                                 if ( base > OSPath ) {
902                                         c1 = *(base - 1);
903                                 }
904                                 c2 = *( base + strlen( fsgame ) );
905                                 if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) {
906                                         break;
907                                 }
908                                 base = strstr( base + 1, fsgame );
909                         }
910                 }
911         }
912
913         if ( base ) {
914                 s = strstr( base, "/" );
915                 if ( !s ) {
916                         s = strstr( base, "\\" );
917                 }
918                 if ( s ) {
919                         strcpy( relativePath, s + 1 );
920                         if ( fs_debug.GetInteger() > 1 ) {
921                                 common->Printf( "idFileSystem::OSPathToRelativePath: %s becomes %s\n", OSPath, relativePath );
922                         }
923                         return relativePath;
924                 }
925         }
926
927         if ( !ignoreWarning ) {
928                 common->Warning( "idFileSystem::OSPathToRelativePath failed on %s", OSPath );
929         }
930         strcpy( relativePath, "" );
931         return relativePath;
932 }
933
934 /*
935 =====================
936 idFileSystemLocal::RelativePathToOSPath
937
938 Returns a fully qualified path that can be used with stdio libraries
939 =====================
940 */
941 const char *idFileSystemLocal::RelativePathToOSPath( const char *relativePath, const char *basePath ) {
942         const char *path = cvarSystem->GetCVarString( basePath );
943         if ( !path[0] ) {
944                 path = fs_savepath.GetString();
945         }
946         return BuildOSPath( path, gameFolder, relativePath );
947 }
948
949 /*
950 =================
951 idFileSystemLocal::RemoveFile
952 =================
953 */
954 void idFileSystemLocal::RemoveFile( const char *relativePath ) {
955         idStr OSPath;
956
957         if ( fs_devpath.GetString()[0] ) {
958                 OSPath = BuildOSPath( fs_devpath.GetString(), gameFolder, relativePath );
959                 remove( OSPath );
960         }
961
962         OSPath = BuildOSPath( fs_savepath.GetString(), gameFolder, relativePath );
963         remove( OSPath );
964
965         ClearDirCache();
966 }
967
968 /*
969 ================
970 idFileSystemLocal::FileIsInPAK
971 ================
972 */
973 bool idFileSystemLocal::FileIsInPAK( const char *relativePath ) {
974         searchpath_t    *search;
975         pack_t                  *pak;
976         fileInPack_t    *pakFile;
977         long                    hash;
978
979         if ( !searchPaths ) {
980                 common->FatalError( "Filesystem call made without initialization\n" );
981         }
982
983         if ( !relativePath ) {
984                 common->FatalError( "idFileSystemLocal::FileIsInPAK: NULL 'relativePath' parameter passed\n" );
985         }
986
987         // qpaths are not supposed to have a leading slash
988         if ( relativePath[0] == '/' || relativePath[0] == '\\' ) {
989                 relativePath++;
990         }
991
992         // make absolutely sure that it can't back up the path.
993         // The searchpaths do guarantee that something will always
994         // be prepended, so we don't need to worry about "c:" or "//limbo" 
995         if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) {
996                 return false;
997         }
998
999         //
1000         // search through the path, one element at a time
1001         //
1002
1003         hash = HashFileName( relativePath );
1004
1005         for ( search = searchPaths; search; search = search->next ) {
1006                 // is the element a pak file?
1007                 if ( search->pack && search->pack->hashTable[hash] ) {
1008
1009                         // disregard if it doesn't match one of the allowed pure pak files - or is a localization file
1010                         if ( serverPaks.Num() ) {
1011                                 GetPackStatus( search->pack );
1012                                 if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
1013                                         continue; // not on the pure server pak list
1014                                 }
1015                         }
1016
1017                         // look through all the pak file elements
1018                         pak = search->pack;
1019                         pakFile = pak->hashTable[hash];
1020                         do {
1021                                 // case and separator insensitive comparisons
1022                                 if ( !FilenameCompare( pakFile->name, relativePath ) ) {
1023                                         return true;
1024                                 }
1025                                 pakFile = pakFile->next;
1026                         } while( pakFile != NULL );
1027                 }
1028         }
1029         return false;
1030 }
1031
1032 /*
1033 ============
1034 idFileSystemLocal::ReadFile
1035
1036 Filename are relative to the search path
1037 a null buffer will just return the file length and time without loading
1038 timestamp can be NULL if not required
1039 ============
1040 */
1041 int idFileSystemLocal::ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp ) {
1042         idFile *        f;
1043         byte *          buf;
1044         int                     len;
1045         bool            isConfig;
1046
1047         if ( !searchPaths ) {
1048                 common->FatalError( "Filesystem call made without initialization\n" );
1049         }
1050
1051         if ( !relativePath || !relativePath[0] ) {
1052                 common->FatalError( "idFileSystemLocal::ReadFile with empty name\n" );
1053         }
1054
1055         if ( timestamp ) {
1056                 *timestamp = FILE_NOT_FOUND_TIMESTAMP;
1057         }
1058
1059         if ( buffer ) {
1060                 *buffer = NULL;
1061         }
1062
1063         buf = NULL;     // quiet compiler warning
1064
1065         // if this is a .cfg file and we are playing back a journal, read
1066         // it from the journal file
1067         if ( strstr( relativePath, ".cfg" ) == relativePath + strlen( relativePath ) - 4 ) {
1068                 isConfig = true;
1069                 if ( eventLoop && eventLoop->JournalLevel() == 2 ) {
1070                         int             r;
1071
1072                         loadCount++;
1073                         loadStack++;
1074
1075                         common->DPrintf( "Loading %s from journal file.\n", relativePath );
1076                         len = 0;
1077                         r = eventLoop->com_journalDataFile->Read( &len, sizeof( len ) );
1078                         if ( r != sizeof( len ) ) {
1079                                 *buffer = NULL;
1080                                 return -1;
1081                         }
1082                         buf = (byte *)Mem_ClearedAlloc(len+1);
1083                         *buffer = buf;
1084                         r = eventLoop->com_journalDataFile->Read( buf, len );
1085                         if ( r != len ) {
1086                                 common->FatalError( "Read from journalDataFile failed" );
1087                         }
1088
1089                         // guarantee that it will have a trailing 0 for string operations
1090                         buf[len] = 0;
1091
1092                         return len;
1093                 }
1094         } else {
1095                 isConfig = false;
1096         }
1097
1098         // look for it in the filesystem or pack files
1099         f = OpenFileRead( relativePath, ( buffer != NULL ) );
1100         if ( f == NULL ) {
1101                 if ( buffer ) {
1102                         *buffer = NULL;
1103                 }
1104                 return -1;
1105         }
1106         len = f->Length();
1107
1108         if ( timestamp ) {
1109                 *timestamp = f->Timestamp();
1110         }
1111         
1112         if ( !buffer ) {
1113                 CloseFile( f );
1114                 return len;
1115         }
1116
1117         loadCount++;
1118         loadStack++;
1119
1120         buf = (byte *)Mem_ClearedAlloc(len+1);
1121         *buffer = buf;
1122
1123         f->Read( buf, len );
1124
1125         // guarantee that it will have a trailing 0 for string operations
1126         buf[len] = 0;
1127         CloseFile( f );
1128
1129         // if we are journalling and it is a config file, write it to the journal file
1130         if ( isConfig && eventLoop && eventLoop->JournalLevel() == 1 ) {
1131                 common->DPrintf( "Writing %s to journal file.\n", relativePath );
1132                 eventLoop->com_journalDataFile->Write( &len, sizeof( len ) );
1133                 eventLoop->com_journalDataFile->Write( buf, len );
1134                 eventLoop->com_journalDataFile->Flush();
1135         }
1136
1137         return len;
1138 }
1139
1140 /*
1141 =============
1142 idFileSystemLocal::FreeFile
1143 =============
1144 */
1145 void idFileSystemLocal::FreeFile( void *buffer ) {
1146         if ( !searchPaths ) {
1147                 common->FatalError( "Filesystem call made without initialization\n" );
1148         }
1149         if ( !buffer ) {
1150                 common->FatalError( "idFileSystemLocal::FreeFile( NULL )" );
1151         }
1152         loadStack--;
1153
1154         Mem_Free( buffer );
1155 }
1156
1157 /*
1158 ============
1159 idFileSystemLocal::WriteFile
1160
1161 Filenames are relative to the search path
1162 ============
1163 */
1164 int idFileSystemLocal::WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath ) {
1165         idFile *f;
1166
1167         if ( !searchPaths ) {
1168                 common->FatalError( "Filesystem call made without initialization\n" );
1169         }
1170
1171         if ( !relativePath || !buffer ) {
1172                 common->FatalError( "idFileSystemLocal::WriteFile: NULL parameter" );
1173         }
1174
1175         f = idFileSystemLocal::OpenFileWrite( relativePath, basePath );
1176         if ( !f ) {
1177                 common->Printf( "Failed to open %s\n", relativePath );
1178                 return -1;
1179         }
1180
1181         size = f->Write( buffer, size );
1182
1183         CloseFile( f );
1184
1185         return size;
1186 }
1187
1188 /*
1189 =================
1190 idFileSystemLocal::ParseAddonDef
1191 =================
1192 */
1193 addonInfo_t *idFileSystemLocal::ParseAddonDef( const char *buf, const int len ) {
1194         idLexer         src;
1195         idToken         token, token2;
1196         addonInfo_t     *info;
1197
1198         src.LoadMemory( buf, len, "<addon.conf>" );
1199         src.SetFlags( DECL_LEXER_FLAGS );
1200         if ( !src.SkipUntilString( "addonDef" ) ) {
1201                 src.Warning( "ParseAddonDef: no addonDef" );
1202                 return NULL;
1203         }
1204         if ( !src.ReadToken( &token ) ) {
1205                 src.Warning( "Expected {" );
1206                 return NULL;
1207         }
1208         info = new addonInfo_t;
1209         // read addonDef
1210         while ( 1 ) {
1211                 if ( !src.ReadToken( &token ) ) {
1212                         delete info;
1213                         return NULL;
1214                 }
1215                 if ( !token.Icmp( "}" ) ) {
1216                         break;
1217                 }
1218                 if ( token.type != TT_STRING ) {
1219                         src.Warning( "Expected quoted string, but found '%s'", token.c_str() );
1220                         delete info;
1221                         return NULL;
1222                 }
1223                 int checksum;
1224                 if ( sscanf( token.c_str(), "0x%x", &checksum ) != 1 && sscanf( token.c_str(), "%x", &checksum ) != 1 ) {
1225                         src.Warning( "Could not parse checksum '%s'", token.c_str() );
1226                         delete info;
1227                         return NULL;
1228                 }
1229                 info->depends.Append( checksum );
1230         }
1231         // read any number of mapDef entries
1232         while ( 1 ) {
1233                 if ( !src.SkipUntilString( "mapDef" ) ) {
1234                         return info;
1235                 }
1236                 if ( !src.ReadToken( &token ) ) {
1237                         src.Warning( "Expected map path" );
1238                         info->mapDecls.DeleteContents( true );
1239                         delete info;
1240                         return NULL;
1241                 }
1242                 idDict *dict = new idDict;
1243                 dict->Set( "path", token.c_str() );
1244                 if ( !src.ReadToken( &token ) ) {
1245                         src.Warning( "Expected {" );
1246                         info->mapDecls.DeleteContents( true );
1247                         delete dict;
1248                         delete info;
1249                         return NULL;
1250                 }
1251                 while ( 1 ) {
1252                         if ( !src.ReadToken( &token ) ) {
1253                                 break;
1254                         }
1255                         if ( !token.Icmp( "}" ) ) {
1256                                 break;
1257                         }
1258                         if ( token.type != TT_STRING ) {
1259                                 src.Warning( "Expected quoted string, but found '%s'", token.c_str() );
1260                                 info->mapDecls.DeleteContents( true );
1261                                 delete dict;
1262                                 delete info;
1263                                 return NULL;
1264                         }
1265
1266                         if ( !src.ReadToken( &token2 ) ) {
1267                                 src.Warning( "Unexpected end of file" );
1268                                 info->mapDecls.DeleteContents( true );
1269                                 delete dict;
1270                                 delete info;
1271                                 return NULL;
1272                         }
1273
1274                         if ( dict->FindKey( token ) ) {
1275                                 src.Warning( "'%s' already defined", token.c_str() );
1276                         }
1277                         dict->Set( token, token2 );
1278                 }
1279                 info->mapDecls.Append( dict );
1280         }
1281         assert( false );
1282         return NULL;
1283 }
1284
1285 /*
1286 =================
1287 idFileSystemLocal::LoadZipFile
1288 =================
1289 */
1290 pack_t *idFileSystemLocal::LoadZipFile( const char *zipfile ) {
1291         fileInPack_t *  buildBuffer;
1292         pack_t *                pack;
1293         unzFile                 uf;
1294         int                             err;
1295         unz_global_info gi;
1296         char                    filename_inzip[MAX_ZIPPED_FILE_NAME];
1297         unz_file_info   file_info;
1298         int                             i;
1299         long                    hash;
1300         int                             fs_numHeaderLongs;
1301         int *                   fs_headerLongs;
1302         FILE                    *f;
1303         int                             len;
1304         int                             confHash;
1305         fileInPack_t    *pakFile;
1306
1307         f = OpenOSFile( zipfile, "rb" );
1308         if ( !f ) {
1309                 return NULL;
1310         }
1311         fseek( f, 0, SEEK_END );
1312         len = ftell( f );
1313         fclose( f );
1314
1315         fs_numHeaderLongs = 0;
1316
1317         uf = unzOpen( zipfile );
1318         err = unzGetGlobalInfo( uf, &gi );
1319
1320         if ( err != UNZ_OK ) {
1321                 return NULL;
1322         }
1323
1324         buildBuffer = new fileInPack_t[gi.number_entry];
1325         pack = new pack_t;
1326         for( i = 0; i < FILE_HASH_SIZE; i++ ) {
1327                 pack->hashTable[i] = NULL;
1328         }
1329
1330         pack->pakFilename = zipfile;
1331         pack->handle = uf;
1332         pack->numfiles = gi.number_entry;
1333         pack->buildBuffer = buildBuffer;
1334         pack->referenced = false;
1335         pack->binary = BINARY_UNKNOWN;
1336         pack->addon = false;
1337         pack->addon_search = false;
1338         pack->addon_info = NULL;
1339         pack->pureStatus = PURE_UNKNOWN;
1340         pack->isNew = false;
1341
1342         pack->length = len;
1343
1344         unzGoToFirstFile(uf);
1345         fs_headerLongs = (int *)Mem_ClearedAlloc( gi.number_entry * sizeof(int) );
1346         for ( i = 0; i < (int)gi.number_entry; i++ ) {
1347                 err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0 );
1348                 if ( err != UNZ_OK ) {
1349                         break;
1350                 }
1351                 if ( file_info.uncompressed_size > 0 ) {
1352                         fs_headerLongs[fs_numHeaderLongs++] = LittleLong( file_info.crc );
1353                 }
1354                 hash = HashFileName( filename_inzip );
1355                 buildBuffer[i].name = filename_inzip;
1356                 buildBuffer[i].name.ToLower();
1357                 buildBuffer[i].name.BackSlashesToSlashes();
1358                 // store the file position in the zip
1359                 unzGetCurrentFileInfoPosition( uf, &buildBuffer[i].pos );
1360                 // add the file to the hash
1361                 buildBuffer[i].next = pack->hashTable[hash];
1362                 pack->hashTable[hash] = &buildBuffer[i];
1363                 // go to the next file in the zip
1364                 unzGoToNextFile(uf);
1365         }
1366
1367         // check if this is an addon pak
1368         pack->addon = false;
1369         confHash = HashFileName( ADDON_CONFIG );
1370         for ( pakFile = pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) {
1371                 if ( !FilenameCompare( pakFile->name, ADDON_CONFIG ) ) {                        
1372                         pack->addon = true;                     
1373                         idFile_InZip *file = ReadFileFromZip( pack, pakFile, ADDON_CONFIG );
1374                         // may be just an empty file if you don't bother about the mapDef
1375                         if ( file && file->Length() ) {
1376                                 char *buf;
1377                                 buf = new char[ file->Length() + 1 ];
1378                                 file->Read( (void *)buf, file->Length() );
1379                                 buf[ file->Length() ] = '\0';
1380                                 pack->addon_info = ParseAddonDef( buf, file->Length() );
1381                                 delete[] buf;
1382                         }
1383                         if ( file ) {
1384                                 CloseFile( file );
1385                         }
1386                         break;
1387                 }
1388         }
1389
1390         pack->checksum = MD4_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
1391         pack->checksum = LittleLong( pack->checksum );
1392
1393         Mem_Free( fs_headerLongs );
1394
1395         return pack;
1396 }
1397
1398 /*
1399 ===============
1400 idFileSystemLocal::AddZipFile
1401 adds a downloaded pak file to the list so we can work out what we have and what we still need
1402 the isNew flag is set to true, indicating that we cannot add this pak to the search lists without a restart
1403 ===============
1404 */
1405 int idFileSystemLocal::AddZipFile( const char *path ) {
1406         idStr                   fullpath = fs_savepath.GetString();
1407         pack_t                  *pak;
1408         searchpath_t    *search, *last;
1409
1410         fullpath.AppendPath( path );
1411         pak = LoadZipFile( fullpath );
1412         if ( !pak ) {
1413                 common->Warning( "AddZipFile %s failed\n", path );
1414                 return 0;
1415         }
1416         // insert the pak at the end of the search list - temporary until we restart
1417         pak->isNew = true;
1418         search = new searchpath_t;
1419         search->dir = NULL;
1420         search->pack = pak;
1421         search->next = NULL;
1422         last = searchPaths;
1423         while ( last->next ) {
1424                 last = last->next;
1425         }
1426         last->next = search;
1427         common->Printf( "Appended pk4 %s with checksum 0x%x\n", pak->pakFilename.c_str(), pak->checksum );
1428         return pak->checksum;
1429 }
1430
1431 /*
1432 ===============
1433 idFileSystemLocal::AddUnique
1434 ===============
1435 */
1436 int idFileSystemLocal::AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const {
1437         int i, hashKey;
1438
1439         hashKey = hashIndex.GenerateKey( name );
1440         for ( i = hashIndex.First( hashKey ); i >= 0; i = hashIndex.Next( i ) ) {
1441                 if ( list[i].Icmp( name ) == 0 ) {
1442                         return i;
1443                 }
1444         }
1445         i = list.Append( name );
1446         hashIndex.Add( hashKey, i );
1447         return i;
1448 }
1449
1450 /*
1451 ===============
1452 idFileSystemLocal::GetExtensionList
1453 ===============
1454 */
1455 void idFileSystemLocal::GetExtensionList( const char *extension, idStrList &extensionList ) const {
1456         int s, e, l;
1457
1458         l = idStr::Length( extension );
1459         s = 0;
1460         while( 1 ) {
1461                 e = idStr::FindChar( extension, '|', s, l );
1462                 if ( e != -1 ) {
1463                         extensionList.Append( idStr( extension, s, e ) );
1464                         s = e + 1;
1465                 } else {
1466                         extensionList.Append( idStr( extension, s, l ) );
1467                         break;
1468                 }
1469         }
1470 }
1471
1472 /*
1473 ===============
1474 idFileSystemLocal::GetFileList
1475
1476 Does not clear the list first so this can be used to progressively build a file list.
1477 When 'sort' is true only the new files added to the list are sorted.
1478 ===============
1479 */
1480 int idFileSystemLocal::GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir ) {
1481         searchpath_t *  search;
1482         fileInPack_t *  buildBuffer;
1483         int                             i, j;
1484         int                             pathLength;
1485         int                             length;
1486         const char *    name;
1487         pack_t *                pak;
1488         idStr                   work;
1489
1490         if ( !searchPaths ) {
1491                 common->FatalError( "Filesystem call made without initialization\n" );
1492         }
1493
1494         if ( !extensions.Num() ) {
1495                 return 0;
1496         }
1497
1498         if ( !relativePath ) {
1499                 return 0;
1500         }
1501         pathLength = strlen( relativePath );
1502         if ( pathLength ) {
1503                 pathLength++;   // for the trailing '/'
1504         }
1505
1506         // search through the path, one element at a time, adding to list
1507         for( search = searchPaths; search != NULL; search = search->next ) {
1508                 if ( search->dir ) {
1509                         if(gamedir && strlen(gamedir)) {
1510                                 if(search->dir->gamedir != gamedir) {
1511                                         continue;
1512                                 }
1513                         }
1514
1515                         idStrList       sysFiles;
1516                         idStr           netpath;
1517
1518                         netpath = BuildOSPath( search->dir->path, search->dir->gamedir, relativePath );
1519
1520                         for ( i = 0; i < extensions.Num(); i++ ) {
1521
1522                                 // scan for files in the filesystem
1523                                 ListOSFiles( netpath, extensions[i], sysFiles );
1524
1525                                 // if we are searching for directories, remove . and ..
1526                                 if ( extensions[i][0] == '/' && extensions[i][1] == 0 ) {
1527                                         sysFiles.Remove( "." );
1528                                         sysFiles.Remove( ".." );
1529                                 }
1530
1531                                 for( j = 0; j < sysFiles.Num(); j++ ) {
1532                                         // unique the match
1533                                         if ( fullRelativePath ) {
1534                                                 work = relativePath;
1535                                                 work += "/";
1536                                                 work += sysFiles[j];
1537                                                 AddUnique( work, list, hashIndex );
1538                                         }
1539                                         else {
1540                                                 AddUnique( sysFiles[j], list, hashIndex );
1541                                         }
1542                                 }
1543                         }
1544                 } else if ( search->pack ) {
1545                         // look through all the pak file elements
1546
1547                         // exclude any extra packs if we have server paks to search
1548                         if ( serverPaks.Num() ) {
1549                                 GetPackStatus( search->pack );
1550                                 if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
1551                                         continue; // not on the pure server pak list
1552                                 }
1553                         }
1554
1555                         pak = search->pack;
1556                         buildBuffer = pak->buildBuffer;
1557                         for( i = 0; i < pak->numfiles; i++ ) {
1558
1559                                 length = buildBuffer[i].name.Length();
1560
1561                                 // if the name is not long anough to at least contain the path
1562                                 if ( length <= pathLength ) {
1563                                         continue;
1564                                 }
1565
1566                                 name = buildBuffer[i].name;
1567
1568
1569                                 // check for a path match without the trailing '/'
1570                                 if ( pathLength && idStr::Icmpn( name, relativePath, pathLength - 1 ) != 0 ) {
1571                                         continue;
1572                                 }
1573  
1574                                 // ensure we have a path, and not just a filename containing the path
1575                                 if ( name[ pathLength ] == '\0' || name[pathLength - 1] != '/' ) {
1576                                         continue;
1577                                 }
1578  
1579                                 // make sure the file is not in a subdirectory
1580                                 for ( j = pathLength; name[j+1] != '\0'; j++ ) {
1581                                         if ( name[j] == '/' ) {
1582                                                 break;
1583                                         }
1584                                 }
1585                                 if ( name[j+1] ) {
1586                                         continue;
1587                                 }
1588
1589                                 // check for extension match
1590                                 for ( j = 0; j < extensions.Num(); j++ ) {
1591                                         if ( length >= extensions[j].Length() && extensions[j].Icmp( name + length - extensions[j].Length() ) == 0 ) {
1592                                                 break;
1593                                         }
1594                                 }
1595                                 if ( j >= extensions.Num() ) {
1596                                         continue;
1597                                 }
1598
1599                                 // unique the match
1600                                 if ( fullRelativePath ) {
1601                                         work = relativePath;
1602                                         work += "/";
1603                                         work += name + pathLength;
1604                                         work.StripTrailing( '/' );
1605                                         AddUnique( work, list, hashIndex );
1606                                 } else {
1607                                         work = name + pathLength;
1608                                         work.StripTrailing( '/' );
1609                                         AddUnique( work, list, hashIndex );
1610                                 }
1611                         }
1612                 }
1613         }
1614
1615         return list.Num();
1616 }
1617
1618 /*
1619 ===============
1620 idFileSystemLocal::ListFiles
1621 ===============
1622 */
1623 idFileList *idFileSystemLocal::ListFiles( const char *relativePath, const char *extension, bool sort, bool fullRelativePath, const char* gamedir ) {
1624         idHashIndex hashIndex( 4096, 4096 );
1625         idStrList extensionList;
1626
1627         idFileList *fileList = new idFileList;
1628         fileList->basePath = relativePath;
1629
1630         GetExtensionList( extension, extensionList );
1631
1632         GetFileList( relativePath, extensionList, fileList->list, hashIndex, fullRelativePath, gamedir );
1633
1634         if ( sort ) {
1635                 idStrListSortPaths( fileList->list );
1636         }
1637
1638         return fileList;
1639 }
1640
1641 /*
1642 ===============
1643 idFileSystemLocal::GetFileListTree
1644 ===============
1645 */
1646 int idFileSystemLocal::GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir ) {
1647         int i;
1648         idStrList slash, folders( 128 );
1649         idHashIndex folderHashIndex( 1024, 128 );
1650
1651         // recurse through the subdirectories
1652         slash.Append( "/" );
1653         GetFileList( relativePath, slash, folders, folderHashIndex, true, gamedir );
1654         for ( i = 0; i < folders.Num(); i++ ) {
1655                 if ( folders[i][0] == '.' ) {
1656                         continue;
1657                 }
1658                 if ( folders[i].Icmp( relativePath ) == 0 ){
1659                         continue;
1660                 }
1661                 GetFileListTree( folders[i], extensions, list, hashIndex, gamedir );
1662         }
1663
1664         // list files in the current directory
1665         GetFileList( relativePath, extensions, list, hashIndex, true, gamedir );
1666
1667         return list.Num();
1668 }
1669
1670 /*
1671 ===============
1672 idFileSystemLocal::ListFilesTree
1673 ===============
1674 */
1675 idFileList *idFileSystemLocal::ListFilesTree( const char *relativePath, const char *extension, bool sort, const char* gamedir ) {
1676         idHashIndex hashIndex( 4096, 4096 );
1677         idStrList extensionList;
1678
1679         idFileList *fileList = new idFileList();
1680         fileList->basePath = relativePath;
1681         fileList->list.SetGranularity( 4096 );
1682
1683         GetExtensionList( extension, extensionList );
1684
1685         GetFileListTree( relativePath, extensionList, fileList->list, hashIndex, gamedir );
1686
1687         if ( sort ) {
1688                 idStrListSortPaths( fileList->list );
1689         }
1690
1691         return fileList;
1692 }
1693
1694 /*
1695 ===============
1696 idFileSystemLocal::FreeFileList
1697 ===============
1698 */
1699 void idFileSystemLocal::FreeFileList( idFileList *fileList ) {
1700         delete fileList;
1701 }
1702
1703 /*
1704 ===============
1705 idFileSystemLocal::ListMods
1706 ===============
1707 */
1708 idModList *idFileSystemLocal::ListMods( void ) {
1709         int             i;
1710         const int       MAX_DESCRIPTION = 256;
1711         char            desc[ MAX_DESCRIPTION ];
1712
1713         idStrList       dirs;
1714         idStrList       pk4s;
1715
1716         idModList       *list = new idModList;
1717
1718         const char      *search[ 4 ];
1719         int                     isearch;
1720
1721         search[0] = fs_savepath.GetString();
1722         search[1] = fs_devpath.GetString();
1723         search[2] = fs_basepath.GetString();
1724         search[3] = fs_cdpath.GetString();
1725
1726         for ( isearch = 0; isearch < 4; isearch++ ) {
1727
1728                 dirs.Clear();
1729                 pk4s.Clear();
1730
1731                 // scan for directories
1732                 ListOSFiles( search[ isearch ], "/", dirs );
1733
1734                 dirs.Remove( "." );
1735                 dirs.Remove( ".." );
1736                 dirs.Remove( "base" );
1737                 dirs.Remove( "pb" );
1738
1739                 // see if there are any pk4 files in each directory
1740                 for( i = 0; i < dirs.Num(); i++ ) {
1741                         idStr gamepath = BuildOSPath( search[ isearch ], dirs[ i ], "" );
1742                         ListOSFiles( gamepath, ".pk4", pk4s );
1743                         if ( pk4s.Num() ) {
1744                                 if ( !list->mods.Find( dirs[ i ] ) ) {
1745                                         // D3 1.3 #31, only list d3xp if the pak is present
1746                                         if ( dirs[ i ].Icmp( "d3xp" ) || HasD3XP() ) {
1747                                                 list->mods.Append( dirs[ i ] );
1748                                         }
1749                                 }
1750                         }
1751                 }
1752         }
1753            
1754         list->mods.Sort();
1755
1756         // read the descriptions for each mod - search all paths
1757         for ( i = 0; i < list->mods.Num(); i++ ) {
1758
1759                 for ( isearch = 0; isearch < 4; isearch++ ) {
1760
1761                         idStr descfile = BuildOSPath( search[ isearch ], list->mods[ i ], "description.txt" );
1762                         FILE *f = OpenOSFile( descfile, "r" );
1763                         if ( f ) {
1764                                 if ( fgets( desc, MAX_DESCRIPTION, f ) ) {
1765                                         list->descriptions.Append( desc );
1766                                         fclose( f );
1767                                         break;
1768                                 } else {
1769                                         common->DWarning( "Error reading %s", descfile.c_str() );
1770                                         fclose( f );
1771                                         continue;
1772                                 }
1773                         }
1774                 }
1775
1776                 if ( isearch == 4 ) {
1777                         list->descriptions.Append( list->mods[ i ] );
1778                 }
1779         }
1780
1781         list->mods.Insert( "" );
1782         list->descriptions.Insert( "Doom 3" );
1783
1784         assert( list->mods.Num() == list->descriptions.Num() );
1785
1786         return list;
1787 }
1788
1789 /*
1790 ===============
1791 idFileSystemLocal::FreeModList
1792 ===============
1793 */
1794 void idFileSystemLocal::FreeModList( idModList *modList ) {
1795         delete modList;
1796 }
1797
1798 /*
1799 ===============
1800 idDEntry::Matches
1801 ===============
1802 */
1803 bool idDEntry::Matches(const char *directory, const char *extension) const {
1804         if ( !idDEntry::directory.Icmp( directory ) && !idDEntry::extension.Icmp( extension ) ) {
1805                 return true;
1806         }
1807         return false;
1808 }
1809
1810 /*
1811 ===============
1812 idDEntry::Init
1813 ===============
1814 */
1815 void idDEntry::Init( const char *directory, const char *extension, const idStrList &list ) {
1816         idDEntry::directory = directory;
1817         idDEntry::extension = extension;
1818         idStrList::operator=(list);
1819 }
1820
1821 /*
1822 ===============
1823 idDEntry::Clear
1824 ===============
1825 */
1826 void idDEntry::Clear( void ) {
1827         directory.Clear();
1828         extension.Clear();
1829         idStrList::Clear();
1830 }
1831
1832 /*
1833 ===============
1834 idFileSystemLocal::ListOSFiles
1835
1836  call to the OS for a listing of files in an OS directory
1837  optionally, perform some caching of the entries
1838 ===============
1839 */
1840 int     idFileSystemLocal::ListOSFiles( const char *directory, const char *extension, idStrList &list ) {
1841         int i, j, ret;
1842
1843         if ( !extension ) {
1844                 extension = "";
1845         }
1846
1847         if ( !fs_caseSensitiveOS.GetBool() ) {
1848                 return Sys_ListFiles( directory, extension, list );
1849         }
1850
1851         // try in cache
1852         i = dir_cache_index - 1;
1853         while( i >= dir_cache_index - dir_cache_count ) {
1854                 j = (i+MAX_CACHED_DIRS) % MAX_CACHED_DIRS;
1855                 if ( dir_cache[j].Matches( directory, extension ) ) {
1856                         if ( fs_debug.GetInteger() ) {
1857                                 //common->Printf( "idFileSystemLocal::ListOSFiles: cache hit: %s\n", directory );
1858                         }
1859                         list = dir_cache[j];
1860                         return list.Num();
1861                 }
1862                 i--;
1863         }
1864
1865         if ( fs_debug.GetInteger() ) {
1866                 //common->Printf( "idFileSystemLocal::ListOSFiles: cache miss: %s\n", directory );
1867         }       
1868
1869         ret = Sys_ListFiles( directory, extension, list );
1870
1871         if ( ret == -1 ) {
1872                 return -1;
1873         }
1874
1875         // push a new entry
1876         dir_cache[dir_cache_index].Init( directory, extension, list );
1877         dir_cache_index = (++dir_cache_index) % MAX_CACHED_DIRS;
1878         if ( dir_cache_count < MAX_CACHED_DIRS ) {
1879                 dir_cache_count++;
1880         }
1881
1882         return ret;
1883 }
1884
1885 /*
1886 ================
1887 idFileSystemLocal::Dir_f
1888 ================
1889 */
1890 void idFileSystemLocal::Dir_f( const idCmdArgs &args ) {
1891         idStr           relativePath;
1892         idStr           extension;
1893         idFileList *fileList;
1894         int                     i;
1895
1896         if ( args.Argc() < 2 || args.Argc() > 3 ) {
1897                 common->Printf( "usage: dir <directory> [extension]\n" );
1898                 return;
1899         }
1900
1901         if ( args.Argc() == 2 ) {
1902                 relativePath = args.Argv( 1 );
1903                 extension = "";
1904         }
1905         else {
1906                 relativePath = args.Argv( 1 );
1907                 extension = args.Argv( 2 );
1908                 if ( extension[0] != '.' ) {
1909                         common->Warning( "extension should have a leading dot" );
1910                 }
1911         }
1912         relativePath.BackSlashesToSlashes();
1913         relativePath.StripTrailing( '/' );
1914
1915         common->Printf( "Listing of %s/*%s\n", relativePath.c_str(), extension.c_str() );
1916         common->Printf( "---------------\n" );
1917
1918         fileList = fileSystemLocal.ListFiles( relativePath, extension );
1919
1920         for ( i = 0; i < fileList->GetNumFiles(); i++ ) {
1921                 common->Printf( "%s\n", fileList->GetFile( i ) );
1922         }
1923         common->Printf( "%d files\n", fileList->list.Num() );
1924
1925         fileSystemLocal.FreeFileList( fileList );
1926 }
1927
1928 /*
1929 ================
1930 idFileSystemLocal::DirTree_f
1931 ================
1932 */
1933 void idFileSystemLocal::DirTree_f( const idCmdArgs &args ) {
1934         idStr           relativePath;
1935         idStr           extension;
1936         idFileList *fileList;
1937         int                     i;
1938
1939         if ( args.Argc() < 2 || args.Argc() > 3 ) {
1940                 common->Printf( "usage: dirtree <directory> [extension]\n" );
1941                 return;
1942         }
1943
1944         if ( args.Argc() == 2 ) {
1945                 relativePath = args.Argv( 1 );
1946                 extension = "";
1947         }
1948         else {
1949                 relativePath = args.Argv( 1 );
1950                 extension = args.Argv( 2 );
1951                 if ( extension[0] != '.' ) {
1952                         common->Warning( "extension should have a leading dot" );
1953                 }
1954         }
1955         relativePath.BackSlashesToSlashes();
1956         relativePath.StripTrailing( '/' );
1957
1958         common->Printf( "Listing of %s/*%s /s\n", relativePath.c_str(), extension.c_str() );
1959         common->Printf( "---------------\n" );
1960
1961         fileList = fileSystemLocal.ListFilesTree( relativePath, extension );
1962
1963         for ( i = 0; i < fileList->GetNumFiles(); i++ ) {
1964                 common->Printf( "%s\n", fileList->GetFile( i ) );
1965         }
1966         common->Printf( "%d files\n", fileList->list.Num() );
1967
1968         fileSystemLocal.FreeFileList( fileList );
1969 }
1970
1971 /*
1972 ============
1973 idFileSystemLocal::Path_f
1974 ============
1975 */
1976 void idFileSystemLocal::Path_f( const idCmdArgs &args ) {
1977         searchpath_t *sp;
1978         int i;
1979         idStr status;
1980
1981         common->Printf( "Current search path:\n" );
1982         for ( sp = fileSystemLocal.searchPaths; sp; sp = sp->next ) {
1983                 if ( sp->pack ) {
1984                         if ( com_developer.GetBool() ) {
1985                                 sprintf( status, "%s (%i files - 0x%x %s", sp->pack->pakFilename.c_str(), sp->pack->numfiles, sp->pack->checksum, sp->pack->referenced ? "referenced" : "not referenced" );
1986                                 if ( sp->pack->addon ) {
1987                                         status += " - addon)\n";
1988                                 } else {
1989                                         status += ")\n";
1990                                 }
1991                                 common->Printf( status.c_str() );
1992                         } else {
1993                                 common->Printf( "%s (%i files)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles );
1994                         }
1995                         if ( fileSystemLocal.serverPaks.Num() ) {
1996                                 if ( fileSystemLocal.serverPaks.Find( sp->pack ) ) {
1997                                         common->Printf( "    on the pure list\n" );
1998                                 } else {
1999                                         common->Printf( "    not on the pure list\n" );
2000                                 }
2001                         }
2002                 } else {
2003                         common->Printf( "%s/%s\n", sp->dir->path.c_str(), sp->dir->gamedir.c_str() );
2004                 }
2005         }
2006         common->Printf( "game DLL: 0x%x in pak: 0x%x\n", fileSystemLocal.gameDLLChecksum, fileSystemLocal.gamePakChecksum );
2007 #if ID_FAKE_PURE
2008         common->Printf( "Note: ID_FAKE_PURE is enabled\n" );
2009 #endif
2010         for( i = 0; i < MAX_GAME_OS; i++ ) {
2011                 if ( fileSystemLocal.gamePakForOS[ i ] ) {
2012                         common->Printf( "OS %d - pak 0x%x\n", i, fileSystemLocal.gamePakForOS[ i ] );
2013                 }
2014         }
2015         // show addon packs that are *not* in the search lists
2016         common->Printf( "Addon pk4s:\n" );
2017         for ( sp = fileSystemLocal.addonPaks; sp; sp = sp->next ) {
2018                 if ( com_developer.GetBool() ) {
2019                         common->Printf( "%s (%i files - 0x%x)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles, sp->pack->checksum );
2020                 } else {
2021                         common->Printf( "%s (%i files)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles );
2022                 }               
2023         }
2024 }
2025
2026 /*
2027 ============
2028 idFileSystemLocal::GetOSMask
2029 ============
2030 */
2031 int idFileSystemLocal::GetOSMask( void ) {
2032         int i, ret = 0;
2033         for( i = 0; i < MAX_GAME_OS; i++ ) {
2034                 if ( fileSystemLocal.gamePakForOS[ i ] ) {
2035                         ret |= ( 1 << i );
2036                 }
2037         }
2038         if ( !ret ) {
2039                 return -1;
2040         }
2041         return ret;
2042 }
2043
2044 /*
2045 ============
2046 idFileSystemLocal::TouchFile_f
2047
2048 The only purpose of this function is to allow game script files to copy
2049 arbitrary files furing an "fs_copyfiles 1" run.
2050 ============
2051 */
2052 void idFileSystemLocal::TouchFile_f( const idCmdArgs &args ) {
2053         idFile *f;
2054
2055         if ( args.Argc() != 2 ) {
2056                 common->Printf( "Usage: touchFile <file>\n" );
2057                 return;
2058         }
2059
2060         f = fileSystemLocal.OpenFileRead( args.Argv( 1 ) );
2061         if ( f ) {
2062                 fileSystemLocal.CloseFile( f );
2063         }
2064 }
2065
2066 /*
2067 ============
2068 idFileSystemLocal::TouchFileList_f
2069
2070 Takes a text file and touches every file in it, use one file per line.
2071 ============
2072 */
2073 void idFileSystemLocal::TouchFileList_f( const idCmdArgs &args ) {
2074         
2075         if ( args.Argc() != 2 ) {
2076                 common->Printf( "Usage: touchFileList <filename>\n" );
2077                 return;
2078         }
2079
2080         const char *buffer = NULL;
2081         idParser src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT );
2082         if ( fileSystem->ReadFile( args.Argv( 1 ), ( void** )&buffer, NULL ) && buffer ) {
2083                 src.LoadMemory( buffer, strlen( buffer ), args.Argv( 1 ) );
2084                 if ( src.IsLoaded() ) {
2085                         idToken token;
2086                         while( src.ReadToken( &token ) ) {
2087                                 common->Printf( "%s\n", token.c_str() );
2088                                 session->UpdateScreen();
2089                                 idFile *f = fileSystemLocal.OpenFileRead( token );
2090                                 if ( f ) {
2091                                         fileSystemLocal.CloseFile( f );
2092                                 }
2093                         }
2094                 }
2095         }
2096
2097 }
2098
2099
2100 /*
2101 ================
2102 idFileSystemLocal::AddGameDirectory
2103
2104 Sets gameFolder, adds the directory to the head of the search paths, then loads any pk4 files.
2105 ================
2106 */
2107 void idFileSystemLocal::AddGameDirectory( const char *path, const char *dir ) {
2108         int                             i;
2109         searchpath_t *  search;
2110         pack_t *                pak;
2111         idStr                   pakfile;
2112         idStrList               pakfiles;
2113
2114         // check if the search path already exists
2115         for ( search = searchPaths; search; search = search->next ) {
2116                 // if this element is a pak file
2117                 if ( !search->dir ) {
2118                         continue;
2119                 }
2120                 if ( search->dir->path.Cmp( path ) == 0 && search->dir->gamedir.Cmp( dir ) == 0 ) {
2121                         return;
2122                 }
2123         }
2124
2125         gameFolder = dir;
2126
2127         //
2128         // add the directory to the search path
2129         //
2130         search = new searchpath_t;
2131         search->dir = new directory_t;
2132         search->pack = NULL;
2133
2134         search->dir->path = path;
2135         search->dir->gamedir = dir;
2136         search->next = searchPaths;
2137         searchPaths = search;
2138
2139         // find all pak files in this directory
2140         pakfile = BuildOSPath( path, dir, "" );
2141         pakfile[ pakfile.Length() - 1 ] = 0;    // strip the trailing slash
2142
2143         ListOSFiles( pakfile, ".pk4", pakfiles );
2144
2145         // sort them so that later alphabetic matches override
2146         // earlier ones. This makes pak1.pk4 override pak0.pk4
2147         pakfiles.Sort();
2148
2149         for ( i = 0; i < pakfiles.Num(); i++ ) {
2150                 pakfile = BuildOSPath( path, dir, pakfiles[i] );
2151                 pak = LoadZipFile( pakfile );
2152                 if ( !pak ) {
2153                         continue;
2154                 }
2155                 // insert the pak after the directory it comes from
2156                 search = new searchpath_t;
2157                 search->dir = NULL;
2158                 search->pack = pak;
2159                 search->next = searchPaths->next;
2160                 searchPaths->next = search;
2161                 common->Printf( "Loaded pk4 %s with checksum 0x%x\n", pakfile.c_str(), pak->checksum );
2162         }
2163 }
2164
2165 /*
2166 ================
2167 idFileSystemLocal::SetupGameDirectories
2168
2169   Takes care of the correct search order.
2170 ================
2171 */
2172 void idFileSystemLocal::SetupGameDirectories( const char *gameName ) {
2173         // setup cdpath
2174         if ( fs_cdpath.GetString()[0] ) {
2175                 AddGameDirectory( fs_cdpath.GetString(), gameName );
2176         }
2177
2178         // setup basepath
2179         if ( fs_basepath.GetString()[0] ) {
2180                 AddGameDirectory( fs_basepath.GetString(), gameName );
2181         }
2182
2183         // setup devpath
2184         if ( fs_devpath.GetString()[0] ) {
2185                 AddGameDirectory( fs_devpath.GetString(), gameName );
2186         }
2187
2188         // setup savepath
2189         if ( fs_savepath.GetString()[0] ) {
2190                 AddGameDirectory( fs_savepath.GetString(), gameName );
2191         }
2192 }
2193
2194 /*
2195 ===============
2196 idFileSystemLocal::FollowDependencies
2197 ===============
2198 */
2199 void idFileSystemLocal::FollowAddonDependencies( pack_t *pak ) {
2200         assert( pak );
2201         if ( !pak->addon_info || !pak->addon_info->depends.Num() ) {
2202                 return;
2203         }
2204         int i, num = pak->addon_info->depends.Num();
2205         for ( i = 0; i < num; i++ ) {
2206                 pack_t *deppak = GetPackForChecksum( pak->addon_info->depends[ i ], true );
2207                 if ( deppak ) {
2208                         // make sure it hasn't been marked for search already
2209                         if ( !deppak->addon_search ) {
2210                                 // must clean addonChecksums as we go
2211                                 int addon_index = addonChecksums.FindIndex( deppak->checksum );
2212                                 if ( addon_index >= 0 ) {
2213                                         addonChecksums.RemoveIndex( addon_index );
2214                                 }
2215                                 deppak->addon_search = true;
2216                                 common->Printf( "Addon pk4 %s 0x%x depends on pak %s 0x%x, will be searched\n",
2217                                                                 pak->pakFilename.c_str(), pak->checksum,
2218                                                                 deppak->pakFilename.c_str(), deppak->checksum );
2219                                 FollowAddonDependencies( deppak );
2220                         }
2221                 } else {
2222                         common->Printf( "Addon pk4 %s 0x%x depends on unknown pak 0x%x\n",
2223                                                         pak->pakFilename.c_str(), pak->checksum, pak->addon_info->depends[ i ] );
2224                 }
2225         }
2226 }
2227
2228 /*
2229 ================
2230 idFileSystemLocal::Startup
2231 ================
2232 */
2233 void idFileSystemLocal::Startup( void ) {
2234         searchpath_t    **search;
2235         int                             i;
2236         pack_t                  *pak;
2237         int                             addon_index;
2238
2239         common->Printf( "------ Initializing File System ------\n" );
2240
2241         if ( restartChecksums.Num() ) {
2242                 common->Printf( "restarting in pure mode with %d pak files\n", restartChecksums.Num() );
2243         }
2244         if ( addonChecksums.Num() ) {
2245                 common->Printf( "restarting filesystem with %d addon pak file(s) to include\n", addonChecksums.Num() );
2246         }
2247
2248         SetupGameDirectories( BASE_GAMEDIR );
2249
2250         // fs_game_base override
2251         if ( fs_game_base.GetString()[0] &&
2252                  idStr::Icmp( fs_game_base.GetString(), BASE_GAMEDIR ) ) {
2253                 SetupGameDirectories( fs_game_base.GetString() );
2254         }
2255
2256         // fs_game override
2257         if ( fs_game.GetString()[0] &&
2258                  idStr::Icmp( fs_game.GetString(), BASE_GAMEDIR ) &&
2259                  idStr::Icmp( fs_game.GetString(), fs_game_base.GetString() ) ) {
2260                 SetupGameDirectories( fs_game.GetString() );
2261         }
2262
2263         // currently all addons are in the search list - deal with filtering out and dependencies now
2264         // scan through and deal with dependencies
2265         search = &searchPaths;
2266         while ( *search ) {
2267                 if ( !( *search )->pack || !( *search )->pack->addon ) {
2268                         search = &( ( *search )->next );
2269                         continue;
2270                 }
2271                 pak = ( *search )->pack;
2272                 if ( fs_searchAddons.GetBool() ) {
2273                         // when we have fs_searchAddons on we should never have addonChecksums
2274                         assert( !addonChecksums.Num() );
2275                         pak->addon_search = true;
2276                         search = &( ( *search )->next );
2277                         continue;
2278                 }
2279                 addon_index = addonChecksums.FindIndex( pak->checksum );
2280                 if ( addon_index >= 0 ) {
2281                         assert( !pak->addon_search );   // any pak getting flagged as addon_search should also have been removed from addonChecksums already
2282                         pak->addon_search = true;
2283                         addonChecksums.RemoveIndex( addon_index );
2284                         FollowAddonDependencies( pak );
2285                 }
2286                 search = &( ( *search )->next );
2287         }
2288
2289         // now scan to filter out addons not marked addon_search
2290         search = &searchPaths;
2291         while ( *search ) {
2292                 if ( !( *search )->pack || !( *search )->pack->addon ) {
2293                         search = &( ( *search )->next );
2294                         continue;
2295                 }
2296                 assert( !( *search )->dir );
2297                 pak = ( *search )->pack;
2298                 if ( pak->addon_search ) {
2299                         common->Printf( "Addon pk4 %s with checksum 0x%x is on the search list\n",
2300                                                         pak->pakFilename.c_str(), pak->checksum );
2301                         search = &( ( *search )->next );
2302                 } else {
2303                         // remove from search list, put in addons list
2304                         searchpath_t *paksearch = *search;
2305                         *search = ( *search )->next;
2306                         paksearch->next = addonPaks;
2307                         addonPaks = paksearch;
2308                         common->Printf( "Addon pk4 %s with checksum 0x%x is on addon list\n",
2309                                                         pak->pakFilename.c_str(), pak->checksum );                              
2310                 }
2311         }
2312
2313         // all addon paks found and accounted for
2314         assert( !addonChecksums.Num() );
2315         addonChecksums.Clear(); // just in case
2316
2317         if ( restartChecksums.Num() ) {
2318                 search = &searchPaths;
2319                 while ( *search ) {
2320                         if ( !( *search )->pack ) {
2321                                 search = &( ( *search )->next );
2322                                 continue;
2323                         }
2324                         if ( ( i = restartChecksums.FindIndex( ( *search )->pack->checksum ) ) != -1 ) {
2325                                 if ( i == 0 ) {
2326                                         // this pak is the next one in the pure search order
2327                                         serverPaks.Append( ( *search )->pack );
2328                                         restartChecksums.RemoveIndex( 0 );
2329                                         if ( !restartChecksums.Num() ) {
2330                                                 break; // early out, we're done
2331                                         }
2332                                         search = &( ( *search )->next );
2333                                         continue;
2334                                 } else {
2335                                         // this pak will be on the pure list, but order is not right yet
2336                                         searchpath_t    *aux;
2337                                         aux = ( *search )->next;
2338                                         if ( !aux ) {
2339                                                 // last of the list can't be swapped back
2340                                                 if ( fs_debug.GetBool() ) {
2341                                                         common->Printf( "found pure checksum %x at index %d, but the end of search path is reached\n", ( *search )->pack->checksum, i );
2342                                                         idStr checks;
2343                                                         checks.Clear();
2344                                                         for ( i = 0; i < serverPaks.Num(); i++ ) {
2345                                                                 checks += va( "%p ", serverPaks[ i ] );
2346                                                         }
2347                                                         common->Printf( "%d pure paks - %s \n", serverPaks.Num(), checks.c_str() );
2348                                                         checks.Clear();
2349                                                         for ( i = 0; i < restartChecksums.Num(); i++ ) {
2350                                                                 checks += va( "%x ", restartChecksums[ i ] );
2351                                                         }
2352                                                         common->Printf( "%d paks left - %s\n", restartChecksums.Num(), checks.c_str() );
2353                                                 }
2354                                                 common->FatalError( "Failed to restart with pure mode restrictions for server connect" );
2355                                         }
2356                                         // put this search path at the end of the list
2357                                         searchpath_t *search_end;
2358                                         search_end = ( *search )->next;
2359                                         while ( search_end->next ) {
2360                                                 search_end = search_end->next;
2361                                         }
2362                                         search_end->next = *search;
2363                                         *search = ( *search )->next;
2364                                         search_end->next->next = NULL;
2365                                         continue;
2366                                 }
2367                         }
2368                         // this pak is not on the pure list
2369                         search = &( ( *search )->next );
2370                 }
2371                 // the list must be empty
2372                 if ( restartChecksums.Num() ) {
2373                         if ( fs_debug.GetBool() ) {
2374                                 idStr checks;
2375                                 checks.Clear();
2376                                 for ( i = 0; i < serverPaks.Num(); i++ ) {
2377                                         checks += va( "%p ", serverPaks[ i ] );
2378                                 }
2379                                 common->Printf( "%d pure paks - %s \n", serverPaks.Num(), checks.c_str() );
2380                                 checks.Clear();
2381                                 for ( i = 0; i < restartChecksums.Num(); i++ ) {
2382                                         checks += va( "%x ", restartChecksums[ i ] );
2383                                 }
2384                                 common->Printf( "%d paks left - %s\n", restartChecksums.Num(), checks.c_str() );
2385                         }
2386                         common->FatalError( "Failed to restart with pure mode restrictions for server connect" );
2387                 }
2388                 // also the game pak checksum
2389                 // we could check if the game pak is actually present, but we would not be restarting if there wasn't one @ first pure check
2390                 gamePakChecksum = restartGamePakChecksum;
2391         }
2392
2393         // add our commands
2394         cmdSystem->AddCommand( "dir", Dir_f, CMD_FL_SYSTEM, "lists a folder", idCmdSystem::ArgCompletion_FileName );
2395         cmdSystem->AddCommand( "dirtree", DirTree_f, CMD_FL_SYSTEM, "lists a folder with subfolders" );
2396         cmdSystem->AddCommand( "path", Path_f, CMD_FL_SYSTEM, "lists search paths" );
2397         cmdSystem->AddCommand( "touchFile", TouchFile_f, CMD_FL_SYSTEM, "touches a file" );
2398         cmdSystem->AddCommand( "touchFileList", TouchFileList_f, CMD_FL_SYSTEM, "touches a list of files" );
2399
2400         // print the current search paths
2401         Path_f( idCmdArgs() );
2402
2403         common->Printf( "file system initialized.\n" );
2404         common->Printf( "--------------------------------------\n" );
2405 }
2406
2407 /*
2408 ===================
2409 idFileSystemLocal::SetRestrictions
2410
2411 Looks for product keys and restricts media add on ability
2412 if the full version is not found
2413 ===================
2414 */
2415 void idFileSystemLocal::SetRestrictions( void ) {
2416 #ifdef ID_DEMO_BUILD
2417         common->Printf( "\nRunning in restricted demo mode.\n\n" );
2418         // make sure that the pak file has the header checksum we expect
2419         searchpath_t    *search;
2420         for ( search = searchPaths; search; search = search->next ) {
2421                 if ( search->pack ) {
2422                         // a tiny attempt to keep the checksum from being scannable from the exe
2423                         if ( ( search->pack->checksum ^ 0x84268436u ) != ( DEMO_PAK_CHECKSUM ^ 0x84268436u ) ) {
2424                                 common->FatalError( "Corrupted %s: 0x%x", search->pack->pakFilename.c_str(), search->pack->checksum );
2425                         }
2426                 }
2427         }
2428         cvarSystem->SetCVarBool( "fs_restrict", true );
2429 #endif
2430 }
2431
2432 /*
2433 =====================
2434 idFileSystemLocal::UpdatePureServerChecksums
2435 =====================
2436 */
2437 void idFileSystemLocal::UpdatePureServerChecksums( void ) {
2438         searchpath_t    *search;
2439         int                             i;
2440         pureStatus_t    status;
2441
2442         serverPaks.Clear();
2443         for ( search = searchPaths; search; search = search->next ) {
2444                 // is the element a referenced pak file?
2445                 if ( !search->pack ) {
2446                         continue;
2447                 }
2448                 status = GetPackStatus( search->pack );
2449                 if ( status == PURE_NEVER ) {
2450                         continue;
2451                 }
2452                 if ( status == PURE_NEUTRAL && !search->pack->referenced ) {
2453                         continue;
2454                 }
2455                 serverPaks.Append( search->pack );
2456                 if ( serverPaks.Num() >= MAX_PURE_PAKS ) {
2457                         common->FatalError( "MAX_PURE_PAKS ( %d ) exceeded\n", MAX_PURE_PAKS );
2458                 }
2459         }
2460         if ( fs_debug.GetBool() ) {
2461                 idStr checks;
2462                 for ( i = 0; i < serverPaks.Num(); i++ ) {
2463                         checks += va( "%x ", serverPaks[ i ]->checksum );
2464                 }
2465                 common->Printf( "set pure list - %d paks ( %s)\n", serverPaks.Num(), checks.c_str() );
2466         }
2467 }
2468
2469 /*
2470 =====================
2471 idFileSystemLocal::UpdateGamePakChecksums
2472 =====================
2473 */
2474 bool idFileSystemLocal::UpdateGamePakChecksums( void ) {
2475         searchpath_t    *search;
2476         fileInPack_t    *pakFile;
2477         int                             confHash;
2478         idFile                  *confFile;
2479         char                    *buf;
2480         idLexer                 *lexConf;
2481         idToken                 token;
2482         int                             id;
2483
2484         confHash = HashFileName( BINARY_CONFIG );
2485
2486         memset( gamePakForOS, 0, sizeof( gamePakForOS ) );
2487         for ( search = searchPaths; search; search = search->next ) {
2488                 if ( !search->pack ) {
2489                         continue;
2490                 }
2491                 search->pack->binary = BINARY_NO;
2492                 for ( pakFile = search->pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) {
2493                         if ( !FilenameCompare( pakFile->name, BINARY_CONFIG ) ) {
2494                                 search->pack->binary = BINARY_YES;
2495                                 confFile = ReadFileFromZip( search->pack, pakFile, BINARY_CONFIG );
2496                                 buf = new char[ confFile->Length() + 1 ];
2497                                 confFile->Read( (void *)buf, confFile->Length() );
2498                                 buf[ confFile->Length() ] = '\0';
2499                                 lexConf = new idLexer( buf, confFile->Length(), confFile->GetFullPath() );
2500                                 while ( lexConf->ReadToken( &token ) ) {
2501                                         if ( token.IsNumeric() ) {
2502                                                 id = atoi( token );
2503                                                 if ( id < MAX_GAME_OS && !gamePakForOS[ id ] ) {
2504                                                         if ( fs_debug.GetBool() ) {
2505                                                                 common->Printf( "Adding game pak checksum for OS %d: %s 0x%x\n", id, confFile->GetFullPath(), search->pack->checksum );
2506                                                         }
2507                                                         gamePakForOS[ id ] = search->pack->checksum;
2508                                                 }
2509                                         }
2510                                 }
2511                                 CloseFile( confFile );
2512                                 delete lexConf;
2513                                 delete[] buf;
2514                         }
2515                 }
2516         }
2517
2518         // some sanity checks on the game code references
2519         // make sure that at least the local OS got a pure reference
2520         if ( !gamePakForOS[ BUILD_OS_ID ] ) {
2521                 common->Warning( "No game code pak reference found for the local OS" );
2522                 return false;
2523         }
2524
2525         if ( !cvarSystem->GetCVarBool( "net_serverAllowServerMod" ) &&
2526                 gamePakChecksum != gamePakForOS[ BUILD_OS_ID ] ) {
2527                 common->Warning( "The current game code doesn't match pak files (net_serverAllowServerMod is off)" );
2528                 return false;
2529         }
2530
2531         return true;
2532 }
2533
2534 /*
2535 =====================
2536 idFileSystemLocal::GetPackForChecksum
2537 =====================
2538 */
2539 pack_t* idFileSystemLocal::GetPackForChecksum( int checksum, bool searchAddons ) {
2540         searchpath_t    *search;
2541         for ( search = searchPaths; search; search = search->next ) {
2542                 if ( !search->pack ) {
2543                         continue;
2544                 }
2545                 if ( search->pack->checksum == checksum ) {
2546                         return search->pack;
2547                 }
2548         }
2549         if ( searchAddons ) {
2550                 for ( search = addonPaks; search; search = search->next ) {
2551                         assert( search->pack && search->pack->addon );
2552                         if ( search->pack->checksum == checksum ) {
2553                                 return search->pack;
2554                         }
2555                 }
2556         }
2557         return NULL;
2558 }
2559
2560 /*
2561 ===============
2562 idFileSystemLocal::ValidateDownloadPakForChecksum
2563 ===============
2564 */
2565 int idFileSystemLocal::ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isBinary ) {
2566         int                     i;
2567         idStrList       testList;
2568         idStr           name;
2569         idStr           relativePath;
2570         bool            pakBinary;
2571         pack_t          *pak = GetPackForChecksum( checksum );
2572
2573         if ( !pak ) {
2574                 return 0;
2575         }
2576
2577         // validate this pak for a potential download
2578         // ignore pak*.pk4 for download. those are reserved to distribution and cannot be downloaded
2579         name = pak->pakFilename;
2580         name.StripPath();
2581         if ( strstr( name.c_str(), "pak" ) == name.c_str() ) {
2582                 common->DPrintf( "%s is not a donwloadable pak\n", pak->pakFilename.c_str() );
2583                 return 0;
2584         }
2585         // check the binary
2586         // a pure server sets the binary flag when starting the game
2587         assert( pak->binary != BINARY_UNKNOWN );
2588         pakBinary = ( pak->binary == BINARY_YES ) ? true : false;
2589         if ( isBinary != pakBinary ) {
2590                 common->DPrintf( "%s binary flag mismatch\n", pak->pakFilename.c_str() );
2591                 return 0;
2592         }
2593
2594         // extract a path that includes the fs_game: != OSPathToRelativePath
2595         testList.Append( fs_savepath.GetString() );
2596         testList.Append( fs_devpath.GetString() );
2597         testList.Append( fs_basepath.GetString() );
2598         testList.Append( fs_cdpath.GetString() );
2599         for ( i = 0; i < testList.Num(); i ++ ) {
2600                 if ( testList[ i ].Length() && !testList[ i ].Icmpn( pak->pakFilename, testList[ i ].Length() ) ) {
2601                         relativePath = pak->pakFilename.c_str() + testList[ i ].Length() + 1;
2602                         break;
2603                 }
2604         }
2605         if ( i == testList.Num() ) {
2606                 common->Warning( "idFileSystem::ValidateDownloadPak: failed to extract relative path for %s", pak->pakFilename.c_str() );
2607                 return 0;
2608         }
2609         idStr::Copynz( path, relativePath, MAX_STRING_CHARS );
2610         return pak->length;
2611 }
2612
2613 /*
2614 =====================
2615 idFileSystemLocal::ClearPureChecksums
2616 =====================
2617 */
2618 void idFileSystemLocal::ClearPureChecksums( void ) {
2619         common->DPrintf( "Cleared pure server lock\n" );
2620         serverPaks.Clear();
2621 }
2622
2623 /*
2624 =====================
2625 idFileSystemLocal::SetPureServerChecksums
2626 set the pure paks according to what the server asks
2627 if that's not possible, identify why and build an answer
2628 can be:
2629   loadedFileFromDir - some files were loaded from directories instead of paks (a restart in pure pak-only is required)
2630   missing/wrong checksums - some pak files would need to be installed/updated (downloaded for instance)
2631   some pak files currently referenced are not referenced by the server
2632   wrong order - if the pak order doesn't match, means some stuff could have been loaded from somewhere else
2633 server referenced files are prepended to the list if possible ( that doesn't break pureness )
2634 DLL:
2635   the checksum of the pak containing the DLL is maintained seperately, the server can send different replies by OS
2636 =====================
2637 */
2638 fsPureReply_t idFileSystemLocal::SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int _gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum ) {
2639         pack_t                  *pack;
2640         int                             i, j, imissing;
2641         bool                    success = true;
2642         bool                    canPrepend = true;
2643         char                    dllName[MAX_OSPATH];
2644         int                             dllHash;
2645         fileInPack_t *  pakFile;
2646
2647         sys->DLL_GetFileName( "game", dllName, MAX_OSPATH );
2648         dllHash = HashFileName( dllName );
2649
2650         imissing = 0;
2651         missingChecksums[ 0 ] = 0;
2652         assert( missingGamePakChecksum );
2653         *missingGamePakChecksum = 0;
2654
2655         if ( pureChecksums[ 0 ] == 0 ) {
2656                 ClearPureChecksums();
2657                 return PURE_OK;
2658         }
2659
2660         if ( !serverPaks.Num() ) {
2661                 // there was no pure lockdown yet - lock to what we already have
2662                 UpdatePureServerChecksums();
2663         }
2664         i = 0; j = 0;
2665         while ( pureChecksums[ i ] ) {
2666                 if ( j < serverPaks.Num() && serverPaks[ j ]->checksum == pureChecksums[ i ] ) {
2667                         canPrepend = false; // once you start matching into the list there is no prepending anymore
2668                         i++; j++; // the pak is matched, is in the right order, continue..
2669                 } else {
2670                         pack = GetPackForChecksum( pureChecksums[ i ], true );
2671                         if ( pack && pack->addon && !pack->addon_search ) {
2672                                 // this is an addon pack, and it's not on our current search list
2673                                 // setting success to false meaning that a restart including this addon is required
2674                                 if ( fs_debug.GetBool() ) {
2675                                         common->Printf( "pak %s checksumed 0x%x is on addon list. Restart required.\n", pack->pakFilename.c_str(), pack->checksum );
2676                                 }
2677                                 success = false;
2678                         }
2679                         if ( pack && pack->isNew ) {
2680                                 // that's a downloaded pack, we will need to restart
2681                                 if ( fs_debug.GetBool() ) {
2682                                         common->Printf( "pak %s checksumed 0x%x is a newly downloaded file. Restart required.\n", pack->pakFilename.c_str(), pack->checksum );
2683                                 }
2684                                 success = false;
2685                         }
2686                         if ( pack ) {
2687                                 if ( canPrepend ) {
2688                                         // we still have a chance
2689                                         if ( fs_debug.GetBool() ) {
2690                                                 common->Printf( "prepend pak %s checksumed 0x%x at index %d\n", pack->pakFilename.c_str(), pack->checksum, j );
2691                                         }
2692                                         // NOTE: there is a light possibility this adds at the end of the list if UpdatePureServerChecksums didn't set anything
2693                                         serverPaks.Insert( pack, j );
2694                                         i++; j++; // continue..
2695                                 } else {
2696                                         success = false;
2697                                         if ( fs_debug.GetBool() ) {
2698                                                 // verbose the situation
2699                                                 if ( serverPaks.Find( pack ) ) {
2700                                                         common->Printf( "pak %s checksumed 0x%x is in the pure list at wrong index. Current index is %d, found at %d\n", pack->pakFilename.c_str(), pack->checksum, j, serverPaks.FindIndex( pack ) );
2701                                                 } else {
2702                                                         common->Printf( "pak %s checksumed 0x%x can't be added to pure list because of search order\n", pack->pakFilename.c_str(), pack->checksum );
2703                                                 }
2704                                         }
2705                                         i++; // advance server checksums only
2706                                 }
2707                         } else {
2708                                 // didn't find a matching checksum
2709                                 success = false;
2710                                 missingChecksums[ imissing++ ] = pureChecksums[ i ];
2711                                 missingChecksums[ imissing ] = 0;
2712                                 if ( fs_debug.GetBool() ) {
2713                                         common->Printf( "checksum not found - 0x%x\n", pureChecksums[ i ] );
2714                                 }
2715                                 i++; // advance the server checksums only
2716                         }
2717                 }
2718         }
2719         while ( j < serverPaks.Num() ) {
2720                 success = false; // just in case some extra pak files are referenced at the end of our local list
2721                 if ( fs_debug.GetBool() ) {
2722                         common->Printf( "pak %s checksumed 0x%x is an extra reference at the end of local pure list\n", serverPaks[ j ]->pakFilename.c_str(), serverPaks[ j ]->checksum );
2723                 }
2724                 j++;
2725         }
2726
2727         // DLL checksuming
2728         if ( !_gamePakChecksum ) {
2729                 // server doesn't have knowledge of code we can use ( OS issue )
2730                 return PURE_NODLL;
2731         }
2732         assert( gameDLLChecksum );
2733 #if ID_FAKE_PURE
2734         gamePakChecksum = _gamePakChecksum;
2735 #endif
2736         if ( _gamePakChecksum != gamePakChecksum ) {
2737                 // current DLL is wrong, search for a pak with the approriate checksum
2738                 // ( search all paks, the pure list is not relevant here )
2739                 pack = GetPackForChecksum( _gamePakChecksum );
2740                 if ( !pack ) {
2741                         if ( fs_debug.GetBool() ) {
2742                                 common->Printf( "missing the game code pak ( 0x%x )\n", _gamePakChecksum );
2743                         }
2744                         // if there are other paks missing they have also been marked above
2745                         *missingGamePakChecksum = _gamePakChecksum;
2746                         return PURE_MISSING;
2747                 }
2748                 // if assets paks are missing, don't try any of the DLL restart / NODLL
2749                 if ( imissing ) {
2750                         return PURE_MISSING;
2751                 }
2752                 // we have a matching pak
2753                 if ( fs_debug.GetBool() ) {
2754                         common->Printf( "server's game code pak candidate is '%s' ( 0x%x )\n", pack->pakFilename.c_str(), pack->checksum );
2755                 }
2756                 // make sure there is a valid DLL for us
2757                 if ( pack->hashTable[ dllHash ] ) {
2758                         for ( pakFile = pack->hashTable[ dllHash ]; pakFile; pakFile = pakFile->next ) {
2759                                 if ( !FilenameCompare( pakFile->name, dllName ) ) {
2760                                         gamePakChecksum = _gamePakChecksum;             // this will be used to extract the DLL in pure mode FindDLL
2761                                         return PURE_RESTART;
2762                                 }
2763                         }
2764                 }
2765                 common->Warning( "media is misconfigured. server claims pak '%s' ( 0x%x ) has media for us, but '%s' is not found\n", pack->pakFilename.c_str(), pack->checksum, dllName );
2766                 return PURE_NODLL;
2767         }
2768
2769         // we reply to missing after DLL check so it can be part of the list
2770         if ( imissing ) {
2771                 return PURE_MISSING;
2772         }
2773
2774         // one last check
2775         if ( loadedFileFromDir ) {
2776                 success = false;
2777                 if ( fs_debug.GetBool() ) {
2778                         common->Printf( "SetPureServerChecksums: there are files loaded from dir\n" );
2779                 }
2780         }
2781         return ( success ? PURE_OK : PURE_RESTART );
2782 }
2783
2784 /*
2785 =====================
2786 idFileSystemLocal::GetPureServerChecksums
2787 =====================
2788 */
2789 void idFileSystemLocal::GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int *_gamePakChecksum ) {
2790         int i;
2791
2792         for ( i = 0; i < serverPaks.Num(); i++ ) {
2793                 checksums[ i ] = serverPaks[ i ]->checksum;
2794         }
2795         checksums[ i ] = 0;
2796         if ( _gamePakChecksum ) {
2797                 if ( OS >= 0 ) {
2798                         *_gamePakChecksum = gamePakForOS[ OS ];
2799                 } else {
2800                         *_gamePakChecksum = gamePakChecksum;
2801                 }
2802         }
2803 }
2804
2805 /*
2806 =====================
2807 idFileSystemLocal::SetRestartChecksums
2808 =====================
2809 */
2810 void idFileSystemLocal::SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum ) {
2811         int             i;
2812         pack_t  *pack;
2813
2814         restartChecksums.Clear();
2815         i = 0;
2816         while ( pureChecksums[ i ] ) {
2817                 pack = GetPackForChecksum( pureChecksums[ i ], true );
2818                 if ( !pack ) {
2819                         common->FatalError( "SetRestartChecksums failed: no pak for checksum 0x%x\n", pureChecksums[i] );
2820                 }
2821                 if ( pack->addon && addonChecksums.FindIndex( pack->checksum ) < 0 ) {
2822                         // can't mark it pure if we're not even gonna search it :-)
2823                         addonChecksums.Append( pack->checksum );
2824                 }
2825                 restartChecksums.Append( pureChecksums[ i ] );
2826                 i++;
2827         }
2828         restartGamePakChecksum = gamePakChecksum;
2829 }
2830
2831 /*
2832 ================
2833 idFileSystemLocal::Init
2834
2835 Called only at inital startup, not when the filesystem
2836 is resetting due to a game change
2837 ================
2838 */
2839 void idFileSystemLocal::Init( void ) {
2840         // allow command line parms to override our defaults
2841         // we have to specially handle this, because normal command
2842         // line variable sets don't happen until after the filesystem
2843         // has already been initialized
2844         common->StartupVariable( "fs_basepath", false );
2845         common->StartupVariable( "fs_savepath", false );
2846         common->StartupVariable( "fs_cdpath", false );
2847         common->StartupVariable( "fs_devpath", false );
2848         common->StartupVariable( "fs_game", false );
2849         common->StartupVariable( "fs_game_base", false );
2850         common->StartupVariable( "fs_copyfiles", false );
2851         common->StartupVariable( "fs_restrict", false );
2852         common->StartupVariable( "fs_searchAddons", false );
2853
2854 #if !ID_ALLOW_D3XP
2855         if ( fs_game.GetString()[0] && !idStr::Icmp( fs_game.GetString(), "d3xp" ) ) {
2856                  fs_game.SetString( NULL );
2857         }
2858         if ( fs_game_base.GetString()[0] && !idStr::Icmp( fs_game_base.GetString(), "d3xp" ) ) {
2859                   fs_game_base.SetString( NULL );
2860         }
2861 #endif  
2862         
2863         if ( fs_basepath.GetString()[0] == '\0' ) {
2864                 fs_basepath.SetString( Sys_DefaultBasePath() );
2865         }
2866         if ( fs_savepath.GetString()[0] == '\0' ) {
2867                 fs_savepath.SetString( Sys_DefaultSavePath() );
2868         }
2869         if ( fs_cdpath.GetString()[0] == '\0' ) {
2870                 fs_cdpath.SetString( Sys_DefaultCDPath() );
2871         }
2872
2873         if ( fs_devpath.GetString()[0] == '\0' ) {
2874 #ifdef WIN32
2875                 fs_devpath.SetString( fs_cdpath.GetString()[0] ? fs_cdpath.GetString() : fs_basepath.GetString() );
2876 #else
2877                 fs_devpath.SetString( fs_savepath.GetString() );
2878 #endif
2879         }
2880
2881         // try to start up normally
2882         Startup( );
2883
2884         // see if we are going to allow add-ons
2885         SetRestrictions();
2886
2887         // spawn a thread to handle background file reads
2888         StartBackgroundDownloadThread();
2889
2890         // if we can't find default.cfg, assume that the paths are
2891         // busted and error out now, rather than getting an unreadable
2892         // graphics screen when the font fails to load
2893         // Dedicated servers can run with no outside files at all
2894         if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) {
2895                 common->FatalError( "Couldn't load default.cfg" );
2896         }
2897 }
2898
2899 /*
2900 ================
2901 idFileSystemLocal::Restart
2902 ================
2903 */
2904 void idFileSystemLocal::Restart( void ) {
2905         // free anything we currently have loaded
2906         Shutdown( true );
2907
2908         Startup( );
2909
2910         // see if we are going to allow add-ons
2911         SetRestrictions();
2912
2913         // if we can't find default.cfg, assume that the paths are
2914         // busted and error out now, rather than getting an unreadable
2915         // graphics screen when the font fails to load
2916         if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) {
2917                 common->FatalError( "Couldn't load default.cfg" );
2918         }
2919 }
2920
2921 /*
2922 ================
2923 idFileSystemLocal::Shutdown
2924
2925 Frees all resources and closes all files
2926 ================
2927 */
2928 void idFileSystemLocal::Shutdown( bool reloading ) {
2929         searchpath_t *sp, *next, *loop;
2930
2931         gameFolder.Clear();
2932
2933         serverPaks.Clear();
2934         if ( !reloading ) {
2935                 restartChecksums.Clear();
2936                 addonChecksums.Clear();
2937         }
2938         loadedFileFromDir = false;
2939         gameDLLChecksum = 0;
2940         gamePakChecksum = 0;
2941
2942         ClearDirCache();
2943
2944         // free everything - loop through searchPaths and addonPaks
2945         for ( loop = searchPaths; loop; loop == searchPaths ? loop = addonPaks : loop = NULL ) {
2946                 for ( sp = loop; sp; sp = next ) {
2947                         next = sp->next;
2948
2949                         if ( sp->pack ) {
2950                                 unzClose( sp->pack->handle );
2951                                 delete [] sp->pack->buildBuffer;
2952                                 if ( sp->pack->addon_info ) {
2953                                         sp->pack->addon_info->mapDecls.DeleteContents( true );
2954                                         delete sp->pack->addon_info;
2955                                 }
2956                                 delete sp->pack;
2957                         }
2958                         if ( sp->dir ) {
2959                                 delete sp->dir;
2960                         }
2961                         delete sp;
2962                 }
2963         }
2964
2965         // any FS_ calls will now be an error until reinitialized
2966         searchPaths = NULL;
2967         addonPaks = NULL;
2968
2969         cmdSystem->RemoveCommand( "path" );
2970         cmdSystem->RemoveCommand( "dir" );
2971         cmdSystem->RemoveCommand( "dirtree" );
2972         cmdSystem->RemoveCommand( "touchFile" );
2973
2974         mapDict.Clear();
2975 }
2976
2977 /*
2978 ================
2979 idFileSystemLocal::IsInitialized
2980 ================
2981 */
2982 bool idFileSystemLocal::IsInitialized( void ) const {
2983         return ( searchPaths != NULL );
2984 }
2985
2986
2987 /*
2988 =================================================================================
2989
2990 Opening files
2991
2992 =================================================================================
2993 */
2994
2995 /*
2996 ===========
2997 idFileSystemLocal::FileAllowedFromDir
2998 ===========
2999 */
3000 bool idFileSystemLocal::FileAllowedFromDir( const char *path ) {
3001         unsigned int l;
3002
3003         l = strlen( path );
3004
3005         if ( !strcmp( path + l - 4, ".cfg" )            // for config files
3006                 || !strcmp( path + l - 4, ".dat" )              // for journal files
3007                 || !strcmp( path + l - 4, ".dll" )              // dynamic modules are handled a different way for pure
3008                 || !strcmp( path + l - 3, ".so" )
3009                 || ( l > 6 && !strcmp( path + l - 6, ".dylib" ) )
3010                 || ( l > 10 && !strcmp( path + l - 10, ".scriptcfg" ) ) // configuration script, such as map cycle
3011 #if ID_PURE_ALLOWDDS
3012                  || !strcmp( path + l - 4, ".dds" )
3013 #endif
3014                  ) {
3015                 // note: cd and xp keys, as well as config.spec are opened through an explicit OS path and don't hit this
3016                 return true;
3017         }
3018         // savegames
3019         if ( strstr( path, "savegames" ) == path &&
3020                 ( !strcmp( path + l - 4, ".tga" ) || !strcmp( path + l -4, ".txt" ) || !strcmp( path + l - 5, ".save" ) ) ) {
3021                 return true;
3022         }
3023         // screen shots
3024         if ( strstr( path, "screenshots" ) == path && !strcmp( path + l - 4, ".tga" ) ) {
3025                 return true;
3026         }
3027         // objective tgas
3028         if ( strstr( path, "maps/game" ) == path && 
3029                 !strcmp( path + l - 4, ".tga" ) ) {
3030                 return true;
3031         }
3032         // splash screens extracted from addons
3033         if ( strstr( path, "guis/assets/splash/addon" ) == path &&
3034                  !strcmp( path + l -4, ".tga" ) ) {
3035                 return true;
3036         }
3037
3038         return false;
3039 }
3040
3041 /*
3042 ===========
3043 idFileSystemLocal::GetPackStatus
3044 ===========
3045 */
3046 pureStatus_t idFileSystemLocal::GetPackStatus( pack_t *pak ) {
3047         int                             i, l, hashindex;
3048         fileInPack_t    *file;
3049         bool                    abrt;
3050         idStr                   name;
3051
3052         if ( pak->pureStatus != PURE_UNKNOWN ) {
3053                 return pak->pureStatus;
3054         }
3055
3056         // check content for PURE_NEVER
3057         i = 0;
3058         file = pak->buildBuffer;
3059         for ( hashindex = 0; hashindex < FILE_HASH_SIZE; hashindex++ ) {
3060                 abrt = false;
3061                 file = pak->hashTable[ hashindex ];
3062                 while ( file ) {
3063                         abrt = true;
3064                         l = file->name.Length();
3065                         for ( int j = 0; pureExclusions[j].func != NULL; j++ ) {
3066                                 if ( pureExclusions[j].func( pureExclusions[j], l, file->name ) ) {
3067                                         abrt = false;
3068                                         break;
3069                                 }
3070                         }
3071                         if ( abrt ) {
3072                                 common->DPrintf( "pak '%s' candidate for pure: '%s'\n", pak->pakFilename.c_str(), file->name.c_str() );
3073                                 break;
3074                         }
3075                         file = file->next;
3076                         i++;
3077                 }
3078                 if ( abrt ) {
3079                         break;
3080                 }
3081         }
3082         if ( i == pak->numfiles ) {
3083                 pak->pureStatus = PURE_NEVER;
3084                 return PURE_NEVER;
3085         }
3086
3087         // check pak name for PURE_ALWAYS
3088         pak->pakFilename.ExtractFileName( name );
3089         if ( !name.IcmpPrefixPath( "pak" ) ) {
3090                 pak->pureStatus = PURE_ALWAYS;
3091                 return PURE_ALWAYS;
3092         }
3093
3094         pak->pureStatus = PURE_NEUTRAL;
3095         return PURE_NEUTRAL;
3096 }
3097
3098 /*
3099 ===========
3100 idFileSystemLocal::ReadFileFromZip
3101 ===========
3102 */
3103 idFile_InZip * idFileSystemLocal::ReadFileFromZip( pack_t *pak, fileInPack_t *pakFile, const char *relativePath ) {
3104         unz_s *                 zfi;
3105         FILE *                  fp;
3106         idFile_InZip *file = new idFile_InZip();
3107
3108         // open a new file on the pakfile
3109         file->z = unzReOpen( pak->pakFilename, pak->handle );
3110         if ( file->z == NULL ) {
3111                 common->FatalError( "Couldn't reopen %s", pak->pakFilename.c_str() );
3112         }
3113         file->name = relativePath;
3114         file->fullPath = pak->pakFilename + "/" + relativePath;
3115         zfi = (unz_s *)file->z;
3116         // in case the file was new
3117         fp = zfi->file;
3118         // set the file position in the zip file (also sets the current file info)
3119         unzSetCurrentFileInfoPosition( pak->handle, pakFile->pos );
3120         // copy the file info into the unzip structure
3121         memcpy( zfi, pak->handle, sizeof(unz_s) );
3122         // we copy this back into the structure
3123         zfi->file = fp;
3124         // open the file in the zip
3125         unzOpenCurrentFile( file->z );
3126         file->zipFilePos = pakFile->pos;
3127         file->fileSize = zfi->cur_file_info.uncompressed_size;
3128         return file;
3129 }
3130
3131 /*
3132 ===========
3133 idFileSystemLocal::OpenFileReadFlags
3134
3135 Finds the file in the search path, following search flag recommendations
3136 Returns filesize and an open FILE pointer.
3137 Used for streaming data out of either a
3138 separate file or a ZIP file.
3139 ===========
3140 */
3141 idFile *idFileSystemLocal::OpenFileReadFlags( const char *relativePath, int searchFlags, pack_t **foundInPak, bool allowCopyFiles, const char* gamedir ) {
3142         searchpath_t *  search;
3143         idStr                   netpath;
3144         pack_t *                pak;
3145         fileInPack_t *  pakFile;
3146         directory_t *   dir;
3147         long                    hash;
3148         FILE *                  fp;
3149         
3150         if ( !searchPaths ) {
3151                 common->FatalError( "Filesystem call made without initialization\n" );
3152         }
3153
3154         if ( !relativePath ) {
3155                 common->FatalError( "idFileSystemLocal::OpenFileRead: NULL 'relativePath' parameter passed\n" );
3156         }
3157
3158         if ( foundInPak ) {
3159                 *foundInPak = NULL;
3160         }
3161
3162         // qpaths are not supposed to have a leading slash
3163         if ( relativePath[0] == '/' || relativePath[0] == '\\' ) {
3164                 relativePath++;
3165         }
3166
3167         // make absolutely sure that it can't back up the path.
3168         // The searchpaths do guarantee that something will always
3169         // be prepended, so we don't need to worry about "c:" or "//limbo" 
3170         if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) {
3171                 return NULL;
3172         }
3173         
3174         // edge case
3175         if ( relativePath[0] == '\0' ) {
3176                 return NULL;
3177         }
3178
3179         // make sure the doomkey file is only readable by game at initialization
3180         // any other time the key should only be accessed in memory using the provided functions
3181         if( common->IsInitialized() && ( idStr::Icmp( relativePath, CDKEY_FILE ) == 0 || idStr::Icmp( relativePath, XPKEY_FILE ) == 0 ) ) {
3182                 return NULL;
3183         }
3184
3185         //
3186         // search through the path, one element at a time
3187         //
3188
3189         hash = HashFileName( relativePath );
3190
3191         for ( search = searchPaths; search; search = search->next ) {
3192                 if ( search->dir && ( searchFlags & FSFLAG_SEARCH_DIRS ) ) {
3193                         // check a file in the directory tree
3194
3195                         // if we are running restricted, the only files we
3196                         // will allow to come from the directory are .cfg files
3197                         if ( fs_restrict.GetBool() || serverPaks.Num() ) {
3198                                 if ( !FileAllowedFromDir( relativePath ) ) {
3199                                         continue;
3200                                 }
3201                         }
3202
3203                         dir = search->dir;
3204
3205                         if(gamedir && strlen(gamedir)) {
3206                                 if(dir->gamedir != gamedir) {
3207                                         continue;
3208                                 }
3209                         }
3210                         
3211                         netpath = BuildOSPath( dir->path, dir->gamedir, relativePath );
3212                         fp = OpenOSFileCorrectName( netpath, "rb" );
3213                         if ( !fp ) {
3214                                 continue;
3215                         }
3216
3217                         idFile_Permanent *file = new idFile_Permanent();
3218                         file->o = fp;
3219                         file->name = relativePath;
3220                         file->fullPath = netpath;
3221                         file->mode = ( 1 << FS_READ );
3222                         file->fileSize = DirectFileLength( file->o );
3223                         if ( fs_debug.GetInteger() ) {
3224                                 common->Printf( "idFileSystem::OpenFileRead: %s (found in '%s/%s')\n", relativePath, dir->path.c_str(), dir->gamedir.c_str() );
3225                         }
3226
3227                         if ( !loadedFileFromDir && !FileAllowedFromDir( relativePath ) ) {
3228                                 if ( restartChecksums.Num() ) {
3229                                         common->FatalError( "'%s' loaded from directory: Failed to restart with pure mode restrictions for server connect", relativePath );
3230                                 }
3231                                 common->DPrintf( "filesystem: switching to pure mode will require a restart. '%s' loaded from directory.\n", relativePath );
3232                                 loadedFileFromDir = true;
3233                         }
3234
3235                         // if fs_copyfiles is set
3236                         if ( allowCopyFiles && fs_copyfiles.GetInteger() ) {
3237
3238                                 idStr copypath;
3239                                 idStr name;
3240                                 copypath = BuildOSPath( fs_savepath.GetString(), dir->gamedir, relativePath );
3241                                 netpath.ExtractFileName( name );
3242                                 copypath.StripFilename( );
3243                                 copypath += PATHSEPERATOR_STR;
3244                                 copypath += name;
3245
3246                                 bool isFromCDPath = !dir->path.Cmp( fs_cdpath.GetString() );
3247                                 bool isFromSavePath = !dir->path.Cmp( fs_savepath.GetString() );
3248                                 bool isFromBasePath = !dir->path.Cmp( fs_basepath.GetString() );
3249
3250                                 switch ( fs_copyfiles.GetInteger() ) {
3251                                         case 1:
3252                                                 // copy from cd path only
3253                                                 if ( isFromCDPath ) {
3254                                                         CopyFile( netpath, copypath );
3255                                                 }
3256                                                 break;
3257                                         case 2:
3258                                                 // from cd path + timestamps
3259                                                 if ( isFromCDPath ) {
3260                                                         CopyFile( netpath, copypath );
3261                                                 } else if ( isFromSavePath || isFromBasePath ) {
3262                                                         idStr sourcepath;
3263                                                         sourcepath = BuildOSPath( fs_cdpath.GetString(), dir->gamedir, relativePath );
3264                                                         FILE *f1 = OpenOSFile( sourcepath, "r" );
3265                                                         if ( f1 ) {
3266                                                                 ID_TIME_T t1 = Sys_FileTimeStamp( f1 );
3267                                                                 fclose( f1 );
3268                                                                 FILE *f2 = OpenOSFile( copypath, "r" );
3269                                                                 if ( f2 ) {
3270                                                                         ID_TIME_T t2 = Sys_FileTimeStamp( f2 );
3271                                                                         fclose( f2 );
3272                                                                         if ( t1 > t2 ) {
3273                                                                                 CopyFile( sourcepath, copypath );
3274                                                                         }
3275                                                                 }
3276                                                         }
3277                                                 }
3278                                                 break;
3279                                         case 3:
3280                                                 if ( isFromCDPath || isFromBasePath ) {
3281                                                         CopyFile( netpath, copypath );
3282                                                 }
3283                                                 break;
3284                                         case 4:
3285                                                 if ( isFromCDPath && !isFromBasePath ) {
3286                                                         CopyFile( netpath, copypath );
3287                                                 }
3288                                                 break;
3289                                 }
3290                         }
3291
3292                         return file;
3293                 } else if ( search->pack && ( searchFlags & FSFLAG_SEARCH_PAKS ) ) {
3294
3295                         if ( !search->pack->hashTable[hash] ) {
3296                                 continue;
3297                         }
3298
3299                         // disregard if it doesn't match one of the allowed pure pak files
3300                         if ( serverPaks.Num() ) {
3301                                 GetPackStatus( search->pack );
3302                                 if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
3303                                         continue; // not on the pure server pak list
3304                                 }
3305                         }
3306
3307                         // look through all the pak file elements
3308                         pak = search->pack;
3309
3310                         if ( searchFlags & FSFLAG_BINARY_ONLY ) {
3311                                 // make sure this pak is tagged as a binary file
3312                                 if ( pak->binary == BINARY_UNKNOWN ) {
3313                                         int                             confHash;
3314                                         fileInPack_t    *pakFile;
3315                                         confHash = HashFileName( BINARY_CONFIG );
3316                                         pak->binary = BINARY_NO;
3317                                         for ( pakFile = search->pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) {
3318                                                 if ( !FilenameCompare( pakFile->name, BINARY_CONFIG ) ) {
3319                                                         pak->binary = BINARY_YES;
3320                                                         break;
3321                                                 }
3322                                         }
3323                                 }
3324                                 if ( pak->binary == BINARY_NO ) {
3325                                         continue; // not a binary pak, skip
3326                                 }
3327                         }
3328
3329                         for ( pakFile = pak->hashTable[hash]; pakFile; pakFile = pakFile->next ) {
3330                                 // case and separator insensitive comparisons
3331                                 if ( !FilenameCompare( pakFile->name, relativePath ) ) {
3332                                         idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath );
3333
3334                                         if ( foundInPak ) {
3335                                                 *foundInPak = pak;
3336                                         }
3337
3338                                         if ( !pak->referenced && !( searchFlags & FSFLAG_PURE_NOREF ) ) {
3339                                                 // mark this pak referenced
3340                                                 if ( fs_debug.GetInteger( ) ) {
3341                                                         common->Printf( "idFileSystem::OpenFileRead: %s -> adding %s to referenced paks\n", relativePath, pak->pakFilename.c_str() );
3342                                                 }
3343                                                 pak->referenced = true;
3344                                         }
3345
3346                                         if ( fs_debug.GetInteger( ) ) {
3347                                                 common->Printf( "idFileSystem::OpenFileRead: %s (found in '%s')\n", relativePath, pak->pakFilename.c_str() );
3348                                         }
3349                                         return file;
3350                                 }
3351                         }
3352                 }
3353         }
3354
3355         if ( searchFlags & FSFLAG_SEARCH_ADDONS ) {
3356                 for ( search = addonPaks; search; search = search->next ) {
3357                         assert( search->pack );
3358                         fileInPack_t    *pakFile;
3359                         pak = search->pack;
3360                         for ( pakFile = pak->hashTable[hash]; pakFile; pakFile = pakFile->next ) {
3361                                 if ( !FilenameCompare( pakFile->name, relativePath ) ) {
3362                                         idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath );
3363                                         if ( foundInPak ) {
3364                                                 *foundInPak = pak;
3365                                         }
3366                                         // we don't toggle pure on paks found in addons - they can't be used without a reloadEngine anyway
3367                                         if ( fs_debug.GetInteger( ) ) {
3368                                                 common->Printf( "idFileSystem::OpenFileRead: %s (found in addon pk4 '%s')\n", relativePath, search->pack->pakFilename.c_str() );
3369                                         }
3370                                         return file;
3371                                 }
3372                         }
3373                 }
3374         }
3375         
3376         if ( fs_debug.GetInteger( ) ) {
3377                 common->Printf( "Can't find %s\n", relativePath );
3378         }
3379         
3380         return NULL;
3381 }
3382
3383 /*
3384 ===========
3385 idFileSystemLocal::OpenFileRead
3386 ===========
3387 */
3388 idFile *idFileSystemLocal::OpenFileRead( const char *relativePath, bool allowCopyFiles, const char* gamedir ) {
3389         return OpenFileReadFlags( relativePath, FSFLAG_SEARCH_DIRS | FSFLAG_SEARCH_PAKS, NULL, allowCopyFiles, gamedir );
3390 }
3391
3392 /*
3393 ===========
3394 idFileSystemLocal::OpenFileWrite
3395 ===========
3396 */
3397 idFile *idFileSystemLocal::OpenFileWrite( const char *relativePath, const char *basePath ) {
3398         const char *path;
3399         idStr OSpath;
3400         idFile_Permanent *f;
3401
3402         if ( !searchPaths ) {
3403                 common->FatalError( "Filesystem call made without initialization\n" );
3404         }
3405
3406         path = cvarSystem->GetCVarString( basePath );
3407         if ( !path[0] ) {
3408                 path = fs_savepath.GetString();
3409         }
3410
3411         OSpath = BuildOSPath( path, gameFolder, relativePath );
3412
3413         if ( fs_debug.GetInteger() ) {
3414                 common->Printf( "idFileSystem::OpenFileWrite: %s\n", OSpath.c_str() );
3415         }
3416
3417         // if the dir we are writing to is in our current list, it will be outdated
3418         // so just flush everything
3419         ClearDirCache();
3420
3421         common->DPrintf( "writing to: %s\n", OSpath.c_str() );
3422         CreateOSPath( OSpath );
3423
3424         f = new idFile_Permanent();
3425         f->o = OpenOSFile( OSpath, "wb" );
3426         if ( !f->o ) {
3427                 delete f;
3428                 return NULL;
3429         }
3430         f->name = relativePath;
3431         f->fullPath = OSpath;
3432         f->mode = ( 1 << FS_WRITE );
3433         f->handleSync = false;
3434         f->fileSize = 0;
3435
3436         return f;
3437 }
3438
3439 /*
3440 ===========
3441 idFileSystemLocal::OpenExplicitFileRead
3442 ===========
3443 */
3444 idFile *idFileSystemLocal::OpenExplicitFileRead( const char *OSPath ) {
3445         idFile_Permanent *f;
3446
3447         if ( !searchPaths ) {
3448                 common->FatalError( "Filesystem call made without initialization\n" );
3449         }
3450
3451         if ( fs_debug.GetInteger() ) {
3452                 common->Printf( "idFileSystem::OpenExplicitFileRead: %s\n", OSPath );
3453         }
3454
3455         common->DPrintf( "idFileSystem::OpenExplicitFileRead - reading from: %s\n", OSPath );
3456
3457         f = new idFile_Permanent();
3458         f->o = OpenOSFile( OSPath, "rb" );
3459         if ( !f->o ) {
3460                 delete f;
3461                 return NULL;
3462         }
3463         f->name = OSPath;
3464         f->fullPath = OSPath;
3465         f->mode = ( 1 << FS_READ );
3466         f->handleSync = false;
3467         f->fileSize = DirectFileLength( f->o );
3468
3469         return f;
3470 }
3471
3472 /*
3473 ===========
3474 idFileSystemLocal::OpenExplicitFileWrite
3475 ===========
3476 */
3477 idFile *idFileSystemLocal::OpenExplicitFileWrite( const char *OSPath ) {
3478         idFile_Permanent *f;
3479
3480         if ( !searchPaths ) {
3481                 common->FatalError( "Filesystem call made without initialization\n" );
3482         }
3483
3484         if ( fs_debug.GetInteger() ) {
3485                 common->Printf( "idFileSystem::OpenExplicitFileWrite: %s\n", OSPath );
3486         }
3487
3488         common->DPrintf( "writing to: %s\n", OSPath );
3489         CreateOSPath( OSPath );
3490
3491         f = new idFile_Permanent();
3492         f->o = OpenOSFile( OSPath, "wb" );
3493         if ( !f->o ) {
3494                 delete f;
3495                 return NULL;
3496         }
3497         f->name = OSPath;
3498         f->fullPath = OSPath;
3499         f->mode = ( 1 << FS_WRITE );
3500         f->handleSync = false;
3501         f->fileSize = 0;
3502
3503         return f;
3504 }
3505
3506 /*
3507 ===========
3508 idFileSystemLocal::OpenFileAppend
3509 ===========
3510 */
3511 idFile *idFileSystemLocal::OpenFileAppend( const char *relativePath, bool sync, const char *basePath ) {
3512         const char *path;
3513         idStr OSpath;
3514         idFile_Permanent *f;
3515
3516         if ( !searchPaths ) {
3517                 common->FatalError( "Filesystem call made without initialization\n" );
3518         }
3519
3520         path = cvarSystem->GetCVarString( basePath );
3521         if ( !path[0] ) {
3522                 path = fs_savepath.GetString();
3523         }
3524
3525         OSpath = BuildOSPath( path, gameFolder, relativePath );
3526         CreateOSPath( OSpath );
3527
3528         if ( fs_debug.GetInteger() ) {
3529                 common->Printf( "idFileSystem::OpenFileAppend: %s\n", OSpath.c_str() );
3530         }
3531
3532         f = new idFile_Permanent();
3533         f->o = OpenOSFile( OSpath, "ab" );
3534         if ( !f->o ) {
3535                 delete f;
3536                 return NULL;
3537         }
3538         f->name = relativePath;
3539         f->fullPath = OSpath;
3540         f->mode = ( 1 << FS_WRITE ) + ( 1 << FS_APPEND );
3541         f->handleSync = sync;
3542         f->fileSize = DirectFileLength( f->o );
3543
3544         return f;
3545 }
3546
3547 /*
3548 ================
3549 idFileSystemLocal::OpenFileByMode
3550 ================
3551 */
3552 idFile *idFileSystemLocal::OpenFileByMode( const char *relativePath, fsMode_t mode ) {
3553         if ( mode == FS_READ ) {
3554                 return OpenFileRead( relativePath );
3555         }
3556         if ( mode == FS_WRITE ) {
3557                 return OpenFileWrite( relativePath );
3558         }
3559         if ( mode == FS_APPEND ) {
3560                 return OpenFileAppend( relativePath, true );
3561         }
3562         common->FatalError( "idFileSystemLocal::OpenFileByMode: bad mode" );
3563         return NULL;
3564 }
3565
3566 /*
3567 ==============
3568 idFileSystemLocal::CloseFile
3569 ==============
3570 */
3571 void idFileSystemLocal::CloseFile( idFile *f ) {
3572         if ( !searchPaths ) {
3573                 common->FatalError( "Filesystem call made without initialization\n" );
3574         }
3575         delete f;
3576 }
3577
3578
3579 /*
3580 =================================================================================
3581
3582 back ground loading
3583
3584 =================================================================================
3585 */
3586
3587 /*
3588 =================
3589 idFileSystemLocal::CurlWriteFunction
3590 =================
3591 */
3592 size_t idFileSystemLocal::CurlWriteFunction( void *ptr, size_t size, size_t nmemb, void *stream ) {
3593         backgroundDownload_t *bgl = (backgroundDownload_t *)stream;
3594         if ( !bgl->f ) {
3595                 return size * nmemb;
3596         }
3597         #ifdef _WIN32
3598                 return _write( static_cast<idFile_Permanent*>(bgl->f)->GetFilePtr()->_file, ptr, size * nmemb );
3599         #else
3600                 return fwrite( ptr, size, nmemb, static_cast<idFile_Permanent*>(bgl->f)->GetFilePtr() );
3601         #endif
3602 }
3603
3604 /*
3605 =================
3606 idFileSystemLocal::CurlProgressFunction
3607 =================
3608 */
3609 int idFileSystemLocal::CurlProgressFunction( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow ) {
3610         backgroundDownload_t *bgl = (backgroundDownload_t *)clientp;
3611         if ( bgl->url.status == DL_ABORTING ) {
3612                 return 1;
3613         }
3614         bgl->url.dltotal = dltotal;
3615         bgl->url.dlnow = dlnow;
3616         return 0;
3617 }
3618
3619 /*
3620 ===================
3621 BackgroundDownload
3622
3623 Reads part of a file from a background thread.
3624 ===================
3625 */
3626 dword BackgroundDownloadThread( void *parms ) {
3627         while( 1 ) {
3628                 Sys_EnterCriticalSection();
3629                 backgroundDownload_t    *bgl = fileSystemLocal.backgroundDownloads;
3630                 if ( !bgl ) {
3631                         Sys_LeaveCriticalSection();
3632                         Sys_WaitForEvent();
3633                         continue;
3634                 }
3635                 // remove this from the list
3636                 fileSystemLocal.backgroundDownloads = bgl->next;
3637                 Sys_LeaveCriticalSection();
3638
3639                 bgl->next = NULL;
3640
3641                 if ( bgl->opcode == DLTYPE_FILE ) {
3642                         // use the low level read function, because fread may allocate memory
3643                         #if defined(WIN32)
3644                                 _read( static_cast<idFile_Permanent*>(bgl->f)->GetFilePtr()->_file, bgl->file.buffer, bgl->file.length );
3645                         #else
3646                                 fread(  bgl->file.buffer, bgl->file.length, 1, static_cast<idFile_Permanent*>(bgl->f)->GetFilePtr() );
3647                         #endif
3648                         bgl->completed = true;
3649                 } else {
3650 #if ID_ENABLE_CURL
3651                         // DLTYPE_URL
3652                         // use a local buffer for curl error since the size define is local
3653                         char error_buf[ CURL_ERROR_SIZE ];
3654                         bgl->url.dlerror[ 0 ] = '\0';
3655                         CURL *session = curl_easy_init();
3656                         CURLcode ret;
3657                         if ( !session ) {
3658                                 bgl->url.dlstatus = CURLE_FAILED_INIT;
3659                                 bgl->url.status = DL_FAILED;
3660                                 bgl->completed = true;
3661                                 continue;
3662                         }
3663                         ret = curl_easy_setopt( session, CURLOPT_ERRORBUFFER, error_buf );
3664                         if ( ret ) {
3665                                 bgl->url.dlstatus = ret;
3666                                 bgl->url.status = DL_FAILED;
3667                                 bgl->completed = true;
3668                                 continue;
3669                         }
3670                         ret = curl_easy_setopt( session, CURLOPT_URL, bgl->url.url.c_str() );
3671                         if ( ret ) {
3672                                 bgl->url.dlstatus = ret;
3673                                 bgl->url.status = DL_FAILED;
3674                                 bgl->completed = true;
3675                                 continue;
3676                         }
3677                         ret = curl_easy_setopt( session, CURLOPT_FAILONERROR, 1 );
3678                         if ( ret ) {
3679                                 bgl->url.dlstatus = ret;
3680                                 bgl->url.status = DL_FAILED;
3681                                 bgl->completed = true;
3682                                 continue;
3683                         }
3684                         ret = curl_easy_setopt( session, CURLOPT_WRITEFUNCTION, idFileSystemLocal::CurlWriteFunction );
3685                         if ( ret ) {
3686                                 bgl->url.dlstatus = ret;
3687                                 bgl->url.status = DL_FAILED;
3688                                 bgl->completed = true;
3689                                 continue;
3690                         }
3691                         ret = curl_easy_setopt( session, CURLOPT_WRITEDATA, bgl );
3692                         if ( ret ) {
3693                                 bgl->url.dlstatus = ret;
3694                                 bgl->url.status = DL_FAILED;
3695                                 bgl->completed = true;
3696                                 continue;
3697                         }
3698                         ret = curl_easy_setopt( session, CURLOPT_NOPROGRESS, 0 );
3699                         if ( ret ) {
3700                                 bgl->url.dlstatus = ret;
3701                                 bgl->url.status = DL_FAILED;
3702                                 bgl->completed = true;
3703                                 continue;
3704                         }
3705                         ret = curl_easy_setopt( session, CURLOPT_PROGRESSFUNCTION, idFileSystemLocal::CurlProgressFunction );
3706                         if ( ret ) {
3707                                 bgl->url.dlstatus = ret;
3708                                 bgl->url.status = DL_FAILED;
3709                                 bgl->completed = true;
3710                                 continue;
3711                         }
3712                         ret = curl_easy_setopt( session, CURLOPT_PROGRESSDATA, bgl );
3713                         if ( ret ) {
3714                                 bgl->url.dlstatus = ret;
3715                                 bgl->url.status = DL_FAILED;
3716                                 bgl->completed = true;
3717                                 continue;
3718                         }
3719                         bgl->url.dlnow = 0;
3720                         bgl->url.dltotal = 0;
3721                         bgl->url.status = DL_INPROGRESS;
3722                         ret = curl_easy_perform( session );
3723                         if ( ret ) {
3724                                 Sys_Printf( "curl_easy_perform failed: %s\n", error_buf );
3725                                 idStr::Copynz( bgl->url.dlerror, error_buf, MAX_STRING_CHARS );
3726                                 bgl->url.dlstatus = ret;
3727                                 bgl->url.status = DL_FAILED;
3728                                 bgl->completed = true;
3729                                 continue;
3730                         }
3731                         bgl->url.status = DL_DONE;
3732                         bgl->completed = true;
3733 #else
3734                         bgl->url.status = DL_FAILED;
3735                         bgl->completed = true;
3736 #endif
3737                 }
3738         }
3739         return 0;
3740 }
3741
3742 /*
3743 =================
3744 idFileSystemLocal::StartBackgroundReadThread
3745 =================
3746 */
3747 void idFileSystemLocal::StartBackgroundDownloadThread() {
3748         if ( !backgroundThread.threadHandle ) {
3749                 Sys_CreateThread( (xthread_t)BackgroundDownloadThread, NULL, THREAD_NORMAL, backgroundThread, "backgroundDownload", g_threads, &g_thread_count );
3750                 if ( !backgroundThread.threadHandle ) {
3751                         common->Warning( "idFileSystemLocal::StartBackgroundDownloadThread: failed" );
3752                 }
3753         } else {
3754                 common->Printf( "background thread already running\n" );
3755         }
3756 }
3757
3758 /*
3759 =================
3760 idFileSystemLocal::BackgroundDownload
3761 =================
3762 */
3763 void idFileSystemLocal::BackgroundDownload( backgroundDownload_t *bgl ) {
3764         if ( bgl->opcode == DLTYPE_FILE ) {
3765                 if ( dynamic_cast<idFile_Permanent *>(bgl->f) ) {
3766                         // add the bgl to the background download list
3767                         Sys_EnterCriticalSection();
3768                         bgl->next = backgroundDownloads;
3769                         backgroundDownloads = bgl;
3770                         Sys_TriggerEvent();
3771                         Sys_LeaveCriticalSection();
3772                 } else {
3773                         // read zipped file directly
3774                         bgl->f->Seek( bgl->file.position, FS_SEEK_SET );
3775                         bgl->f->Read( bgl->file.buffer, bgl->file.length );
3776                         bgl->completed = true;
3777                 }
3778         } else {
3779                 Sys_EnterCriticalSection();
3780                 bgl->next = backgroundDownloads;
3781                 backgroundDownloads = bgl;
3782                 Sys_TriggerEvent();
3783                 Sys_LeaveCriticalSection();
3784         }
3785 }
3786
3787 /*
3788 =================
3789 idFileSystemLocal::PerformingCopyFiles
3790 =================
3791 */
3792 bool idFileSystemLocal::PerformingCopyFiles( void ) const {
3793         return fs_copyfiles.GetInteger() > 0;
3794 }
3795
3796 /*
3797 =================
3798 idFileSystemLocal::FindPakForFileChecksum
3799 =================
3800 */
3801 pack_t *idFileSystemLocal::FindPakForFileChecksum( const char *relativePath, int findChecksum, bool bReference ) {
3802         searchpath_t    *search;
3803         pack_t                  *pak;
3804         fileInPack_t    *pakFile;
3805         int                             hash;
3806         assert( !serverPaks.Num() );
3807         hash = HashFileName( relativePath );
3808         for ( search = searchPaths; search; search = search->next ) {
3809                 if ( search->pack && search->pack->hashTable[ hash ] ) {
3810                         pak = search->pack;
3811                         for ( pakFile = pak->hashTable[ hash ]; pakFile; pakFile = pakFile->next ) {
3812                                 if ( !FilenameCompare( pakFile->name, relativePath ) ) {
3813                                         idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath );
3814                                         if ( findChecksum == GetFileChecksum( file ) ) {
3815                                                 if ( fs_debug.GetBool() ) {
3816                                                         common->Printf( "found '%s' with checksum 0x%x in pak '%s'\n", relativePath, findChecksum, pak->pakFilename.c_str() );
3817                                                 }
3818                                                 if ( bReference ) {
3819                                                         pak->referenced = true;
3820                                                         // FIXME: use dependencies for pak references
3821                                                 }
3822                                                 CloseFile( file );
3823                                                 return pak;
3824                                         } else if ( fs_debug.GetBool() ) {
3825                                                 common->Printf( "'%s' in pak '%s' has != checksum %x\n", relativePath, pak->pakFilename.c_str(), GetFileChecksum( file ) );
3826                                         }
3827                                         CloseFile( file );
3828                                 }
3829                         }
3830                 }
3831         }
3832         if ( fs_debug.GetBool() ) {
3833                 common->Printf( "no pak file found for '%s' checksumed %x\n", relativePath, findChecksum );
3834         }
3835         return NULL;
3836 }
3837
3838 /*
3839 =================
3840 idFileSystemLocal::GetFileChecksum
3841 =================
3842 */
3843 int idFileSystemLocal::GetFileChecksum( idFile *file ) {
3844         int len, ret;
3845         byte *buf;
3846
3847         file->Seek( 0, FS_SEEK_END );
3848         len = file->Tell();
3849         file->Seek( 0, FS_SEEK_SET );
3850         buf = (byte *)Mem_Alloc( len );
3851         if ( file->Read( buf, len ) != len ) {
3852                 common->FatalError( "Short read in idFileSystemLocal::GetFileChecksum()\n" );
3853         }
3854         ret = MD4_BlockChecksum( buf, len );
3855         Mem_Free( buf );
3856         return ret;
3857 }
3858
3859 /*
3860 =================
3861 idFileSystemLocal::FindDLL
3862 =================
3863 */
3864 void idFileSystemLocal::FindDLL( const char *name, char _dllPath[ MAX_OSPATH ], bool updateChecksum ) {
3865         idFile                  *dllFile = NULL;
3866         char                    dllName[MAX_OSPATH];
3867         idStr                   dllPath;
3868         int                             dllHash;
3869         pack_t                  *inPak;
3870         pack_t                  *pak;
3871         fileInPack_t    *pakFile;       
3872
3873         sys->DLL_GetFileName( name, dllName, MAX_OSPATH );
3874         dllHash = HashFileName( dllName );
3875
3876 #if ID_FAKE_PURE
3877         if ( 1 ) {
3878 #else
3879         if ( !serverPaks.Num() ) {
3880 #endif
3881                 // from executable directory first - this is handy for developement
3882                 dllPath = Sys_EXEPath( );
3883                 dllPath.StripFilename( );
3884                 dllPath.AppendPath( dllName );
3885                 dllFile = OpenExplicitFileRead( dllPath );
3886         }
3887         if ( !dllFile ) {
3888                 if ( !serverPaks.Num() ) {
3889                         // not running in pure mode, try to extract from a pak file first
3890                         dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_PAKS | FSFLAG_PURE_NOREF | FSFLAG_BINARY_ONLY, &inPak );
3891                         if ( dllFile ) {
3892                                 common->Printf( "found DLL in pak file: %s\n", dllFile->GetFullPath() );
3893                                 dllPath = RelativePathToOSPath( dllName, "fs_savepath" );
3894                                 CopyFile( dllFile, dllPath );
3895                                 CloseFile( dllFile );
3896                                 dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS );
3897                                 if ( !dllFile ) {
3898                                         common->Error( "DLL extraction to fs_savepath failed\n" );
3899                                 } else if ( updateChecksum ) {
3900                                         gameDLLChecksum = GetFileChecksum( dllFile );
3901                                         gamePakChecksum = inPak->checksum;
3902                                         updateChecksum = false; // don't try again below
3903                                 }
3904                         } else {
3905                                 // didn't find a source in a pak file, try in the directory
3906                                 dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS );
3907                                 if ( dllFile ) {
3908                                         if ( updateChecksum ) {
3909                                                 gameDLLChecksum = GetFileChecksum( dllFile );
3910                                                 // see if we can mark a pak file
3911                                                 pak = FindPakForFileChecksum( dllName, gameDLLChecksum, false );
3912                                                 pak ? gamePakChecksum = pak->checksum : gamePakChecksum = 0;
3913                                                 updateChecksum = false;
3914                                         }
3915                                 }
3916                         }
3917                 } else {
3918                         // we are in pure mode. this path to be reached only for game DLL situations
3919                         // with a code pak checksum given by server
3920                         assert( gamePakChecksum );
3921                         assert( updateChecksum );
3922                         pak = GetPackForChecksum( gamePakChecksum );
3923                         if ( !pak ) {
3924                                 // not supposed to happen, bug in pure code?
3925                                 common->Warning( "FindDLL in pure mode: game pak not found ( 0x%x )\n", gamePakChecksum );
3926                         } else {
3927                                 // extract and copy
3928                                 for ( pakFile = pak->hashTable[dllHash]; pakFile; pakFile = pakFile->next ) {
3929                                         if ( !FilenameCompare( pakFile->name, dllName ) ) {
3930                                                 dllFile = ReadFileFromZip( pak, pakFile, dllName );
3931                                                 common->Printf( "found DLL in game pak file: %s\n", pak->pakFilename.c_str() );
3932                                                 dllPath = RelativePathToOSPath( dllName, "fs_savepath" );
3933                                                 CopyFile( dllFile, dllPath );
3934                                                 CloseFile( dllFile );
3935                                                 dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS );
3936                                                 if ( !dllFile ) {
3937                                                         common->Error( "DLL extraction to fs_savepath failed\n" );
3938                                                 } else {
3939                                                         gameDLLChecksum = GetFileChecksum( dllFile );
3940                                                         updateChecksum = false; // don't try again below
3941                                                 }                                               
3942                                         }
3943                                 }
3944                         }
3945                 }
3946         }
3947         if ( updateChecksum ) {
3948                 if ( dllFile ) {
3949                         gameDLLChecksum = GetFileChecksum( dllFile );
3950                 } else {
3951                         gameDLLChecksum = 0;
3952                 }
3953                 gamePakChecksum = 0;
3954         }
3955         if ( dllFile ) {
3956                 dllPath = dllFile->GetFullPath( );
3957                 CloseFile( dllFile );
3958                 dllFile = NULL;
3959         } else {
3960                 dllPath = "";
3961         }
3962         idStr::snPrintf( _dllPath, MAX_OSPATH, dllPath.c_str() );
3963 }
3964
3965 /*
3966 ================
3967 idFileSystemLocal::ClearDirCache
3968 ================
3969 */
3970 void idFileSystemLocal::ClearDirCache( void ) {
3971         int i;
3972
3973         dir_cache_index = 0;
3974         dir_cache_count = 0;
3975         for( i = 0; i < MAX_CACHED_DIRS; i++ ) {
3976                 dir_cache[ i ].Clear();
3977         }
3978 }
3979
3980 /*
3981 ===============
3982 idFileSystemLocal::HasD3XP
3983 ===============
3984 */
3985 bool idFileSystemLocal::HasD3XP( void ) {
3986         int                     i;
3987         idStrList       dirs, pk4s;
3988         idStr           gamepath;
3989
3990         if ( d3xp == -1 ) {
3991                 return false;
3992         } else if ( d3xp == 1 ) {
3993                 return true;
3994         }
3995         
3996 #if 0
3997         // check for a d3xp directory with a pk4 file
3998         // copied over from ListMods - only looks in basepath
3999         ListOSFiles( fs_basepath.GetString(), "/", dirs );
4000         for ( i = 0; i < dirs.Num(); i++ ) {
4001                 if ( dirs[i].Icmp( "d3xp" ) == 0 ) {
4002                         gamepath = BuildOSPath( fs_basepath.GetString(), dirs[ i ], "" );
4003                         ListOSFiles( gamepath, ".pk4", pk4s );
4004                         if ( pk4s.Num() ) {
4005                                 d3xp = 1;
4006                                 return true;
4007                         }
4008                 }
4009         }
4010 #elif ID_ALLOW_D3XP
4011         // check for d3xp's d3xp/pak000.pk4 in any search path
4012         // checking wether the pak is loaded by checksum wouldn't be enough:
4013         // we may have a different fs_game right now but still need to reply that it's installed
4014         const char      *search[4];
4015         idFile          *pakfile;
4016         search[0] = fs_savepath.GetString();
4017         search[1] = fs_devpath.GetString();
4018         search[2] = fs_basepath.GetString();
4019         search[3] = fs_cdpath.GetString();
4020         for ( i = 0; i < 4; i++ ) {
4021                 pakfile = OpenExplicitFileRead( BuildOSPath( search[ i ], "d3xp", "pak000.pk4" ) );
4022                 if ( pakfile ) {
4023                         CloseFile( pakfile );
4024                         d3xp = 1;
4025                         return true;
4026                 }
4027         }
4028 #endif
4029
4030 #if ID_ALLOW_D3XP
4031         // if we didn't find a pk4 file then the user might have unpacked so look for default.cfg file
4032         // that's the old way mostly used during developement. don't think it hurts to leave it there
4033         ListOSFiles( fs_basepath.GetString(), "/", dirs );
4034         for ( i = 0; i < dirs.Num(); i++ ) {
4035                 if ( dirs[i].Icmp( "d3xp" ) == 0 ) {
4036                         
4037                         gamepath = BuildOSPath( fs_savepath.GetString(), dirs[ i ], "default.cfg" );
4038                         idFile* cfg = OpenExplicitFileRead(gamepath);
4039                         if(cfg) {
4040                                 CloseFile(cfg);
4041                                 d3xp = 1;
4042                                 return true;
4043                         }
4044                 }
4045         }
4046 #endif
4047         d3xp = -1;
4048         return false;
4049 }
4050
4051 /*
4052 ===============
4053 idFileSystemLocal::RunningD3XP
4054 ===============
4055 */
4056 bool idFileSystemLocal::RunningD3XP( void ) {
4057         // TODO: mark the checksum of the gold XP and check for it being referenced ( for double mod support )
4058         // a simple fs_game check should be enough for now..
4059         if ( !idStr::Icmp( fs_game.GetString(), "d3xp" ) ||
4060                  !idStr::Icmp( fs_game_base.GetString(), "d3xp" ) ) {
4061                 return true;
4062         }
4063         return false;
4064 }
4065
4066 /*
4067 ===============
4068 idFileSystemLocal::MakeTemporaryFile
4069 ===============
4070 */
4071 idFile * idFileSystemLocal::MakeTemporaryFile( void ) {
4072         FILE *f = tmpfile();
4073         if ( !f ) {
4074                 common->Warning( "idFileSystem::MakeTemporaryFile failed: %s", strerror( errno ) );
4075                 return NULL;
4076         }
4077         idFile_Permanent *file = new idFile_Permanent();
4078         file->o = f;
4079         file->name = "<tempfile>";
4080         file->fullPath = "<tempfile>";
4081         file->mode = ( 1 << FS_READ ) + ( 1 << FS_WRITE );
4082         file->fileSize = 0;
4083         return file;
4084 }
4085
4086 /*
4087 ===============
4088 idFileSystemLocal::FindFile
4089 ===============
4090 */
4091  findFile_t idFileSystemLocal::FindFile( const char *path, bool scheduleAddons ) {
4092         pack_t *pak;
4093         idFile *f = OpenFileReadFlags( path, FSFLAG_SEARCH_DIRS | FSFLAG_SEARCH_PAKS | FSFLAG_SEARCH_ADDONS, &pak );
4094         if ( !f ) {
4095                 return FIND_NO;
4096         }
4097         if ( !pak ) {
4098                 // found in FS, not even in paks
4099                 return FIND_YES;
4100         }
4101         // marking addons for inclusion on reload - may need to do that even when already in the search path
4102         if ( scheduleAddons && pak->addon && addonChecksums.FindIndex( pak->checksum ) < 0 ) {
4103                 addonChecksums.Append( pak->checksum );                 
4104         }
4105         // an addon that's not on search list yet? that will require a restart
4106         if ( pak->addon && !pak->addon_search ) {
4107                 delete f;
4108                 return FIND_ADDON;
4109         }
4110         delete f;
4111         return FIND_YES;
4112 }
4113
4114 /*
4115 ===============
4116 idFileSystemLocal::GetNumMaps
4117 account for actual decls and for addon maps
4118 ===============
4119 */
4120 int idFileSystemLocal::GetNumMaps() {
4121         int                             i;
4122         searchpath_t    *search = NULL;
4123         int                             ret = declManager->GetNumDecls( DECL_MAPDEF );
4124         
4125         // add to this all addon decls - coming from all addon packs ( searched or not )
4126         for ( i = 0; i < 2; i++ ) {
4127                 if ( i == 0 ) {
4128                         search = searchPaths;
4129                 } else if ( i == 1 ) {
4130                         search = addonPaks;
4131                 }
4132                 for ( ; search ; search = search->next ) {
4133                         if ( !search->pack || !search->pack->addon || !search->pack->addon_info ) {
4134                                 continue;
4135                         }
4136                         ret += search->pack->addon_info->mapDecls.Num();
4137                 }
4138         }
4139         return ret;
4140 }
4141
4142 /*
4143 ===============
4144 idFileSystemLocal::GetMapDecl
4145 retrieve the decl dictionary, add a 'path' value
4146 ===============
4147 */
4148 const idDict * idFileSystemLocal::GetMapDecl( int idecl ) {
4149         int                                     i;
4150         const idDecl                    *mapDecl;
4151         const idDeclEntityDef   *mapDef;
4152         int                                             numdecls = declManager->GetNumDecls( DECL_MAPDEF );
4153         searchpath_t                    *search = NULL;
4154         
4155         if ( idecl < numdecls ) {
4156                 mapDecl = declManager->DeclByIndex( DECL_MAPDEF, idecl );
4157                 mapDef = static_cast<const idDeclEntityDef *>( mapDecl );
4158                 if ( !mapDef ) {
4159                         common->Error( "idFileSystemLocal::GetMapDecl %d: not found\n", idecl );
4160                 }
4161                 mapDict = mapDef->dict;
4162                 mapDict.Set( "path", mapDef->GetName() );
4163                 return &mapDict;
4164         }
4165         idecl -= numdecls;
4166         for ( i = 0; i < 2; i++ ) {
4167                 if ( i == 0 ) {
4168                         search = searchPaths;
4169                 } else if ( i == 1 ) {
4170                         search = addonPaks;
4171                 }
4172                 for ( ; search ; search = search->next ) {
4173                         if ( !search->pack || !search->pack->addon || !search->pack->addon_info ) {
4174                                 continue;
4175                         }
4176                         // each addon may have a bunch of map decls
4177                         if ( idecl < search->pack->addon_info->mapDecls.Num() ) {
4178                                 mapDict = *search->pack->addon_info->mapDecls[ idecl ];
4179                                 return &mapDict;
4180                         }
4181                         idecl -= search->pack->addon_info->mapDecls.Num();
4182                         assert( idecl >= 0 );
4183                 }
4184         }
4185         return NULL;
4186 }
4187
4188 /*
4189 ===============
4190 idFileSystemLocal::FindMapScreenshot
4191 ===============
4192 */
4193 void idFileSystemLocal::FindMapScreenshot( const char *path, char *buf, int len ) {
4194         idFile  *file;
4195         idStr   mapname = path;
4196
4197         mapname.StripPath();
4198         mapname.StripFileExtension();
4199         
4200         idStr::snPrintf( buf, len, "guis/assets/splash/%s.tga", mapname.c_str() );
4201         if ( ReadFile( buf, NULL, NULL ) == -1 ) {
4202                 // try to extract from an addon
4203                 file = OpenFileReadFlags( buf, FSFLAG_SEARCH_ADDONS );
4204                 if ( file ) {
4205                         // save it out to an addon splash directory
4206                         int dlen = file->Length();
4207                         char *data = new char[ dlen ];
4208                         file->Read( data, dlen );
4209                         CloseFile( file );
4210                         idStr::snPrintf( buf, len, "guis/assets/splash/addon/%s.tga", mapname.c_str() );
4211                         WriteFile( buf, data, dlen );
4212                         delete[] data;
4213                 } else {
4214                         idStr::Copynz( buf, "guis/assets/splash/pdtempa", len );
4215                 }
4216         }
4217 }