PageRenderTime 117ms CodeModel.GetById 55ms RepoModel.GetById 1ms app.codeStats 0ms

/neo/framework/FileSystem.cpp

https://bitbucket.org/thayamizu/doom-3
C++ | 4217 lines | 3104 code | 354 blank | 759 comment | 661 complexity | 88d6c3609e62929b425374411469ad52 MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause, Unlicense, CC0-1.0, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. ===========================================================================
  3. Doom 3 GPL Source Code
  4. Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
  6. Doom 3 Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. 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.
  17. 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.
  18. ===========================================================================
  19. */
  20. #include "../idlib/precompiled.h"
  21. #pragma hdrstop
  22. #include "Unzip.h"
  23. #ifdef WIN32
  24. #include <io.h> // for _read
  25. #else
  26. #if !__MACH__ && __MWERKS__
  27. #include <types.h>
  28. #include <stat.h>
  29. #else
  30. #include <sys/types.h>
  31. #include <sys/stat.h>
  32. #endif
  33. #include <unistd.h>
  34. #endif
  35. #if ID_ENABLE_CURL
  36. #include "../curl/include/curl/curl.h"
  37. #endif
  38. /*
  39. =============================================================================
  40. DOOM FILESYSTEM
  41. All of Doom's data access is through a hierarchical file system, but the contents of
  42. the file system can be transparently merged from several sources.
  43. A "relativePath" is a reference to game file data, which must include a terminating zero.
  44. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any references
  45. outside the Doom directory system.
  46. The "base path" is the path to the directory holding all the game directories and
  47. usually the executable. It defaults to the current directory, but can be overridden
  48. with "+set fs_basepath c:\doom" on the command line. The base path cannot be modified
  49. at all after startup.
  50. The "save path" is the path to the directory where game files will be saved. It defaults
  51. to the base path, but can be overridden with a "+set fs_savepath c:\doom" on the
  52. command line. Any files that are created during the game (demos, screenshots, etc.) will
  53. be created reletive to the save path.
  54. The "cd path" is the path to an alternate hierarchy that will be searched if a file
  55. is not located in the base path. A user can do a partial install that copies some
  56. data to a base path created on their hard drive and leave the rest on the cd. It defaults
  57. to the current directory, but it can be overridden with "+set fs_cdpath g:\doom" on the
  58. command line.
  59. The "dev path" is the path to an alternate hierarchy where the editors and tools used
  60. during development (Radiant, AF editor, dmap, runAAS) will write files to. It defaults to
  61. the cd path, but can be overridden with a "+set fs_devpath c:\doom" on the command line.
  62. If a user runs the game directly from a CD, the base path would be on the CD. This
  63. should still function correctly, but all file writes will fail (harmlessly).
  64. The "base game" is the directory under the paths where data comes from by default, and
  65. can be either "base" or "demo".
  66. The "current game" may be the same as the base game, or it may be the name of another
  67. directory under the paths that should be searched for files before looking in the base
  68. game. The game directory is set with "+set fs_game myaddon" on the command line. This is
  69. the basis for addons.
  70. No other directories outside of the base game and current game will ever be referenced by
  71. filesystem functions.
  72. To save disk space and speed up file loading, directory trees can be collapsed into zip
  73. files. The files use a ".pk4" extension to prevent users from unzipping them accidentally,
  74. but otherwise they are simply normal zip files. A game directory can have multiple zip
  75. files of the form "pak0.pk4", "pak1.pk4", etc. Zip files are searched in decending order
  76. from the highest number to the lowest, and will always take precedence over the filesystem.
  77. This allows a pk4 distributed as a patch to override all existing data.
  78. Because we will have updated executables freely available online, there is no point to
  79. trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
  80. should be exactly the same executables as release versions, but with different data that
  81. automatically restricts where game media can come from to prevent add-ons from working.
  82. After the paths are initialized, Doom will look for the product.txt file. If not found
  83. and verified, the game will run in restricted mode. In restricted mode, only files
  84. contained in demo/pak0.pk4 will be available for loading, and only if the zip header is
  85. verified to not have been modified. A single exception is made for DoomConfig.cfg. Files
  86. can still be written out in restricted mode, so screenshots and demos are allowed.
  87. Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
  88. if there is a valid product.txt under the basepath or cdpath.
  89. If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
  90. path, it will be copied over to the save path. This is a development aid to help build
  91. test releases and to copy working sets of files.
  92. If the "fs_copyfiles" cvar is set to 2, any file found in fs_cdpath that is newer than
  93. it's fs_savepath version will be copied to fs_savepath (in addition to the fs_copyfiles 1
  94. behaviour).
  95. If the "fs_copyfiles" cvar is set to 3, files from both basepath and cdpath will be copied
  96. over to the save path. This is useful when copying working sets of files mainly from base
  97. path with an additional cd path (which can be a slower network drive for instance).
  98. If the "fs_copyfiles" cvar is set to 4, files that exist in the cd path but NOT the base path
  99. will be copied to the save path
  100. NOTE: fs_copyfiles and case sensitivity. On fs_caseSensitiveOS 0 filesystems ( win32 ), the
  101. copied files may change casing when copied over.
  102. The relative path "sound/newstuff/test.wav" would be searched for in the following places:
  103. for save path, dev path, base path, cd path:
  104. for current game, base game:
  105. search directory
  106. search zip files
  107. downloaded files, to be written to save path + current game's directory
  108. The filesystem can be safely shutdown and reinitialized with different
  109. basedir / cddir / game combinations, but all other subsystems that rely on it
  110. (sound, video) must also be forced to restart.
  111. "fs_caseSensitiveOS":
  112. This cvar is set on operating systems that use case sensitive filesystems (Linux and OSX)
  113. It is a common situation to have the media reference filenames, whereas the file on disc
  114. only matches in a case-insensitive way. When "fs_caseSensitiveOS" is set, the filesystem
  115. will always do a case insensitive search.
  116. IMPORTANT: This only applies to files, and not to directories. There is no case-insensitive
  117. matching of directories. All directory names should be lowercase, when "com_developer" is 1,
  118. the filesystem will warn when it catches bad directory situations (regardless of the
  119. "fs_caseSensitiveOS" setting)
  120. When bad casing in directories happen and "fs_caseSensitiveOS" is set, BuildOSPath will
  121. attempt to correct the situation by forcing the path to lowercase. This assumes the media
  122. is stored all lowercase.
  123. "additional mod path search":
  124. fs_game_base can be used to set an additional search path
  125. in search order, fs_game, fs_game_base, BASEGAME
  126. for instance to base a mod of D3 + D3XP assets, fs_game mymod, fs_game_base d3xp
  127. =============================================================================
  128. */
  129. // define to fix special-cases for GetPackStatus so that files that shipped in
  130. // the wrong place for Doom 3 don't break pure servers.
  131. #define DOOM3_PURE_SPECIAL_CASES
  132. typedef bool (*pureExclusionFunc_t)( const struct pureExclusion_s &excl, int l, const idStr &name );
  133. typedef struct pureExclusion_s {
  134. int nameLen;
  135. int extLen;
  136. const char * name;
  137. const char * ext;
  138. pureExclusionFunc_t func;
  139. } pureExclusion_t;
  140. bool excludeExtension( const pureExclusion_t &excl, int l, const idStr &name ) {
  141. if ( l > excl.extLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) ) {
  142. return true;
  143. }
  144. return false;
  145. }
  146. bool excludePathPrefixAndExtension( const pureExclusion_t &excl, int l, const idStr &name ) {
  147. if ( l > excl.nameLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) && !name.IcmpPrefixPath( excl.name ) ) {
  148. return true;
  149. }
  150. return false;
  151. }
  152. bool excludeFullName( const pureExclusion_t &excl, int l, const idStr &name ) {
  153. if ( l == excl.nameLen && !name.Icmp( excl.name ) ) {
  154. return true;
  155. }
  156. return false;
  157. }
  158. static pureExclusion_t pureExclusions[] = {
  159. { 0, 0, NULL, "/", excludeExtension },
  160. { 0, 0, NULL, "\\", excludeExtension },
  161. { 0, 0, NULL, ".pda", excludeExtension },
  162. { 0, 0, NULL, ".gui", excludeExtension },
  163. { 0, 0, NULL, ".pd", excludeExtension },
  164. { 0, 0, NULL, ".lang", excludeExtension },
  165. { 0, 0, "sound/VO", ".ogg", excludePathPrefixAndExtension },
  166. { 0, 0, "sound/VO", ".wav", excludePathPrefixAndExtension },
  167. #if defined DOOM3_PURE_SPECIAL_CASES
  168. // add any special-case files or paths for pure servers here
  169. { 0, 0, "sound/ed/marscity/vo_intro_cutscene.ogg", NULL, excludeFullName },
  170. { 0, 0, "sound/weapons/soulcube/energize_01.ogg", NULL, excludeFullName },
  171. { 0, 0, "sound/xian/creepy/vocal_fx", ".ogg", excludePathPrefixAndExtension },
  172. { 0, 0, "sound/xian/creepy/vocal_fx", ".wav", excludePathPrefixAndExtension },
  173. { 0, 0, "sound/feedback", ".ogg", excludePathPrefixAndExtension },
  174. { 0, 0, "sound/feedback", ".wav", excludePathPrefixAndExtension },
  175. { 0, 0, "guis/assets/mainmenu/chnote.tga", NULL, excludeFullName },
  176. { 0, 0, "sound/levels/alphalabs2/uac_better_place.ogg", NULL, excludeFullName },
  177. { 0, 0, "textures/bigchars.tga", NULL, excludeFullName },
  178. { 0, 0, "dds/textures/bigchars.dds", NULL, excludeFullName },
  179. { 0, 0, "fonts", ".tga", excludePathPrefixAndExtension },
  180. { 0, 0, "dds/fonts", ".dds", excludePathPrefixAndExtension },
  181. { 0, 0, "default.cfg", NULL, excludeFullName },
  182. // russian zpak001.pk4
  183. { 0, 0, "fonts", ".dat", excludePathPrefixAndExtension },
  184. { 0, 0, "guis/temp.guied", NULL, excludeFullName },
  185. #endif
  186. { 0, 0, NULL, NULL, NULL }
  187. };
  188. // ensures that lengths for pure exclusions are correct
  189. class idInitExclusions {
  190. public:
  191. idInitExclusions() {
  192. for ( int i = 0; pureExclusions[i].func != NULL; i++ ) {
  193. if ( pureExclusions[i].name ) {
  194. pureExclusions[i].nameLen = idStr::Length( pureExclusions[i].name );
  195. }
  196. if ( pureExclusions[i].ext ) {
  197. pureExclusions[i].extLen = idStr::Length( pureExclusions[i].ext );
  198. }
  199. }
  200. }
  201. };
  202. static idInitExclusions initExclusions;
  203. #define MAX_ZIPPED_FILE_NAME 2048
  204. #define FILE_HASH_SIZE 1024
  205. typedef struct fileInPack_s {
  206. idStr name; // name of the file
  207. unsigned long pos; // file info position in zip
  208. struct fileInPack_s * next; // next file in the hash
  209. } fileInPack_t;
  210. typedef enum {
  211. BINARY_UNKNOWN = 0,
  212. BINARY_YES,
  213. BINARY_NO
  214. } binaryStatus_t;
  215. typedef enum {
  216. PURE_UNKNOWN = 0, // need to run the pak through GetPackStatus
  217. PURE_NEUTRAL, // neutral regarding pureness. gets in the pure list if referenced
  218. PURE_ALWAYS, // always referenced - for pak* named files, unless NEVER
  219. PURE_NEVER // VO paks. may be referenced, won't be in the pure lists
  220. } pureStatus_t;
  221. typedef struct {
  222. idList<int> depends;
  223. idList<idDict *> mapDecls;
  224. } addonInfo_t;
  225. typedef struct {
  226. idStr pakFilename; // c:\doom\base\pak0.pk4
  227. unzFile handle;
  228. int checksum;
  229. int numfiles;
  230. int length;
  231. bool referenced;
  232. binaryStatus_t binary;
  233. bool addon; // this is an addon pack - addon_search tells if it's 'active'
  234. bool addon_search; // is in the search list
  235. addonInfo_t *addon_info;
  236. pureStatus_t pureStatus;
  237. bool isNew; // for downloaded paks
  238. fileInPack_t *hashTable[FILE_HASH_SIZE];
  239. fileInPack_t *buildBuffer;
  240. } pack_t;
  241. typedef struct {
  242. idStr path; // c:\doom
  243. idStr gamedir; // base
  244. } directory_t;
  245. typedef struct searchpath_s {
  246. pack_t * pack; // only one of pack / dir will be non NULL
  247. directory_t * dir;
  248. struct searchpath_s *next;
  249. } searchpath_t;
  250. // search flags when opening a file
  251. #define FSFLAG_SEARCH_DIRS ( 1 << 0 )
  252. #define FSFLAG_SEARCH_PAKS ( 1 << 1 )
  253. #define FSFLAG_PURE_NOREF ( 1 << 2 )
  254. #define FSFLAG_BINARY_ONLY ( 1 << 3 )
  255. #define FSFLAG_SEARCH_ADDONS ( 1 << 4 )
  256. // 3 search path (fs_savepath fs_basepath fs_cdpath)
  257. // + .jpg and .tga
  258. #define MAX_CACHED_DIRS 6
  259. // how many OSes to handle game paks for ( we don't have to know them precisely )
  260. #define MAX_GAME_OS 6
  261. #define BINARY_CONFIG "binary.conf"
  262. #define ADDON_CONFIG "addon.conf"
  263. class idDEntry : public idStrList {
  264. public:
  265. idDEntry() {}
  266. virtual ~idDEntry() {}
  267. bool Matches( const char *directory, const char *extension ) const;
  268. void Init( const char *directory, const char *extension, const idStrList &list );
  269. void Clear( void );
  270. private:
  271. idStr directory;
  272. idStr extension;
  273. };
  274. class idFileSystemLocal : public idFileSystem {
  275. public:
  276. idFileSystemLocal( void );
  277. virtual void Init( void );
  278. virtual void StartBackgroundDownloadThread( void );
  279. virtual void Restart( void );
  280. virtual void Shutdown( bool reloading );
  281. virtual bool IsInitialized( void ) const;
  282. virtual bool PerformingCopyFiles( void ) const;
  283. virtual idModList * ListMods( void );
  284. virtual void FreeModList( idModList *modList );
  285. virtual idFileList * ListFiles( const char *relativePath, const char *extension, bool sort = false, bool fullRelativePath = false, const char* gamedir = NULL );
  286. virtual idFileList * ListFilesTree( const char *relativePath, const char *extension, bool sort = false, const char* gamedir = NULL );
  287. virtual void FreeFileList( idFileList *fileList );
  288. virtual const char * OSPathToRelativePath( const char *OSPath );
  289. virtual const char * RelativePathToOSPath( const char *relativePath, const char *basePath );
  290. virtual const char * BuildOSPath( const char *base, const char *game, const char *relativePath );
  291. virtual void CreateOSPath( const char *OSPath );
  292. virtual bool FileIsInPAK( const char *relativePath );
  293. virtual void UpdatePureServerChecksums( void );
  294. virtual bool UpdateGamePakChecksums( void );
  295. virtual fsPureReply_t SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum );
  296. virtual void GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int *gamePakChecksum );
  297. virtual void SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum );
  298. virtual void ClearPureChecksums( void );
  299. virtual int GetOSMask( void );
  300. virtual int ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp );
  301. virtual void FreeFile( void *buffer );
  302. virtual int WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" );
  303. virtual void RemoveFile( const char *relativePath );
  304. virtual idFile * OpenFileReadFlags( const char *relativePath, int searchFlags, pack_t **foundInPak = NULL, bool allowCopyFiles = true, const char* gamedir = NULL );
  305. virtual idFile * OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL );
  306. virtual idFile * OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath" );
  307. virtual idFile * OpenFileAppend( const char *relativePath, bool sync = false, const char *basePath = "fs_basepath" );
  308. virtual idFile * OpenFileByMode( const char *relativePath, fsMode_t mode );
  309. virtual idFile * OpenExplicitFileRead( const char *OSPath );
  310. virtual idFile * OpenExplicitFileWrite( const char *OSPath );
  311. virtual void CloseFile( idFile *f );
  312. virtual void BackgroundDownload( backgroundDownload_t *bgl );
  313. virtual void ResetReadCount( void ) { readCount = 0; }
  314. virtual void AddToReadCount( int c ) { readCount += c; }
  315. virtual int GetReadCount( void ) { return readCount; }
  316. virtual void FindDLL( const char *basename, char dllPath[ MAX_OSPATH ], bool updateChecksum );
  317. virtual void ClearDirCache( void );
  318. virtual bool HasD3XP( void );
  319. virtual bool RunningD3XP( void );
  320. virtual void CopyFile( const char *fromOSPath, const char *toOSPath );
  321. virtual int ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isBinary );
  322. virtual idFile * MakeTemporaryFile( void );
  323. virtual int AddZipFile( const char *path );
  324. virtual findFile_t FindFile( const char *path, bool scheduleAddons );
  325. virtual int GetNumMaps();
  326. virtual const idDict * GetMapDecl( int i );
  327. virtual void FindMapScreenshot( const char *path, char *buf, int len );
  328. virtual bool FilenameCompare( const char *s1, const char *s2 ) const;
  329. static void Dir_f( const idCmdArgs &args );
  330. static void DirTree_f( const idCmdArgs &args );
  331. static void Path_f( const idCmdArgs &args );
  332. static void TouchFile_f( const idCmdArgs &args );
  333. static void TouchFileList_f( const idCmdArgs &args );
  334. private:
  335. friend dword BackgroundDownloadThread( void *parms );
  336. searchpath_t * searchPaths;
  337. int readCount; // total bytes read
  338. int loadCount; // total files read
  339. int loadStack; // total files in memory
  340. idStr gameFolder; // this will be a single name without separators
  341. searchpath_t *addonPaks; // not loaded up, but we saw them
  342. idDict mapDict; // for GetMapDecl
  343. static idCVar fs_debug;
  344. static idCVar fs_restrict;
  345. static idCVar fs_copyfiles;
  346. static idCVar fs_basepath;
  347. static idCVar fs_savepath;
  348. static idCVar fs_cdpath;
  349. static idCVar fs_devpath;
  350. static idCVar fs_game;
  351. static idCVar fs_game_base;
  352. static idCVar fs_caseSensitiveOS;
  353. static idCVar fs_searchAddons;
  354. backgroundDownload_t * backgroundDownloads;
  355. backgroundDownload_t defaultBackgroundDownload;
  356. xthreadInfo backgroundThread;
  357. idList<pack_t *> serverPaks;
  358. bool loadedFileFromDir; // set to true once a file was loaded from a directory - can't switch to pure anymore
  359. idList<int> restartChecksums; // used during a restart to set things in right order
  360. idList<int> addonChecksums; // list of checksums that should go to the search list directly ( for restarts )
  361. int restartGamePakChecksum;
  362. int gameDLLChecksum; // the checksum of the last loaded game DLL
  363. int gamePakChecksum; // the checksum of the pak holding the loaded game DLL
  364. int gamePakForOS[ MAX_GAME_OS ];
  365. idDEntry dir_cache[ MAX_CACHED_DIRS ]; // fifo
  366. int dir_cache_index;
  367. int dir_cache_count;
  368. int d3xp; // 0: didn't check, -1: not installed, 1: installed
  369. private:
  370. void ReplaceSeparators( idStr &path, char sep = PATHSEPERATOR_CHAR );
  371. long HashFileName( const char *fname ) const;
  372. int ListOSFiles( const char *directory, const char *extension, idStrList &list );
  373. FILE * OpenOSFile( const char *name, const char *mode, idStr *caseSensitiveName = NULL );
  374. FILE * OpenOSFileCorrectName( idStr &path, const char *mode );
  375. int DirectFileLength( FILE *o );
  376. void CopyFile( idFile *src, const char *toOSPath );
  377. int AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const;
  378. void GetExtensionList( const char *extension, idStrList &extensionList ) const;
  379. int GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir = NULL );
  380. int GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir = NULL );
  381. pack_t * LoadZipFile( const char *zipfile );
  382. void AddGameDirectory( const char *path, const char *dir );
  383. void SetupGameDirectories( const char *gameName );
  384. void Startup( void );
  385. void SetRestrictions( void );
  386. // some files can be obtained from directories without compromising si_pure
  387. bool FileAllowedFromDir( const char *path );
  388. // searches all the paks, no pure check
  389. pack_t * GetPackForChecksum( int checksum, bool searchAddons = false );
  390. // searches all the paks, no pure check
  391. pack_t * FindPakForFileChecksum( const char *relativePath, int fileChecksum, bool bReference );
  392. idFile_InZip * ReadFileFromZip( pack_t *pak, fileInPack_t *pakFile, const char *relativePath );
  393. int GetFileChecksum( idFile *file );
  394. pureStatus_t GetPackStatus( pack_t *pak );
  395. addonInfo_t * ParseAddonDef( const char *buf, const int len );
  396. void FollowAddonDependencies( pack_t *pak );
  397. static size_t CurlWriteFunction( void *ptr, size_t size, size_t nmemb, void *stream );
  398. // curl_progress_callback in curl.h
  399. static int CurlProgressFunction( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow );
  400. };
  401. idCVar idFileSystemLocal::fs_restrict( "fs_restrict", "", CVAR_SYSTEM | CVAR_INIT | CVAR_BOOL, "" );
  402. idCVar idFileSystemLocal::fs_debug( "fs_debug", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
  403. idCVar idFileSystemLocal::fs_copyfiles( "fs_copyfiles", "0", CVAR_SYSTEM | CVAR_INIT | CVAR_INTEGER, "", 0, 4, idCmdSystem::ArgCompletion_Integer<0,3> );
  404. idCVar idFileSystemLocal::fs_basepath( "fs_basepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
  405. idCVar idFileSystemLocal::fs_savepath( "fs_savepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
  406. idCVar idFileSystemLocal::fs_cdpath( "fs_cdpath", "", CVAR_SYSTEM | CVAR_INIT, "" );
  407. idCVar idFileSystemLocal::fs_devpath( "fs_devpath", "", CVAR_SYSTEM | CVAR_INIT, "" );
  408. idCVar idFileSystemLocal::fs_game( "fs_game", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "mod path" );
  409. 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" );
  410. #ifdef WIN32
  411. idCVar idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
  412. #else
  413. idCVar idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "1", CVAR_SYSTEM | CVAR_BOOL, "" );
  414. #endif
  415. idCVar idFileSystemLocal::fs_searchAddons( "fs_searchAddons", "0", CVAR_SYSTEM | CVAR_BOOL, "search all addon pk4s ( disables addon functionality )" );
  416. idFileSystemLocal fileSystemLocal;
  417. idFileSystem * fileSystem = &fileSystemLocal;
  418. /*
  419. ================
  420. idFileSystemLocal::idFileSystemLocal
  421. ================
  422. */
  423. idFileSystemLocal::idFileSystemLocal( void ) {
  424. searchPaths = NULL;
  425. readCount = 0;
  426. loadCount = 0;
  427. loadStack = 0;
  428. dir_cache_index = 0;
  429. dir_cache_count = 0;
  430. d3xp = 0;
  431. loadedFileFromDir = false;
  432. restartGamePakChecksum = 0;
  433. memset( &backgroundThread, 0, sizeof( backgroundThread ) );
  434. addonPaks = NULL;
  435. }
  436. /*
  437. ================
  438. idFileSystemLocal::HashFileName
  439. return a hash value for the filename
  440. ================
  441. */
  442. long idFileSystemLocal::HashFileName( const char *fname ) const {
  443. int i;
  444. long hash;
  445. char letter;
  446. hash = 0;
  447. i = 0;
  448. while( fname[i] != '\0' ) {
  449. letter = idStr::ToLower( fname[i] );
  450. if ( letter == '.' ) {
  451. break; // don't include extension
  452. }
  453. if ( letter == '\\' ) {
  454. letter = '/'; // damn path names
  455. }
  456. hash += (long)(letter) * (i+119);
  457. i++;
  458. }
  459. hash &= (FILE_HASH_SIZE-1);
  460. return hash;
  461. }
  462. /*
  463. ===========
  464. idFileSystemLocal::FilenameCompare
  465. Ignore case and separator char distinctions
  466. ===========
  467. */
  468. bool idFileSystemLocal::FilenameCompare( const char *s1, const char *s2 ) const {
  469. int c1, c2;
  470. do {
  471. c1 = *s1++;
  472. c2 = *s2++;
  473. if ( c1 >= 'a' && c1 <= 'z' ) {
  474. c1 -= ('a' - 'A');
  475. }
  476. if ( c2 >= 'a' && c2 <= 'z' ) {
  477. c2 -= ('a' - 'A');
  478. }
  479. if ( c1 == '\\' || c1 == ':' ) {
  480. c1 = '/';
  481. }
  482. if ( c2 == '\\' || c2 == ':' ) {
  483. c2 = '/';
  484. }
  485. if ( c1 != c2 ) {
  486. return true; // strings not equal
  487. }
  488. } while( c1 );
  489. return false; // strings are equal
  490. }
  491. /*
  492. ================
  493. idFileSystemLocal::OpenOSFile
  494. optional caseSensitiveName is set to case sensitive file name as found on disc (fs_caseSensitiveOS only)
  495. ================
  496. */
  497. FILE *idFileSystemLocal::OpenOSFile( const char *fileName, const char *mode, idStr *caseSensitiveName ) {
  498. int i;
  499. FILE *fp;
  500. idStr fpath, entry;
  501. idStrList list;
  502. #ifndef __MWERKS__
  503. #ifndef WIN32
  504. // some systems will let you fopen a directory
  505. struct stat buf;
  506. if ( stat( fileName, &buf ) != -1 && !S_ISREG(buf.st_mode) ) {
  507. return NULL;
  508. }
  509. #endif
  510. #endif
  511. fp = fopen( fileName, mode );
  512. if ( !fp && fs_caseSensitiveOS.GetBool() ) {
  513. fpath = fileName;
  514. fpath.StripFilename();
  515. fpath.StripTrailing( PATHSEPERATOR_CHAR );
  516. if ( ListOSFiles( fpath, NULL, list ) == -1 ) {
  517. return NULL;
  518. }
  519. for ( i = 0; i < list.Num(); i++ ) {
  520. entry = fpath + PATHSEPERATOR_CHAR + list[i];
  521. if ( !entry.Icmp( fileName ) ) {
  522. fp = fopen( entry, mode );
  523. if ( fp ) {
  524. if ( caseSensitiveName ) {
  525. *caseSensitiveName = entry;
  526. caseSensitiveName->StripPath();
  527. }
  528. if ( fs_debug.GetInteger() ) {
  529. common->Printf( "idFileSystemLocal::OpenFileRead: changed %s to %s\n", fileName, entry.c_str() );
  530. }
  531. break;
  532. } else {
  533. // not supposed to happen if ListOSFiles is doing it's job correctly
  534. common->Warning( "idFileSystemLocal::OpenFileRead: fs_caseSensitiveOS 1 could not open %s", entry.c_str() );
  535. }
  536. }
  537. }
  538. } else if ( caseSensitiveName ) {
  539. *caseSensitiveName = fileName;
  540. caseSensitiveName->StripPath();
  541. }
  542. return fp;
  543. }
  544. /*
  545. ================
  546. idFileSystemLocal::OpenOSFileCorrectName
  547. ================
  548. */
  549. FILE *idFileSystemLocal::OpenOSFileCorrectName( idStr &path, const char *mode ) {
  550. idStr caseName;
  551. FILE *f = OpenOSFile( path.c_str(), mode, &caseName );
  552. if ( f ) {
  553. path.StripFilename();
  554. path += PATHSEPERATOR_STR;
  555. path += caseName;
  556. }
  557. return f;
  558. }
  559. /*
  560. ================
  561. idFileSystemLocal::DirectFileLength
  562. ================
  563. */
  564. int idFileSystemLocal::DirectFileLength( FILE *o ) {
  565. int pos;
  566. int end;
  567. pos = ftell( o );
  568. fseek( o, 0, SEEK_END );
  569. end = ftell( o );
  570. fseek( o, pos, SEEK_SET );
  571. return end;
  572. }
  573. /*
  574. ============
  575. idFileSystemLocal::CreateOSPath
  576. Creates any directories needed to store the given filename
  577. ============
  578. */
  579. void idFileSystemLocal::CreateOSPath( const char *OSPath ) {
  580. char *ofs;
  581. // make absolutely sure that it can't back up the path
  582. // FIXME: what about c: ?
  583. if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
  584. #ifdef _DEBUG
  585. common->DPrintf( "refusing to create relative path \"%s\"\n", OSPath );
  586. #endif
  587. return;
  588. }
  589. idStr path( OSPath );
  590. for( ofs = &path[ 1 ]; *ofs ; ofs++ ) {
  591. if ( *ofs == PATHSEPERATOR_CHAR ) {
  592. // create the directory
  593. *ofs = 0;
  594. Sys_Mkdir( path );
  595. *ofs = PATHSEPERATOR_CHAR;
  596. }
  597. }
  598. }
  599. /*
  600. =================
  601. idFileSystemLocal::CopyFile
  602. Copy a fully specified file from one place to another
  603. =================
  604. */
  605. void idFileSystemLocal::CopyFile( const char *fromOSPath, const char *toOSPath ) {
  606. FILE *f;
  607. int len;
  608. byte *buf;
  609. common->Printf( "copy %s to %s\n", fromOSPath, toOSPath );
  610. f = OpenOSFile( fromOSPath, "rb" );
  611. if ( !f ) {
  612. return;
  613. }
  614. fseek( f, 0, SEEK_END );
  615. len = ftell( f );
  616. fseek( f, 0, SEEK_SET );
  617. buf = (byte *)Mem_Alloc( len );
  618. if ( fread( buf, 1, len, f ) != (unsigned int)len ) {
  619. common->FatalError( "short read in idFileSystemLocal::CopyFile()\n" );
  620. }
  621. fclose( f );
  622. CreateOSPath( toOSPath );
  623. f = OpenOSFile( toOSPath, "wb" );
  624. if ( !f ) {
  625. common->Printf( "could not create destination file\n" );
  626. Mem_Free( buf );
  627. return;
  628. }
  629. if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) {
  630. common->FatalError( "short write in idFileSystemLocal::CopyFile()\n" );
  631. }
  632. fclose( f );
  633. Mem_Free( buf );
  634. }
  635. /*
  636. =================
  637. idFileSystemLocal::CopyFile
  638. =================
  639. */
  640. void idFileSystemLocal::CopyFile( idFile *src, const char *toOSPath ) {
  641. FILE *f;
  642. int len;
  643. byte *buf;
  644. common->Printf( "copy %s to %s\n", src->GetName(), toOSPath );
  645. src->Seek( 0, FS_SEEK_END );
  646. len = src->Tell();
  647. src->Seek( 0, FS_SEEK_SET );
  648. buf = (byte *)Mem_Alloc( len );
  649. if ( src->Read( buf, len ) != len ) {
  650. common->FatalError( "Short read in idFileSystemLocal::CopyFile()\n" );
  651. }
  652. CreateOSPath( toOSPath );
  653. f = OpenOSFile( toOSPath, "wb" );
  654. if ( !f ) {
  655. common->Printf( "could not create destination file\n" );
  656. Mem_Free( buf );
  657. return;
  658. }
  659. if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) {
  660. common->FatalError( "Short write in idFileSystemLocal::CopyFile()\n" );
  661. }
  662. fclose( f );
  663. Mem_Free( buf );
  664. }
  665. /*
  666. ====================
  667. idFileSystemLocal::ReplaceSeparators
  668. Fix things up differently for win/unix/mac
  669. ====================
  670. */
  671. void idFileSystemLocal::ReplaceSeparators( idStr &path, char sep ) {
  672. char *s;
  673. for( s = &path[ 0 ]; *s ; s++ ) {
  674. if ( *s == '/' || *s == '\\' ) {
  675. *s = sep;
  676. }
  677. }
  678. }
  679. /*
  680. ===================
  681. idFileSystemLocal::BuildOSPath
  682. ===================
  683. */
  684. const char *idFileSystemLocal::BuildOSPath( const char *base, const char *game, const char *relativePath ) {
  685. static char OSPath[MAX_STRING_CHARS];
  686. idStr newPath;
  687. if ( fs_caseSensitiveOS.GetBool() || com_developer.GetBool() ) {
  688. // extract the path, make sure it's all lowercase
  689. idStr testPath, fileName;
  690. sprintf( testPath, "%s/%s", game , relativePath );
  691. testPath.StripFilename();
  692. if ( testPath.HasUpper() ) {
  693. common->Warning( "Non-portable: path contains uppercase characters: %s", testPath.c_str() );
  694. // attempt a fixup on the fly
  695. if ( fs_caseSensitiveOS.GetBool() ) {
  696. testPath.ToLower();
  697. fileName = relativePath;
  698. fileName.StripPath();
  699. sprintf( newPath, "%s/%s/%s", base, testPath.c_str(), fileName.c_str() );
  700. ReplaceSeparators( newPath );
  701. common->DPrintf( "Fixed up to %s\n", newPath.c_str() );
  702. idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
  703. return OSPath;
  704. }
  705. }
  706. }
  707. idStr strBase = base;
  708. strBase.StripTrailing( '/' );
  709. strBase.StripTrailing( '\\' );
  710. sprintf( newPath, "%s/%s/%s", strBase.c_str(), game, relativePath );
  711. ReplaceSeparators( newPath );
  712. idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
  713. return OSPath;
  714. }
  715. /*
  716. ================
  717. idFileSystemLocal::OSPathToRelativePath
  718. takes a full OS path, as might be found in data from a media creation
  719. program, and converts it to a relativePath by stripping off directories
  720. Returns false if the osPath tree doesn't match any of the existing
  721. search paths.
  722. ================
  723. */
  724. const char *idFileSystemLocal::OSPathToRelativePath( const char *OSPath ) {
  725. static char relativePath[MAX_STRING_CHARS];
  726. char *s, *base;
  727. // skip a drive letter?
  728. // search for anything with "base" in it
  729. // Ase files from max may have the form of:
  730. // "//Purgatory/purgatory/doom/base/models/mapobjects/bitch/hologirl.tga"
  731. // which won't match any of our drive letter based search paths
  732. bool ignoreWarning = false;
  733. #ifdef ID_DEMO_BUILD
  734. base = strstr( OSPath, BASE_GAMEDIR );
  735. idStr tempStr = OSPath;
  736. tempStr.ToLower();
  737. if ( ( strstr( tempStr, "//" ) || strstr( tempStr, "w:" ) ) && strstr( tempStr, "/doom/base/") ) {
  738. // will cause a warning but will load the file. ase models have
  739. // hard coded doom/base/ in the material names
  740. base = strstr( OSPath, "base" );
  741. ignoreWarning = true;
  742. }
  743. #else
  744. // look for the first complete directory name
  745. base = (char *)strstr( OSPath, BASE_GAMEDIR );
  746. while ( base ) {
  747. char c1 = '\0', c2;
  748. if ( base > OSPath ) {
  749. c1 = *(base - 1);
  750. }
  751. c2 = *( base + strlen( BASE_GAMEDIR ) );
  752. if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) {
  753. break;
  754. }
  755. base = strstr( base + 1, BASE_GAMEDIR );
  756. }
  757. #endif
  758. // fs_game and fs_game_base support - look for first complete name with a mod path
  759. // ( fs_game searched before fs_game_base )
  760. const char *fsgame = NULL;
  761. int igame = 0;
  762. for ( igame = 0; igame < 2; igame++ ) {
  763. if ( igame == 0 ) {
  764. fsgame = fs_game.GetString();
  765. } else if ( igame == 1 ) {
  766. fsgame = fs_game_base.GetString();
  767. }
  768. if ( base == NULL && fsgame && strlen( fsgame ) ) {
  769. base = (char *)strstr( OSPath, fsgame );
  770. while ( base ) {
  771. char c1 = '\0', c2;
  772. if ( base > OSPath ) {
  773. c1 = *(base - 1);
  774. }
  775. c2 = *( base + strlen( fsgame ) );
  776. if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) {
  777. break;
  778. }
  779. base = strstr( base + 1, fsgame );
  780. }
  781. }
  782. }
  783. if ( base ) {
  784. s = strstr( base, "/" );
  785. if ( !s ) {
  786. s = strstr( base, "\\" );
  787. }
  788. if ( s ) {
  789. strcpy( relativePath, s + 1 );
  790. if ( fs_debug.GetInteger() > 1 ) {
  791. common->Printf( "idFileSystem::OSPathToRelativePath: %s becomes %s\n", OSPath, relativePath );
  792. }
  793. return relativePath;
  794. }
  795. }
  796. if ( !ignoreWarning ) {
  797. common->Warning( "idFileSystem::OSPathToRelativePath failed on %s", OSPath );
  798. }
  799. strcpy( relativePath, "" );
  800. return relativePath;
  801. }
  802. /*
  803. =====================
  804. idFileSystemLocal::RelativePathToOSPath
  805. Returns a fully qualified path that can be used with stdio libraries
  806. =====================
  807. */
  808. const char *idFileSystemLocal::RelativePathToOSPath( const char *relativePath, const char *basePath ) {
  809. const char *path = cvarSystem->GetCVarString( basePath );
  810. if ( !path[0] ) {
  811. path = fs_savepath.GetString();
  812. }
  813. return BuildOSPath( path, gameFolder, relativePath );
  814. }
  815. /*
  816. =================
  817. idFileSystemLocal::RemoveFile
  818. =================
  819. */
  820. void idFileSystemLocal::RemoveFile( const char *relativePath ) {
  821. idStr OSPath;
  822. if ( fs_devpath.GetString()[0] ) {
  823. OSPath = BuildOSPath( fs_devpath.GetString(), gameFolder, relativePath );
  824. remove( OSPath );
  825. }
  826. OSPath = BuildOSPath( fs_savepath.GetString(), gameFolder, relativePath );
  827. remove( OSPath );
  828. ClearDirCache();
  829. }
  830. /*
  831. ================
  832. idFileSystemLocal::FileIsInPAK
  833. ================
  834. */
  835. bool idFileSystemLocal::FileIsInPAK( const char *relativePath ) {
  836. searchpath_t *search;
  837. pack_t *pak;
  838. fileInPack_t *pakFile;
  839. long hash;
  840. if ( !searchPaths ) {
  841. common->FatalError( "Filesystem call made without initialization\n" );
  842. }
  843. if ( !relativePath ) {
  844. common->FatalError( "idFileSystemLocal::FileIsInPAK: NULL 'relativePath' parameter passed\n" );
  845. }
  846. // qpaths are not supposed to have a leading slash
  847. if ( relativePath[0] == '/' || relativePath[0] == '\\' ) {
  848. relativePath++;
  849. }
  850. // make absolutely sure that it can't back up the path.
  851. // The searchpaths do guarantee that something will always
  852. // be prepended, so we don't need to worry about "c:" or "//limbo"
  853. if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) {
  854. return false;
  855. }
  856. //
  857. // search through the path, one element at a time
  858. //
  859. hash = HashFileName( relativePath );
  860. for ( search = searchPaths; search; search = search->next ) {
  861. // is the element a pak file?
  862. if ( search->pack && search->pack->hashTable[hash] ) {
  863. // disregard if it doesn't match one of the allowed pure pak files - or is a localization file
  864. if ( serverPaks.Num() ) {
  865. GetPackStatus( search->pack );
  866. if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
  867. continue; // not on the pure server pak list
  868. }
  869. }
  870. // look through all the pak file elements
  871. pak = search->pack;
  872. pakFile = pak->hashTable[hash];
  873. do {
  874. // case and separator insensitive comparisons
  875. if ( !FilenameCompare( pakFile->name, relativePath ) ) {
  876. return true;
  877. }
  878. pakFile = pakFile->next;
  879. } while( pakFile != NULL );
  880. }
  881. }
  882. return false;
  883. }
  884. /*
  885. ============
  886. idFileSystemLocal::ReadFile
  887. Filename are relative to the search path
  888. a null buffer will just return the file length and time without loading
  889. timestamp can be NULL if not required
  890. ============
  891. */
  892. int idFileSystemLocal::ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp ) {
  893. idFile * f;
  894. byte * buf;
  895. int len;
  896. bool isConfig;
  897. if ( !searchPaths ) {
  898. common->FatalError( "Filesystem call made without initialization\n" );
  899. }
  900. if ( !relativePath || !relativePath[0] ) {
  901. common->FatalError( "idFileSystemLocal::ReadFile with empty name\n" );
  902. }
  903. if ( timestamp ) {
  904. *timestamp = FILE_NOT_FOUND_TIMESTAMP;
  905. }
  906. if ( buffer ) {
  907. *buffer = NULL;
  908. }
  909. buf = NULL; // quiet compiler warning
  910. // if this is a .cfg file and we are playing back a journal, read
  911. // it from the journal file
  912. if ( strstr( relativePath, ".cfg" ) == relativePath + strlen( relativePath ) - 4 ) {
  913. isConfig = true;
  914. if ( eventLoop && eventLoop->JournalLevel() == 2 ) {
  915. int r;
  916. loadCount++;
  917. loadStack++;
  918. common->DPrintf( "Loading %s from journal file.\n", relativePath );
  919. len = 0;
  920. r = eventLoop->com_journalDataFile->Read( &len, sizeof( len ) );
  921. if ( r != sizeof( len ) ) {
  922. *buffer = NULL;
  923. return -1;
  924. }
  925. buf = (byte *)Mem_ClearedAlloc(len+1);
  926. *buffer = buf;
  927. r = eventLoop->com_journalDataFile->Read( buf, len );
  928. if ( r != len ) {
  929. common->FatalError( "Read from journalDataFile failed" );
  930. }
  931. // guarantee that it will have a trailing 0 for string operations
  932. buf[len] = 0;
  933. return len;
  934. }
  935. } else {
  936. isConfig = false;
  937. }
  938. // look for it in the filesystem or pack files
  939. f = OpenFileRead( relativePath, ( buffer != NULL ) );
  940. if ( f == NULL ) {
  941. if ( buffer ) {
  942. *buffer = NULL;
  943. }
  944. return -1;
  945. }
  946. len = f->Length();
  947. if ( timestamp ) {
  948. *timestamp = f->Timestamp();
  949. }
  950. if ( !buffer ) {
  951. CloseFile( f );
  952. return len;
  953. }
  954. loadCount++;
  955. loadStack++;
  956. buf = (byte *)Mem_ClearedAlloc(len+1);
  957. *buffer = buf;
  958. f->Read( buf, len );
  959. // guarantee that it will have a trailing 0 for string operations
  960. buf[len] = 0;
  961. CloseFile( f );
  962. // if we are journalling and it is a config file, write it to the journal file
  963. if ( isConfig && eventLoop && eventLoop->JournalLevel() == 1 ) {
  964. common->DPrintf( "Writing %s to journal file.\n", relativePath );
  965. eventLoop->com_journalDataFile->Write( &len, sizeof( len ) );
  966. eventLoop->com_journalDataFile->Write( buf, len );
  967. eventLoop->com_journalDataFile->Flush();
  968. }
  969. return len;
  970. }
  971. /*
  972. =============
  973. idFileSystemLocal::FreeFile
  974. =============
  975. */
  976. void idFileSystemLocal::FreeFile( void *buffer ) {
  977. if ( !searchPaths ) {
  978. common->FatalError( "Filesystem call made without initialization\n" );
  979. }
  980. if ( !buffer ) {
  981. common->FatalError( "idFileSystemLocal::FreeFile( NULL )" );
  982. }
  983. loadStack--;
  984. Mem_Free( buffer );
  985. }
  986. /*
  987. ============
  988. idFileSystemLocal::WriteFile
  989. Filenames are relative to the search path
  990. ============
  991. */
  992. int idFileSystemLocal::WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath ) {
  993. idFile *f;
  994. if ( !searchPaths ) {
  995. common->FatalError( "Filesystem call made without initialization\n" );
  996. }
  997. if ( !relativePath || !buffer ) {
  998. common->FatalError( "idFileSystemLocal::WriteFile: NULL parameter" );
  999. }
  1000. f = idFileSystemLocal::OpenFileWrite( relativePath, basePath );
  1001. if ( !f ) {
  1002. common->Printf( "Failed to open %s\n", relativePath );
  1003. return -1;
  1004. }
  1005. size = f->Write( buffer, size );
  1006. CloseFile( f );
  1007. return size;
  1008. }
  1009. /*
  1010. =================
  1011. idFileSystemLocal::ParseAddonDef
  1012. =================
  1013. */
  1014. addonInfo_t *idFileSystemLocal::ParseAddonDef( const char *buf, const int len ) {
  1015. idLexer src;
  1016. idToken token, token2;
  1017. addonInfo_t *info;
  1018. src.LoadMemory( buf, len, "<addon.conf>" );
  1019. src.SetFlags( DECL_LEXER_FLAGS );
  1020. if ( !src.SkipUntilString( "addonDef" ) ) {
  1021. src.Warning( "ParseAddonDef: no addonDef" );
  1022. return NULL;
  1023. }
  1024. if ( !src.ReadToken( &token ) ) {
  1025. src.Warning( "Expected {" );
  1026. return NULL;
  1027. }
  1028. info = new addonInfo_t;
  1029. // read addonDef
  1030. while ( 1 ) {
  1031. if ( !src.ReadToken( &token ) ) {
  1032. delete info;
  1033. return NULL;
  1034. }
  1035. if ( !token.Icmp( "}" ) ) {
  1036. break;
  1037. }
  1038. if ( token.type != TT_STRING ) {
  1039. src.Warning( "Expected quoted string, but found '%s'", token.c_str() );
  1040. delete info;
  1041. return NULL;
  1042. }
  1043. int checksum;
  1044. if ( sscanf( token.c_str(), "0x%x", &checksum ) != 1 && sscanf( token.c_str(), "%x", &checksum ) != 1 ) {
  1045. src.Warning( "Could not parse checksum '%s'", token.c_str() );
  1046. delete info;
  1047. return NULL;
  1048. }
  1049. info->depends.Append( checksum );
  1050. }
  1051. // read any number of mapDef entries
  1052. while ( 1 ) {
  1053. if ( !src.SkipUntilString( "mapDef" ) ) {
  1054. return info;
  1055. }
  1056. if ( !src.ReadToken( &token ) ) {
  1057. src.Warning( "Expected map path" );
  1058. info->mapDecls.DeleteContents( true );
  1059. delete info;
  1060. return NULL;
  1061. }
  1062. idDict *dict = new idDict;
  1063. dict->Set( "path", token.c_str() );
  1064. if ( !src.ReadToken( &token ) ) {
  1065. src.Warning( "Expected {" );
  1066. info->mapDecls.DeleteContents( true );
  1067. delete dict;
  1068. delete info;
  1069. return NULL;
  1070. }
  1071. while ( 1 ) {
  1072. if ( !src.ReadToken( &token ) ) {
  1073. break;
  1074. }
  1075. if ( !token.Icmp( "}" ) ) {
  1076. break;
  1077. }
  1078. if ( token.type != TT_STRING ) {
  1079. src.Warning( "Expected quoted string, but found '%s'", token.c_str() );
  1080. info->mapDecls.DeleteContents( true );
  1081. delete dict;
  1082. delete info;
  1083. return NULL;
  1084. }
  1085. if ( !src.ReadToken( &token2 ) ) {
  1086. src.Warning( "Unexpected end of file" );
  1087. info->mapDecls.DeleteContents( true );
  1088. delete dict;
  1089. delete info;
  1090. return NULL;
  1091. }
  1092. if ( dict->FindKey( token ) ) {
  1093. src.Warning( "'%s' already defined", token.c_str() );
  1094. }
  1095. dict->Set( token, token2 );
  1096. }
  1097. info->mapDecls.Append( dict );
  1098. }
  1099. assert( false );
  1100. return NULL;
  1101. }
  1102. /*
  1103. =================
  1104. idFileSystemLocal::LoadZipFile
  1105. =================
  1106. */
  1107. pack_t *idFileSystemLocal::LoadZipFile( const char *zipfile ) {
  1108. fileInPack_t * buildBuffer;
  1109. pack_t * pack;
  1110. unzFile uf;
  1111. int err;
  1112. unz_global_info gi;
  1113. char filename_inzip[MAX_ZIPPED_FILE_NAME];
  1114. unz_file_info file_info;
  1115. int i;
  1116. long hash;
  1117. int fs_numHeaderLongs;
  1118. int * fs_headerLongs;
  1119. FILE *f;
  1120. int len;
  1121. int confHash;
  1122. fileInPack_t *pakFile;
  1123. f = OpenOSFile( zipfile, "rb" );
  1124. if ( !f ) {
  1125. return NULL;
  1126. }
  1127. fseek( f, 0, SEEK_END );
  1128. len = ftell( f );
  1129. fclose( f );
  1130. fs_numHeaderLongs = 0;
  1131. uf = unzOpen( zipfile );
  1132. err = unzGetGlobalInfo( uf, &gi );
  1133. if ( err != UNZ_OK ) {
  1134. return NULL;
  1135. }
  1136. buildBuffer = new fileInPack_t[gi.number_entry];
  1137. pack = new pack_t;
  1138. for( i = 0; i < FILE_HASH_SIZE; i++ ) {
  1139. pack->hashTable[i] = NULL;
  1140. }
  1141. pack->pakFilename = zipfile;
  1142. pack->handle = uf;
  1143. pack->numfiles = gi.number_entry;
  1144. pack->buildBuffer = buildBuffer;
  1145. pack->referenced = false;
  1146. pack->binary = BINARY_UNKNOWN;
  1147. pack->addon = false;
  1148. pack->addon_search = false;
  1149. pack->addon_info = NULL;
  1150. pack->pureStatus = PURE_UNKNOWN;
  1151. pack->isNew = false;
  1152. pack->length = len;
  1153. unzGoToFirstFile(uf);
  1154. fs_headerLongs = (int *)Mem_ClearedAlloc( gi.number_entry * sizeof(int) );
  1155. for ( i = 0; i < (int)gi.number_entry; i++ ) {
  1156. err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0 );
  1157. if ( err != UNZ_OK ) {
  1158. break;
  1159. }
  1160. if ( file_info.uncompressed_size > 0 ) {
  1161. fs_headerLongs[fs_numHeaderLongs++] = LittleLong( file_info.crc );
  1162. }
  1163. hash = HashFileName( filename_inzip );
  1164. buildBuffer[i].name = filename_inzip;
  1165. buildBuffer[i].name.ToLower();
  1166. buildBuffer[i].name.BackSlashesToSlashes();
  1167. // store the file position in the zip
  1168. unzGetCurrentFileInfoPosition( uf, &buildBuffer[i].pos );
  1169. // add the file to the hash
  1170. buildBuffer[i].next = pack->hashTable[hash];
  1171. pack->hashTable[hash] = &buildBuffer[i];
  1172. // go to the next file in the zip
  1173. unzGoToNextFile(uf);
  1174. }
  1175. // check if this is an addon pak
  1176. pack->addon = false;
  1177. confHash = HashFileName( ADDON_CONFIG );
  1178. for ( pakFile = pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) {
  1179. if ( !FilenameCompare( pakFile->name, ADDON_CONFIG ) ) {
  1180. pack->addon = true;
  1181. idFile_InZip *file = ReadFileFromZip( pack, pakFile, ADDON_CONFIG );
  1182. // may be just an empty file if you don't bother about the mapDef
  1183. if ( file && file->Length() ) {
  1184. char *buf;
  1185. buf = new char[ file->Length() + 1 ];
  1186. file->Read( (void *)buf, file->Length() );
  1187. buf[ file->Length() ] = '\0';
  1188. pack->addon_info = ParseAddonDef( buf, file->Length() );
  1189. delete[] buf;
  1190. }
  1191. if ( file ) {
  1192. CloseFile( file );
  1193. }
  1194. break;
  1195. }
  1196. }
  1197. pack->checksum = MD4_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
  1198. pack->checksum = LittleLong( pack->checksum );
  1199. Mem_Free( fs_headerLongs );
  1200. return pack;
  1201. }
  1202. /*
  1203. ===============
  1204. idFileSystemLocal::AddZipFile
  1205. adds a downloaded pak file to the list so we can work out what we have and what we still need
  1206. the isNew flag is set to true, indicating that we cannot add this pak to the search lists without a restart
  1207. ===============
  1208. */
  1209. int idFileSystemLocal::AddZipFile( const char *path ) {
  1210. idStr fullpath = fs_savepath.GetString();
  1211. pack_t *pak;
  1212. searchpath_t *search, *last;
  1213. fullpath.AppendPath( path );
  1214. pak = LoadZipFile( fullpath );
  1215. if ( !pak ) {
  1216. common->Warning( "AddZipFile %s failed\n", path );
  1217. return 0;
  1218. }
  1219. // insert the pak at the end of the search list - temporary until we restart
  1220. pak->isNew = true;
  1221. search = new searchpath_t;
  1222. search->dir = NULL;
  1223. search->pack = pak;
  1224. search->next = NULL;
  1225. last = searchPaths;
  1226. while ( last->next ) {
  1227. last = last->next;
  1228. }
  1229. last->next = search;
  1230. common->Printf( "Appended pk4 %s with checksum 0x%x\n", pak->pakFilename.c_str(), pak->checksum );
  1231. return pak->checksum;
  1232. }
  1233. /*
  1234. ===============
  1235. idFileSystemLocal::AddUnique
  1236. ===============
  1237. */
  1238. int idFileSystemLocal::AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const {
  1239. int i, hashKey;
  1240. hashKey = hashIndex.GenerateKey( name );
  1241. for ( i = hashIndex.First( hashKey ); i >= 0; i = hashIndex.Next( i ) ) {
  1242. if ( list[i].Icmp( name ) == 0 ) {
  1243. return i;
  1244. }
  1245. }
  1246. i = list.Append( name );
  1247. hashIndex.Add( hashKey, i );
  1248. return i;
  1249. }
  1250. /*
  1251. ===============
  1252. idFileSystemLocal::GetExtensionList
  1253. ===============
  1254. */
  1255. void idFileSystemLocal::GetExtensionList( const char *extension, idStrList &extensionList ) const {
  1256. int s, e, l;
  1257. l = idStr::Length( extension );
  1258. s = 0;
  1259. while( 1 ) {
  1260. e = idStr::FindChar( extension, '|', s, l );
  1261. if ( e != -1 ) {
  1262. extensionList.Append( idStr( extension, s, e ) );
  1263. s = e + 1;
  1264. } else {
  1265. extensionList.Append( idStr( extension, s, l ) );
  1266. break;
  1267. }
  1268. }
  1269. }
  1270. /*
  1271. ===============
  1272. idFileSystemLocal::GetFileList
  1273. Does not clear the list first so this can be used to progressively build a file list.
  1274. When 'sort' is true only the new files added to the list are sorted.
  1275. ===============
  1276. */
  1277. int idFileSystemLocal::GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir ) {
  1278. searchpath_t * search;
  1279. fileInPack_t * buildBuffer;
  1280. int i, j;
  1281. int pathLength;
  1282. int length;
  1283. const char * name;
  1284. pack_t * pak;
  1285. idStr work;
  1286. if ( !searchPaths ) {
  1287. common->FatalError( "Filesystem call made without initialization\n" );
  1288. }
  1289. if ( !extensions.Num() ) {
  1290. return 0;
  1291. }
  1292. if ( !relativePath ) {
  1293. return 0;
  1294. }
  1295. pathLength = strlen( relativePath );
  1296. if ( pathLength ) {
  1297. pathLength++; // for the trailing '/'
  1298. }
  1299. // search through the path, one element at a time, adding to list
  1300. for( search = searchPaths; search != NULL; search = search->next ) {
  1301. if ( search->dir ) {
  1302. if(gamedir && strlen(gamedir)) {
  1303. if(search->dir->gamedir != gamedir) {
  1304. continue;
  1305. }
  1306. }
  1307. idStrList sysFiles;
  1308. idStr netpath;
  1309. netpath = BuildOSPath( search->dir->path, search->dir->gamedir, relativePath );
  1310. for ( i = 0; i < extensions.Num(); i++ ) {
  1311. // scan for files in the filesystem
  1312. ListOSFiles( netpath, extensions[i], sysFiles );
  1313. // if we are searching for directories, remove . and ..
  1314. if ( extensions[i][0] == '/' && extensions[i][1] == 0 ) {
  1315. sysFiles.Remove( "." );
  1316. sysFiles.Remove( ".." );
  1317. }
  1318. for( j = 0; j < sysFiles.Num(); j++ ) {
  1319. // unique the match
  1320. if ( fullRelativePath ) {
  1321. work = relativePath;
  1322. work += "/";
  1323. work += sysFiles[j];
  1324. AddUnique( work, list, hashIndex );
  1325. }
  1326. else {
  1327. AddUnique( sysFiles[j], list, hashIndex );
  1328. }
  1329. }
  1330. }
  1331. } else if ( search->pack ) {
  1332. // look through all the pak file elements
  1333. // exclude any extra packs if we have server paks to search
  1334. if ( serverPaks.Num() ) {
  1335. GetPackStatus( search->pack );
  1336. if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
  1337. continue; // not on the pure server pak list
  1338. }
  1339. }
  1340. pak = search->pack;
  1341. buildBuffer = pak->buildBuffer;
  1342. for( i = 0; i < pak->numfiles; i++ ) {
  1343. length = buildBuffer[i].name.Length();
  1344. // if the name is not long anough to at least contain the path
  1345. if ( length <= pathLength ) {
  1346. continue;
  1347. }
  1348. name = buildBuffer[i].name;
  1349. // check for a path match without the trailing '/'
  1350. if ( pathLength && idStr::Icmpn( name, relativePath, pathLength - 1 ) != 0 ) {
  1351. continue;
  1352. }
  1353. // ensure we have a path, and not just a filename containing the path
  1354. if ( name[ pathLength ] == '\0' || name[pathLength - 1] != '/' ) {
  1355. continue;
  1356. }
  1357. // make sure the file is not in a subdirectory
  1358. for ( j = pathLength; name[j+1] != '\0'; j++ ) {
  1359. if ( name[j] == '/' ) {
  1360. break;
  1361. }
  1362. }
  1363. if ( name[j+1] ) {
  1364. continue;
  1365. }
  1366. // check for extension match
  1367. for ( j = 0; j < extensions.Num(); j++ ) {
  1368. if ( length >= extensions[j].Length() && extensions[j].Icmp( name + length - extensions[j].Length() ) == 0 ) {
  1369. break;
  1370. }
  1371. }

Large files files are truncated, but you can click here to view the full file