Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9480 lines
266 KiB

  1. //===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "basefilesystem.h"
  8. #include "tier0/vprof.h"
  9. #include "tier1/characterset.h"
  10. #include "tier1/utlbuffer.h"
  11. #include "tier1/convar.h"
  12. #include "tier1/keyvalues.h"
  13. #include "tier0/icommandline.h"
  14. #include "tier0/stacktools.h"
  15. #include "generichash.h"
  16. #include "tier1/utllinkedlist.h"
  17. #include "filesystem/IQueuedLoader.h"
  18. #include "filesystem/IXboxInstaller.h"
  19. #include "tier2/tier2.h"
  20. #include "tier1/lzmaDecoder.h"
  21. #include "vstdlib/vstrtools.h"
  22. #include "zip_utils.h"
  23. #include "fmtstr.h"
  24. #ifdef _X360
  25. #include "xbox/xbox_launch.h"
  26. #include "xbox/xbox_console.h"
  27. #elif defined( _PS3 )
  28. #include <cell/sysmodule.h>
  29. #endif
  30. #ifndef DEDICATED
  31. #include "keyvaluescompiler.h"
  32. #endif
  33. #include "ifilelist.h"
  34. #ifdef IS_WINDOWS_PC
  35. // Needed for getting file type string
  36. #define WIN32_LEAN_AND_MEAN
  37. #include <shellapi.h>
  38. #endif
  39. #if defined( _X360 )
  40. #include "xbox\xbox_win32stubs.h"
  41. #undef GetCurrentDirectory
  42. #endif
  43. #ifdef _PS3
  44. #include "ps3/ps3_core.h"
  45. #include "ps3_pathinfo.h"
  46. #include "tls_ps3.h"
  47. #include <cell/fios.h>
  48. // extern bool g_bUseBdvdGameData;
  49. #ifndef PLATFORM_EXT
  50. #pragma message("PLATFORM_EXT define is missing, wtf?")
  51. #define PLATFORM_EXT ".ps3"
  52. #endif // ifndef PLATFORM_EXT
  53. void getcwd(...) { AssertMsg(false, "getcwd does not exist on PS3\n"); }
  54. bool SetupFios();
  55. bool TeardownFios();
  56. #endif // _PS3
  57. #ifdef _X360
  58. #define FS_DVDDEV_REMAP_ROOT "d:"
  59. #define FS_DVDDEV_ROOT "d:\\dvddev"
  60. #define FS_EXCLUDE_PATHS_FILENAME "xbox_exclude_paths.txt"
  61. #elif defined( _PS3 )
  62. #define FS_DVDDEV_REMAP_ROOT g_pPS3PathInfo->GameImagePath()
  63. #define FS_DVDDEV_ROOT "/app_home/dvddev"
  64. #define FS_EXCLUDE_PATHS_FILENAME "ps3_exclude_paths.txt"
  65. #else
  66. #define FS_DVDDEV_REMAP_ROOT ""
  67. #define FS_DVDDEV_ROOT "dvddev???:::"
  68. #define FS_EXCLUDE_PATHS_FILENAME "allbad_exclude_paths.txt"
  69. #endif
  70. #ifdef _GAMECONSOLE
  71. static bool IsDvdDevPathString( char const *szPath )
  72. {
  73. if ( IsGameConsole() && StringAfterPrefix( szPath, FS_DVDDEV_ROOT ) &&
  74. szPath[ sizeof( FS_DVDDEV_ROOT ) - 1 ] == CORRECT_PATH_SEPARATOR )
  75. {
  76. return true;
  77. }
  78. else if ( IsX360() )
  79. {
  80. const char *pFirstDir = V_strstr( szPath, ":" );
  81. if ( pFirstDir )
  82. {
  83. // skip past colon/slash
  84. pFirstDir += 2;
  85. return ( V_strnicmp( pFirstDir, "dvddev", 6 ) == false );
  86. }
  87. }
  88. return false;
  89. }
  90. #else
  91. #define IsDvdDevPathString( x ) false
  92. #endif
  93. // memdbgon must be the last include file in a .cpp file!!!
  94. #include "tier0/memdbgon.h"
  95. #pragma warning( disable : 4355 ) // warning C4355: 'this' : used in base member initializer list
  96. ConVar fs_report_sync_opens( "fs_report_sync_opens", "0", FCVAR_RELEASE, "0:Off, 1:Always, 2:Not during map load" );
  97. ConVar fs_report_sync_opens_callstack( "fs_report_sync_opens_callstack", "0", 0, "0 to not display the call-stack when we hit a fs_report_sync_opens warning. Set to 1 to display the call-stack." );
  98. ConVar fs_report_long_reads( "fs_report_long_reads", "0", 0, "0:Off, 1:All (for tracking accumulated duplicate read times), >1:Microsecond threshold" );
  99. ConVar fs_warning_mode( "fs_warning_mode", "0", 0, "0:Off, 1:Warn main thread, 2:Warn other threads" );
  100. ConVar fs_monitor_read_from_pack( "fs_monitor_read_from_pack", "0", 0, "0:Off, 1:Any, 2:Sync only" );
  101. #if IsPlatformPS3()
  102. ConVar fs_fios_spew_prefetches( "fs_fios_spew_prefetches", "0", 0, "Set this to 1 to output prefetch operations, otherwise set this to 0." );
  103. ConVar fs_fios_enabled( "fs_fios_enabled", "0", 0, "Set this to 1 to enable FIOS, otherwise set this to 0." );
  104. #endif
  105. #define BSPOUTPUT 0 // bsp output flag -- determines type of fs_log output to generate
  106. static void AddSeperatorAndFixPath( char *str );
  107. // Case-insensitive symbol table for path IDs.
  108. CUtlSymbolTableMT g_PathIDTable( 0, 32, true );
  109. int g_iNextSearchPathID = 1;
  110. #if defined (_PS3)
  111. // Copied from zip_utils.cpp (we don't want to add the file to the project (for now))
  112. BEGIN_BYTESWAP_DATADESC( ZIP_EndOfCentralDirRecord )
  113. DEFINE_FIELD( signature, FIELD_INTEGER ),
  114. DEFINE_FIELD( numberOfThisDisk, FIELD_SHORT ),
  115. DEFINE_FIELD( numberOfTheDiskWithStartOfCentralDirectory, FIELD_SHORT ),
  116. DEFINE_FIELD( nCentralDirectoryEntries_ThisDisk, FIELD_SHORT ),
  117. DEFINE_FIELD( nCentralDirectoryEntries_Total, FIELD_SHORT ),
  118. DEFINE_FIELD( centralDirectorySize, FIELD_INTEGER ),
  119. DEFINE_FIELD( startOfCentralDirOffset, FIELD_INTEGER ),
  120. DEFINE_FIELD( commentLength, FIELD_SHORT ),
  121. END_BYTESWAP_DATADESC()
  122. BEGIN_BYTESWAP_DATADESC( ZIP_FileHeader )
  123. DEFINE_FIELD( signature, FIELD_INTEGER ),
  124. DEFINE_FIELD( versionMadeBy, FIELD_SHORT ),
  125. DEFINE_FIELD( versionNeededToExtract, FIELD_SHORT ),
  126. DEFINE_FIELD( flags, FIELD_SHORT ),
  127. DEFINE_FIELD( compressionMethod, FIELD_SHORT ),
  128. DEFINE_FIELD( lastModifiedTime, FIELD_SHORT ),
  129. DEFINE_FIELD( lastModifiedDate, FIELD_SHORT ),
  130. DEFINE_FIELD( crc32, FIELD_INTEGER ),
  131. DEFINE_FIELD( compressedSize, FIELD_INTEGER ),
  132. DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ),
  133. DEFINE_FIELD( fileNameLength, FIELD_SHORT ),
  134. DEFINE_FIELD( extraFieldLength, FIELD_SHORT ),
  135. DEFINE_FIELD( fileCommentLength, FIELD_SHORT ),
  136. DEFINE_FIELD( diskNumberStart, FIELD_SHORT ),
  137. DEFINE_FIELD( internalFileAttribs, FIELD_SHORT ),
  138. DEFINE_FIELD( externalFileAttribs, FIELD_INTEGER ),
  139. DEFINE_FIELD( relativeOffsetOfLocalHeader, FIELD_INTEGER ),
  140. END_BYTESWAP_DATADESC()
  141. #endif
  142. void FixUpPathCaseForPS3(const char* pFilePath)
  143. {
  144. char* prev_ptr = NULL;
  145. char* last_ptr = NULL;
  146. // This is really bad but the EA code does it so let's give it a try for now
  147. char* pFilePathNonConst = const_cast< char * >( pFilePath );
  148. char* ptr = pFilePathNonConst;
  149. //Convert all "\" to forward "/" and reformat relative paths
  150. while ( *ptr )
  151. {
  152. if ( *ptr == '\\' || *ptr == '/' )
  153. {
  154. *ptr='/';
  155. while(ptr[1]=='\\' || ptr[1] == '/') //Get rid of multiple slashes
  156. {
  157. strcpy(ptr+1,ptr+2);
  158. }
  159. if(strncmp(ptr+1,"..",2)==0 && ptr[3]!=0 && ptr[4]!=0 && last_ptr) //Some relative paths are used at runtime in Team Fortress
  160. {
  161. //printf("Changing relative path %s to ... ", pFilePathNonConst);
  162. strcpy(last_ptr+1, ptr+4); //Remove relative path
  163. if(strncmp(last_ptr+1,"..",2)==0 && last_ptr[3]!=0 && last_ptr[4]!=0 && prev_ptr) //Sometimes get /../../ strings
  164. {
  165. strcpy(prev_ptr+1, last_ptr+4);
  166. if(strncmp(prev_ptr+1,"..",2)==0)
  167. {
  168. printf("Error: Can't process PS3 filenames containing /../../../\n");
  169. Assert(0);
  170. }
  171. }
  172. //printf("%s\n", pFilePathNonConst);
  173. }
  174. prev_ptr = last_ptr;
  175. last_ptr = ptr;
  176. }
  177. ptr++;
  178. }
  179. // terrible, terrible cruft: savegames (*.HL?) are written with uppercase from a million
  180. // different places. For now, I'm just going to leave them alone here, rather than try
  181. // to find every single possible place that has a savegame go through it (as an alias
  182. // of a copy of an alias of a string that's impossible to track by grepping). Y-U-C-K.
  183. if ( V_strstr(pFilePath, ".HL") )
  184. {
  185. return;
  186. }
  187. //PS3 file system is case sensitive (though this isn't enforced for /app_home/)
  188. if(pFilePathNonConst[0]=='/')
  189. {
  190. // if we're in the USRDIR directory, don't mess with paths up to that point
  191. char *pAfterUsrDir = V_strstr(pFilePathNonConst, "USRDIR");
  192. if ( pAfterUsrDir )
  193. {
  194. strlwr( pAfterUsrDir + 6 );
  195. }
  196. else if ((strnicmp(pFilePathNonConst,"/app_home/",10)==0) || (strnicmp(pFilePathNonConst,"/dev_bdvd/",10)==0) || (strnicmp(pFilePathNonConst,"/host_root/",11)==0))
  197. {
  198. strlwr(pFilePathNonConst+10);
  199. }
  200. else if (strnicmp(pFilePathNonConst,"/dev_hdd0/game/",15)==0)
  201. {
  202. strlwr(pFilePathNonConst+15);
  203. }
  204. else
  205. {
  206. //Lowercase everything after second "/"
  207. ptr=strchr(pFilePathNonConst,'/');
  208. if (ptr) ptr=strchr(ptr+1,'/');
  209. if (ptr) strlwr(ptr);
  210. }
  211. }
  212. else
  213. {
  214. //Lowercase everything
  215. strlwr(pFilePathNonConst);
  216. }
  217. }
  218. // Look for cases like materials\\blah.vmt.
  219. bool V_CheckDoubleSlashes( const char *pStr )
  220. {
  221. int len = V_strlen( pStr );
  222. for ( int i=1; i < len-1; i++ )
  223. {
  224. if ( (pStr[i] == '/' || pStr[i] == '\\') && (pStr[i+1] == '/' || pStr[i+1] == '\\') )
  225. return true;
  226. }
  227. return false;
  228. }
  229. //
  230. // Format relative filename when used under a search path
  231. // allows "symlinking" official workshop locations into
  232. // official locations in shipping depots.
  233. //
  234. // Returns passed pFileName if no symlinking occurs,
  235. // or pointer to temp symlink buffer containing the symlink target.
  236. //
  237. static char const * V_FormatFilenameForSymlinking( char (&tempSymlinkBuffer)[MAX_PATH], char const *pFileName )
  238. {
  239. if ( !pFileName )
  240. return NULL;
  241. if ( !V_strnicmp( pFileName, "maps", 4 ) &&
  242. ( ( pFileName[4] == CORRECT_PATH_SEPARATOR ) || ( pFileName[4] == INCORRECT_PATH_SEPARATOR ) ) &&
  243. !V_strnicmp( pFileName + 5, "workshop", 8 ) &&
  244. ( ( pFileName[13] == CORRECT_PATH_SEPARATOR ) || ( pFileName[13] == INCORRECT_PATH_SEPARATOR ) ) )
  245. {
  246. // maps/workshop/
  247. if ( ( false
  248. /** Removed for partner depot **/
  249. ) &&
  250. ( ( pFileName[23] == CORRECT_PATH_SEPARATOR ) || ( pFileName[23] == INCORRECT_PATH_SEPARATOR ) ) )
  251. {
  252. Q_snprintf( tempSymlinkBuffer, ARRAYSIZE( tempSymlinkBuffer ), "maps%c%s", pFileName[4], pFileName + 24 );
  253. return tempSymlinkBuffer;
  254. }
  255. }
  256. static bool bLoadBannedWords = ( !!CommandLine()->FindParm( "-usebanlist" ) ) || (!!CommandLine()->FindParm( "-perfectworld" ) );
  257. if ( bLoadBannedWords )
  258. {
  259. if ( !V_strnicmp( pFileName, "maps", 4 ) &&
  260. ( ( pFileName[ 4 ] == CORRECT_PATH_SEPARATOR ) || ( pFileName[ 4 ] == INCORRECT_PATH_SEPARATOR ) ) &&
  261. !V_strnicmp( pFileName + 5, "ar_monastery", 12 ) )
  262. {
  263. // maps/ar_monastery -> maps/ar_shoots
  264. Q_snprintf( tempSymlinkBuffer, ARRAYSIZE( tempSymlinkBuffer ), "maps%car_shoots%s", pFileName[ 4 ], pFileName + 17 );
  265. return tempSymlinkBuffer;
  266. }
  267. }
  268. return pFileName; // nothing symlinked here
  269. }
  270. // This can be used to easily fix a filename on the stack.
  271. #define CHECK_DOUBLE_SLASHES( x ) Assert( V_CheckDoubleSlashes(x) == false );
  272. // Win32 dedicated.dll contains both filesystem_steam.cpp and filesystem_stdio.cpp, so it has two
  273. // CBaseFileSystem objects. We'll let it manage BaseFileSystem() itself.
  274. #if !( defined(_WIN32) && defined(DEDICATED) ) || defined( _PS3 )
  275. static CBaseFileSystem *g_pBaseFileSystem;
  276. CBaseFileSystem *BaseFileSystem()
  277. {
  278. return g_pBaseFileSystem;
  279. }
  280. #endif
  281. ConVar filesystem_buffer_size( "filesystem_buffer_size", "0", 0, "Size of per file buffers. 0 for none" );
  282. class CFileHandleTimer : public CFastTimer
  283. {
  284. public:
  285. FileHandle_t m_hFile;
  286. char m_szName[ MAX_PATH ];
  287. };
  288. struct FileOpenDuplicateTime_t
  289. {
  290. char m_szName[ MAX_PATH ];
  291. int m_nLoadCount;
  292. float m_flAccumulatedMicroSeconds;
  293. FileOpenDuplicateTime_t()
  294. {
  295. m_szName[ 0 ] = '\0';
  296. m_nLoadCount = 0;
  297. m_flAccumulatedMicroSeconds = 0.0f;
  298. }
  299. };
  300. CUtlVector< FileOpenDuplicateTime_t* > g_FileOpenDuplicateTimes; // Used to debug approximate time spent reading files duplicate times
  301. CThreadFastMutex g_FileOpenDuplicateTimesMutex;
  302. #if defined( TRACK_BLOCKING_IO )
  303. // If we hit more than 100 items in a frame, we're probably doing a level load...
  304. #define MAX_ITEMS 100
  305. class CBlockingFileItemList : public IBlockingFileItemList
  306. {
  307. public:
  308. CBlockingFileItemList( CBaseFileSystem *fs )
  309. :
  310. m_pFS( fs ),
  311. m_bLocked( false )
  312. {
  313. }
  314. // You can't call any of the below calls without calling these methods!!!!
  315. virtual void LockMutex()
  316. {
  317. Assert( !m_bLocked );
  318. if ( m_bLocked )
  319. return;
  320. m_bLocked = true;
  321. m_pFS->BlockingFileAccess_EnterCriticalSection();
  322. }
  323. virtual void UnlockMutex()
  324. {
  325. Assert( m_bLocked );
  326. if ( !m_bLocked )
  327. return;
  328. m_pFS->BlockingFileAccess_LeaveCriticalSection();
  329. m_bLocked = false;
  330. }
  331. virtual int First() const
  332. {
  333. if ( !m_bLocked )
  334. {
  335. Error( "CBlockingFileItemList::First() w/o calling EnterCriticalSectionFirst!" );
  336. }
  337. return m_Items.Head();
  338. }
  339. virtual int Next( int i ) const
  340. {
  341. if ( !m_bLocked )
  342. {
  343. Error( "CBlockingFileItemList::Next() w/o calling EnterCriticalSectionFirst!" );
  344. }
  345. return m_Items.Next( i );
  346. }
  347. virtual int InvalidIndex() const
  348. {
  349. return m_Items.InvalidIndex();
  350. }
  351. virtual const FileBlockingItem& Get( int index ) const
  352. {
  353. if ( !m_bLocked )
  354. {
  355. Error( "CBlockingFileItemList::Get( %d ) w/o calling EnterCriticalSectionFirst!", index );
  356. }
  357. return m_Items[ index ];
  358. }
  359. virtual void Reset()
  360. {
  361. if ( !m_bLocked )
  362. {
  363. Error( "CBlockingFileItemList::Reset() w/o calling EnterCriticalSectionFirst!" );
  364. }
  365. m_Items.RemoveAll();
  366. }
  367. void Add( const FileBlockingItem& item )
  368. {
  369. // Ack, should use a linked list probably...
  370. while ( m_Items.Count() > MAX_ITEMS )
  371. {
  372. m_Items.Remove( m_Items.Head() );
  373. }
  374. m_Items.AddToTail( item );
  375. }
  376. private:
  377. CUtlLinkedList< FileBlockingItem, unsigned short > m_Items;
  378. CBaseFileSystem *m_pFS;
  379. bool m_bLocked;
  380. };
  381. #endif
  382. CUtlSymbol CBaseFileSystem::m_GamePathID;
  383. CUtlSymbol CBaseFileSystem::m_BSPPathID;
  384. DVDMode_t CBaseFileSystem::m_DVDMode;
  385. bool CBaseFileSystem::m_bFoundXboxImageInCache;
  386. bool CBaseFileSystem::m_bLaunchedFromXboxHDD;
  387. bool CBaseFileSystem::m_bDVDHosted;
  388. bool CBaseFileSystem::m_bAllowXboxInstall;
  389. bool CBaseFileSystem::m_bSearchPathsPatchedAfterInstall;
  390. CUtlVector< FileNameHandle_t > CBaseFileSystem::m_ExcludeFilePaths;
  391. CUtlSortVector< DLCContent_t, CDLCLess > CBaseFileSystem::m_DLCContents;
  392. CUtlVector< DLCCorrupt_t > CBaseFileSystem::m_CorruptDLC;
  393. CUtlBuffer g_UpdateZipBuffer;
  394. CUtlBuffer g_XLSPPatchZipBuffer;
  395. class CStoreIDEntry
  396. {
  397. public:
  398. CStoreIDEntry() {}
  399. CStoreIDEntry( const char *pPathIDStr, int storeID )
  400. {
  401. m_PathIDString = pPathIDStr;
  402. m_StoreID = storeID;
  403. }
  404. public:
  405. CUtlSymbol m_PathIDString;
  406. int m_StoreID;
  407. };
  408. //-----------------------------------------------------------------------------
  409. // CSimpleFileList (used by CFileCRCTracker).
  410. // Uses a dictionary to refer to the list of files.
  411. //-----------------------------------------------------------------------------
  412. class CFileSystemReloadFileList : public IFileList
  413. {
  414. public:
  415. CFileSystemReloadFileList( CBaseFileSystem *pFileSystem )
  416. {
  417. m_pFileSystem = pFileSystem;
  418. }
  419. virtual void Release()
  420. {
  421. delete this;
  422. }
  423. // The engine is calling this for any files it wants to be pure.
  424. // Return true if this file should be reloaded based on its current state and the whitelist that we have now.
  425. virtual bool IsFileInList( const char *pFilename )
  426. {
  427. bool bRet = m_pFileSystem->ShouldGameReloadFile( pFilename );
  428. return bRet;
  429. }
  430. private:
  431. CBaseFileSystem *m_pFileSystem;
  432. };
  433. static CStoreIDEntry* FindPrevFileByStoreID( CUtlDict< CUtlVector<CStoreIDEntry>* ,int> &filesByStoreID, const char *pFilename, const char *pPathIDStr, int foundStoreID )
  434. {
  435. int iEntry = filesByStoreID.Find( pFilename );
  436. if ( iEntry == filesByStoreID.InvalidIndex() )
  437. {
  438. CUtlVector<CStoreIDEntry> *pList = new CUtlVector<CStoreIDEntry>;
  439. pList->AddToTail( CStoreIDEntry(pPathIDStr, foundStoreID) );
  440. filesByStoreID.Insert( pFilename, pList );
  441. return NULL;
  442. }
  443. else
  444. {
  445. // Now is there a previous entry with a different path ID string and the same store ID?
  446. CUtlVector<CStoreIDEntry> *pList = filesByStoreID[iEntry];
  447. for ( int i=0; i < pList->Count(); i++ )
  448. {
  449. CStoreIDEntry &entry = pList->Element( i );
  450. if ( entry.m_StoreID == foundStoreID && V_stricmp( entry.m_PathIDString.String(), pPathIDStr ) != 0 )
  451. return &entry;
  452. }
  453. return NULL;
  454. }
  455. }
  456. //-----------------------------------------------------------------------------
  457. // IIOStats implementation
  458. //-----------------------------------------------------------------------------
  459. #ifndef _CERT
  460. class CIoStats : public IIoStats
  461. {
  462. public:
  463. CIoStats();
  464. ~CIoStats();
  465. virtual void OnFileSeek( int nTimeInMs );
  466. virtual void OnFileRead( int nTimeInMs, int nBytesRead );
  467. virtual void OnFileOpen( const char * pFileName );
  468. virtual int GetNumberOfFileSeeks();
  469. virtual int GetTimeInFileSeek();
  470. virtual int GetNumberOfFileReads();
  471. virtual int GetTimeInFileReads();
  472. virtual int GetFileReadTotalSize();
  473. virtual int GetNumberOfFileOpens();
  474. void Reset();
  475. private:
  476. CInterlockedInt m_nNumberOfFileSeeks;
  477. CInterlockedInt m_nTimeInFileSeek;
  478. CInterlockedInt m_nNumberOfFileReads;
  479. CInterlockedInt m_nTimeInFileRead;
  480. CInterlockedInt m_nFileReadTotalSize;
  481. CInterlockedInt m_nNumberOfFileOpens;
  482. };
  483. static CIoStats s_IoStats;
  484. CIoStats::CIoStats()
  485. :
  486. m_nNumberOfFileSeeks( 0 ),
  487. m_nTimeInFileSeek( 0 ),
  488. m_nNumberOfFileReads( 0 ),
  489. m_nTimeInFileRead( 0 ),
  490. m_nFileReadTotalSize( 0 ),
  491. m_nNumberOfFileOpens( 0 )
  492. {
  493. // Do nothing...
  494. }
  495. CIoStats::~CIoStats()
  496. {
  497. // Do nothing...
  498. }
  499. void CIoStats::OnFileSeek( int nTimeInMs )
  500. {
  501. ++m_nNumberOfFileSeeks;
  502. m_nTimeInFileSeek += nTimeInMs;
  503. }
  504. void CIoStats::OnFileRead( int nTimeInMs, int nBytesRead )
  505. {
  506. ++m_nNumberOfFileReads;
  507. m_nTimeInFileRead += nTimeInMs;
  508. m_nFileReadTotalSize += nBytesRead;
  509. }
  510. void CIoStats::OnFileOpen( const char * pFileName )
  511. {
  512. ++m_nNumberOfFileOpens;
  513. }
  514. int CIoStats::GetNumberOfFileSeeks()
  515. {
  516. return m_nNumberOfFileSeeks;
  517. }
  518. int CIoStats::GetTimeInFileSeek()
  519. {
  520. return m_nTimeInFileSeek;
  521. }
  522. int CIoStats::GetNumberOfFileReads()
  523. {
  524. return m_nNumberOfFileReads;
  525. }
  526. int CIoStats::GetTimeInFileReads()
  527. {
  528. return m_nTimeInFileRead;
  529. }
  530. int CIoStats::GetFileReadTotalSize()
  531. {
  532. return m_nFileReadTotalSize;
  533. }
  534. int CIoStats::GetNumberOfFileOpens()
  535. {
  536. return m_nNumberOfFileOpens;
  537. }
  538. void CIoStats::Reset()
  539. {
  540. m_nNumberOfFileSeeks = 0;
  541. m_nTimeInFileSeek = 0;
  542. m_nNumberOfFileReads = 0;
  543. m_nTimeInFileRead = 0;
  544. m_nFileReadTotalSize = 0;
  545. m_nNumberOfFileOpens = 0;
  546. }
  547. #endif
  548. //-----------------------------------------------------------------------------
  549. // constructor
  550. //-----------------------------------------------------------------------------
  551. CBaseFileSystem::CBaseFileSystem()
  552. : m_FileTracker( this ), m_FileWhitelist( NULL ),m_FileTracker2( this )
  553. {
  554. #if !( defined(_WIN32) && defined(DEDICATED) )
  555. g_pBaseFileSystem = this;
  556. #endif
  557. g_pFullFileSystem = this; // Left in for non tier Apps, tools, etc...
  558. m_WhitelistFileTrackingEnabled = -1;
  559. // If this changes then FileNameHandleInternal_t/FileNameHandle_t needs to be fixed!!!
  560. Assert( sizeof( CUtlSymbol ) == sizeof( short ) );
  561. // Clear out statistics
  562. memset( &m_Stats, 0, sizeof(m_Stats) );
  563. m_fwLevel = FILESYSTEM_WARNING_REPORTUNCLOSED;
  564. m_pfnWarning = NULL;
  565. m_pLogFile = NULL;
  566. m_bOutputDebugString = false;
  567. m_WhitelistSpewFlags = 0;
  568. m_DirtyDiskReportFunc = NULL;
  569. m_pThreadPool = NULL;
  570. #if defined( TRACK_BLOCKING_IO )
  571. m_pBlockingItems = new CBlockingFileItemList( this );
  572. m_bBlockingFileAccessReportingEnabled = false;
  573. m_bAllowSynchronousLogging = true;
  574. #endif
  575. m_iMapLoad = 0;
  576. m_DVDMode = DVDMODE_STRICT;
  577. if ( IsGameConsole() )
  578. {
  579. if ( CommandLine()->FindParm( "-dvddev" ) )
  580. {
  581. // FIXME: Is there a way of determining the host is a vista machine without
  582. // needing to explicitly say so on the command line?
  583. bool bIsVista = ( CommandLine()->CheckParm( "-vista" ) != NULL );
  584. if ( bIsVista )
  585. {
  586. m_DVDMode = DVDMODE_DEV_VISTA;
  587. }
  588. else
  589. {
  590. m_DVDMode = DVDMODE_DEV;
  591. }
  592. }
  593. }
  594. #ifdef SUPPORT_IODELAY_MONITORING
  595. m_pDelayThread = NULL;
  596. m_flDelayLimit = 0;
  597. #endif
  598. }
  599. //-----------------------------------------------------------------------------
  600. // Purpose:
  601. //-----------------------------------------------------------------------------
  602. CBaseFileSystem::~CBaseFileSystem()
  603. {
  604. m_PathIDInfos.PurgeAndDeleteElements();
  605. #if defined( TRACK_BLOCKING_IO )
  606. delete m_pBlockingItems;
  607. #endif
  608. // Free the whitelist.
  609. RegisterFileWhitelist( NULL, NULL, NULL );
  610. }
  611. //-----------------------------------------------------------------------------
  612. // Methods of IAppSystem
  613. //-----------------------------------------------------------------------------
  614. void *CBaseFileSystem::QueryInterface( const char *pInterfaceName )
  615. {
  616. // We also implement the IMatSystemSurface interface
  617. if (!Q_strncmp( pInterfaceName, BASEFILESYSTEM_INTERFACE_VERSION, Q_strlen(BASEFILESYSTEM_INTERFACE_VERSION) + 1))
  618. return (IBaseFileSystem*)this;
  619. return NULL;
  620. }
  621. #ifdef _PS3
  622. // this is strictly a debug variable used to catch errors where we load and tear down more than one filesystem:
  623. static int s_PS3_libfs_ref_count = 0;
  624. #endif
  625. InitReturnVal_t CBaseFileSystem::Init()
  626. {
  627. m_FileTracker2.InitAsyncThread();
  628. InitReturnVal_t nRetVal = BaseClass::Init();
  629. if ( nRetVal != INIT_OK )
  630. return nRetVal;
  631. #ifdef _PS3
  632. // load the PS3's file system module to memory
  633. AssertMsg1( s_PS3_libfs_ref_count == 0, "%d CBaseFileSystems were instantiated!\n", s_PS3_libfs_ref_count+1 );
  634. if ( cellSysmoduleLoadModule(CELL_SYSMODULE_FS) != CELL_OK )
  635. {
  636. Error( "Could not load system libfs!\n" );
  637. }
  638. else
  639. {
  640. s_PS3_libfs_ref_count += 1;
  641. }
  642. #endif
  643. // This is a special tag to allow iterating just the BSP file, it doesn't show up in the list per se, but gets converted to "GAME" in the filter function
  644. m_BSPPathID = g_PathIDTable.AddString( "BSP" );
  645. m_GamePathID = g_PathIDTable.AddString( "GAME" );
  646. if ( getenv( "fs_debug" ) )
  647. {
  648. m_bOutputDebugString = true;
  649. }
  650. const char *logFileName = CommandLine()->ParmValue( "-fs_log" );
  651. if ( logFileName )
  652. {
  653. m_pLogFile = fopen( logFileName, "w" ); // STEAM OK
  654. if ( !m_pLogFile )
  655. return INIT_FAILED;
  656. fprintf( m_pLogFile, "@echo off\n" );
  657. fprintf( m_pLogFile, "setlocal\n" );
  658. const char *fs_target = CommandLine()->ParmValue( "-fs_target" );
  659. if( fs_target )
  660. {
  661. fprintf( m_pLogFile, "set fs_target=\"%s\"\n", fs_target );
  662. }
  663. fprintf( m_pLogFile, "if \"%%fs_target%%\" == \"\" goto error\n" );
  664. fprintf( m_pLogFile, "@echo on\n" );
  665. }
  666. InitAsync();
  667. if ( IsGameConsole() )
  668. {
  669. BuildExcludeList();
  670. }
  671. #if defined( _X360 )
  672. MEM_ALLOC_CREDIT();
  673. #if defined( _DEMO )
  674. // under demo conditions cannot allow install or use existing install
  675. // slam to expected state, do not override
  676. m_bLaunchedFromXboxHDD = false;
  677. m_bFoundXboxImageInCache = false;
  678. m_bAllowXboxInstall = false;
  679. m_bDVDHosted = true;
  680. #else
  681. // determine the type of system where we launched from
  682. // this allows other systems (like the installer) to conditionalize the install process
  683. // MS may very well auto-install for us at a later date
  684. DWORD dwDummyFlags;
  685. char szFileSystemName[MAX_PATH];
  686. DWORD dwResult = GetVolumeInformation(
  687. "D:\\",
  688. NULL,
  689. 0,
  690. NULL,
  691. 0,
  692. &dwDummyFlags,
  693. szFileSystemName,
  694. sizeof( szFileSystemName ) );
  695. if ( dwResult != 0 )
  696. {
  697. m_bLaunchedFromXboxHDD = ( V_stricmp( szFileSystemName, "FATX" ) == 0 );
  698. }
  699. if ( m_DVDMode == DVDMODE_STRICT )
  700. {
  701. // must be in a strict dvd environment and not explicitly disabled
  702. if ( !CommandLine()->FindParm( "-noinstall" ) )
  703. {
  704. // the install is allowed if we launched from anywhere but the HDD
  705. // or it can be tested from the HDD by forcing with command line options
  706. m_bAllowXboxInstall = ( m_bLaunchedFromXboxHDD == false ) ||
  707. ( CommandLine()->FindParm( "-installer" ) != 0 ) ||
  708. ( CommandLine()->FindParm( "-install" ) != 0 );
  709. if ( m_bAllowXboxInstall )
  710. {
  711. // install may have already occurred
  712. m_bFoundXboxImageInCache = IsAlreadyInstalledToXboxHDDCache();
  713. if ( m_bFoundXboxImageInCache )
  714. {
  715. // we are using the installed image
  716. // no further installer activity is ever allowed (as the targets will be opened)
  717. m_bAllowXboxInstall = false;
  718. }
  719. }
  720. }
  721. // The update zip is designed to be held resident to avoid MU yanking or other transient issues.
  722. // The zip is expected to be < 100K and is a special compressed format.
  723. const char *pszUpdatePath = "UPDATE:\\update\\update" PLATFORM_EXT ".zip";
  724. if ( !IsCert() && !FileExists( pszUpdatePath ) )
  725. {
  726. // allows us to fallback and test when it is in the image
  727. pszUpdatePath = "D:\\update\\update" PLATFORM_EXT ".zip";
  728. }
  729. ReadFile( pszUpdatePath, NULL, g_UpdateZipBuffer, 0, 0 );
  730. }
  731. // if we are in any way HDD based, we do not want the reduced DVD experience
  732. m_bDVDHosted = ( m_bAllowXboxInstall || CommandLine()->FindParm( "-dvdtest" ) ||
  733. ( !m_bLaunchedFromXboxHDD && !m_bFoundXboxImageInCache ) );
  734. #endif
  735. #elif defined( _PS3 )
  736. m_bLaunchedFromXboxHDD = true;
  737. m_bFoundXboxImageInCache = false;
  738. m_bAllowXboxInstall = false;
  739. m_bDVDHosted = false;
  740. SetupFios();
  741. #endif
  742. return INIT_OK;
  743. }
  744. void CBaseFileSystem::Shutdown()
  745. {
  746. ShutdownAsync();
  747. m_FileTracker2.ShutdownAsync();
  748. #if !defined( _X360 ) && !defined( _PS3 )
  749. if( m_pLogFile )
  750. {
  751. if( CommandLine()->FindParm( "-fs_logbins" ) >= 0 )
  752. {
  753. char cwd[MAX_FILEPATH];
  754. getcwd( cwd, MAX_FILEPATH-1 );
  755. fprintf( m_pLogFile, "set binsrc=\"%s\"\n", cwd );
  756. fprintf( m_pLogFile, "mkdir \"%%fs_target%%\"\n" );
  757. fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2.exe\" \"%%fs_target%%\"\n" );
  758. fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2.dat\" \"%%fs_target%%\"\n" );
  759. fprintf( m_pLogFile, "mkdir \"%%fs_target%%\\bin\"\n" );
  760. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\*.asi\" \"%%fs_target%%\\bin\"\n" );
  761. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\materialsystem.dll\" \"%%fs_target%%\\bin\"\n" );
  762. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\shaderapidx9.dll\" \"%%fs_target%%\\bin\"\n" );
  763. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\filesystem_stdio.dll\" \"%%fs_target%%\\bin\"\n" );
  764. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\soundemittersystem.dll\" \"%%fs_target%%\\bin\"\n" );
  765. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\stdshader*.dll\" \"%%fs_target%%\\bin\"\n" );
  766. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\shader_nv*.dll\" \"%%fs_target%%\\bin\"\n" );
  767. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\launcher.dll\" \"%%fs_target%%\\bin\"\n" );
  768. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\engine.dll\" \"%%fs_target%%\\bin\"\n" );
  769. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\mss32.dll\" \"%%fs_target%%\\bin\"\n" );
  770. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\tier0.dll\" \"%%fs_target%%\\bin\"\n" );
  771. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vgui2.dll\" \"%%fs_target%%\\bin\"\n" );
  772. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vguimatsurface.dll\" \"%%fs_target%%\\bin\"\n" );
  773. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\voice_miles.dll\" \"%%fs_target%%\\bin\"\n" );
  774. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vphysics.dll\" \"%%fs_target%%\\bin\"\n" );
  775. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vstdlib.dll\" \"%%fs_target%%\\bin\"\n" );
  776. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\studiorender.dll\" \"%%fs_target%%\\bin\"\n" );
  777. fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vaudio_miles.dll\" \"%%fs_target%%\\bin\"\n" );
  778. fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2\\resource\\*.ttf\" \"%%fs_target%%\\hl2\\resource\"\n" );
  779. fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2\\bin\\gameui.dll\" \"%%fs_target%%\\hl2\\bin\"\n" );
  780. }
  781. fprintf( m_pLogFile, "goto done\n" );
  782. fprintf( m_pLogFile, ":error\n" );
  783. fprintf( m_pLogFile, "echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\"\n" );
  784. fprintf( m_pLogFile, "echo ERROR: must set fs_target=targetpath (ie. \"set fs_target=u:\\destdir\")!\n" );
  785. fprintf( m_pLogFile, "echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\"\n" );
  786. fprintf( m_pLogFile, ":done\n" );
  787. fclose( m_pLogFile ); // STEAM OK
  788. }
  789. #endif
  790. RemoveAllSearchPaths();
  791. Trace_DumpUnclosedFiles();
  792. #if defined( _PS3 )
  793. TeardownFios();
  794. if ( --s_PS3_libfs_ref_count == 0 )
  795. {
  796. cellSysmoduleUnloadModule(CELL_SYSMODULE_FS);
  797. }
  798. else
  799. {
  800. AssertMsg( false, "More than one CBaseFileSystem was instantiated! Failsafe triggered to refcount sysutil libfs.\n" );
  801. }
  802. #endif
  803. BaseClass::Shutdown();
  804. }
  805. void CBaseFileSystem::BuildExcludeList()
  806. {
  807. if ( !IsGameConsole() || m_DVDMode != DVDMODE_DEV )
  808. {
  809. // xbox only
  810. return;
  811. }
  812. // clear prior results
  813. m_ExcludeFilePaths.Purge();
  814. // exclude paths are valid only in dvddev mode
  815. // the optional exclude path file should exist in the dvddev cache
  816. // populate the exclusion list
  817. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  818. if ( ReadFile( FS_DVDDEV_ROOT "/" FS_EXCLUDE_PATHS_FILENAME, NULL, buf, 0, 0 ) )
  819. {
  820. characterset_t breakSet;
  821. CharacterSetBuild( &breakSet, "" );
  822. char szPath[MAX_PATH];
  823. char szToken[MAX_PATH];
  824. for ( ;; )
  825. {
  826. int nTokenSize = buf.ParseToken( &breakSet, szToken, sizeof( szToken ) );
  827. if ( nTokenSize <= 0 )
  828. {
  829. break;
  830. }
  831. char *pToken = szToken;
  832. #ifndef _CERT
  833. // At some point, we should also test that the corresponding file exist in the normal path
  834. // It is a bit more complicated on consoles due to the zip files.
  835. V_ComposeFileName( FS_DVDDEV_ROOT, pToken, szPath, sizeof( szPath ) );
  836. bool bExist = FileExists( szPath );
  837. if ( bExist == false)
  838. {
  839. Warning( "File '%s' does not exist. Won't be added to dvddev.\n", szPath );
  840. continue;
  841. }
  842. #endif
  843. V_ComposeFileName( FS_DVDDEV_REMAP_ROOT, pToken, szPath, sizeof( szPath ) );
  844. FileNameHandle_t hFileName = FindOrAddFileName( szPath );
  845. if ( m_ExcludeFilePaths.Find( hFileName ) == -1 )
  846. {
  847. m_ExcludeFilePaths.AddToTail( hFileName );
  848. DevMsg( "File '%s' added to dvddev.\n", szPath );
  849. }
  850. }
  851. }
  852. }
  853. //-----------------------------------------------------------------------------
  854. // Computes a full write path
  855. //-----------------------------------------------------------------------------
  856. inline void CBaseFileSystem::ComputeFullWritePath( char* pDest, int maxlen, const char *pRelativePath, const char *pWritePathID )
  857. {
  858. Q_strncpy( pDest, GetWritePath( pRelativePath, pWritePathID ), maxlen );
  859. Q_strncat( pDest, pRelativePath, maxlen, COPY_ALL_CHARACTERS );
  860. Q_FixSlashes( pDest );
  861. }
  862. //-----------------------------------------------------------------------------
  863. // Purpose:
  864. // Input : src1 -
  865. // src2 -
  866. // Output : Returns true on success, false on failure.
  867. //-----------------------------------------------------------------------------
  868. bool CBaseFileSystem::OpenedFileLessFunc( COpenedFile const& src1, COpenedFile const& src2 )
  869. {
  870. return src1.m_pFile < src2.m_pFile;
  871. }
  872. void CBaseFileSystem::InstallDirtyDiskReportFunc( FSDirtyDiskReportFunc_t func )
  873. {
  874. m_DirtyDiskReportFunc = func;
  875. }
  876. //-----------------------------------------------------------------------------
  877. // Purpose:
  878. // Input : *fullpath -
  879. //-----------------------------------------------------------------------------
  880. void CBaseFileSystem::LogAccessToFile( char const *accesstype, char const *fullpath, char const *options )
  881. {
  882. LOCAL_THREAD_LOCK();
  883. if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES )
  884. {
  885. FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES, "---FS%s: %s %s (%.3f)\n", ThreadInMainThread() ? "" : "[a]", accesstype, fullpath, Plat_FloatTime() );
  886. }
  887. int c = m_LogFuncs.Count();
  888. if ( !c )
  889. return;
  890. for ( int i = 0; i < c; ++i )
  891. {
  892. ( m_LogFuncs[ i ] )( fullpath, options );
  893. }
  894. }
  895. //-----------------------------------------------------------------------------
  896. // Purpose:
  897. // Input : *filename -
  898. // *options -
  899. // Output : FILE
  900. //-----------------------------------------------------------------------------
  901. FILE *CBaseFileSystem::Trace_FOpen( const char *filename, const char *options, unsigned flags, int64 *size, CFileLoadInfo *pInfo )
  902. {
  903. if ( m_NonexistingFilesExtensions.GetNumStrings() )
  904. {
  905. if ( char const *pszExt = V_GetFileExtension( filename ) )
  906. {
  907. AUTO_LOCK( m_OpenedFilesMutex );
  908. UtlSymId_t symFound = m_NonexistingFilesExtensions.Find( pszExt );
  909. if ( ( symFound != UTL_INVAL_SYMBOL ) && m_NonexistingFilesExtensions[ symFound ] )
  910. {
  911. DevWarning( "Known VPK-only extension [%s], file {%s} declared missing. Run with -fullfsvalveds to search filesystem.\n", pszExt, filename );
  912. return NULL;
  913. }
  914. }
  915. }
  916. #ifdef NONEXISTING_FILES_CACHE_SUPPORT
  917. bool bReadOnlyRequest = !strchr(options,'w') && !strchr(options,'a') && !strchr(options,'+');
  918. static bool s_bNeverCheckFS = !CommandLine()->FindParm( "-alwayscheckfs" );
  919. if ( s_bNeverCheckFS )
  920. {
  921. AUTO_LOCK( m_OpenedFilesMutex );
  922. UtlSymId_t symFound = m_NonexistingFilesCache.Find( filename );
  923. if ( symFound != UTL_INVAL_SYMBOL )
  924. {
  925. double &refCacheTime = m_NonexistingFilesCache[ symFound ];
  926. if ( bReadOnlyRequest )
  927. {
  928. if ( refCacheTime != 0.0 )
  929. {
  930. Warning( "Trace_FOpen: duplicate request for missing file: %s [was missing %.3f sec ago]\n", filename, Plat_FloatTime() - refCacheTime );
  931. return NULL; // we looked for this file already, it doesn't exist
  932. }
  933. else
  934. {
  935. // This file was previously missing, but a write request was made and could have created the file, so this read call should fall through
  936. }
  937. }
  938. else
  939. {
  940. // This is possibly a write request, so remove cached ENOENT record
  941. Warning( "Trace_FOpen: possibly write request for missing file: %s [was missing %.3f sec ago]\n", filename, Plat_FloatTime() - refCacheTime );
  942. refCacheTime = 0.0f;
  943. }
  944. }
  945. else
  946. {
  947. // Nothing known about this file, fall through into syscall to fopen
  948. }
  949. }
  950. #endif
  951. AUTOBLOCKREPORTER_FN( Trace_FOpen, this, true, filename, FILESYSTEM_BLOCKING_SYNCHRONOUS, FileBlockingItem::FB_ACCESS_OPEN );
  952. FILE *fp = FS_fopen( filename, options, flags, size, pInfo );
  953. #ifdef NONEXISTING_FILES_CACHE_SUPPORT
  954. if ( s_bNeverCheckFS && !fp && bReadOnlyRequest )
  955. {
  956. double dblNow = Plat_FloatTime();
  957. AUTO_LOCK( m_OpenedFilesMutex );
  958. m_NonexistingFilesCache[ filename ] = dblNow;
  959. Warning( "Trace_FOpen: missing file: %s [will never check again]\n", filename );
  960. }
  961. #endif
  962. if ( fp )
  963. {
  964. if ( options[0] == 'r' )
  965. {
  966. FS_setbufsize(fp, filesystem_buffer_size.GetInt() );
  967. }
  968. else
  969. {
  970. FS_setbufsize(fp, 32*1024 );
  971. }
  972. AUTO_LOCK( m_OpenedFilesMutex );
  973. COpenedFile file;
  974. file.SetName( filename );
  975. file.m_pFile = fp;
  976. m_OpenedFiles.AddToTail( file );
  977. LogAccessToFile( "open", filename, options );
  978. }
  979. return fp;
  980. }
  981. void CBaseFileSystem::GetFileNameForHandle( FileHandle_t handle, char *buf, size_t buflen )
  982. {
  983. V_strncpy( buf, "Unknown", buflen );
  984. /*
  985. CFileHandle *fh = ( CFileHandle *)handle;
  986. if ( !fh )
  987. {
  988. buf[ 0 ] = 0;
  989. return;
  990. }
  991. // Pack file filehandles store the underlying name for convenience
  992. if ( fh->IsPack() )
  993. {
  994. Q_strncpy( buf, fh->Name(), buflen );
  995. return;
  996. }
  997. AUTO_LOCK( m_OpenedFilesMutex );
  998. COpenedFile file;
  999. file.m_pFile = fh->GetFileHandle();
  1000. int result = m_OpenedFiles.Find( file );
  1001. if ( result != -1 )
  1002. {
  1003. COpenedFile found = m_OpenedFiles[ result ];
  1004. Q_strncpy( buf, found.GetName(), buflen );
  1005. }
  1006. else
  1007. {
  1008. buf[ 0 ] = 0;
  1009. }
  1010. */
  1011. }
  1012. //-----------------------------------------------------------------------------
  1013. // Purpose:
  1014. // Input : *fp -
  1015. //-----------------------------------------------------------------------------
  1016. void CBaseFileSystem::Trace_FClose( FILE *fp )
  1017. {
  1018. if ( fp )
  1019. {
  1020. m_OpenedFilesMutex.Lock();
  1021. COpenedFile file;
  1022. file.m_pFile = fp;
  1023. int result = m_OpenedFiles.Find( file );
  1024. if ( result != -1 /*m_OpenedFiles.InvalidIdx()*/ )
  1025. {
  1026. COpenedFile found = m_OpenedFiles[ result ];
  1027. if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES )
  1028. {
  1029. FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES, "---FS%s: close %s %p %i (%.3f)\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), fp, m_OpenedFiles.Count(), Plat_FloatTime() );
  1030. }
  1031. m_OpenedFiles.Remove( result );
  1032. }
  1033. else
  1034. {
  1035. Assert( 0 );
  1036. if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES )
  1037. {
  1038. FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES, "Tried to close unknown file pointer %p\n", fp );
  1039. }
  1040. }
  1041. m_OpenedFilesMutex.Unlock();
  1042. FS_fclose( fp );
  1043. }
  1044. }
  1045. void CBaseFileSystem::Trace_FRead( int size, FILE* fp )
  1046. {
  1047. if ( !fp || m_fwLevel < FILESYSTEM_WARNING_REPORTALLACCESSES_READ )
  1048. return;
  1049. AUTO_LOCK( m_OpenedFilesMutex );
  1050. COpenedFile file;
  1051. file.m_pFile = fp;
  1052. int result = m_OpenedFiles.Find( file );
  1053. if( result != -1 )
  1054. {
  1055. COpenedFile found = m_OpenedFiles[ result ];
  1056. FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES_READ, "---FS%s: read %s %i %p (%.3f)\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), size, fp, Plat_FloatTime() );
  1057. }
  1058. else
  1059. {
  1060. FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES_READ, "Tried to read %i bytes from unknown file pointer %p\n", size, fp );
  1061. }
  1062. }
  1063. void CBaseFileSystem::Trace_FWrite( int size, FILE* fp )
  1064. {
  1065. if ( !fp || m_fwLevel < FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE )
  1066. return;
  1067. COpenedFile file;
  1068. file.m_pFile = fp;
  1069. AUTO_LOCK( m_OpenedFilesMutex );
  1070. int result = m_OpenedFiles.Find( file );
  1071. if( result != -1 )
  1072. {
  1073. COpenedFile found = m_OpenedFiles[ result ];
  1074. FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE, "---FS%s: write %s %i %p\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), size, fp );
  1075. }
  1076. else
  1077. {
  1078. FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE, "Tried to write %i bytes from unknown file pointer %p\n", size, fp );
  1079. }
  1080. }
  1081. //-----------------------------------------------------------------------------
  1082. // Purpose:
  1083. //-----------------------------------------------------------------------------
  1084. void CBaseFileSystem::Trace_DumpUnclosedFiles( void )
  1085. {
  1086. AUTO_LOCK( m_OpenedFilesMutex );
  1087. for ( int i = 0 ; i < m_OpenedFiles.Count(); i++ )
  1088. {
  1089. COpenedFile *found = &m_OpenedFiles[ i ];
  1090. if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTUNCLOSED )
  1091. {
  1092. FileSystemWarning( FILESYSTEM_WARNING_REPORTUNCLOSED, "File %s was never closed\n", found->GetName() );
  1093. }
  1094. }
  1095. }
  1096. //-----------------------------------------------------------------------------
  1097. // Purpose:
  1098. //-----------------------------------------------------------------------------
  1099. void CBaseFileSystem::PrintOpenedFiles( void )
  1100. {
  1101. FileWarningLevel_t saveLevel = m_fwLevel;
  1102. m_fwLevel = FILESYSTEM_WARNING_REPORTUNCLOSED;
  1103. Trace_DumpUnclosedFiles();
  1104. m_fwLevel = saveLevel;
  1105. }
  1106. void CBaseFileSystem::AddVPKFile( char const *pBasename, SearchPathAdd_t addType )
  1107. {
  1108. // Ensure that the passed in file name has a .vpk extension. Otherwise the check
  1109. // for already having the .vpk file will always fail and the same file may get
  1110. // added dozens of times, wasting hundreds of MB of memory.
  1111. const char *pExtension = strrchr( pBasename, '.' );
  1112. Assert( pExtension && V_strcmp( pExtension, ".vpk" ) == 0 );
  1113. if ( !pExtension || V_strcmp( pExtension, ".vpk" ) )
  1114. {
  1115. Warning( "Extensionless VPK file '%s' specified. Ignoring.\n", pBasename );
  1116. return;
  1117. }
  1118. #ifdef SUPPORT_VPK
  1119. char nameBuf[MAX_PATH];
  1120. Q_MakeAbsolutePath( nameBuf, sizeof( nameBuf ), pBasename );
  1121. #ifdef _WIN32
  1122. Q_strlower( nameBuf );
  1123. #endif
  1124. Q_FixSlashes( nameBuf );
  1125. // see if we already have this vpk file
  1126. for( int i = 0; i < m_VPKFiles.Count(); i++ )
  1127. {
  1128. if ( ! V_strcmp( m_VPKFiles[i]->FullPathName(), nameBuf ) )
  1129. {
  1130. return; // already have this one
  1131. }
  1132. }
  1133. char pszFName[MAX_PATH];
  1134. CPackedStore *pNew = new CPackedStore( nameBuf, pszFName, this );
  1135. pNew->RegisterFileTracker( (IThreadedFileMD5Processor *)&m_FileTracker2 );
  1136. if ( pNew->IsEmpty() )
  1137. {
  1138. delete pNew;
  1139. }
  1140. else
  1141. {
  1142. if ( PATH_ADD_TO_TAIL == addType )
  1143. {
  1144. m_VPKFiles.AddToTail( pNew );
  1145. }
  1146. else
  1147. {
  1148. m_VPKFiles.AddToHead( pNew );
  1149. }
  1150. char szRelativePathName[512];
  1151. Assert ( V_IsAbsolutePath( pNew->FullPathName() ) );
  1152. char szBasePath[MAX_PATH];
  1153. V_strncpy( szBasePath, pNew->FullPathName(), sizeof(szBasePath) );
  1154. V_StripFilename( szBasePath );
  1155. V_StripLastDir( szBasePath, sizeof(szBasePath) );
  1156. V_MakeRelativePath( pNew->FullPathName(), szBasePath, szRelativePathName, sizeof( szRelativePathName ) );
  1157. pNew->m_PackFileID = m_FileTracker2.NotePackFileOpened( pszFName, szRelativePathName, "GAME", 0 );
  1158. }
  1159. #endif
  1160. }
  1161. void CBaseFileSystem::RemoveVPKFile( char const *pBasename )
  1162. {
  1163. #ifdef SUPPORT_VPK
  1164. char nameBuf[MAX_PATH];
  1165. Q_MakeAbsolutePath( nameBuf, sizeof( nameBuf ), pBasename );
  1166. Q_strlower( nameBuf );
  1167. Q_FixSlashes( nameBuf );
  1168. // see if we already have this vpk file
  1169. for( int i = 0; i < m_VPKFiles.Count(); i++ )
  1170. {
  1171. if ( ! V_strcmp( m_VPKFiles[i]->FullPathName(), nameBuf ) )
  1172. {
  1173. delete m_VPKFiles[i];
  1174. m_VPKFiles.Remove( i );
  1175. break; // already have this one
  1176. }
  1177. }
  1178. #endif
  1179. }
  1180. void CBaseFileSystem::GetVPKFileNames( CUtlVector<CUtlString> &destVector )
  1181. {
  1182. #ifdef SUPPORT_VPK
  1183. for( int i = 0; i < m_VPKFiles.Count(); i++ )
  1184. {
  1185. destVector.AddToTail( CUtlString( m_VPKFiles[i]->FullPathName() ) );
  1186. }
  1187. #endif
  1188. }
  1189. //-----------------------------------------------------------------------------
  1190. // Purpose: Adds the specified pack file to the list
  1191. // Output : Returns true on success, false on failure.
  1192. //-----------------------------------------------------------------------------
  1193. bool CBaseFileSystem::AddPackFile( const char *pFileName, const char *pathID )
  1194. {
  1195. CHECK_DOUBLE_SLASHES( pFileName );
  1196. AsyncFinishAll();
  1197. return AddPackFileFromPath( "", pFileName, true, pathID );
  1198. }
  1199. //-----------------------------------------------------------------------------
  1200. // Purpose: Adds a pack file from the specified path
  1201. // Output : Returns true on success, false on failure.
  1202. //-----------------------------------------------------------------------------
  1203. bool CBaseFileSystem::AddPackFileFromPath( const char *pPath, const char *pakfile, bool bCheckForAppendedPack, const char *pathID )
  1204. {
  1205. char fullpath[ MAX_PATH ];
  1206. _snprintf( fullpath, sizeof(fullpath), "%s%s", pPath, pakfile );
  1207. Q_FixSlashes( fullpath );
  1208. struct _stat buf;
  1209. if ( FS_stat( fullpath, &buf ) == -1 )
  1210. return false;
  1211. CPackFile *pf = new CZipPackFile( this );
  1212. pf->m_hPackFileHandleFS = Trace_FOpen( fullpath, "rb", 0, NULL );
  1213. if ( pf->m_hPackFileHandleFS )
  1214. {
  1215. // Get the length of the pack file:
  1216. FS_fseek( ( FILE * )pf->m_hPackFileHandleFS, 0, FILESYSTEM_SEEK_TAIL );
  1217. int64 len = FS_ftell( ( FILE * )pf->m_hPackFileHandleFS );
  1218. FS_fseek( ( FILE * )pf->m_hPackFileHandleFS, 0, FILESYSTEM_SEEK_HEAD );
  1219. if ( !pf->Prepare( len ) )
  1220. {
  1221. // Failed for some reason, ignore it
  1222. Trace_FClose( pf->m_hPackFileHandleFS );
  1223. pf->m_hPackFileHandleFS = NULL;
  1224. delete pf;
  1225. return false;
  1226. }
  1227. }
  1228. #ifdef SUPPORT_VPK
  1229. else
  1230. {
  1231. pf->m_hPackFileHandleVPK = FindFileInVPKs( fullpath );
  1232. if ( pf->m_hPackFileHandleVPK )
  1233. {
  1234. // Get the length of the pack file:
  1235. pf->m_hPackFileHandleVPK.Seek( 0, FILESYSTEM_SEEK_TAIL );
  1236. int64 len = pf->m_hPackFileHandleVPK.Tell();
  1237. pf->m_hPackFileHandleVPK.Seek( 0, FILESYSTEM_SEEK_HEAD );
  1238. if ( !pf->Prepare( len ) )
  1239. {
  1240. // Failed for some reason, ignore it
  1241. delete pf;
  1242. return false;
  1243. }
  1244. }
  1245. }
  1246. #endif
  1247. // Add this pack file to the search path:
  1248. CSearchPath *sp = &m_SearchPaths[ m_SearchPaths.AddToTail() ];
  1249. pf->SetPath( sp->GetPath() );
  1250. pf->m_lPackFileTime = GetFileTime( pakfile );
  1251. sp->SetPath( pPath );
  1252. sp->m_pPathIDInfo->SetPathID( pathID );
  1253. sp->SetPackFile( pf );
  1254. return true;
  1255. }
  1256. // Read a bit of the file from the pack file:
  1257. int CPackFileHandle::Read( void* pBuffer, int nDestSize, int nBytes )
  1258. {
  1259. // Clamp nBytes to not go past the end of the file (async is still possible due to nDestSize)
  1260. if ( nBytes + m_nFilePointer > m_nLength )
  1261. {
  1262. nBytes = m_nLength - m_nFilePointer;
  1263. }
  1264. // Seek to the given file pointer and read
  1265. int nBytesRead = m_pOwner->ReadFromPack( m_nIndex, pBuffer, nDestSize, nBytes, m_nBase + m_nFilePointer );
  1266. m_nFilePointer += nBytesRead;
  1267. return nBytesRead;
  1268. }
  1269. // Seek around inside the pack:
  1270. int CPackFileHandle::Seek( int nOffset, int nWhence )
  1271. {
  1272. if ( nWhence == SEEK_SET )
  1273. {
  1274. m_nFilePointer = nOffset;
  1275. }
  1276. else if ( nWhence == SEEK_CUR )
  1277. {
  1278. m_nFilePointer += nOffset;
  1279. }
  1280. else if ( nWhence == SEEK_END )
  1281. {
  1282. m_nFilePointer = m_nLength + nOffset;
  1283. }
  1284. // Clamp the file pointer to the actual bounds of the file:
  1285. if ( m_nFilePointer > m_nLength )
  1286. {
  1287. m_nFilePointer = m_nLength;
  1288. }
  1289. return m_nFilePointer;
  1290. }
  1291. //-----------------------------------------------------------------------------
  1292. // Low Level I/O routine for reading from pack files.
  1293. // Offsets all reads by the base of the pack file as needed.
  1294. // Return bytes read.
  1295. //-----------------------------------------------------------------------------
  1296. int CPackFile::ReadFromPack( int nIndex, void* buffer, int nDestBytes, int nBytes, int64 nOffset )
  1297. {
  1298. m_mutex.Lock();
  1299. if ( fs_monitor_read_from_pack.GetInt() == 1 || ( fs_monitor_read_from_pack.GetInt() == 2 && ThreadInMainThread() ) )
  1300. {
  1301. // spew info about real i/o request
  1302. char szName[MAX_PATH];
  1303. IndexToFilename( nIndex, szName, sizeof( szName ) );
  1304. Msg( "Read From Pack: Sync I/O: Requested:%7d, Offset:0x%16.16llx, %s\n", nBytes, m_nBaseOffset + nOffset, szName );
  1305. }
  1306. int nBytesRead = 0;
  1307. // Seek to the start of the read area and perform the read: TODO: CHANGE THIS INTO A CFileHandle
  1308. if ( m_hPackFileHandleFS )
  1309. {
  1310. m_fs->FS_fseek( m_hPackFileHandleFS, m_nBaseOffset + nOffset, SEEK_SET );
  1311. nBytesRead = m_fs->FS_fread( buffer, nDestBytes, nBytes, m_hPackFileHandleFS );
  1312. }
  1313. #ifdef SUPPORT_VPK
  1314. else if ( m_hPackFileHandleVPK )
  1315. {
  1316. // We're a packfile embedded in a VPK
  1317. m_hPackFileHandleVPK.Seek( m_nBaseOffset + nOffset, FILESYSTEM_SEEK_HEAD );
  1318. nBytesRead = m_hPackFileHandleVPK.Read( buffer, nBytes );
  1319. }
  1320. #endif
  1321. else
  1322. {
  1323. Error("Failure in CPackFile::ReadFromPack(): m_hPackFileHandleFS and/or m_hPackFileHandleVPK are uninitialized - The file open call(s) likely failed\n");
  1324. }
  1325. m_mutex.Unlock();
  1326. return nBytesRead;
  1327. }
  1328. //-----------------------------------------------------------------------------
  1329. // Open a file inside of a pack file.
  1330. //-----------------------------------------------------------------------------
  1331. CFileHandle *CPackFile::OpenFile( const char *pFileName, const char *pOptions )
  1332. {
  1333. int nIndex, nLength;
  1334. int64 nPosition;
  1335. // find the file's location in the pack
  1336. if ( FindFile( pFileName, nIndex, nPosition, nLength ) )
  1337. {
  1338. m_mutex.Lock();
  1339. #ifdef SUPPORT_VPK
  1340. if ( m_nOpenFiles == 0 && m_hPackFileHandleFS == NULL && !m_hPackFileHandleVPK )
  1341. #else
  1342. if ( m_nOpenFiles == 0 && m_hPackFileHandleFS == NULL )
  1343. #endif
  1344. {
  1345. // Try to open it as a regular file first
  1346. m_hPackFileHandleFS = m_fs->Trace_FOpen( m_ZipName, "rb", 0, NULL );
  1347. #ifdef SUPPORT_VPK
  1348. // Try opening from a VPK
  1349. if ( !m_hPackFileHandleFS )
  1350. {
  1351. m_hPackFileHandleVPK = m_fs->FindFileInVPKs( pFileName );
  1352. }
  1353. #endif
  1354. }
  1355. m_nOpenFiles++;
  1356. m_mutex.Unlock();
  1357. CPackFileHandle* ph = new CPackFileHandle( this, nPosition, nLength, nIndex );
  1358. CFileHandle *fh = new CFileHandle( m_fs );
  1359. fh->m_pPackFileHandle = ph;
  1360. fh->m_nLength = nLength;
  1361. // The default mode for fopen is text, so require 'b' for binary
  1362. if ( strstr( pOptions, "b" ) == NULL )
  1363. {
  1364. fh->m_type = FT_PACK_TEXT;
  1365. }
  1366. else
  1367. {
  1368. fh->m_type = FT_PACK_BINARY;
  1369. }
  1370. fh->SetName( pFileName );
  1371. return fh;
  1372. }
  1373. return NULL;
  1374. }
  1375. //-----------------------------------------------------------------------------
  1376. // Get a directory entry from a pack's preload section
  1377. //-----------------------------------------------------------------------------
  1378. ZIP_PreloadDirectoryEntry* CZipPackFile::GetPreloadEntry( int nEntryIndex )
  1379. {
  1380. if ( !m_pPreloadHeader )
  1381. {
  1382. return NULL;
  1383. }
  1384. // If this entry doesn't have a corresponding preload entry, fail.
  1385. if ( m_PackFiles[nEntryIndex].m_nPreloadIdx == INVALID_PRELOAD_ENTRY )
  1386. {
  1387. return NULL;
  1388. }
  1389. return &m_pPreloadDirectory[m_PackFiles[nEntryIndex].m_nPreloadIdx];
  1390. }
  1391. //-----------------------------------------------------------------------------
  1392. // Read a file from the pack
  1393. //-----------------------------------------------------------------------------
  1394. int CZipPackFile::ReadFromPack( int nEntryIndex, void* pBuffer, int nDestBytes, int nBytes, int64 nOffset )
  1395. {
  1396. if ( nEntryIndex >= 0 )
  1397. {
  1398. if ( nBytes <= 0 )
  1399. {
  1400. return 0;
  1401. }
  1402. // X360TBD: This is screwy, it works because m_nBaseOffset is 0 for preload capable zips
  1403. // It comes into play for files out of the embedded bsp zip,
  1404. // this hackery is a pre-bias expecting ReadFromPack() do a symmetric post bias, yuck.
  1405. nOffset -= m_nBaseOffset;
  1406. // Attempt to satisfy request from possible preload section, otherwise fall through
  1407. // A preload entry may be compressed
  1408. ZIP_PreloadDirectoryEntry *pPreloadEntry = GetPreloadEntry( nEntryIndex );
  1409. if ( pPreloadEntry )
  1410. {
  1411. // convert the absolute pack file position to a local file position
  1412. int nLocalOffset = nOffset - m_PackFiles[nEntryIndex].m_nPosition;
  1413. byte *pPreloadData = (byte*)m_pPreloadData + pPreloadEntry->DataOffset;
  1414. CLZMA lzma;
  1415. if ( lzma.IsCompressed( pPreloadData ) )
  1416. {
  1417. unsigned int actualSize = lzma.GetActualSize( pPreloadData );
  1418. if ( nLocalOffset + nBytes <= (int)actualSize )
  1419. {
  1420. // satisfy from compressed preload
  1421. if ( fs_monitor_read_from_pack.GetInt() == 1 )
  1422. {
  1423. char szName[MAX_PATH];
  1424. IndexToFilename( nEntryIndex, szName, sizeof( szName ) );
  1425. Msg( "Read From Pack: [Preload] Requested:%d, Compressed:%d, %s\n", nBytes, pPreloadEntry->Length, szName );
  1426. }
  1427. if ( nLocalOffset == 0 && nDestBytes >= (int)actualSize && nBytes == (int)actualSize )
  1428. {
  1429. // uncompress directly into caller's buffer
  1430. lzma.Uncompress( (unsigned char *)pPreloadData, (unsigned char *)pBuffer );
  1431. return nBytes;
  1432. }
  1433. // uncompress into temporary memory
  1434. CUtlMemory< byte > tempMemory;
  1435. tempMemory.EnsureCapacity( actualSize );
  1436. lzma.Uncompress( pPreloadData, tempMemory.Base() );
  1437. // copy only what caller expects
  1438. V_memcpy( pBuffer, (byte*)tempMemory.Base() + nLocalOffset, nBytes );
  1439. return nBytes;
  1440. }
  1441. }
  1442. else if ( nLocalOffset + nBytes <= (int)pPreloadEntry->Length )
  1443. {
  1444. // satisfy from uncompressed preload
  1445. if ( fs_monitor_read_from_pack.GetInt() == 1 )
  1446. {
  1447. char szName[MAX_PATH];
  1448. IndexToFilename( nEntryIndex, szName, sizeof( szName ) );
  1449. Msg( "Read From Pack: [Preload] Requested:%d, Total:%d, %s\n", nBytes, pPreloadEntry->Length, szName );
  1450. }
  1451. V_memcpy( pBuffer, pPreloadData + nLocalOffset, nBytes );
  1452. return nBytes;
  1453. }
  1454. }
  1455. }
  1456. // fell through as a direct request from within the pack
  1457. // intercept to possible embedded section
  1458. if ( m_pSection )
  1459. {
  1460. // a section is a special update zip that has no files, only preload
  1461. // it has to be in the section
  1462. V_memcpy( pBuffer, (byte*)m_pSection + nOffset, nBytes );
  1463. return nBytes;
  1464. }
  1465. return CPackFile::ReadFromPack( nEntryIndex, pBuffer, nDestBytes, nBytes, nOffset );
  1466. }
  1467. //-----------------------------------------------------------------------------
  1468. // Gets size, position, and index for a file in the pack.
  1469. //-----------------------------------------------------------------------------
  1470. bool CZipPackFile::GetOffsetAndLength( const char *pFileName, int &nBaseIndex, int64 &nFileOffset, int &nLength )
  1471. {
  1472. CZipPackFile::CPackFileEntry lookup;
  1473. lookup.m_HashName = HashStringCaselessConventional( pFileName );
  1474. int idx = m_PackFiles.Find( lookup );
  1475. if ( -1 != idx )
  1476. {
  1477. nFileOffset = m_PackFiles[idx].m_nPosition;
  1478. nLength = m_PackFiles[idx].m_nLength;
  1479. nBaseIndex = idx;
  1480. return true;
  1481. }
  1482. return false;
  1483. }
  1484. bool CZipPackFile::IndexToFilename( int nIndex, char *pBuffer, int nBufferSize )
  1485. {
  1486. if ( nIndex >= 0 )
  1487. {
  1488. m_fs->String( m_PackFiles[nIndex].m_hDebugFilename, pBuffer, nBufferSize );
  1489. return true;
  1490. }
  1491. Q_strncpy( pBuffer, "unknown", nBufferSize );
  1492. return false;
  1493. }
  1494. //-----------------------------------------------------------------------------
  1495. // Find a file in the pack.
  1496. //-----------------------------------------------------------------------------
  1497. bool CZipPackFile::FindFile( const char *pFilename, int &nIndex, int64 &nOffset, int &nLength )
  1498. {
  1499. char szCleanName[MAX_FILEPATH];
  1500. Q_strncpy( szCleanName, pFilename, sizeof( szCleanName ) );
  1501. #ifdef _WIN32
  1502. Q_strlower( szCleanName );
  1503. #endif
  1504. Q_FixSlashes( szCleanName );
  1505. if ( !Q_RemoveDotSlashes( szCleanName ) )
  1506. {
  1507. return false;
  1508. }
  1509. bool bFound = GetOffsetAndLength( szCleanName, nIndex, nOffset, nLength );
  1510. nOffset += m_nBaseOffset;
  1511. return bFound;
  1512. }
  1513. //-----------------------------------------------------------------------------
  1514. // Set up the preload section
  1515. //-----------------------------------------------------------------------------
  1516. void CZipPackFile::SetupPreloadData()
  1517. {
  1518. if ( m_pPreloadHeader || !m_nPreloadSectionSize )
  1519. {
  1520. // already loaded or not availavble
  1521. return;
  1522. }
  1523. MEM_ALLOC_CREDIT_( "xZip" );
  1524. void *pPreload;
  1525. if ( m_pSection )
  1526. {
  1527. pPreload = (byte*)m_pSection + m_nPreloadSectionOffset;
  1528. }
  1529. else
  1530. {
  1531. pPreload = malloc( m_nPreloadSectionSize );
  1532. if ( !pPreload )
  1533. {
  1534. return;
  1535. }
  1536. if ( IsGameConsole() )
  1537. {
  1538. // 360 XZips are always dvd aligned
  1539. Assert( ( m_nPreloadSectionSize % XBOX_DVD_SECTORSIZE ) == 0 );
  1540. Assert( ( m_nPreloadSectionOffset % XBOX_DVD_SECTORSIZE ) == 0 );
  1541. }
  1542. // preload data is loaded as a single unbuffered i/o operation
  1543. ReadFromPack( -1, pPreload, -1, m_nPreloadSectionSize, m_nPreloadSectionOffset );
  1544. }
  1545. // setup the header
  1546. m_pPreloadHeader = (ZIP_PreloadHeader *)pPreload;
  1547. // setup the preload directory
  1548. m_pPreloadDirectory = (ZIP_PreloadDirectoryEntry *)((byte *)m_pPreloadHeader + sizeof( ZIP_PreloadHeader ) );
  1549. // setup the remap table
  1550. m_pPreloadRemapTable = (unsigned short *)((byte *)m_pPreloadDirectory + m_pPreloadHeader->PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) );
  1551. // set the preload data base
  1552. m_pPreloadData = (byte *)m_pPreloadRemapTable + m_pPreloadHeader->DirectoryEntries * sizeof( unsigned short );
  1553. }
  1554. void CZipPackFile::DiscardPreloadData()
  1555. {
  1556. if ( !m_pPreloadHeader )
  1557. {
  1558. // already discarded
  1559. return;
  1560. }
  1561. // a section is an alias, the header becomes an alias, not owned memory
  1562. if ( !m_pSection )
  1563. {
  1564. free( m_pPreloadHeader );
  1565. }
  1566. m_pPreloadHeader = NULL;
  1567. }
  1568. //-----------------------------------------------------------------------------
  1569. // Parse the zip file to build the file directory and preload section
  1570. //-----------------------------------------------------------------------------
  1571. bool CZipPackFile::Prepare( int64 fileLen, int64 nFileOfs )
  1572. {
  1573. if ( !fileLen || fileLen < sizeof( ZIP_EndOfCentralDirRecord ) )
  1574. {
  1575. // nonsense zip
  1576. return false;
  1577. }
  1578. // Pack files are always little-endian
  1579. m_swap.ActivateByteSwapping( IsX360() || IsPS3());
  1580. m_FileLength = fileLen;
  1581. m_nBaseOffset = nFileOfs;
  1582. ZIP_EndOfCentralDirRecord rec = { 0 };
  1583. // Find and read the central header directory from its expected position at end of the file
  1584. bool bCentralDirRecord = false;
  1585. int64 offset = fileLen - sizeof( ZIP_EndOfCentralDirRecord );
  1586. // 360 can have an incompatible format
  1587. bool bCompatibleFormat = true;
  1588. if ( IsGameConsole() )
  1589. {
  1590. // 360 has dependable exact zips, backup to handle possible xzip format
  1591. if ( offset - XZIP_COMMENT_LENGTH >= 0 )
  1592. {
  1593. offset -= XZIP_COMMENT_LENGTH;
  1594. }
  1595. // single i/o operation, scanning forward
  1596. unsigned int nBytes = (unsigned int)( fileLen - offset );
  1597. char *pTemp = (char *)_alloca( nBytes );
  1598. ReadFromPack( -1, pTemp, -1, nBytes, offset );
  1599. while ( offset <= fileLen - sizeof( ZIP_EndOfCentralDirRecord ) )
  1600. {
  1601. memcpy( &rec, pTemp, sizeof( ZIP_EndOfCentralDirRecord ) );
  1602. m_swap.SwapFieldsToTargetEndian( &rec );
  1603. if ( rec.signature == PKID( 5, 6 ) )
  1604. {
  1605. bCentralDirRecord = true;
  1606. if ( rec.commentLength >= 4 )
  1607. {
  1608. char *pComment = pTemp + sizeof( ZIP_EndOfCentralDirRecord );
  1609. if ( !V_strnicmp( pComment, "XZP", 3 ) && pComment[3] != '1' )
  1610. {
  1611. bCompatibleFormat = false;
  1612. }
  1613. }
  1614. break;
  1615. }
  1616. offset++;
  1617. pTemp++;
  1618. }
  1619. }
  1620. else
  1621. {
  1622. // scan entire file from expected location for central dir
  1623. for ( ; offset >= 0; offset-- )
  1624. {
  1625. ReadFromPack( -1, (void*)&rec, -1, sizeof( rec ), offset );
  1626. m_swap.SwapFieldsToTargetEndian( &rec );
  1627. if ( rec.signature == PKID( 5, 6 ) )
  1628. {
  1629. bCentralDirRecord = true;
  1630. break;
  1631. }
  1632. }
  1633. }
  1634. Assert( bCentralDirRecord );
  1635. if ( !bCentralDirRecord )
  1636. {
  1637. // no zip directory, bad zip
  1638. return false;
  1639. }
  1640. int numFilesInZip = rec.nCentralDirectoryEntries_Total;
  1641. if ( numFilesInZip <= 0 )
  1642. {
  1643. // empty valid zip
  1644. return true;
  1645. }
  1646. int firstFileIdx = 0;
  1647. MEM_ALLOC_CREDIT();
  1648. // read central directory into memory and parse
  1649. CUtlBuffer zipDirBuff( 0, rec.centralDirectorySize, 0 );
  1650. zipDirBuff.EnsureCapacity( rec.centralDirectorySize );
  1651. zipDirBuff.ActivateByteSwapping( IsX360() || IsPS3() );
  1652. ReadFromPack( -1, zipDirBuff.Base(), -1, rec.centralDirectorySize, rec.startOfCentralDirOffset );
  1653. zipDirBuff.SeekPut( CUtlBuffer::SEEK_HEAD, rec.centralDirectorySize );
  1654. ZIP_FileHeader zipFileHeader;
  1655. char filename[MAX_PATH];
  1656. // Check for a preload section, expected to be the first file in the zip
  1657. zipDirBuff.GetObjects( &zipFileHeader );
  1658. zipDirBuff.Get( filename, zipFileHeader.fileNameLength );
  1659. filename[zipFileHeader.fileNameLength] = '\0';
  1660. if ( !V_stricmp( filename, PRELOAD_SECTION_NAME ) )
  1661. {
  1662. m_nPreloadSectionSize = zipFileHeader.uncompressedSize;
  1663. m_nPreloadSectionOffset = zipFileHeader.relativeOffsetOfLocalHeader +
  1664. sizeof( ZIP_LocalFileHeader ) +
  1665. zipFileHeader.fileNameLength +
  1666. zipFileHeader.extraFieldLength;
  1667. SetupPreloadData();
  1668. // Set up to extract the remaining files
  1669. int nextOffset = bCompatibleFormat ? zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength : 0;
  1670. zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset );
  1671. firstFileIdx = 1;
  1672. }
  1673. else
  1674. {
  1675. if ( IsGameConsole() )
  1676. {
  1677. // all 360 zip files are expected to have preload sections
  1678. // only during development, maps are allowed to lack them, due to auto-conversion
  1679. Warning( "Xbox Zip File '%s' missing preload section\n", m_ZipName.String() );
  1680. }
  1681. // No preload section, reset buffer pointer
  1682. zipDirBuff.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  1683. }
  1684. // Parse out central directory and determine absolute file positions of data.
  1685. // Supports uncompressed zip files, with or without preload sections
  1686. bool bSuccess = true;
  1687. char tmpString[MAX_PATH];
  1688. CZipPackFile::CPackFileEntry lookup;
  1689. m_PackFiles.EnsureCapacity( numFilesInZip );
  1690. for ( int i = firstFileIdx; i < numFilesInZip; ++i )
  1691. {
  1692. zipDirBuff.GetObjects( &zipFileHeader );
  1693. if ( zipFileHeader.signature != PKID( 1, 2 ) || zipFileHeader.compressionMethod != 0 )
  1694. {
  1695. Msg( "Incompatible pack file detected! %s\n", ( zipFileHeader.compressionMethod != 0 ) ? " File is compressed" : "" );
  1696. bSuccess = false;
  1697. break;
  1698. }
  1699. Assert( zipFileHeader.fileNameLength < sizeof( tmpString ) );
  1700. zipDirBuff.Get( (void *)tmpString, zipFileHeader.fileNameLength );
  1701. tmpString[zipFileHeader.fileNameLength] = '\0';
  1702. Q_FixSlashes( tmpString );
  1703. lookup.m_hDebugFilename = m_fs->FindOrAddFileName( tmpString );
  1704. lookup.m_HashName = HashStringCaselessConventional( tmpString );
  1705. lookup.m_nLength = zipFileHeader.uncompressedSize;
  1706. lookup.m_nPosition = zipFileHeader.relativeOffsetOfLocalHeader +
  1707. sizeof( ZIP_LocalFileHeader ) +
  1708. zipFileHeader.fileNameLength +
  1709. zipFileHeader.extraFieldLength;
  1710. // track the index to this file's possible preload directory entry
  1711. if ( m_pPreloadRemapTable )
  1712. {
  1713. lookup.m_nPreloadIdx = m_pPreloadRemapTable[i];
  1714. }
  1715. else
  1716. {
  1717. lookup.m_nPreloadIdx = INVALID_PRELOAD_ENTRY;
  1718. }
  1719. m_PackFiles.InsertNoSort( lookup );
  1720. int nextOffset = bCompatibleFormat ? zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength : 0;
  1721. zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset );
  1722. }
  1723. m_PackFiles.RedoSort();
  1724. if ( IsGameConsole() )
  1725. {
  1726. // mount optional kv pool
  1727. int nIndex;
  1728. int64 nOffset;
  1729. int nLength;
  1730. if ( FindFile( "kvpool" PLATFORM_EXT ".image", nIndex, nOffset, nLength ) )
  1731. {
  1732. CUtlBuffer kvPoolBuffer;
  1733. int nOptimalSize = AlignValue( nLength, XBOX_DVD_SECTORSIZE );
  1734. void *pBuffer = malloc( nOptimalSize );
  1735. kvPoolBuffer.SetExternalBuffer( pBuffer, nOptimalSize, nLength, CUtlBuffer::READ_ONLY );
  1736. ReadFromPack( nIndex, pBuffer, nOptimalSize, nLength, nOffset );
  1737. m_KVPoolKey = kvPoolBuffer.GetUnsignedInt();
  1738. bSuccess = m_KVStringPool.RestoreFromBuffer( kvPoolBuffer );
  1739. if ( !bSuccess )
  1740. {
  1741. Msg( "Failed to unserialize compiled kvpool\n" );
  1742. m_KVStringPool.RemoveAll();
  1743. m_KVPoolKey = 0;
  1744. }
  1745. free( pBuffer );
  1746. }
  1747. }
  1748. return bSuccess;
  1749. }
  1750. CRC32_t CZipPackFile::GetKVPoolKey()
  1751. {
  1752. return m_KVPoolKey;
  1753. }
  1754. bool CZipPackFile::GetStringFromKVPool( unsigned int key, char *pOutBuff, int buflen )
  1755. {
  1756. return m_KVStringPool.String( (FileNameHandle_t)key, pOutBuff, buflen );
  1757. }
  1758. //-----------------------------------------------------------------------------
  1759. //
  1760. //-----------------------------------------------------------------------------
  1761. CZipPackFile::CZipPackFile( CBaseFileSystem* fs, void *pSection )
  1762. : m_PackFiles(), m_KVStringPool()
  1763. {
  1764. m_fs = fs;
  1765. m_pPreloadDirectory = NULL;
  1766. m_pPreloadData = NULL;
  1767. m_pPreloadHeader = NULL;
  1768. m_pPreloadRemapTable = NULL;
  1769. m_nPreloadSectionOffset = 0;
  1770. m_nPreloadSectionSize = 0;
  1771. m_KVPoolKey = 0;
  1772. #if defined( _GAMECONSOLE )
  1773. m_pSection = pSection;
  1774. #else
  1775. m_pSection = NULL;
  1776. #endif
  1777. }
  1778. CZipPackFile::~CZipPackFile()
  1779. {
  1780. DiscardPreloadData();
  1781. m_KVStringPool.RemoveAll();
  1782. }
  1783. //-----------------------------------------------------------------------------
  1784. // Purpose:
  1785. // Input : src1 -
  1786. // src2 -
  1787. // Output : Returns true on success, false on failure.
  1788. //-----------------------------------------------------------------------------
  1789. bool CZipPackFile::CPackFileLessFunc::Less( CZipPackFile::CPackFileEntry const& src1, CZipPackFile::CPackFileEntry const& src2, void *pCtx )
  1790. {
  1791. return ( src1.m_HashName < src2.m_HashName );
  1792. }
  1793. //-----------------------------------------------------------------------------
  1794. // Purpose: Search pPath for pak?.pak files and add to search path if found
  1795. // Input : *pPath -
  1796. //-----------------------------------------------------------------------------
  1797. #if defined( _GAMECONSOLE )
  1798. #define PACK_NAME_FORMAT "zip%i" PLATFORM_EXT ".zip"
  1799. #define PACK_LOCALIZED_NAME_FORMAT "zip%i_%s" PLATFORM_EXT ".zip"
  1800. #else
  1801. #define PACK_NAME_FORMAT "zip%i.zip"
  1802. #endif
  1803. void CBaseFileSystem::AddPackFiles( const char *pPath, const CUtlSymbol &pathID, SearchPathAdd_t addType, int iForceInsertIndex )
  1804. {
  1805. Assert( ThreadInMainThread() );
  1806. DISK_INTENSIVE();
  1807. // Xbox Update and DLC zips are purposely not using the ZipN decoration so as not to interfere with the
  1808. // install process that wants to move zip0 to the cache partition. These zips also have other mounting
  1809. // requirements that prevent the simpler ZipN discovery logic.
  1810. #if defined( _GAMECONSOLE )
  1811. // hack prepend the update path during first time add only
  1812. // only specific paths get the update override explicitly prepended
  1813. const char *pPathIDString = g_PathIDTable.String( pathID );
  1814. if ( addType == PATH_ADD_TO_TAIL && iForceInsertIndex == 0 &&
  1815. ( !V_stricmp( pPathIDString, "PLATFORM" ) || !V_stricmp( pPathIDString, "GAME" ) || !V_stricmp( pPathIDString, "MOD" ) ) )
  1816. {
  1817. // update search path gets added once per allowed pathID
  1818. bool bFoundSearchPath = false;
  1819. for ( int i = 0; i < m_SearchPaths.Count(); i++ )
  1820. {
  1821. CSearchPath *pSearchPath = &m_SearchPaths[i];
  1822. if ( pSearchPath->GetPathID() == pathID && !V_stricmp( pSearchPath->GetPathString(), "u:\\update\\" ) )
  1823. {
  1824. bFoundSearchPath = true;
  1825. break;
  1826. }
  1827. }
  1828. if ( !bFoundSearchPath && g_UpdateZipBuffer.TellPut() )
  1829. {
  1830. // found update blob in executable
  1831. int nIndex = m_SearchPaths.AddToTail();
  1832. CSearchPath *sp = &m_SearchPaths[ nIndex ];
  1833. // the path and filename are fake but reserved, they denote this binary resident blob
  1834. // these names ensure they get ignored during post hdd install sp fixup
  1835. const char *pFullpath = "u:\\update\\update" PLATFORM_EXT ".zip";
  1836. sp->m_pPathIDInfo = FindOrAddPathIDInfo( pathID, -1 );
  1837. sp->m_storeId = g_iNextSearchPathID++;
  1838. sp->SetPath( g_PathIDTable.AddString( "u:\\update\\" ) );
  1839. // find and alias existing reference
  1840. CPackFile *pf = NULL;
  1841. for ( int iPackFile = 0; iPackFile < m_ZipFiles.Count(); iPackFile++ )
  1842. {
  1843. if ( !Q_stricmp( m_ZipFiles[iPackFile]->m_ZipName.Get(), pFullpath ) )
  1844. {
  1845. // found
  1846. pf = m_ZipFiles[iPackFile];
  1847. sp->SetPackFile( pf );
  1848. pf->AddRef();
  1849. break;
  1850. }
  1851. }
  1852. if ( !pf )
  1853. {
  1854. // there is no 'file', point to the embedded section instead
  1855. pf = new CZipPackFile( this, g_UpdateZipBuffer.Base() );
  1856. pf->SetPath( sp->GetPath() );
  1857. pf->m_bIsExcluded = false;
  1858. pf->m_ZipName = pFullpath;
  1859. m_ZipFiles.AddToTail( pf );
  1860. sp->SetPackFile( pf );
  1861. pf->m_lPackFileTime = 0;
  1862. pf->m_hPackFileHandleFS = NULL;
  1863. pf->Prepare( g_UpdateZipBuffer.TellPut() );
  1864. }
  1865. }
  1866. }
  1867. #endif
  1868. CUtlVector< CUtlString > pakPaths;
  1869. CUtlVector< CUtlString > pakNames;
  1870. CUtlVector< int64 > pakSizes;
  1871. // determine pak files, [zip0..zipN]
  1872. for ( int i = 0; ; i++ )
  1873. {
  1874. char pakfile[MAX_PATH];
  1875. char fullpath[MAX_PATH];
  1876. V_snprintf( pakfile, sizeof( pakfile ), PACK_NAME_FORMAT, i );
  1877. V_ComposeFileName( pPath, pakfile, fullpath, sizeof( fullpath ) );
  1878. struct _stat buf;
  1879. if ( FS_stat( fullpath, &buf ) == -1 )
  1880. break;
  1881. MEM_ALLOC_CREDIT();
  1882. pakPaths.AddToTail( pPath );
  1883. pakNames.AddToTail( pakfile );
  1884. pakSizes.AddToTail( (int64)((unsigned int)buf.st_size) );
  1885. }
  1886. #if defined( _GAMECONSOLE )
  1887. // safety measure, ensure 360 dlc zip search path gets added ONLY once per allowed pathID
  1888. // dlc paths have unique suffixes, _dlc1..._dlcN
  1889. if ( m_DLCContents.Count() && V_stristr( pPath, "_dlc" ) )
  1890. {
  1891. // per pathID, dlc zip should only occur once, but might have already been added
  1892. bool bFoundSearchPath = false;
  1893. for ( int i = 0; i < m_SearchPaths.Count(); i++ )
  1894. {
  1895. CSearchPath *pSearchPath = &m_SearchPaths[i];
  1896. if ( pSearchPath->GetPathID() == pathID && V_stristr( pSearchPath->GetPathString(), pPath ) )
  1897. {
  1898. bFoundSearchPath = true;
  1899. break;
  1900. }
  1901. }
  1902. if ( !bFoundSearchPath )
  1903. {
  1904. char szFullPath[MAX_PATH];
  1905. V_ComposeFileName( pPath, "dlc" PLATFORM_EXT ".zip", szFullPath, sizeof( szFullPath ) );
  1906. struct _stat buf;
  1907. if ( FS_stat( szFullPath, &buf ) != -1 )
  1908. {
  1909. pakPaths.AddToTail( pPath );
  1910. pakNames.AddToTail( "dlc" PLATFORM_EXT ".zip" );
  1911. pakSizes.AddToTail( (__int64)((unsigned int)buf.st_size) );
  1912. }
  1913. }
  1914. }
  1915. #endif
  1916. // Add any zip files in the format zip1.zip ... zip0.zip
  1917. // Add them backwards so zip(N) is higher priority than zip(N-1), etc.
  1918. int pakcount = pakSizes.Count();
  1919. int nCount = 0;
  1920. if ( IsGameConsole() && addType == PATH_ADD_TO_TAIL_ATINDEX )
  1921. {
  1922. // we will be inserting before this
  1923. nCount = iForceInsertIndex;
  1924. }
  1925. for ( int i = pakcount-1; i >= 0; i-- )
  1926. {
  1927. char fullpath[MAX_PATH];
  1928. V_ComposeFileName( pakPaths[i].Get(), pakNames[i].Get(), fullpath, sizeof( fullpath ) );
  1929. int nIndex;
  1930. if ( addType == PATH_ADD_TO_TAIL )
  1931. {
  1932. nIndex = m_SearchPaths.AddToTail();
  1933. }
  1934. else
  1935. {
  1936. nIndex = m_SearchPaths.InsertBefore( nCount );
  1937. ++nCount;
  1938. }
  1939. CSearchPath *sp = &m_SearchPaths[ nIndex ];
  1940. sp->m_pPathIDInfo = FindOrAddPathIDInfo( pathID, -1 );
  1941. sp->m_storeId = g_iNextSearchPathID++;
  1942. sp->SetPath( g_PathIDTable.AddString( pakPaths[i].Get() ) );
  1943. CPackFile *pf = NULL;
  1944. for ( int iPackFile = 0; iPackFile < m_ZipFiles.Count(); iPackFile++ )
  1945. {
  1946. if ( !Q_stricmp( m_ZipFiles[iPackFile]->m_ZipName.Get(), fullpath ) )
  1947. {
  1948. pf = m_ZipFiles[iPackFile];
  1949. sp->SetPackFile( pf );
  1950. pf->AddRef();
  1951. break;
  1952. }
  1953. }
  1954. if ( !pf )
  1955. {
  1956. MEM_ALLOC_CREDIT();
  1957. pf = new CZipPackFile( this );
  1958. pf->SetPath( sp->GetPath() );
  1959. pf->m_ZipName = fullpath;
  1960. m_ZipFiles.AddToTail( pf );
  1961. sp->SetPackFile( pf );
  1962. pf->m_lPackFileTime = GetFileTime( fullpath );
  1963. pf->m_hPackFileHandleFS = Trace_FOpen( fullpath, "rb", 0, NULL );
  1964. if ( pf->m_hPackFileHandleFS )
  1965. {
  1966. FS_setbufsize( pf->m_hPackFileHandleFS, 32*1024 ); // 32k buffer.
  1967. if ( pf->Prepare( pakSizes[i] ) )
  1968. {
  1969. FS_setbufsize( pf->m_hPackFileHandleFS, filesystem_buffer_size.GetInt() );
  1970. }
  1971. else
  1972. {
  1973. // Failed for some reason, ignore it
  1974. if ( pf->m_hPackFileHandleFS )
  1975. {
  1976. Trace_FClose( pf->m_hPackFileHandleFS );
  1977. pf->m_hPackFileHandleFS = NULL;
  1978. }
  1979. m_SearchPaths.Remove( nIndex );
  1980. }
  1981. }
  1982. #ifdef SUPPORT_VPK
  1983. else
  1984. {
  1985. pf->m_hPackFileHandleVPK = FindFileInVPKs( fullpath );
  1986. if ( !pf->m_hPackFileHandleVPK || !pf->Prepare( pakSizes[i] ) )
  1987. {
  1988. m_SearchPaths.Remove( nIndex );
  1989. }
  1990. }
  1991. #endif
  1992. }
  1993. }
  1994. }
  1995. //-----------------------------------------------------------------------------
  1996. // Purpose: Wipe all map (.bsp) pak file search paths
  1997. //-----------------------------------------------------------------------------
  1998. void CBaseFileSystem::RemoveAllMapSearchPaths( void )
  1999. {
  2000. AsyncFinishAll();
  2001. int c = m_SearchPaths.Count();
  2002. for ( int i = c - 1; i >= 0; i-- )
  2003. {
  2004. if ( !( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) )
  2005. {
  2006. continue;
  2007. }
  2008. m_SearchPaths.Remove( i );
  2009. }
  2010. }
  2011. //-----------------------------------------------------------------------------
  2012. // Purpose:
  2013. //-----------------------------------------------------------------------------
  2014. void CBaseFileSystem::AddMapPackFile( const char *pPath, const char *pPathID, SearchPathAdd_t addType )
  2015. {
  2016. char tempPathID[MAX_PATH];
  2017. ParsePathID( pPath, pPathID, tempPathID );
  2018. char newPath[ MAX_FILEPATH ];
  2019. // +2 for '\0' and potential slash added at end.
  2020. Q_strncpy( newPath, pPath, sizeof( newPath ) );
  2021. #ifdef _WIN32 // don't do this on linux!
  2022. Q_strlower( newPath );
  2023. #endif
  2024. Q_FixSlashes( newPath );
  2025. // Open the .bsp and find the map lump
  2026. char fullpath[ MAX_FILEPATH ];
  2027. if ( Q_IsAbsolutePath( newPath ) ) // If it's an absolute path, just use that.
  2028. {
  2029. Q_strncpy( fullpath, newPath, sizeof(fullpath) );
  2030. }
  2031. else
  2032. {
  2033. if ( !GetLocalPath( newPath, fullpath, sizeof(fullpath) ) )
  2034. {
  2035. // Couldn't find that .bsp file!!!
  2036. return;
  2037. }
  2038. }
  2039. int c = m_SearchPaths.Count();
  2040. for ( int i = c - 1; i >= 0; i-- )
  2041. {
  2042. if ( !( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) )
  2043. continue;
  2044. if ( Q_stricmp( m_SearchPaths[i].GetPackFile()->m_ZipName.Get(), fullpath ) == 0 )
  2045. {
  2046. // Already set as map path
  2047. return;
  2048. }
  2049. }
  2050. // Remove previous
  2051. RemoveAllMapSearchPaths();
  2052. #ifdef SUPPORT_VPK
  2053. CPackedStoreFileHandle psHandle = FindFileInVPKs( pPath );
  2054. if ( psHandle )
  2055. {
  2056. // Get the .bsp file header
  2057. BSPHeader_t header;
  2058. memset( &header, 0, sizeof( header ) );
  2059. m_Stats.nBytesRead += psHandle.Read( &header, sizeof( header ) );
  2060. m_Stats.nReads++;
  2061. if ( header.ident != IDBSPHEADER || header.m_nVersion < MINBSPVERSION || header.m_nVersion > BSPVERSION )
  2062. {
  2063. return;
  2064. }
  2065. // Find the LUMP_PAKFILE offset
  2066. lump_t *packfile = &header.lumps[ LUMP_PAKFILE ];
  2067. if ( packfile->filelen <= sizeof( lump_t ) )
  2068. {
  2069. // It's empty or only contains a file header ( so there are no entries ), so don't add to search paths
  2070. return;
  2071. }
  2072. // Seek to correct position
  2073. psHandle.Seek( packfile->fileofs, FILESYSTEM_SEEK_HEAD );
  2074. CPackFile *pf = new CZipPackFile( this );
  2075. pf->m_bIsMapPath = true;
  2076. pf->m_hPackFileHandleFS = NULL;
  2077. pf->m_hPackFileHandleVPK = psHandle;
  2078. MEM_ALLOC_CREDIT();
  2079. pf->m_ZipName = fullpath;
  2080. if ( pf->Prepare( packfile->filelen, packfile->fileofs ) )
  2081. {
  2082. int nIndex;
  2083. if ( addType == PATH_ADD_TO_TAIL )
  2084. {
  2085. nIndex = m_SearchPaths.AddToTail();
  2086. }
  2087. else
  2088. {
  2089. nIndex = m_SearchPaths.AddToHead();
  2090. }
  2091. CSearchPath *sp = &m_SearchPaths[ nIndex ];
  2092. sp->SetPackFile( pf );
  2093. sp->m_storeId = g_iNextSearchPathID++;
  2094. sp->SetPath( g_PathIDTable.AddString( newPath ) );
  2095. sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 );
  2096. if ( IsDvdDevPathString( newPath ) )
  2097. {
  2098. sp->m_bIsDvdDevPath = true;
  2099. }
  2100. pf->SetPath( sp->GetPath() );
  2101. pf->m_lPackFileTime = GetFileTime( newPath );
  2102. //pf->m_PackFileID = m_FileTracker2.NotePackFileOpened( pPath, pPathID, packfile->filelen );
  2103. m_ZipFiles.AddToTail( pf );
  2104. }
  2105. else
  2106. {
  2107. delete pf;
  2108. }
  2109. }
  2110. else
  2111. #endif
  2112. {
  2113. FILE *fp = Trace_FOpen( fullpath, "rb", 0, NULL );
  2114. if ( !fp )
  2115. {
  2116. // Couldn't open it
  2117. FileSystemWarning( FILESYSTEM_WARNING, "Couldn't open .bsp %s for embedded pack file check\n", fullpath );
  2118. return;
  2119. }
  2120. // Get the .bsp file header
  2121. BSPHeader_t header;
  2122. memset( &header, 0, sizeof( header ) );
  2123. m_Stats.nBytesRead += FS_fread( &header, sizeof( header ), fp );
  2124. m_Stats.nReads++;
  2125. if ( header.ident != IDBSPHEADER || header.m_nVersion < MINBSPVERSION || header.m_nVersion > BSPVERSION )
  2126. {
  2127. Trace_FClose( fp );
  2128. return;
  2129. }
  2130. // Find the LUMP_PAKFILE offset
  2131. lump_t *packfile = &header.lumps[ LUMP_PAKFILE ];
  2132. if ( packfile->filelen <= sizeof( lump_t ) )
  2133. {
  2134. // It's empty or only contains a file header ( so there are no entries ), so don't add to search paths
  2135. Trace_FClose( fp );
  2136. return;
  2137. }
  2138. // Seek to correct position
  2139. FS_fseek( fp, packfile->fileofs, FILESYSTEM_SEEK_HEAD );
  2140. CPackFile *pf = new CZipPackFile( this );
  2141. pf->m_bIsMapPath = true;
  2142. pf->m_hPackFileHandleFS = fp;
  2143. #ifdef SUPPORT_VPK
  2144. pf->m_hPackFileHandleVPK = psHandle;
  2145. #endif
  2146. MEM_ALLOC_CREDIT();
  2147. pf->m_ZipName = fullpath;
  2148. if ( pf->Prepare( packfile->filelen, packfile->fileofs ) )
  2149. {
  2150. int nIndex;
  2151. if ( addType == PATH_ADD_TO_TAIL )
  2152. {
  2153. nIndex = m_SearchPaths.AddToTail();
  2154. }
  2155. else
  2156. {
  2157. nIndex = m_SearchPaths.AddToHead();
  2158. }
  2159. CSearchPath *sp = &m_SearchPaths[ nIndex ];
  2160. sp->SetPackFile( pf );
  2161. sp->m_storeId = g_iNextSearchPathID++;
  2162. sp->SetPath( g_PathIDTable.AddString( newPath ) );
  2163. sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 );
  2164. if ( IsDvdDevPathString( newPath ) )
  2165. {
  2166. sp->m_bIsDvdDevPath = true;
  2167. }
  2168. pf->SetPath( sp->GetPath() );
  2169. pf->m_lPackFileTime = GetFileTime( newPath );
  2170. Trace_FClose( pf->m_hPackFileHandleFS );
  2171. pf->m_hPackFileHandleFS = NULL;
  2172. //pf->m_PackFileID = m_FileTracker2.NotePackFileOpened( pPath, pPathID, packfile->filelen );
  2173. m_ZipFiles.AddToTail( pf );
  2174. }
  2175. else
  2176. {
  2177. delete pf;
  2178. }
  2179. }
  2180. }
  2181. void CBaseFileSystem::BeginMapAccess()
  2182. {
  2183. #if IsPlatformPS3()
  2184. // SONY Fios library does not play well with QueuedLoader, so suspend it.
  2185. // Adding a delay in the prefetch actually creates a soft-lock.
  2186. SuspendPrefetches( "IFileSystem::BeginMapAccess" );
  2187. #endif
  2188. if ( m_iMapLoad++ == 0 )
  2189. {
  2190. int c = m_SearchPaths.Count();
  2191. for( int i = 0; i < c; i++ )
  2192. {
  2193. CSearchPath *pSearchPath = &m_SearchPaths[i];
  2194. CPackFile *pPackFile = pSearchPath->GetPackFile();
  2195. if ( pPackFile && pPackFile->m_bIsMapPath )
  2196. {
  2197. pPackFile->AddRef();
  2198. pPackFile->m_mutex.Lock();
  2199. #ifdef SUPPORT_VPK
  2200. if ( pPackFile->m_nOpenFiles == 0 && pPackFile->m_hPackFileHandleFS == NULL && !pPackFile->m_hPackFileHandleVPK )
  2201. #else
  2202. if ( pPackFile->m_nOpenFiles == 0 && pPackFile->m_hPackFileHandleFS == NULL )
  2203. #endif
  2204. {
  2205. // Try opening the file as a regular file
  2206. pPackFile->m_hPackFileHandleFS = Trace_FOpen( pPackFile->m_ZipName, "rb", 0, NULL );
  2207. #ifdef SUPPORT_VPK
  2208. if ( !pPackFile->m_hPackFileHandleFS )
  2209. {
  2210. pPackFile->m_hPackFileHandleVPK = FindFileInVPKs( pPackFile->m_ZipName );
  2211. }
  2212. #endif
  2213. }
  2214. pPackFile->m_nOpenFiles++;
  2215. pPackFile->m_mutex.Unlock();
  2216. }
  2217. }
  2218. }
  2219. }
  2220. void CBaseFileSystem::EndMapAccess()
  2221. {
  2222. if ( m_iMapLoad-- == 1 )
  2223. {
  2224. int c = m_SearchPaths.Count();
  2225. for( int i = 0; i < c; i++ )
  2226. {
  2227. CSearchPath *pSearchPath = &m_SearchPaths[i];
  2228. CPackFile *pPackFile = pSearchPath->GetPackFile();
  2229. if ( pPackFile && pPackFile->m_bIsMapPath )
  2230. {
  2231. pPackFile->m_mutex.Lock();
  2232. pPackFile->m_nOpenFiles--;
  2233. if ( pPackFile->m_nOpenFiles == 0 )
  2234. {
  2235. if ( pPackFile->m_hPackFileHandleFS )
  2236. {
  2237. Trace_FClose( pPackFile->m_hPackFileHandleFS );
  2238. pPackFile->m_hPackFileHandleFS = NULL;
  2239. }
  2240. }
  2241. pPackFile->m_mutex.Unlock();
  2242. pPackFile->Release();
  2243. }
  2244. }
  2245. }
  2246. #if IsPlatformPS3()
  2247. ResumePrefetches( "IFileSystem::EndMapAccess" );
  2248. #endif
  2249. }
  2250. void CBaseFileSystem::PrintSearchPaths( void )
  2251. {
  2252. int i;
  2253. Msg( "---------------\n" );
  2254. Msg( "%-20s %s\n", "Path ID:", "File Path:" );
  2255. int c = m_SearchPaths.Count();
  2256. for( i = 0; i < c; i++ )
  2257. {
  2258. CSearchPath *pSearchPath = &m_SearchPaths[i];
  2259. const char *pszPack = "";
  2260. const char *pszType = "";
  2261. if ( pSearchPath->GetPackFile() && pSearchPath->GetPackFile()->m_bIsMapPath )
  2262. {
  2263. pszType = "(map)";
  2264. }
  2265. else if ( pSearchPath->GetPackFile() )
  2266. {
  2267. pszType = "(pack) ";
  2268. pszPack = pSearchPath->GetPackFile()->m_ZipName;
  2269. }
  2270. Msg( "%-20s \"%s\" %s%s\n", (const char *)pSearchPath->GetPathIDString(), pSearchPath->GetPathString(), pszType, pszPack );
  2271. }
  2272. if ( IsGameConsole() && m_ExcludeFilePaths.Count() )
  2273. {
  2274. // dump current list
  2275. Msg( "\nExclude Paths:\n" );
  2276. char szPath[MAX_PATH];
  2277. for ( int i = 0; i < m_ExcludeFilePaths.Count(); i++ )
  2278. {
  2279. if ( String( m_ExcludeFilePaths[i], szPath, sizeof( szPath ) ) )
  2280. {
  2281. Msg( "\"%s\"\n", szPath );
  2282. }
  2283. }
  2284. }
  2285. PrintDLCInfo();
  2286. }
  2287. //-----------------------------------------------------------------------------
  2288. // Purpose: This is where search paths are created. map files are created at head of list (they occur after
  2289. // file system paths have already been set ) so they get highest priority. Otherwise, we add the disk (non-packfile)
  2290. // path and then the paks if they exist for the path
  2291. // Input : *pPath -
  2292. //-----------------------------------------------------------------------------
  2293. void CBaseFileSystem::AddSearchPathInternal( const char *pPath, const char *pathID, SearchPathAdd_t addType, bool bAddPackFiles, int iForceInsertIndex )
  2294. {
  2295. AsyncFinishAll();
  2296. Assert( ThreadInMainThread() );
  2297. // Map pak files have their own handler
  2298. if ( V_stristr( pPath, ".bsp" ) )
  2299. {
  2300. AddMapPackFile( pPath, pathID, addType );
  2301. return;
  2302. }
  2303. // Clean up the name
  2304. char newPath[ MAX_FILEPATH ];
  2305. if ( pPath[0] == 0 )
  2306. {
  2307. newPath[0] = newPath[1] = 0;
  2308. }
  2309. else
  2310. {
  2311. if ( IsGameConsole() || Q_IsAbsolutePath( pPath ) )
  2312. {
  2313. Q_strncpy( newPath, pPath, sizeof( newPath ) );
  2314. }
  2315. else
  2316. {
  2317. Q_MakeAbsolutePath( newPath, sizeof(newPath), pPath );
  2318. }
  2319. #ifdef _WIN32
  2320. Q_strlower( newPath );
  2321. #endif
  2322. AddSeperatorAndFixPath( newPath );
  2323. }
  2324. // Make sure that it doesn't already exist
  2325. CUtlSymbol pathSym, pathIDSym;
  2326. pathSym = g_PathIDTable.AddString( newPath );
  2327. pathIDSym = g_PathIDTable.AddString( pathID );
  2328. int i;
  2329. int c = m_SearchPaths.Count();
  2330. int id = 0;
  2331. for ( i = 0; i < c; i++ )
  2332. {
  2333. CSearchPath *pSearchPath = &m_SearchPaths[i];
  2334. if ( pSearchPath->GetPath() == pathSym && pSearchPath->GetPathID() == pathIDSym )
  2335. {
  2336. if ( ( addType == PATH_ADD_TO_HEAD && i == 0 ) || ( addType == PATH_ADD_TO_TAIL ) )
  2337. {
  2338. return; // this entry is already at the head
  2339. }
  2340. else
  2341. {
  2342. m_SearchPaths.Remove(i); // remove it from its current position so we can add it back to the head
  2343. i--;
  2344. c--;
  2345. }
  2346. }
  2347. if ( !id && pSearchPath->GetPath() == pathSym )
  2348. {
  2349. // get first found - all reference the same store
  2350. id = pSearchPath->m_storeId;
  2351. }
  2352. }
  2353. if (!id)
  2354. {
  2355. id = g_iNextSearchPathID++;
  2356. }
  2357. if ( IsGameConsole() && bAddPackFiles && ( !Q_stricmp( pathID, "DEFAULT_WRITE_PATH" ) || !Q_stricmp( pathID, "LOGDIR" ) ) )
  2358. {
  2359. // xbox can be assured that no zips would ever be loaded on its write path
  2360. // otherwise xbox reloads zips because of mirrored drive mappings
  2361. bAddPackFiles = false;
  2362. }
  2363. // Add to list
  2364. bool bAdded = false;
  2365. int nIndex = m_SearchPaths.Count();
  2366. if ( IsGameConsole() && addType == PATH_ADD_TO_TAIL_ATINDEX )
  2367. {
  2368. nIndex = iForceInsertIndex;
  2369. }
  2370. if ( bAddPackFiles )
  2371. {
  2372. // Add pack files for this path next
  2373. int lastCount = m_SearchPaths.Count();
  2374. AddPackFiles( newPath, pathIDSym, addType, iForceInsertIndex );
  2375. bAdded = m_SearchPaths.Count() != lastCount;
  2376. }
  2377. if ( IsGameConsole() && addType == PATH_ADD_TO_TAIL_ATINDEX )
  2378. {
  2379. // isolated this specific pathadd hack behavior to not destablize existing state
  2380. // this behavior is for install and dlc searchpath modifications
  2381. if ( V_stristr( pPath, "_dlc" ) )
  2382. {
  2383. if ( !bAdded )
  2384. {
  2385. // dlc should have had a pack file, if it didn't mount shouldn't be adding in anything else
  2386. return;
  2387. }
  2388. if ( !V_stristr( pPath, XBX_GetLanguageString() ) )
  2389. {
  2390. // only dlc that is non-localized (language dlc doesn't have maps) gets a fallthrough directory due to map paths
  2391. // every dlc thus far has had maps
  2392. m_SearchPaths.InsertAfter( nIndex );
  2393. nIndex++;
  2394. }
  2395. }
  2396. }
  2397. else
  2398. {
  2399. if ( addType == PATH_ADD_TO_HEAD )
  2400. {
  2401. nIndex = m_SearchPaths.Count() - nIndex;
  2402. Assert( nIndex >= 0 );
  2403. }
  2404. if ( IsPC() || !bAddPackFiles || !bAdded )
  2405. {
  2406. // Grab last entry and set the path
  2407. m_SearchPaths.InsertBefore( nIndex );
  2408. }
  2409. else if ( IsGameConsole() && bAddPackFiles && bAdded )
  2410. {
  2411. bool bAddNonZipPath = true;
  2412. if ( bAddNonZipPath )
  2413. {
  2414. // 360 needs to find files (for the preload hit) in the zip first for fast loading
  2415. // 360 always adds the non-pack search path *after* the pack file but respects the overall list ordering
  2416. // fixup the indexes
  2417. if ( addType == PATH_ADD_TO_HEAD )
  2418. {
  2419. m_SearchPaths.InsertBefore( nIndex );
  2420. }
  2421. else
  2422. {
  2423. nIndex = m_SearchPaths.Count() - 1;
  2424. m_SearchPaths.InsertAfter( nIndex );
  2425. nIndex++;
  2426. }
  2427. }
  2428. }
  2429. }
  2430. // setup the 'base' path
  2431. CSearchPath *sp = &m_SearchPaths[ nIndex ];
  2432. sp->SetPath( pathSym );
  2433. sp->m_pPathIDInfo = FindOrAddPathIDInfo( pathIDSym, -1 );
  2434. // all matching paths have a reference to the same store
  2435. sp->m_storeId = id;
  2436. // classify the dvddev path
  2437. if ( IsDvdDevPathString( newPath ) )
  2438. {
  2439. sp->m_bIsDvdDevPath = true;
  2440. }
  2441. }
  2442. //-----------------------------------------------------------------------------
  2443. // Create the search path.
  2444. //-----------------------------------------------------------------------------
  2445. void CBaseFileSystem::AddSearchPath( const char *pPath, const char *pathID, SearchPathAdd_t addType )
  2446. {
  2447. char tempSymlinkBuffer[MAX_PATH];
  2448. pPath = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pPath );
  2449. #if !defined( _X360 )
  2450. // The PC has no concept of update/dlc discovery, it explicitly adds them now
  2451. // This layout matches the Xbox's search path layout, when the Xbox does late bind the DLC
  2452. // any platform, game, or mod search paths get subverted in order to prepend the DLC path
  2453. const char *pGameName = "csgo";
  2454. // CSGO compatibility VPKs
  2455. if ( const char *pActualPathID = pathID ? StringAfterPrefix( pathID, "COMPAT:" ) : NULL )
  2456. {
  2457. #ifdef SUPPORT_VPK
  2458. // Check if we are running with VPKs?
  2459. if ( m_VPKFiles.Count() > 0 )
  2460. {
  2461. char newVPK[ MAX_PATH ] = {};
  2462. sprintf( newVPK, "%s/pakxv_%s.vpk", pGameName, pPath );
  2463. AddVPKFile( newVPK, addType );
  2464. return;
  2465. }
  2466. #endif
  2467. if ( addType != PATH_ADD_TO_HEAD )
  2468. return; // in non-vpk mode compatibility paths can only be added to head
  2469. // Build compatibility syntax path and proceed with adding
  2470. pathID = pActualPathID;
  2471. V_sprintf_safe( tempSymlinkBuffer, "csgo/compatibility/%s/", pPath );
  2472. pPath = tempSymlinkBuffer;
  2473. }
  2474. if ( V_stristr( pPath, pGameName ) &&
  2475. ( !Q_stricmp( pathID, "GAME" ) || !Q_stricmp( pathID, "MOD" ) || !Q_stricmp( pathID, "PLATFORM" ) ) )
  2476. {
  2477. char szPathHead[MAX_PATH];
  2478. char szUpdatePath[MAX_PATH];
  2479. V_strncpy( szPathHead, pPath, sizeof( szPathHead ) );
  2480. V_StripLastDir( szPathHead, sizeof( szPathHead ) );
  2481. // xlsppatch trumps all
  2482. V_ComposeFileName( szPathHead, "xlsppatch", szUpdatePath, sizeof( szUpdatePath ) );
  2483. struct _stat buf;
  2484. if ( FS_stat( szUpdatePath, &buf ) != -1 )
  2485. {
  2486. // found
  2487. AddSearchPathInternal( szUpdatePath, pathID, addType, true );
  2488. }
  2489. // followed by update
  2490. V_ComposeFileName( szPathHead, "update", szUpdatePath, sizeof( szUpdatePath ) );
  2491. if ( FS_stat( szUpdatePath, &buf ) != -1 )
  2492. {
  2493. // found
  2494. AddSearchPathInternal( szUpdatePath, pathID, addType, true );
  2495. }
  2496. // followed by dlc
  2497. if ( !Q_stricmp( pathID, "GAME" ) || !Q_stricmp( pathID, "MOD" ) )
  2498. {
  2499. // DS would have all DLC dirs
  2500. // find highest DLC dir available
  2501. int nHighestDLC = 1;
  2502. for ( ;nHighestDLC <= 99; nHighestDLC++ )
  2503. {
  2504. V_ComposeFileName( szPathHead, CFmtStr( "%s_dlc%d", pGameName, nHighestDLC ), szUpdatePath, sizeof( szUpdatePath ) );
  2505. if ( FS_stat( szUpdatePath, &buf ) == -1 )
  2506. {
  2507. // does not exist, highest dlc available is previous
  2508. nHighestDLC--;
  2509. break;
  2510. }
  2511. V_ComposeFileName( szPathHead, CFmtStr( "%s_dlc%d/dlc_disabled.txt", pGameName, nHighestDLC ), szUpdatePath, sizeof( szUpdatePath ) );
  2512. if ( FS_stat( szUpdatePath, &buf ) != -1 )
  2513. {
  2514. // disabled, highest dlc available is previous
  2515. nHighestDLC--;
  2516. break;
  2517. }
  2518. }
  2519. // mount in correct order
  2520. for ( ;nHighestDLC >= 1; nHighestDLC-- )
  2521. {
  2522. V_ComposeFileName( szPathHead, CFmtStr( "%s_dlc%d", pGameName, nHighestDLC ), szUpdatePath, sizeof( szUpdatePath ) );
  2523. AddSearchPathInternal( szUpdatePath, pathID, addType, true );
  2524. #ifdef SUPPORT_VPK
  2525. // scan for vpk's
  2526. for( int i = 1 ; i < 99; i++ )
  2527. {
  2528. char newVPK[MAX_PATH];
  2529. sprintf( newVPK, "%s/pak%02d_dir.vpk", szUpdatePath, i );
  2530. // we will fopen to bypass pathing, etc
  2531. FILE *pstdiofile = fopen( newVPK, "rb" );
  2532. if ( pstdiofile )
  2533. {
  2534. fclose( pstdiofile );
  2535. sprintf( newVPK, "%s/pak%02d.vpk", szUpdatePath, i );
  2536. AddVPKFile( newVPK );
  2537. }
  2538. else
  2539. {
  2540. break;
  2541. }
  2542. }
  2543. #endif
  2544. }
  2545. }
  2546. }
  2547. #endif
  2548. int currCount = m_SearchPaths.Count();
  2549. AddSearchPathInternal( pPath, pathID, addType, true );
  2550. #ifdef SUPPORT_VPK
  2551. // scan for vpk's
  2552. for( int i = 1 ; i < 99; i++ )
  2553. {
  2554. char newVPK[MAX_PATH];
  2555. sprintf( newVPK, "%s/pak%02d_dir.vpk", pPath, i );
  2556. // we will fopen to bypass pathing, etc
  2557. FILE *pstdiofile = fopen( newVPK, "rb" );
  2558. if ( pstdiofile )
  2559. {
  2560. fclose( pstdiofile );
  2561. sprintf( newVPK, "%s/pak%02d.vpk", pPath, i );
  2562. AddVPKFile( newVPK );
  2563. }
  2564. else
  2565. {
  2566. break;
  2567. }
  2568. }
  2569. #endif
  2570. if ( IsGameConsole() &&
  2571. ( m_DVDMode == DVDMODE_DEV ) &&
  2572. ( !V_stricmp( pathID, "MOD" ) || !V_stricmp( pathID, "GAME" ) || !V_stricmp( pathID, "PLATFORM" ) ) )
  2573. {
  2574. // dvd development mode clones a search path based on the remote path for fall through
  2575. const char *pBasePath = CommandLine()->ParmValue( "-basedir" );
  2576. if ( pBasePath && !V_stristr( pPath, ".bsp" ) )
  2577. {
  2578. // isolate the search sub path from the base path
  2579. const char *pSearchSubPath = StringAfterPrefix( pPath, pBasePath );
  2580. if ( pSearchSubPath )
  2581. {
  2582. if ( pSearchSubPath[0] == CORRECT_PATH_SEPARATOR )
  2583. {
  2584. pSearchSubPath++;
  2585. }
  2586. // substitue the dvddev path head
  2587. char szDvdDevPath[MAX_PATH];
  2588. V_ComposeFileName( FS_DVDDEV_ROOT, pSearchSubPath, szDvdDevPath, sizeof( szDvdDevPath ) );
  2589. // no pack files are allowed on the fall through dvddev path
  2590. AddSearchPathInternal( szDvdDevPath, pathID, addType, false );
  2591. }
  2592. }
  2593. }
  2594. if ( currCount != m_SearchPaths.Count() )
  2595. {
  2596. #if !defined( DEDICATED ) && !defined( _CERT )
  2597. if ( IsGameConsole() )
  2598. {
  2599. // spew updated search paths
  2600. // PrintSearchPaths();
  2601. }
  2602. #endif
  2603. }
  2604. }
  2605. //-----------------------------------------------------------------------------
  2606. // Patches the search path after install has finished. This is a hack until
  2607. // a reboot occurs. This is really bad, but our system has no fundamental
  2608. // way of manipulating search paths, so had to bury this here. This is designed
  2609. // to be innvoked ONCE after the installer has completed to swap in the
  2610. // cache based paths. The reboot causes a normal search path build out using
  2611. // the proper paths.
  2612. //-----------------------------------------------------------------------------
  2613. bool CBaseFileSystem::FixupSearchPathsAfterInstall()
  2614. {
  2615. #if defined( _X360 )
  2616. if ( m_bSearchPathsPatchedAfterInstall )
  2617. {
  2618. // do not want to ever call this twice
  2619. return true;
  2620. }
  2621. AsyncFinishAll();
  2622. // this is incredibly hardcoded and fragile
  2623. // after shipping need to revisit and generalize for installs
  2624. // this assumes exact knowledge of how zips are mounted and the install footprint
  2625. for ( int i = 0; i < m_SearchPaths.Count(); i++ )
  2626. {
  2627. const char *pPathID = m_SearchPaths[i].GetPathIDString();
  2628. if ( V_stricmp( pPathID, "GAME" ) && V_stricmp( pPathID, "MOD" ) )
  2629. {
  2630. // only consider these paths
  2631. continue;
  2632. }
  2633. const char *pPath = m_SearchPaths[i].GetPathString();
  2634. const char *pColon = strchr( pPath, ':' );
  2635. if ( !pColon ||
  2636. !V_stristr( pPath, "csgo" ) ||
  2637. V_stristr( pPath, "_lv" ) ||
  2638. V_stristr( pPath, "_dlc" ) ||
  2639. V_stristr( pPath, "_tempcontent" ) )
  2640. {
  2641. // ignore relative paths, can't patch those
  2642. // ignore any non csgo path
  2643. // ignore lv, dlc, tempcontent path, not installing those zips
  2644. continue;
  2645. }
  2646. if ( !m_SearchPaths[i].GetPackFile() || m_SearchPaths[i].GetPackFile()->m_bIsMapPath )
  2647. {
  2648. // ignore non pack based paths
  2649. // ignore bsps
  2650. continue;
  2651. }
  2652. if ( !V_stristr( m_SearchPaths[i].GetPackFile()->m_ZipName.String(), "zip0" PLATFORM_EXT ".zip" ) )
  2653. {
  2654. // only patching zip0
  2655. continue;
  2656. }
  2657. // Not installing localized data
  2658. if ( m_SearchPaths[i].m_bIsLocalizedPath )
  2659. {
  2660. continue;
  2661. }
  2662. char szNewPath[MAX_PATH];
  2663. V_snprintf( szNewPath, sizeof( szNewPath ), "%s%s", CACHE_PATH_CSTIKRE15, pColon+1 );
  2664. V_FixSlashes( szNewPath );
  2665. int lastCount = m_SearchPaths.Count();
  2666. AddSearchPathInternal( szNewPath, pPathID, PATH_ADD_TO_TAIL_ATINDEX, true, i );
  2667. int numNewPaths = m_SearchPaths.Count() - lastCount;
  2668. if ( numNewPaths )
  2669. {
  2670. // skip paths all the paths we just added
  2671. i += numNewPaths;
  2672. // this is really bad, skip past the zip we just considered, the next iteration will skip us to the next zip
  2673. i++;
  2674. }
  2675. m_bSearchPathsPatchedAfterInstall = true;
  2676. }
  2677. if ( m_bSearchPathsPatchedAfterInstall )
  2678. {
  2679. // cache paths got added
  2680. // shutdown non cache paths
  2681. // must do multiple passes until no removal occurs
  2682. bool bRemoved;
  2683. while ( 1 )
  2684. {
  2685. bRemoved = false;
  2686. for ( int i = 0; i < m_SearchPaths.Count(); i++ )
  2687. {
  2688. const char *pPathID = m_SearchPaths[i].GetPathIDString();
  2689. if ( V_stricmp( pPathID, "GAME" ) && V_stricmp( pPathID, "MOD" ) )
  2690. {
  2691. // only consider these paths
  2692. continue;
  2693. }
  2694. const char *pPath = m_SearchPaths[i].GetPathString();
  2695. const char *pColon = strchr( pPath, ':' );
  2696. if ( !pColon ||
  2697. !V_stristr( pPath, "csgo" ) ||
  2698. V_stristr( pPath, "_lv" ) ||
  2699. V_stristr( pPath, "_dlc" ) ||
  2700. V_stristr( pPath, "_tempcontent" ) )
  2701. {
  2702. // ignore relative paths, can't patch those
  2703. // ignore any non csgo path
  2704. // ignore lv, dlc, or tempcontent path, not installing those zips
  2705. continue;
  2706. }
  2707. if ( !m_SearchPaths[i].GetPackFile() || m_SearchPaths[i].GetPackFile()->m_bIsMapPath )
  2708. {
  2709. // ignore non pack based paths
  2710. // ignore bsps
  2711. continue;
  2712. }
  2713. // Not installing localized data
  2714. if ( m_SearchPaths[i].m_bIsLocalizedPath )
  2715. {
  2716. continue;
  2717. }
  2718. if ( V_stristr( pPath, "cache:" ) || !V_stristr( m_SearchPaths[i].GetPackFile()->m_ZipName.String(), "zip0" PLATFORM_EXT ".zip" ) )
  2719. {
  2720. // ignore any cache oriented paths
  2721. // only want to remove non-cache paths of zip0.360.zip
  2722. continue;
  2723. }
  2724. m_SearchPaths.Remove( i );
  2725. bRemoved = true;
  2726. break;
  2727. }
  2728. if ( !bRemoved )
  2729. {
  2730. break;
  2731. }
  2732. }
  2733. }
  2734. #endif
  2735. return m_bSearchPathsPatchedAfterInstall;
  2736. }
  2737. //-----------------------------------------------------------------------------
  2738. // ConCommands can just blindly call this to ensure user's request to flush
  2739. // data picks up any intended changes they may have done locally.
  2740. //-----------------------------------------------------------------------------
  2741. void CBaseFileSystem::SyncDvdDevCache()
  2742. {
  2743. if ( !IsGameConsole() || m_DVDMode != DVDMODE_DEV )
  2744. {
  2745. // xbox dvddev only
  2746. return;
  2747. }
  2748. #if defined( _X360 )
  2749. XBX_rSyncDvdDevCache();
  2750. #endif
  2751. BuildExcludeList();
  2752. }
  2753. //-----------------------------------------------------------------------------
  2754. // Returns the search path, each path is separated by ;s. Returns the length of the string returned
  2755. // Pack search paths include the pack name, so that callers can still form absolute paths
  2756. // and that absolute path can be sent to the filesystem, and mounted as a file inside a pack.
  2757. //-----------------------------------------------------------------------------
  2758. int CBaseFileSystem::GetSearchPath( const char *pathID, bool bGetPackFiles, char *pPath, int nMaxLen )
  2759. {
  2760. AUTO_LOCK( m_SearchPathsMutex );
  2761. int nLen = 0;
  2762. if ( nMaxLen )
  2763. {
  2764. pPath[0] = 0;
  2765. }
  2766. CSearchPathsIterator iter( this, pathID, bGetPackFiles ? FILTER_NONE : FILTER_CULLPACK );
  2767. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  2768. {
  2769. CPackFile *pPackFile = pSearchPath->GetPackFile();
  2770. if ( nLen >= nMaxLen )
  2771. {
  2772. // Add 1 for the semicolon if our length is not 0
  2773. nLen += (nLen > 0) ? 1 : 0;
  2774. if ( !pPackFile )
  2775. {
  2776. nLen += Q_strlen( pSearchPath->GetPathString() );
  2777. }
  2778. else
  2779. {
  2780. // full path and slash
  2781. nLen += Q_strlen( pPackFile->m_ZipName.String() ) + 1;
  2782. }
  2783. continue;
  2784. }
  2785. if ( nLen != 0 )
  2786. {
  2787. pPath[nLen++] = ';';
  2788. }
  2789. if ( !pPackFile )
  2790. {
  2791. Q_strncpy( &pPath[nLen], pSearchPath->GetPathString(), nMaxLen - nLen );
  2792. nLen += Q_strlen( pSearchPath->GetPathString() );
  2793. }
  2794. else
  2795. {
  2796. // full path and slash
  2797. Q_strncpy( &pPath[nLen], pPackFile->m_ZipName.String(), nMaxLen - nLen );
  2798. V_AppendSlash( &pPath[nLen], nMaxLen - nLen );
  2799. nLen += Q_strlen( pPackFile->m_ZipName.String() ) + 1;
  2800. }
  2801. }
  2802. // Return 1 extra for the NULL terminator
  2803. return nLen + 1;
  2804. }
  2805. //-----------------------------------------------------------------------------
  2806. // Returns the search path IDs, each path is separated by ;s. Returns the length of the string returned
  2807. //-----------------------------------------------------------------------------
  2808. int CBaseFileSystem::GetSearchPathID( char *pPath, int nMaxLen )
  2809. {
  2810. AUTO_LOCK( m_SearchPathsMutex );
  2811. if ( nMaxLen )
  2812. {
  2813. pPath[0] = 0;
  2814. }
  2815. // determine unique PathIDs
  2816. CUtlVector< CUtlSymbol > list;
  2817. for ( int i = 0 ; i < m_SearchPaths.Count(); i++ )
  2818. {
  2819. CUtlSymbol pathID = m_SearchPaths[i].GetPathID();
  2820. if ( pathID != UTL_INVAL_SYMBOL && list.Find( pathID ) == -1 )
  2821. {
  2822. list.AddToTail( pathID );
  2823. V_strncat( pPath, m_SearchPaths[i].GetPathIDString(), nMaxLen );
  2824. V_strncat( pPath, ";", nMaxLen );
  2825. }
  2826. }
  2827. return strlen( pPath ) + 1;
  2828. }
  2829. //-----------------------------------------------------------------------------
  2830. // Purpose:
  2831. // Input : *pPath -
  2832. // Output : Returns true on success, false on failure.
  2833. //-----------------------------------------------------------------------------
  2834. bool CBaseFileSystem::RemoveSearchPath( const char *pPath, const char *pathID )
  2835. {
  2836. char tempSymlinkBuffer[MAX_PATH];
  2837. pPath = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pPath );
  2838. AsyncFinishAll();
  2839. char newPath[ MAX_FILEPATH ];
  2840. newPath[ 0 ] = 0;
  2841. if ( const char *pActualPathID = pathID ? StringAfterPrefix( pathID, "COMPAT:" ) : NULL )
  2842. {
  2843. #ifdef SUPPORT_VPK
  2844. if ( m_VPKFiles.Count() > 0 )
  2845. {
  2846. char newVPK[ MAX_PATH ] = {};
  2847. sprintf( newVPK, "pakxv_%s", pPath );
  2848. while ( m_VPKFiles.Count() > 0 )
  2849. {
  2850. CPackedStore *pVPK = m_VPKFiles.Head();
  2851. char szExistingVPKBase[ MAX_PATH ] = {};
  2852. V_FileBase( pVPK->BaseName(), szExistingVPKBase, sizeof( szExistingVPKBase ) );
  2853. char const *szAfterRemovedPathVPK = StringAfterPrefix( szExistingVPKBase, newVPK );
  2854. if ( szAfterRemovedPathVPK && ( *szAfterRemovedPathVPK == 0 || *szAfterRemovedPathVPK == '_' ) )
  2855. {
  2856. m_VPKFiles.RemoveMultipleFromHead( 1 );
  2857. delete pVPK;
  2858. }
  2859. else
  2860. break;
  2861. }
  2862. return true;
  2863. }
  2864. #endif
  2865. int c = m_SearchPaths.Count();
  2866. for ( int i = c - 1; i >= 0; i-- )
  2867. {
  2868. char newCompatPath[ MAX_PATH ] = {};
  2869. sprintf( newCompatPath, "/csgo/compatibility/%s/", pPath );
  2870. Q_FixSlashes( newCompatPath );
  2871. if ( V_strstr( m_SearchPaths[ i ].GetPathString(), newCompatPath ) )
  2872. {
  2873. m_SearchPaths.Remove( i );
  2874. return true;
  2875. }
  2876. }
  2877. }
  2878. if ( pPath )
  2879. {
  2880. // +2 for '\0' and potential slash added at end.
  2881. Q_strncpy( newPath, pPath, sizeof( newPath ) );
  2882. #ifdef _WIN32 // don't do this on linux!
  2883. Q_strlower( newPath );
  2884. #endif
  2885. if ( V_stristr( newPath, ".bsp" ) )
  2886. {
  2887. Q_FixSlashes( newPath );
  2888. }
  2889. else
  2890. {
  2891. AddSeperatorAndFixPath( newPath );
  2892. }
  2893. }
  2894. pPath = newPath;
  2895. CUtlSymbol lookup = g_PathIDTable.AddString( pPath );
  2896. CUtlSymbol id = g_PathIDTable.AddString( pathID );
  2897. bool bret = false;
  2898. // Count backward since we're possibly deleting one or more pack files, too
  2899. int i;
  2900. int c = m_SearchPaths.Count();
  2901. for( i = c - 1; i >= 0; i-- )
  2902. {
  2903. if ( newPath && m_SearchPaths[i].GetPath() != lookup )
  2904. continue;
  2905. if ( FilterByPathID( &m_SearchPaths[i], id ) )
  2906. continue;
  2907. m_SearchPaths.Remove( i );
  2908. bret = true;
  2909. }
  2910. return bret;
  2911. }
  2912. //-----------------------------------------------------------------------------
  2913. // Purpose: Removes all search paths for a given pathID, such as all "GAME" paths.
  2914. // Input : pathID -
  2915. //-----------------------------------------------------------------------------
  2916. void CBaseFileSystem::RemoveSearchPaths( const char *pathID )
  2917. {
  2918. AsyncFinishAll();
  2919. int nCount = m_SearchPaths.Count();
  2920. for (int i = nCount - 1; i >= 0; i--)
  2921. {
  2922. if (!Q_stricmp(m_SearchPaths.Element(i).GetPathIDString(), pathID))
  2923. {
  2924. m_SearchPaths.FastRemove(i);
  2925. }
  2926. }
  2927. }
  2928. //-----------------------------------------------------------------------------
  2929. // Purpose:
  2930. //-----------------------------------------------------------------------------
  2931. CBaseFileSystem::CSearchPath *CBaseFileSystem::FindWritePath( const char *pFilename, const char *pathID )
  2932. {
  2933. CUtlSymbol lookup = g_PathIDTable.AddString( pathID );
  2934. AUTO_LOCK( m_SearchPathsMutex );
  2935. // a pathID has been specified, find the first match in the path list
  2936. int c = m_SearchPaths.Count();
  2937. for ( int i = 0; i < c; i++ )
  2938. {
  2939. // pak files are not allowed to be written to...
  2940. CSearchPath *pSearchPath = &m_SearchPaths[i];
  2941. if ( pSearchPath->GetPackFile() )
  2942. {
  2943. continue;
  2944. }
  2945. if ( pathID && ( pSearchPath->GetPathID() != lookup ) )
  2946. {
  2947. // not the right pathID
  2948. continue;
  2949. }
  2950. if ( IsGameConsole() && ( m_DVDMode == DVDMODE_DEV ) && pFilename && !pSearchPath->m_bIsDvdDevPath )
  2951. {
  2952. bool bIgnorePath = false;
  2953. char szExcludeFile[MAX_PATH];
  2954. char szFilename[MAX_PATH];
  2955. V_ComposeFileName( pSearchPath->GetPathString(), pFilename, szFilename, sizeof( szFilename ) );
  2956. for ( int j = 0; j < m_ExcludeFilePaths.Count(); j++ )
  2957. {
  2958. if ( g_pFullFileSystem->String( m_ExcludeFilePaths[j], szExcludeFile, sizeof( szExcludeFile ) ) )
  2959. {
  2960. if ( !V_stricmp( szFilename, szExcludeFile ) )
  2961. {
  2962. bIgnorePath = true;
  2963. break;
  2964. }
  2965. }
  2966. }
  2967. if ( bIgnorePath )
  2968. {
  2969. // filename matches exclusion path, skip it
  2970. // favoring the next path which should be the path fall through hit
  2971. continue;
  2972. }
  2973. }
  2974. return pSearchPath;
  2975. }
  2976. return NULL;
  2977. }
  2978. //-----------------------------------------------------------------------------
  2979. // Purpose: Finds a search path that should be used for writing to, given a pathID.
  2980. //-----------------------------------------------------------------------------
  2981. const char *CBaseFileSystem::GetWritePath( const char *pFilename, const char *pathID )
  2982. {
  2983. CSearchPath *pSearchPath = NULL;
  2984. if ( pathID && pathID[ 0 ] != '\0' )
  2985. {
  2986. pSearchPath = FindWritePath( pFilename, pathID );
  2987. if ( pSearchPath )
  2988. {
  2989. return pSearchPath->GetPathString();
  2990. }
  2991. FileSystemWarning( FILESYSTEM_WARNING, "Requested non-existent write path %s!\n", pathID );
  2992. }
  2993. pSearchPath = FindWritePath( pFilename, "DEFAULT_WRITE_PATH" );
  2994. if ( pSearchPath )
  2995. {
  2996. return pSearchPath->GetPathString();
  2997. }
  2998. pSearchPath = FindWritePath( pFilename, NULL ); // okay, just return the first search path added!
  2999. if ( pSearchPath )
  3000. {
  3001. return pSearchPath->GetPathString();
  3002. }
  3003. // Hope this is reasonable!!
  3004. return ".\\";
  3005. }
  3006. //-----------------------------------------------------------------------------
  3007. // Reads/writes files to utlbuffers. Attempts alignment fixups for optimal read
  3008. //-----------------------------------------------------------------------------
  3009. #ifdef _PS3
  3010. #define g_pszReadFilename GetTLSGlobals()->pFileSystemReadFilename
  3011. #else
  3012. CTHREADLOCALPTR(char) g_pszReadFilename;
  3013. #endif
  3014. bool CBaseFileSystem::ReadToBuffer( FileHandle_t fp, CUtlBuffer &buf, int nMaxBytes, FSAllocFunc_t pfnAlloc )
  3015. {
  3016. SetBufferSize( fp, 0 ); // TODO: what if it's a pack file? restore buffer size?
  3017. int nBytesToRead = Size( fp );
  3018. if ( nBytesToRead == 0 )
  3019. {
  3020. // no data in file
  3021. return true;
  3022. }
  3023. if ( nMaxBytes > 0 )
  3024. {
  3025. // can't read more than file has
  3026. nBytesToRead = MIN( nMaxBytes, nBytesToRead );
  3027. }
  3028. int nBytesRead = 0;
  3029. int nBytesOffset = 0;
  3030. int iStartPos = Tell( fp );
  3031. if ( nBytesToRead != 0 )
  3032. {
  3033. int nBytesDestBuffer = nBytesToRead;
  3034. unsigned nSizeAlign = 0, nBufferAlign = 0, nOffsetAlign = 0;
  3035. bool bBinary = !( buf.IsText() && !buf.ContainsCRLF() );
  3036. if ( bBinary && !IsPosix() && !buf.IsExternallyAllocated() && !pfnAlloc &&
  3037. ( buf.TellPut() == 0 ) && ( buf.TellGet() == 0 ) && ( iStartPos % 4 == 0 ) &&
  3038. GetOptimalIOConstraints( fp, &nOffsetAlign, &nSizeAlign, &nBufferAlign ) )
  3039. {
  3040. // correct conditions to allow an optimal read
  3041. if ( iStartPos % nOffsetAlign != 0 )
  3042. {
  3043. // move starting position back to nearest alignment
  3044. nBytesOffset = ( iStartPos % nOffsetAlign );
  3045. Assert ( ( iStartPos - nBytesOffset ) % nOffsetAlign == 0 );
  3046. Seek( fp, -nBytesOffset, FILESYSTEM_SEEK_CURRENT );
  3047. // going to read from aligned start, increase target buffer size by offset alignment
  3048. nBytesDestBuffer += nBytesOffset;
  3049. }
  3050. // snap target buffer size to its size alignment
  3051. // add additional alignment slop for target pointer adjustment
  3052. nBytesDestBuffer = AlignValue( nBytesDestBuffer, nSizeAlign ) + nBufferAlign;
  3053. }
  3054. if ( !pfnAlloc )
  3055. {
  3056. buf.EnsureCapacity( nBytesDestBuffer + buf.TellPut() );
  3057. }
  3058. else
  3059. {
  3060. // caller provided allocator
  3061. void *pMemory = (*pfnAlloc)( g_pszReadFilename, nBytesDestBuffer );
  3062. buf.SetExternalBuffer( pMemory, nBytesDestBuffer, 0, buf.GetFlags() & ~CUtlBuffer::EXTERNAL_GROWABLE );
  3063. }
  3064. int seekGet = -1;
  3065. if ( nBytesDestBuffer != nBytesToRead )
  3066. {
  3067. // doing optimal read, align target pointer
  3068. int nAlignedBase = AlignValue( (byte *)buf.Base(), nBufferAlign ) - (byte *)buf.Base();
  3069. buf.SeekPut( CUtlBuffer::SEEK_HEAD, nAlignedBase );
  3070. // the buffer read position is slid forward to ignore the addtional
  3071. // starting offset alignment
  3072. seekGet = nAlignedBase + nBytesOffset;
  3073. }
  3074. nBytesRead = ReadEx( buf.PeekPut(), nBytesDestBuffer - nBufferAlign, nBytesToRead + nBytesOffset, fp );
  3075. buf.SeekPut( CUtlBuffer::SEEK_CURRENT, nBytesRead );
  3076. if ( seekGet != -1 )
  3077. {
  3078. // can only seek the get after data has been put, otherwise buffer sets overflow error
  3079. buf.SeekGet( CUtlBuffer::SEEK_HEAD, seekGet );
  3080. }
  3081. Seek( fp, iStartPos + ( nBytesRead - nBytesOffset ), FILESYSTEM_SEEK_HEAD );
  3082. }
  3083. return (nBytesRead != 0);
  3084. }
  3085. //-----------------------------------------------------------------------------
  3086. // Reads/writes files to utlbuffers
  3087. // NOTE NOTE!!
  3088. // If you change this implementation, copy it into CBaseVMPIFileSystem::ReadFile
  3089. // in vmpi_filesystem.cpp
  3090. //-----------------------------------------------------------------------------
  3091. bool CBaseFileSystem::ReadFile( const char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes, int nStartingByte, FSAllocFunc_t pfnAlloc )
  3092. {
  3093. CHECK_DOUBLE_SLASHES( pFileName );
  3094. bool bBinary = !( buf.IsText() && !buf.ContainsCRLF() );
  3095. FileHandle_t fp = Open( pFileName, ( bBinary ) ? "rb" : "rt", pPath );
  3096. if ( !fp )
  3097. return false;
  3098. if ( nStartingByte != 0 )
  3099. {
  3100. Seek( fp, nStartingByte, FILESYSTEM_SEEK_HEAD );
  3101. }
  3102. if ( pfnAlloc )
  3103. {
  3104. g_pszReadFilename = (char *)pFileName;
  3105. }
  3106. bool bSuccess = ReadToBuffer( fp, buf, nMaxBytes, pfnAlloc );
  3107. Close( fp );
  3108. return bSuccess;
  3109. }
  3110. //-----------------------------------------------------------------------------
  3111. //
  3112. //-----------------------------------------------------------------------------
  3113. int CBaseFileSystem::ReadFileEx( const char *pFileName, const char *pPath, void **ppBuf, bool bNullTerminate, bool bOptimalAlloc, int nMaxBytes, int nStartingByte, FSAllocFunc_t pfnAlloc )
  3114. {
  3115. FileHandle_t fp = Open( pFileName, "rb", pPath );
  3116. if ( !fp )
  3117. {
  3118. return 0;
  3119. }
  3120. if ( IsGameConsole() )
  3121. {
  3122. // callers are sloppy, always want optimal
  3123. bOptimalAlloc = true;
  3124. }
  3125. SetBufferSize( fp, 0 ); // TODO: what if it's a pack file? restore buffer size?
  3126. int nBytesToRead = Size( fp );
  3127. int nBytesRead = 0;
  3128. if ( nMaxBytes > 0 )
  3129. {
  3130. nBytesToRead = MIN( nMaxBytes, nBytesToRead );
  3131. if ( bNullTerminate )
  3132. {
  3133. nBytesToRead--;
  3134. }
  3135. }
  3136. if ( nBytesToRead != 0 )
  3137. {
  3138. int nBytesBuf;
  3139. if ( !*ppBuf )
  3140. {
  3141. nBytesBuf = nBytesToRead + ( ( bNullTerminate ) ? 1 : 0 );
  3142. if ( !pfnAlloc && !bOptimalAlloc )
  3143. {
  3144. *ppBuf = new byte[nBytesBuf];
  3145. }
  3146. else if ( !pfnAlloc )
  3147. {
  3148. *ppBuf = AllocOptimalReadBuffer( fp, nBytesBuf, 0 );
  3149. nBytesBuf = GetOptimalReadSize( fp, nBytesBuf );
  3150. }
  3151. else
  3152. {
  3153. *ppBuf = (*pfnAlloc)( pFileName, nBytesBuf );
  3154. }
  3155. }
  3156. else
  3157. {
  3158. nBytesBuf = nMaxBytes;
  3159. }
  3160. if ( nStartingByte != 0 )
  3161. {
  3162. Seek( fp, nStartingByte, FILESYSTEM_SEEK_HEAD );
  3163. }
  3164. nBytesRead = ReadEx( *ppBuf, nBytesBuf, nBytesToRead, fp );
  3165. if ( bNullTerminate )
  3166. {
  3167. ((byte *)(*ppBuf))[nBytesToRead] = 0;
  3168. }
  3169. }
  3170. Close( fp );
  3171. return nBytesRead;
  3172. }
  3173. //-----------------------------------------------------------------------------
  3174. // NOTE NOTE!!
  3175. // If you change this implementation, copy it into CBaseVMPIFileSystem::WriteFile
  3176. // in vmpi_filesystem.cpp
  3177. //-----------------------------------------------------------------------------
  3178. bool CBaseFileSystem::WriteFile( const char *pFileName, const char *pPath, CUtlBuffer &buf )
  3179. {
  3180. CHECK_DOUBLE_SLASHES( pFileName );
  3181. const char *pWriteFlags = "wb";
  3182. if ( buf.IsText() && !buf.ContainsCRLF() )
  3183. {
  3184. pWriteFlags = "wt";
  3185. }
  3186. FileHandle_t fp = Open( pFileName, pWriteFlags, pPath );
  3187. if ( !fp )
  3188. return false;
  3189. int nBytesWritten = Write( buf.Base(), buf.TellMaxPut(), fp );
  3190. Close( fp );
  3191. return (nBytesWritten != 0);
  3192. }
  3193. bool CBaseFileSystem::UnzipFile( const char *pFileName, const char *pPath, const char *pDestination )
  3194. {
  3195. #ifdef POSIX
  3196. Error( " need to hook up zip for linux" );
  3197. #else
  3198. IZip *pZip = IZip::CreateZip( NULL, true );
  3199. HANDLE hZipFile = pZip->ParseFromDisk( pFileName );
  3200. if ( !hZipFile )
  3201. {
  3202. Msg( "Bad or missing zip file, failed to open '%s'\n", pFileName );
  3203. return false;
  3204. }
  3205. int iZipIndex = -1;
  3206. int iFileSize;
  3207. char szFileName[MAX_PATH];
  3208. // Create Directories
  3209. CreateDirHierarchy( pDestination, pPath );
  3210. while ( 1 )
  3211. {
  3212. // Get the next file in the zip
  3213. szFileName[0] = '\0';
  3214. iFileSize = 0;
  3215. iZipIndex = pZip->GetNextFilename( iZipIndex, szFileName, sizeof( szFileName ), iFileSize );
  3216. // If there aren't any more files then break out of this while
  3217. if ( iZipIndex == -1 )
  3218. break;
  3219. int iFileNameLength = Q_strlen( szFileName );
  3220. if ( szFileName[ iFileNameLength - 1 ] == '/' )
  3221. {
  3222. // Its a directory, so create it
  3223. szFileName[ iFileNameLength - 1 ] = '\0';
  3224. char szFinalName[ MAX_PATH ];
  3225. Q_snprintf( szFinalName, sizeof( szFinalName ), "%s\\%s", pDestination, szFileName );
  3226. CreateDirHierarchy( szFinalName, pPath );
  3227. }
  3228. }
  3229. // Write Files
  3230. while ( 1 )
  3231. {
  3232. szFileName[0] = '\0';
  3233. iFileSize = 0;
  3234. iZipIndex = pZip->GetNextFilename( iZipIndex, szFileName, sizeof( szFileName ), iFileSize );
  3235. // If there aren't any more files then break out of this while
  3236. if ( iZipIndex == -1 )
  3237. break;
  3238. int iFileNameLength = Q_strlen( szFileName );
  3239. if ( szFileName[ iFileNameLength - 1 ] != '/' )
  3240. {
  3241. // It's not a directory, so write the file
  3242. CUtlBuffer fileBuffer;
  3243. fileBuffer.Purge();
  3244. if ( pZip->ReadFileFromZip( hZipFile, szFileName, false, fileBuffer ) )
  3245. {
  3246. char szFinalName[ MAX_PATH ];
  3247. Q_snprintf( szFinalName, sizeof( szFinalName ), "%s\\%s", pDestination, szFileName );
  3248. WriteFile( szFinalName, pPath, fileBuffer );
  3249. }
  3250. }
  3251. }
  3252. ::CloseHandle( hZipFile );
  3253. IZip::ReleaseZip( pZip );
  3254. #endif
  3255. return true;
  3256. }
  3257. //-----------------------------------------------------------------------------
  3258. // Purpose:
  3259. //-----------------------------------------------------------------------------
  3260. void CBaseFileSystem::RemoveAllSearchPaths( void )
  3261. {
  3262. AUTO_LOCK( m_SearchPathsMutex );
  3263. // Sergiy: AaronS said it is a good idea to destroy these paths in reverse order
  3264. while( m_SearchPaths.Count() )
  3265. {
  3266. m_SearchPaths.Remove( m_SearchPaths.Count() - 1 );
  3267. }
  3268. //m_PackFileHandles.Purge();
  3269. }
  3270. void CBaseFileSystem::LogFileAccess( const char *pFullFileName )
  3271. {
  3272. if( !m_pLogFile )
  3273. {
  3274. return;
  3275. }
  3276. if ( IsPS3() )
  3277. {
  3278. AssertMsg( false, "LogFileAccess broken on PS3\n" );
  3279. return;
  3280. }
  3281. char buf[1024];
  3282. #if BSPOUTPUT
  3283. Q_snprintf( buf, sizeof( buf ), "%s\n%s\n", pShortFileName, pFullFileName);
  3284. fprintf( m_pLogFile, "%s", buf ); // STEAM OK
  3285. #else
  3286. char cwd[MAX_FILEPATH];
  3287. getcwd( cwd, MAX_FILEPATH-1 );
  3288. Q_strcat( cwd, "\\", sizeof( cwd ) );
  3289. if( Q_strnicmp( cwd, pFullFileName, strlen( cwd ) ) == 0 )
  3290. {
  3291. const char *pFileNameWithoutExeDir = pFullFileName + strlen( cwd );
  3292. char targetPath[ MAX_FILEPATH ];
  3293. strcpy( targetPath, "%fs_target%\\" );
  3294. strcat( targetPath, pFileNameWithoutExeDir );
  3295. Q_snprintf( buf, sizeof( buf ), "copy \"%s\" \"%s\"\n", pFullFileName, targetPath );
  3296. char tmp[ MAX_FILEPATH ];
  3297. Q_strncpy( tmp, targetPath, sizeof( tmp ) );
  3298. Q_StripFilename( tmp );
  3299. fprintf( m_pLogFile, "mkdir \"%s\"\n", tmp ); // STEAM OK
  3300. fprintf( m_pLogFile, "%s", buf ); // STEAM OK
  3301. }
  3302. else
  3303. {
  3304. Assert( 0 );
  3305. }
  3306. #endif
  3307. }
  3308. class CFileOpenInfo
  3309. {
  3310. public:
  3311. CFileOpenInfo( CBaseFileSystem *pFileSystem, const char *pFileName, const CBaseFileSystem::CSearchPath *path, const char *pOptions, int flags, char **ppszResolvedFilename, bool bTrackCRCs ) :
  3312. m_pFileSystem( pFileSystem ), m_pFileName( pFileName ), m_pPath( path ), m_pOptions( pOptions ), m_Flags( flags ), m_ppszResolvedFilename( ppszResolvedFilename ), m_bTrackCRCs( bTrackCRCs )
  3313. {
  3314. // Multiple threads can access the whitelist simultaneously.
  3315. // That's fine, but make sure it doesn't get freed by another thread.
  3316. if ( IsPC() )
  3317. {
  3318. m_pWhitelist = m_pFileSystem->m_FileWhitelist.AddRef();
  3319. }
  3320. else
  3321. {
  3322. m_pWhitelist = NULL;
  3323. }
  3324. m_pFileHandle = NULL;
  3325. m_bLoadedFromSteamCache = m_bSteamCacheOnly = false;
  3326. if ( m_ppszResolvedFilename )
  3327. *m_ppszResolvedFilename = NULL;
  3328. #ifdef SUPPORT_VPK
  3329. m_pPackFile = NULL;
  3330. m_pVPKFile = NULL;
  3331. #endif
  3332. }
  3333. ~CFileOpenInfo()
  3334. {
  3335. if ( IsGameConsole() )
  3336. {
  3337. return;
  3338. }
  3339. m_pFileSystem->m_FileWhitelist.ReleaseRef( m_pWhitelist );
  3340. }
  3341. void SetAbsolutePath( const char *pFormat, ... )
  3342. {
  3343. va_list marker;
  3344. va_start( marker, pFormat );
  3345. V_vsnprintf( m_AbsolutePath, sizeof( m_AbsolutePath ), pFormat, marker );
  3346. va_end( marker );
  3347. V_FixSlashes( m_AbsolutePath );
  3348. }
  3349. void SetResolvedFilename( const char *pStr )
  3350. {
  3351. if ( m_ppszResolvedFilename )
  3352. {
  3353. Assert( !( *m_ppszResolvedFilename ) );
  3354. *m_ppszResolvedFilename = strdup( pStr );
  3355. }
  3356. }
  3357. // Handles telling CFileTracker about the file we just opened so it can remember
  3358. // where the file came from, and possibly calculate a CRC if necessary.
  3359. void HandleFileCRCTracking( const char *pRelativeFileName, bool bIsAbsolutePath )
  3360. {
  3361. if ( IsGameConsole() )
  3362. {
  3363. return;
  3364. }
  3365. if ( m_pFileSystem->m_WhitelistFileTrackingEnabled == 0 || !m_bTrackCRCs )
  3366. return;
  3367. if ( m_pFileHandle )
  3368. {
  3369. FILE *fp = m_pFileHandle->m_pFile;
  3370. int64 nLength = m_pFileHandle->m_nLength;
  3371. #ifdef SUPPORT_VPK
  3372. if ( m_pVPKFile )
  3373. {
  3374. m_pFileSystem->m_FileTracker2.NotePackFileAccess( pRelativeFileName, m_pPath->GetPathIDString(), m_pFileHandle->m_VPKHandle );
  3375. return;
  3376. }
  3377. #endif
  3378. // we always record hashes of everything we load. we may filter later.
  3379. m_pFileSystem->m_FileTracker2.NoteFileLoadedFromDisk( pRelativeFileName, m_pPath->GetPathIDString(), fp, nLength );
  3380. }
  3381. else if ( m_bSteamCacheOnly )
  3382. {
  3383. // Remember that the file failed to load. We only need to do this in the case where we forced it to ignore files
  3384. // on disk (in which case we'll want it to check the disk next time if sv_pure changed).
  3385. m_pFileSystem->m_FileTracker.NoteFileFailedToLoad( pRelativeFileName, m_pPath->GetPathIDString() );
  3386. }
  3387. }
  3388. // Decides if the file must come from Steam or if it can be allowed to come off disk.
  3389. void DetermineFileLoadInfoParameters( CFileLoadInfo &fileLoadInfo, bool bIsAbsolutePath )
  3390. {
  3391. if ( IsGameConsole() )
  3392. {
  3393. fileLoadInfo.m_bSteamCacheOnly = false;
  3394. #ifdef _PS3
  3395. fileLoadInfo.m_ps3Filetype = PS3_FILETYPE_UNKNOWN;
  3396. #endif
  3397. return;
  3398. }
  3399. if ( m_bTrackCRCs && m_pWhitelist && m_pWhitelist->m_pAllowFromDiskList && !bIsAbsolutePath )
  3400. {
  3401. Assert( !V_IsAbsolutePath( m_pFileName ) ); // (This is what bIsAbsolutePath is supposed to tell us..)
  3402. // Ask the whitelist if this file must come from Steam.
  3403. fileLoadInfo.m_bSteamCacheOnly = !m_pWhitelist->m_pAllowFromDiskList->IsFileInList( m_pFileName );
  3404. }
  3405. else
  3406. {
  3407. fileLoadInfo.m_bSteamCacheOnly = false;
  3408. }
  3409. }
  3410. public:
  3411. CBaseFileSystem *m_pFileSystem;
  3412. CWhitelistSpecs *m_pWhitelist;
  3413. // These are output parameters.
  3414. CFileHandle *m_pFileHandle;
  3415. char **m_ppszResolvedFilename;
  3416. #ifdef SUPPORT_VPK
  3417. CPackFile *m_pPackFile;
  3418. CPackedStore *m_pVPKFile;
  3419. #endif
  3420. const char *m_pFileName;
  3421. const CBaseFileSystem::CSearchPath *m_pPath;
  3422. const char *m_pOptions;
  3423. int m_Flags;
  3424. bool m_bTrackCRCs;
  3425. // Stats about how the file was opened and how we asked the stdio/steam filesystem to open it.
  3426. // Used to decide whether or not we need to generate and store CRCs.
  3427. bool m_bLoadedFromSteamCache; // Did it get loaded out of the Steam cache?
  3428. bool m_bSteamCacheOnly; // Are we asking that this file only come from Steam?
  3429. char m_AbsolutePath[MAX_FILEPATH]; // This is set
  3430. };
  3431. bool CBaseFileSystem::HandleOpenFromZipFile( CFileOpenInfo &openInfo )
  3432. {
  3433. // an absolute path can encode a zip pack file (i.e. caller wants to open the file from within the pack file)
  3434. // format must strictly be ????.zip\????
  3435. // assuming a reasonable restriction that the zip must be a pre-existing search path zip
  3436. char *pZipExt = V_stristr( openInfo.m_AbsolutePath, ".zip" );
  3437. if ( !pZipExt )
  3438. {
  3439. pZipExt = V_stristr( openInfo.m_AbsolutePath, ".bsp" );
  3440. }
  3441. if ( pZipExt && pZipExt[4] == CORRECT_PATH_SEPARATOR && pZipExt[5] )
  3442. {
  3443. // want full path to zip only, terminate at slash
  3444. pZipExt[4] = '\0';
  3445. // want relative portion only, everything after the slash
  3446. char *pRelativeFileName = pZipExt + 5;
  3447. // find the zip
  3448. for ( int i=0; i< m_ZipFiles.Count(); i++ )
  3449. {
  3450. CPackFile *pPackFile = m_ZipFiles[i];
  3451. if ( Q_stricmp( pPackFile->m_ZipName.Get(), openInfo.m_AbsolutePath ) == 0 )
  3452. {
  3453. openInfo.m_pFileHandle = pPackFile->OpenFile( pRelativeFileName, openInfo.m_pOptions );
  3454. #ifdef SUPPORT_VPK
  3455. openInfo.m_pPackFile = pPackFile;
  3456. #endif
  3457. openInfo.HandleFileCRCTracking( pRelativeFileName, false );
  3458. break;
  3459. }
  3460. }
  3461. if ( openInfo.m_pFileHandle )
  3462. openInfo.SetResolvedFilename( openInfo.m_pFileName );
  3463. return true;
  3464. }
  3465. else
  3466. {
  3467. return false;
  3468. }
  3469. }
  3470. void CBaseFileSystem::HandleOpenFromPackFile( CPackFile *pPackFile, CFileOpenInfo &openInfo )
  3471. {
  3472. openInfo.m_pFileHandle = pPackFile->OpenFile( openInfo.m_pFileName, openInfo.m_pOptions );
  3473. #ifdef SUPPORT_VPK
  3474. openInfo.m_pPackFile = pPackFile;
  3475. #endif
  3476. // HACK! The bsp pack's paths may be different now that we've moved it to the workshop subdir...
  3477. // If we're still failing to find it, remove the workshop/id subdir
  3478. if ( !openInfo.m_pFileHandle )
  3479. {
  3480. char szScratch[MAX_PATH];
  3481. V_strcpy_safe( szScratch, openInfo.m_pFileName );
  3482. CFmtStr workshopDir( "%cworkshop%c", CORRECT_PATH_SEPARATOR, CORRECT_PATH_SEPARATOR );
  3483. char *pStart = V_stristr( szScratch, workshopDir.Access() );
  3484. if ( pStart && pStart[0] && pStart[1] )
  3485. {
  3486. *pStart = 0; // null terminate the first section of the path we're keeping
  3487. pStart++;
  3488. size_t len = strlen( pStart );
  3489. const char *pEnd = V_strnchr( pStart, CORRECT_PATH_SEPARATOR, len ); // skip past workshop path
  3490. if ( pEnd && pEnd[0] && pEnd[1] )
  3491. {
  3492. len = strlen( pEnd );
  3493. pEnd = V_strnchr( pEnd+1, CORRECT_PATH_SEPARATOR, len ); // skip past id path
  3494. if ( pEnd )
  3495. {
  3496. CFmtStr pathNoWorkshop( "%s%s", szScratch, pEnd );
  3497. openInfo.m_pFileHandle = pPackFile->OpenFile( pathNoWorkshop.Access(), openInfo.m_pOptions );
  3498. }
  3499. }
  3500. }
  3501. }
  3502. if ( openInfo.m_pFileHandle )
  3503. {
  3504. char tempStr[MAX_PATH*2+2];
  3505. V_snprintf( tempStr, sizeof( tempStr ), "%s%c%s", pPackFile->m_ZipName.String(), CORRECT_PATH_SEPARATOR, openInfo.m_pFileName );
  3506. openInfo.SetResolvedFilename( tempStr );
  3507. }
  3508. // If it's a BSP file, then the BSP file got CRC'd elsewhere so no need to verify stuff in there.
  3509. if ( !pPackFile->m_bIsMapPath )
  3510. openInfo.HandleFileCRCTracking( openInfo.m_pFileName, false );
  3511. }
  3512. void CBaseFileSystem::HandleOpenRegularFile( CFileOpenInfo &openInfo, bool bIsAbsolutePath )
  3513. {
  3514. // Setup the parameters for the call (like to tell Steam to force the file to come out of the Steam caches or not).
  3515. CFileLoadInfo fileLoadInfo;
  3516. openInfo.DetermineFileLoadInfoParameters( fileLoadInfo, bIsAbsolutePath );
  3517. // xbox dvddev mode needs to convolve non-compliant fatx filenames
  3518. // purposely placing this at this level, so only loose files pay the burden
  3519. const char *pFilename = openInfo.m_AbsolutePath;
  3520. bool bFixed = false;
  3521. char fixedFATXFilename[MAX_PATH];
  3522. if ( IsX360() )
  3523. {
  3524. bFixed = FixupFATXFilename( pFilename, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  3525. }
  3526. int64 size;
  3527. FILE *fp = Trace_FOpen( bFixed ? fixedFATXFilename : pFilename, openInfo.m_pOptions, openInfo.m_Flags, &size, &fileLoadInfo );
  3528. if ( fp )
  3529. {
  3530. if ( m_pLogFile )
  3531. {
  3532. LogFileAccess( openInfo.m_AbsolutePath );
  3533. }
  3534. if ( m_bOutputDebugString )
  3535. {
  3536. #ifdef _WIN32
  3537. Plat_DebugString( "fs_debug: " );
  3538. Plat_DebugString( openInfo.m_AbsolutePath );
  3539. Plat_DebugString( "\n" );
  3540. #elif POSIX
  3541. fprintf(stderr, "fs_debug: %s\n", openInfo.m_AbsolutePath );
  3542. #endif
  3543. }
  3544. openInfo.m_pFileHandle = new CFileHandle(this);
  3545. openInfo.m_pFileHandle->m_pFile = fp;
  3546. openInfo.m_pFileHandle->m_type = FT_NORMAL;
  3547. openInfo.m_pFileHandle->m_nLength = size;
  3548. openInfo.SetResolvedFilename( openInfo.m_AbsolutePath );
  3549. // Remember what was returned by the Steam filesystem and track the CRC.
  3550. openInfo.m_bLoadedFromSteamCache = fileLoadInfo.m_bLoadedFromSteamCache;
  3551. openInfo.m_bSteamCacheOnly = fileLoadInfo.m_bSteamCacheOnly;
  3552. openInfo.HandleFileCRCTracking( openInfo.m_pFileName, bIsAbsolutePath );
  3553. }
  3554. }
  3555. //-----------------------------------------------------------------------------
  3556. // Purpose: The base file search goes through here
  3557. // Input : *path -
  3558. // *pFileName -
  3559. // *pOptions -
  3560. // packfile -
  3561. // *filetime -
  3562. // Output : FileHandle_t
  3563. //-----------------------------------------------------------------------------
  3564. FileHandle_t CBaseFileSystem::FindFile(
  3565. const CSearchPath *path,
  3566. const char *pFileName,
  3567. const char *pOptions,
  3568. unsigned flags,
  3569. char **ppszResolvedFilename,
  3570. bool bTrackCRCs )
  3571. {
  3572. VPROF( "CBaseFileSystem::FindFile" );
  3573. char tempSymlinkBuffer[MAX_PATH];
  3574. pFileName = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pFileName );
  3575. CFileOpenInfo openInfo( this, pFileName, path, pOptions, flags, ppszResolvedFilename, bTrackCRCs );
  3576. bool bIsAbsolutePath = V_IsAbsolutePath( pFileName );
  3577. if ( bIsAbsolutePath )
  3578. {
  3579. #ifdef SUPPORT_VPK
  3580. if ( m_VPKFiles.Count() && ( ! V_stristr( pFileName, ".vpk" ) ) )
  3581. {
  3582. // FileSystemWarning( FILESYSTEM_WARNING, "***VPK: FindFile Attempting to use full path with VPK file!\n\tFile: %s\n", pFileName );
  3583. }
  3584. #endif
  3585. openInfo.SetAbsolutePath( "%s", pFileName );
  3586. // Check if it's of the form C:/a/b/c/blah.zip/materials/blah.vtf
  3587. if ( HandleOpenFromZipFile( openInfo ) )
  3588. {
  3589. return (FileHandle_t)openInfo.m_pFileHandle;
  3590. }
  3591. }
  3592. else
  3593. {
  3594. // check vpk file
  3595. #ifdef SUPPORT_VPK
  3596. for( int i = 0 ; i < m_VPKFiles.Count(); i++ )
  3597. {
  3598. CPackedStoreFileHandle fHandle = m_VPKFiles[i]->OpenFile( pFileName );
  3599. if ( fHandle )
  3600. {
  3601. openInfo.m_pFileHandle = new CFileHandle(this);
  3602. openInfo.m_pFileHandle->m_VPKHandle = fHandle;
  3603. openInfo.m_pFileHandle->m_type = FT_NORMAL;
  3604. openInfo.m_pFileHandle->m_nLength = fHandle.m_nFileSize;
  3605. openInfo.SetResolvedFilename( openInfo.m_AbsolutePath );
  3606. // Remember what was returned by the Steam filesystem and track the CRC.
  3607. openInfo.m_bLoadedFromSteamCache = false;
  3608. openInfo.m_bSteamCacheOnly = false;
  3609. openInfo.m_pVPKFile = m_VPKFiles[i];
  3610. openInfo.HandleFileCRCTracking( openInfo.m_pFileName, false );
  3611. return ( FileHandle_t ) openInfo.m_pFileHandle;
  3612. }
  3613. }
  3614. #endif
  3615. // Caller provided a relative path
  3616. if ( path->GetPackFile() )
  3617. {
  3618. HandleOpenFromPackFile( path->GetPackFile(), openInfo );
  3619. return (FileHandle_t)openInfo.m_pFileHandle;
  3620. }
  3621. else
  3622. {
  3623. openInfo.SetAbsolutePath( "%s%s", path->GetPathString(), pFileName );
  3624. }
  3625. }
  3626. // now have an absolute name
  3627. HandleOpenRegularFile( openInfo, bIsAbsolutePath );
  3628. return (FileHandle_t)openInfo.m_pFileHandle;
  3629. }
  3630. FileHandle_t CBaseFileSystem::FindFileInSearchPaths(
  3631. const char *pFileName,
  3632. const char *pOptions,
  3633. const char *pathID,
  3634. unsigned flags,
  3635. char **ppszResolvedFilename,
  3636. bool bTrackCRCs )
  3637. {
  3638. // Run through all the search paths.
  3639. PathTypeFilter_t pathFilter = FILTER_NONE;
  3640. #if defined( _GAMECONSOLE ) && defined( _DEBUG )
  3641. // -pakfallbackfs will perform a filesystem search if the
  3642. // requested file is not in pak zip (very expensive!)
  3643. static
  3644. enum PakFallback_t
  3645. {
  3646. PAK_FALLBACK_UNKNOWN, PAK_FALLBACK_ALLOW, PAK_FALLBACK_RETAIL
  3647. }
  3648. s_PakFallbackType = PAK_FALLBACK_UNKNOWN;
  3649. if ( s_PakFallbackType == PAK_FALLBACK_UNKNOWN )
  3650. {
  3651. s_PakFallbackType = CommandLine()->FindParm( "-pakfallbackfs" ) ? PAK_FALLBACK_ALLOW : PAK_FALLBACK_RETAIL;
  3652. }
  3653. #define IsPakStrictMode() ( s_PakFallbackType != PAK_FALLBACK_ALLOW )
  3654. #else
  3655. #define IsPakStrictMode() true
  3656. #endif
  3657. if ( IsGameConsole() && IsPakStrictMode() )
  3658. {
  3659. if ( flags & FSOPEN_NEVERINPACK )
  3660. {
  3661. pathFilter = FILTER_CULLPACK;
  3662. }
  3663. else
  3664. {
  3665. // most all files on the dvd are expected to be in the pack
  3666. // don't allow disk paths to be searched, which is very expensive on the dvd
  3667. pathFilter = FILTER_CULLNONPACK;
  3668. }
  3669. }
  3670. CSearchPathsIterator iter( this, &pFileName, pathID, pathFilter );
  3671. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  3672. {
  3673. FileHandle_t filehandle = FindFile( pSearchPath, pFileName, pOptions, flags, ppszResolvedFilename, bTrackCRCs );
  3674. if ( filehandle )
  3675. return filehandle;
  3676. }
  3677. return ( FileHandle_t )0;
  3678. }
  3679. //-----------------------------------------------------------------------------
  3680. // Purpose:
  3681. //-----------------------------------------------------------------------------
  3682. FileHandle_t CBaseFileSystem::OpenForRead( const char *pFileName, const char *pOptions, unsigned flags, const char *pathID, char **ppszResolvedFilename )
  3683. {
  3684. VPROF( "CBaseFileSystem::OpenForRead" );
  3685. return FindFileInSearchPaths( pFileName, pOptions, pathID, flags, ppszResolvedFilename, true );
  3686. }
  3687. //-----------------------------------------------------------------------------
  3688. // Purpose:
  3689. //-----------------------------------------------------------------------------
  3690. FileHandle_t CBaseFileSystem::OpenForWrite( const char *pFileName, const char *pOptions, const char *pathID )
  3691. {
  3692. char tempPathID[MAX_PATH];
  3693. ParsePathID( pFileName, pathID, tempPathID );
  3694. // Opening for write or append uses the write path
  3695. // Unless an absolute path is specified...
  3696. const char *pTmpFileName;
  3697. char szScratchFileName[MAX_PATH];
  3698. if ( Q_IsAbsolutePath( pFileName ) )
  3699. {
  3700. pTmpFileName = pFileName;
  3701. }
  3702. else
  3703. {
  3704. ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pFileName, pathID );
  3705. pTmpFileName = szScratchFileName;
  3706. }
  3707. int64 size;
  3708. FILE *fp = Trace_FOpen( pTmpFileName, pOptions, 0, &size );
  3709. if ( !fp )
  3710. {
  3711. return ( FileHandle_t )0;
  3712. }
  3713. CFileHandle *fh = new CFileHandle( this );
  3714. fh->m_nLength = size;
  3715. fh->m_type = FT_NORMAL;
  3716. fh->m_pFile = fp;
  3717. return ( FileHandle_t )fh;
  3718. }
  3719. // This looks for UNC-type filename specifiers, which should be used instead of
  3720. // passing in path ID. So if it finds //mod/cfg/config.cfg, it translates
  3721. // pFilename to "cfg/config.cfg" and pPathID to "mod" (mod is placed in tempPathID).
  3722. void CBaseFileSystem::ParsePathID( const char* &pFilename, const char* &pPathID, char tempPathID[MAX_PATH] )
  3723. {
  3724. tempPathID[0] = 0;
  3725. if ( !pFilename || pFilename[0] == 0 )
  3726. return;
  3727. // FIXME: Pain! Backslashes are used to denote network drives, forward to denote path ids
  3728. // HOORAY! We call FixSlashes everywhere. That will definitely not work
  3729. // I'm not changing it yet though because I don't know how painful the bugs would be that would be generated
  3730. bool bIsForwardSlash = ( pFilename[0] == '/' && pFilename[1] == '/' );
  3731. // bool bIsBackwardSlash = ( pFilename[0] == '\\' && pFilename[1] == '\\' );
  3732. if ( !bIsForwardSlash ) //&& !bIsBackwardSlash )
  3733. return;
  3734. // Parse out the path ID.
  3735. const char *pIn = &pFilename[2];
  3736. char *pOut = tempPathID;
  3737. while ( *pIn && !PATHSEPARATOR( *pIn ) && (pOut - tempPathID) < (MAX_PATH-1) )
  3738. {
  3739. *pOut++ = *pIn++;
  3740. }
  3741. *pOut = 0;
  3742. // They're specifying two path IDs. Ignore the one passed-in.
  3743. // AND only warn if they are inconsistent
  3744. if ( pPathID && Q_stricmp( pPathID, tempPathID ) )
  3745. {
  3746. FileSystemWarning( FILESYSTEM_WARNING, "FS: Specified two path IDs (%s, %s).\n", pFilename, pPathID );
  3747. }
  3748. if ( tempPathID[0] == '*' )
  3749. {
  3750. // * means NULL.
  3751. pPathID = NULL;
  3752. }
  3753. else
  3754. {
  3755. pPathID = tempPathID;
  3756. }
  3757. // Move pFilename up past the part with the path ID.
  3758. if ( *pIn == 0 )
  3759. pFilename = pIn;
  3760. else
  3761. pFilename = pIn + 1;
  3762. }
  3763. //-----------------------------------------------------------------------------
  3764. // Purpose:
  3765. //-----------------------------------------------------------------------------
  3766. FileHandle_t CBaseFileSystem::Open( const char *pFileName, const char *pOptions, const char *pathID )
  3767. {
  3768. return OpenEx( pFileName, pOptions, 0, pathID );
  3769. }
  3770. //-----------------------------------------------------------------------------
  3771. // Purpose:
  3772. //-----------------------------------------------------------------------------
  3773. FileHandle_t CBaseFileSystem::OpenEx( const char *pFileName, const char *pOptions, unsigned flags, const char *pathID, char **ppszResolvedFilename )
  3774. {
  3775. VPROF_BUDGET( "CBaseFileSystem::Open", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  3776. if ( !pFileName )
  3777. return (FileHandle_t)0;
  3778. #ifndef _CERT
  3779. s_IoStats.OnFileOpen( pFileName );
  3780. #endif
  3781. NoteIO();
  3782. CFileHandleTimer *pTimer = NULL;
  3783. bool bReportLongLoads = ( fs_report_long_reads.GetInt() > 0 );
  3784. if ( bReportLongLoads )
  3785. {
  3786. // When a file is opened we add it to the list and note the time
  3787. pTimer = new CFileHandleTimer;
  3788. if ( pTimer != NULL )
  3789. {
  3790. // Need the lock only when adding to the vector, not during construction
  3791. AUTO_LOCK( m_FileHandleTimersMutex );
  3792. m_FileHandleTimers.AddToTail( pTimer );
  3793. pTimer->Start();
  3794. }
  3795. }
  3796. CHECK_DOUBLE_SLASHES( pFileName );
  3797. if ( fs_report_sync_opens.GetInt() > 0 && ThreadInMainThread() &&
  3798. !bReportLongLoads ) // If we're reporting timings we have to delay this spew till after the file has been closed
  3799. {
  3800. Warning( "File::Open( %s ) on main thread.\n", pFileName );
  3801. #if !defined(_CERT) && !IsPS3() && !IsX360()
  3802. if ( fs_report_sync_opens_callstack.GetInt() > 0 )
  3803. {
  3804. // GetCallstack() does not work on PS3, it is using TLS which is not supported in cross-platform manner
  3805. const int CALLSTACK_SIZE = 16;
  3806. void * pAddresses[CALLSTACK_SIZE];
  3807. const int CALLSTACK_SKIP = 1;
  3808. int nCount = GetCallStack( pAddresses, CALLSTACK_SIZE, CALLSTACK_SKIP );
  3809. if ( nCount != 0)
  3810. {
  3811. // Allocate dynamically instead of using the stack, this path is going to be very rarely used
  3812. const int BUFFER_SIZE = 4096;
  3813. char * pBuffer = new char[ BUFFER_SIZE ];
  3814. TranslateStackInfo( pAddresses, CALLSTACK_SIZE, pBuffer, BUFFER_SIZE, "\n", TSISTYLEFLAG_DEFAULT );
  3815. Msg( "%s\n", pBuffer );
  3816. delete[] pBuffer;
  3817. }
  3818. }
  3819. #endif
  3820. }
  3821. // Allow for UNC-type syntax to specify the path ID.
  3822. char tempPathID[MAX_PATH];
  3823. ParsePathID( pFileName, pathID, tempPathID );
  3824. char tempFileName[MAX_PATH];
  3825. Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) );
  3826. Q_FixSlashes( tempFileName );
  3827. #ifdef _WIN32
  3828. Q_strlower( tempFileName );
  3829. #endif
  3830. FileHandle_t hFile;
  3831. // Try each of the search paths in succession
  3832. // FIXME: call createdirhierarchy upon opening for write.
  3833. if ( strstr( pOptions, "r" ) && !strstr( pOptions, "+" ) )
  3834. {
  3835. hFile = OpenForRead( tempFileName, pOptions, flags, pathID, ppszResolvedFilename );
  3836. }
  3837. else
  3838. {
  3839. hFile = OpenForWrite( tempFileName, pOptions, pathID );
  3840. }
  3841. if ( bReportLongLoads )
  3842. {
  3843. // Save the file handle for ID when we close it
  3844. if ( hFile && pTimer )
  3845. {
  3846. pTimer->m_hFile = hFile;
  3847. Q_strncpy( pTimer->m_szName, pFileName, sizeof( pTimer->m_szName ) );
  3848. // See if we've opened this file before so we can accumulate time spent rereading files
  3849. FileOpenDuplicateTime_t *pFileOpenDuplicate = NULL;
  3850. AUTO_LOCK( g_FileOpenDuplicateTimesMutex );
  3851. for ( int nFileOpenDuplicate = g_FileOpenDuplicateTimes.Count() - 1; nFileOpenDuplicate >= 0; --nFileOpenDuplicate )
  3852. {
  3853. FileOpenDuplicateTime_t *pTempFileOpenDuplicate = g_FileOpenDuplicateTimes[ nFileOpenDuplicate ];
  3854. if ( Q_stricmp( pFileName, pTempFileOpenDuplicate->m_szName ) == 0 )
  3855. {
  3856. // Found it!
  3857. pFileOpenDuplicate = pTempFileOpenDuplicate;
  3858. break;
  3859. }
  3860. }
  3861. if ( pFileOpenDuplicate == NULL )
  3862. {
  3863. // We haven't opened this file before, so add it to the list
  3864. pFileOpenDuplicate = new FileOpenDuplicateTime_t;
  3865. if ( pFileOpenDuplicate != NULL )
  3866. {
  3867. g_FileOpenDuplicateTimes.AddToTail( pFileOpenDuplicate );
  3868. Q_strncpy( pFileOpenDuplicate->m_szName, pFileName, sizeof( pTimer->m_szName ) );
  3869. }
  3870. }
  3871. // Increment the number of times we've opened this file
  3872. if ( pFileOpenDuplicate != NULL )
  3873. {
  3874. pFileOpenDuplicate->m_nLoadCount++;
  3875. }
  3876. }
  3877. else
  3878. {
  3879. // File didn't open, pop it off the list
  3880. if ( pTimer != NULL )
  3881. {
  3882. // We need the lock only when removing from the vector, deleting the timer does not need it
  3883. AUTO_LOCK( m_FileHandleTimersMutex );
  3884. for ( int nTimer = m_FileHandleTimers.Count() - 1; nTimer >= 0; --nTimer )
  3885. {
  3886. CFileHandleTimer *pLocalTimer = m_FileHandleTimers[ nTimer ];
  3887. if ( pLocalTimer == pTimer )
  3888. {
  3889. m_FileHandleTimers.Remove( nTimer );
  3890. break;
  3891. }
  3892. }
  3893. delete pTimer;
  3894. }
  3895. }
  3896. }
  3897. return hFile;
  3898. }
  3899. //-----------------------------------------------------------------------------
  3900. // Purpose:
  3901. //-----------------------------------------------------------------------------
  3902. void CBaseFileSystem::Close( FileHandle_t file )
  3903. {
  3904. VPROF_BUDGET( "CBaseFileSystem::Close", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  3905. if ( !file )
  3906. {
  3907. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Close NULL file handle!\n" );
  3908. return;
  3909. }
  3910. unsigned long ulLongLoadThreshold = fs_report_long_reads.GetInt();
  3911. if ( ulLongLoadThreshold > 0 )
  3912. {
  3913. // Let's find the nTimer that matches the file (we assume that we only have to close once
  3914. CFileHandleTimer *pTimer = NULL;
  3915. {
  3916. AUTO_LOCK( m_FileHandleTimersMutex );
  3917. // Still do from the end to the beginning for consistency with previous code (and make access to Count() only once).
  3918. for ( int nTimer = m_FileHandleTimers.Count() - 1; nTimer >= 0; --nTimer )
  3919. {
  3920. CFileHandleTimer *pLocalTimer = m_FileHandleTimers[ nTimer ];
  3921. if ( pLocalTimer && pLocalTimer->m_hFile == file )
  3922. {
  3923. pTimer = pLocalTimer;
  3924. m_FileHandleTimers.Remove( nTimer );
  3925. break;
  3926. }
  3927. }
  3928. }
  3929. // m_FileHandleTimers is not locked here (but we can still access pTimer)
  3930. if ( pTimer != NULL )
  3931. {
  3932. // Found the file, report the time between opening and closing
  3933. pTimer->End();
  3934. unsigned long ulMicroseconds = pTimer->GetDuration().GetMicroseconds();
  3935. if ( ulLongLoadThreshold <= ulMicroseconds )
  3936. {
  3937. Warning( "Open( %lu microsecs, %s )\n", ulMicroseconds, pTimer->m_szName );
  3938. }
  3939. // Accumulate time spent if this file has been opened at least twice
  3940. {
  3941. AUTO_LOCK( g_FileOpenDuplicateTimesMutex );
  3942. for ( int nFileOpenDuplicate = 0; nFileOpenDuplicate < g_FileOpenDuplicateTimes.Count(); nFileOpenDuplicate++ )
  3943. {
  3944. FileOpenDuplicateTime_t *pFileOpenDuplicate = g_FileOpenDuplicateTimes[ nFileOpenDuplicate ];
  3945. if ( Q_stricmp( pTimer->m_szName, pFileOpenDuplicate->m_szName ) == 0 )
  3946. {
  3947. if ( pFileOpenDuplicate->m_nLoadCount > 1 )
  3948. {
  3949. pFileOpenDuplicate->m_flAccumulatedMicroSeconds += pTimer->GetDuration().GetMicrosecondsF();
  3950. }
  3951. break;
  3952. }
  3953. }
  3954. }
  3955. delete pTimer;
  3956. }
  3957. }
  3958. delete (CFileHandle*)file;
  3959. }
  3960. //-----------------------------------------------------------------------------
  3961. // Purpose:
  3962. //-----------------------------------------------------------------------------
  3963. void CBaseFileSystem::Seek( FileHandle_t file, int pos, FileSystemSeek_t whence )
  3964. {
  3965. VPROF_BUDGET( "CBaseFileSystem::Seek", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  3966. CFileHandle *fh = ( CFileHandle *)file;
  3967. if ( !fh )
  3968. {
  3969. FileSystemWarning( FILESYSTEM_WARNING, "Tried to Seek NULL file handle!\n" );
  3970. return;
  3971. }
  3972. #ifndef _CERT
  3973. int nTimeStart = Plat_MSTime();
  3974. #endif
  3975. fh->Seek( pos, whence );
  3976. #ifndef _CERT
  3977. s_IoStats.OnFileSeek( Plat_MSTime() - nTimeStart );
  3978. #endif
  3979. }
  3980. //-----------------------------------------------------------------------------
  3981. // Purpose:
  3982. // Input : file -
  3983. // Output : unsigned int
  3984. //-----------------------------------------------------------------------------
  3985. unsigned int CBaseFileSystem::Tell( FileHandle_t file )
  3986. {
  3987. VPROF_BUDGET( "CBaseFileSystem::Tell", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  3988. if ( !file )
  3989. {
  3990. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Tell NULL file handle!\n" );
  3991. return 0;
  3992. }
  3993. // Pack files are relative
  3994. return (( CFileHandle *)file)->Tell();
  3995. }
  3996. //-----------------------------------------------------------------------------
  3997. // Purpose:
  3998. // Input : file -
  3999. // Output : unsigned int
  4000. //-----------------------------------------------------------------------------
  4001. unsigned int CBaseFileSystem::Size( FileHandle_t file )
  4002. {
  4003. VPROF_BUDGET( "CBaseFileSystem::Size", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4004. if ( !file )
  4005. {
  4006. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Size NULL file handle!\n" );
  4007. return 0;
  4008. }
  4009. return ((CFileHandle *)file)->Size();
  4010. }
  4011. //-----------------------------------------------------------------------------
  4012. // Purpose:
  4013. // Input : file -
  4014. // Output : unsigned int
  4015. //-----------------------------------------------------------------------------
  4016. unsigned int CBaseFileSystem::Size( const char* pFileName, const char *pPathID )
  4017. {
  4018. VPROF_BUDGET( "CBaseFileSystem::Size", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4019. CHECK_DOUBLE_SLASHES( pFileName );
  4020. // handle the case where no name passed...
  4021. if ( !pFileName || !pFileName[0] )
  4022. {
  4023. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Size NULL filename!\n" );
  4024. return 0;
  4025. }
  4026. if ( IsPC() )
  4027. {
  4028. // If we have a whitelist and it's forcing the file to load from Steam instead of from disk,
  4029. // then do this the slow way, otherwise we'll get the wrong file size (i.e. the size of the file on disk).
  4030. CWhitelistSpecs *pWhitelist = m_FileWhitelist.AddRef();
  4031. if ( pWhitelist )
  4032. {
  4033. bool bAllowFromDisk = pWhitelist->m_pAllowFromDiskList->IsFileInList( pFileName );
  4034. m_FileWhitelist.ReleaseRef( pWhitelist );
  4035. if ( !bAllowFromDisk )
  4036. {
  4037. FileHandle_t fh = Open( pFileName, "rb", pPathID );
  4038. if ( fh )
  4039. {
  4040. unsigned int ret = Size( fh );
  4041. Close( fh );
  4042. return ret;
  4043. }
  4044. else
  4045. {
  4046. return 0;
  4047. }
  4048. }
  4049. }
  4050. }
  4051. // Ok, fall through to the fast path.
  4052. int iSize = 0;
  4053. CSearchPathsIterator iter( this, &pFileName, pPathID );
  4054. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  4055. {
  4056. iSize = FastFindFile( pSearchPath, pFileName );
  4057. if ( iSize >= 0 )
  4058. {
  4059. break;
  4060. }
  4061. }
  4062. return iSize;
  4063. }
  4064. //-----------------------------------------------------------------------------
  4065. // Purpose:
  4066. // Input : *path -
  4067. // *pFileName -
  4068. // Output : long
  4069. //-----------------------------------------------------------------------------
  4070. long CBaseFileSystem::FastFileTime( const CSearchPath *path, const char *pFileName )
  4071. {
  4072. struct _stat buf;
  4073. char tempSymlinkBuffer[MAX_PATH];
  4074. pFileName = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pFileName );
  4075. if ( path->GetPackFile() )
  4076. {
  4077. int nIndex, nLength;
  4078. int64 nPosition;
  4079. // If we found the file:
  4080. if ( path->GetPackFile()->FindFile( pFileName, nIndex, nPosition, nLength ) )
  4081. {
  4082. return (path->GetPackFile()->m_lPackFileTime);
  4083. }
  4084. }
  4085. else
  4086. {
  4087. // Is it an absolute path?
  4088. char tempFileName[ MAX_FILEPATH ];
  4089. if ( Q_IsAbsolutePath( pFileName ) )
  4090. {
  4091. Q_strncpy( tempFileName, pFileName, sizeof( tempFileName ) );
  4092. #ifdef SUPPORT_VPK
  4093. if ( m_VPKFiles.Count() )
  4094. {
  4095. #ifdef _DEBUG
  4096. FileSystemWarning( FILESYSTEM_WARNING, "***VPK: FastFileTime Attempting to use full path with VPK file!\n\tFile: %s\n", pFileName );
  4097. #endif
  4098. }
  4099. #endif
  4100. }
  4101. else
  4102. {
  4103. bool bFileInVpk = false;
  4104. #ifdef SUPPORT_VPK
  4105. if ( m_VPKFiles.Count() )
  4106. {
  4107. Q_strncpy( tempFileName, pFileName, sizeof( tempFileName ) );
  4108. Q_FixSlashes( tempFileName );
  4109. // check vpk file
  4110. for ( int i = 0; i < m_VPKFiles.Count(); i++ )
  4111. {
  4112. CPackedStoreFileHandle fHandle = m_VPKFiles[i]->OpenFile( tempFileName );
  4113. if ( fHandle )
  4114. {
  4115. // File found in VPK - return file time of the VPK
  4116. m_VPKFiles[i]->GetPackFileName( fHandle, tempFileName, sizeof( tempFileName ) );
  4117. bFileInVpk = true;
  4118. break;
  4119. }
  4120. }
  4121. }
  4122. #endif
  4123. if ( !bFileInVpk )
  4124. {
  4125. Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", path->GetPathString(), pFileName );
  4126. }
  4127. }
  4128. Q_FixSlashes( tempFileName );
  4129. bool bFixed = false;
  4130. char fixedFATXFilename[MAX_PATH];
  4131. if ( IsX360() )
  4132. {
  4133. bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  4134. }
  4135. if ( FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1 )
  4136. {
  4137. return buf.st_mtime;
  4138. }
  4139. #ifdef LINUX
  4140. // Support Linux and its case sensitive file system
  4141. char realName[MAX_PATH];
  4142. const char *pRealName = findFileInDirCaseInsensitive( tempFileName, realName );
  4143. if ( pRealName && FS_stat( pRealName, &buf ) != -1 )
  4144. {
  4145. return buf.st_mtime;
  4146. }
  4147. #endif
  4148. }
  4149. return ( 0L );
  4150. }
  4151. int CBaseFileSystem::FastFindFile( const CSearchPath *path, const char *pFileName )
  4152. {
  4153. struct _stat buf;
  4154. char tempSymlinkBuffer[MAX_PATH];
  4155. pFileName = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pFileName );
  4156. if ( path->GetPackFile() )
  4157. {
  4158. int nIndexResult;
  4159. int64 nOffsetResult;
  4160. int nLengthResult;
  4161. if ( path->GetPackFile()->FindFile( pFileName, nIndexResult, nOffsetResult, nLengthResult ) )
  4162. {
  4163. return nLengthResult;
  4164. }
  4165. else
  4166. {
  4167. return -1;
  4168. }
  4169. }
  4170. char tempFileName[ MAX_FILEPATH ];
  4171. // Is it an absolute path?
  4172. bool bRelativePath = !Q_IsAbsolutePath( pFileName );
  4173. #ifdef SUPPORT_VPK
  4174. // NOTE: Pack files need relative paths
  4175. if ( !bRelativePath && m_VPKFiles.Count() )
  4176. {
  4177. #ifdef _DEBUG
  4178. FileSystemWarning( FILESYSTEM_WARNING, "***VPK: FastFindFile Attempting to use full path with VPK file!\n\tFile: %s\n", pFileName );
  4179. #endif
  4180. }
  4181. if ( bRelativePath )
  4182. {
  4183. Q_strncpy( tempFileName, pFileName, sizeof( tempFileName ) );
  4184. Q_FixSlashes( tempFileName );
  4185. // check vpk file
  4186. for( int i = 0 ; i < m_VPKFiles.Count(); i++ )
  4187. {
  4188. CPackedStoreFileHandle fHandle = m_VPKFiles[i]->OpenFile( tempFileName );
  4189. if ( fHandle )
  4190. {
  4191. return fHandle.m_nFileSize;
  4192. }
  4193. }
  4194. }
  4195. #endif
  4196. if ( !bRelativePath )
  4197. {
  4198. Q_strncpy( tempFileName, pFileName, sizeof( tempFileName ) );
  4199. }
  4200. else
  4201. {
  4202. Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", path->GetPathString(), pFileName );
  4203. }
  4204. Q_FixSlashes( tempFileName );
  4205. bool bFixed = false;
  4206. char fixedFATXFilename[MAX_PATH];
  4207. if ( IsX360() )
  4208. {
  4209. bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  4210. }
  4211. #if defined(_PS3)
  4212. FixUpPathCaseForPS3(tempFileName);
  4213. #endif
  4214. if ( FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1 )
  4215. {
  4216. LogAccessToFile( "stat", tempFileName, "" );
  4217. return buf.st_size;
  4218. }
  4219. #ifdef LINUX
  4220. // Support Linux and its case sensitive file system
  4221. char realName[MAX_PATH];
  4222. if ( findFileInDirCaseInsensitive( tempFileName, realName ) && FS_stat( realName, &buf ) != -1 )
  4223. {
  4224. return buf.st_size;
  4225. }
  4226. #endif
  4227. return ( -1 );
  4228. }
  4229. //-----------------------------------------------------------------------------
  4230. // Purpose:
  4231. //-----------------------------------------------------------------------------
  4232. bool CBaseFileSystem::EndOfFile( FileHandle_t file )
  4233. {
  4234. if ( !file )
  4235. {
  4236. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to EndOfFile NULL file handle!\n" );
  4237. return true;
  4238. }
  4239. return ((CFileHandle *)file)->EndOfFile();
  4240. }
  4241. //-----------------------------------------------------------------------------
  4242. // Purpose:
  4243. //-----------------------------------------------------------------------------
  4244. int CBaseFileSystem::Read( void *pOutput, int size, FileHandle_t file )
  4245. {
  4246. return ReadEx( pOutput, size, size, file );
  4247. }
  4248. //-----------------------------------------------------------------------------
  4249. // Purpose:
  4250. //-----------------------------------------------------------------------------
  4251. int CBaseFileSystem::ReadEx( void *pOutput, int destSize, int size, FileHandle_t file )
  4252. {
  4253. VPROF_BUDGET( "CBaseFileSystem::Read", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4254. NoteIO();
  4255. if ( !file )
  4256. {
  4257. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Read NULL file handle!\n" );
  4258. return 0;
  4259. }
  4260. if ( size < 0 )
  4261. {
  4262. return 0;
  4263. }
  4264. #ifndef _CERT
  4265. int nTimeStart = Plat_MSTime();
  4266. #endif
  4267. int nRet = ((CFileHandle*)file)->Read(pOutput, destSize, size );
  4268. #ifndef _CERT
  4269. s_IoStats.OnFileRead( Plat_MSTime() - nTimeStart, size );
  4270. #endif
  4271. return nRet;
  4272. }
  4273. //-----------------------------------------------------------------------------
  4274. // Purpose: Takes a passed in KeyValues& head and fills in the precompiled keyvalues data into it.
  4275. // Input : head -
  4276. // type -
  4277. // *filename -
  4278. // *pPathID -
  4279. // Output : Returns true on success, false on failure.
  4280. //-----------------------------------------------------------------------------
  4281. bool CBaseFileSystem::LoadKeyValues( KeyValues& head, KeyValuesPreloadType_t type, char const *filename, char const *pPathID /*= 0*/ )
  4282. {
  4283. bool bret = true;
  4284. #ifndef DEDICATED
  4285. char tempPathID[MAX_PATH];
  4286. ParsePathID( filename, pPathID, tempPathID );
  4287. bret = head.LoadFromFile( this, filename, pPathID );
  4288. #else
  4289. bret = head.LoadFromFile( this, filename, pPathID );
  4290. #endif
  4291. return bret;
  4292. }
  4293. //-----------------------------------------------------------------------------
  4294. // Purpose: If the "PreloadedData" hasn't been purged, then this'll try and instance the KeyValues using the fast path of
  4295. /// compiled keyvalues loaded during startup.
  4296. // Otherwise, it'll just fall through to the regular KeyValues loading routines
  4297. // Input : type -
  4298. // *filename -
  4299. // *pPathID -
  4300. // Output : KeyValues
  4301. //-----------------------------------------------------------------------------
  4302. KeyValues *CBaseFileSystem::LoadKeyValues( KeyValuesPreloadType_t type, char const *filename, char const *pPathID /*= 0*/ )
  4303. {
  4304. KeyValues *kv = new KeyValues( filename );
  4305. if ( kv )
  4306. {
  4307. kv->LoadFromFile( this, filename, pPathID );
  4308. }
  4309. return kv;
  4310. }
  4311. //-----------------------------------------------------------------------------
  4312. // Purpose: This is the fallback method of reading the name of the first key in the file
  4313. // Input : *filename -
  4314. // *pPathID -
  4315. // *rootName -
  4316. // bufsize -
  4317. // Output : Returns true on success, false on failure.
  4318. //-----------------------------------------------------------------------------
  4319. bool CBaseFileSystem::LookupKeyValuesRootKeyName( char const *filename, char const *pPathID, char *rootName, size_t bufsize )
  4320. {
  4321. if ( FileExists( filename, pPathID ) )
  4322. {
  4323. // open file and get shader name
  4324. FileHandle_t hFile = Open( filename, "r", pPathID );
  4325. if ( hFile == FILESYSTEM_INVALID_HANDLE )
  4326. {
  4327. return false;
  4328. }
  4329. char buf[ 128 ];
  4330. ReadLine( buf, sizeof( buf ), hFile );
  4331. Close( hFile );
  4332. // The name will possibly come in as "foo"\n
  4333. // So we need to strip the starting " character
  4334. char *pStart = buf;
  4335. if ( *pStart == '\"' )
  4336. {
  4337. ++pStart;
  4338. }
  4339. // Then copy the rest of the string
  4340. Q_strncpy( rootName, pStart, bufsize );
  4341. // And then strip off the \n and the " character at the end, in that order
  4342. int len = Q_strlen( pStart );
  4343. while ( len > 0 && rootName[ len - 1 ] == '\n' )
  4344. {
  4345. rootName[ len - 1 ] = 0;
  4346. --len;
  4347. }
  4348. while ( len > 0 && rootName[ len - 1 ] == '\"' )
  4349. {
  4350. rootName[ len - 1 ] = 0;
  4351. --len;
  4352. }
  4353. }
  4354. else
  4355. {
  4356. return false;
  4357. }
  4358. return true;
  4359. }
  4360. //-----------------------------------------------------------------------------
  4361. // Purpose:
  4362. //-----------------------------------------------------------------------------
  4363. void CBaseFileSystem::SetupPreloadData()
  4364. {
  4365. int i;
  4366. for ( i = 0; i < m_SearchPaths.Count(); i++ )
  4367. {
  4368. CPackFile* pPF = m_SearchPaths[i].GetPackFile();
  4369. if ( pPF )
  4370. {
  4371. pPF->SetupPreloadData();
  4372. }
  4373. }
  4374. }
  4375. //-----------------------------------------------------------------------------
  4376. // Purpose:
  4377. //-----------------------------------------------------------------------------
  4378. void CBaseFileSystem::DiscardPreloadData()
  4379. {
  4380. int i;
  4381. for( i = 0; i < m_SearchPaths.Count(); i++ )
  4382. {
  4383. CPackFile* pf = m_SearchPaths[i].GetPackFile();
  4384. if ( pf )
  4385. {
  4386. pf->DiscardPreloadData();
  4387. }
  4388. }
  4389. }
  4390. //-----------------------------------------------------------------------------
  4391. // Purpose:
  4392. //-----------------------------------------------------------------------------
  4393. int CBaseFileSystem::Write( void const* pInput, int size, FileHandle_t file )
  4394. {
  4395. VPROF_BUDGET( "CBaseFileSystem::Write", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4396. AUTOBLOCKREPORTER_FH( Write, this, true, file, FILESYSTEM_BLOCKING_SYNCHRONOUS, FileBlockingItem::FB_ACCESS_WRITE );
  4397. CFileHandle *fh = ( CFileHandle *)file;
  4398. if ( !fh )
  4399. {
  4400. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Write NULL file handle!\n" );
  4401. return 0;
  4402. }
  4403. return fh->Write( pInput, size );
  4404. }
  4405. //-----------------------------------------------------------------------------
  4406. // Purpose:
  4407. //-----------------------------------------------------------------------------
  4408. int CBaseFileSystem::FPrintf( FileHandle_t file, const char *pFormat, ... )
  4409. {
  4410. va_list args;
  4411. va_start( args, pFormat );
  4412. VPROF_BUDGET( "CBaseFileSystem::FPrintf", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4413. CFileHandle *fh = ( CFileHandle *)file;
  4414. if ( !fh )
  4415. {
  4416. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to FPrintf NULL file handle!\n" );
  4417. return 0;
  4418. }
  4419. /*
  4420. if ( !fh->GetFileHandle() )
  4421. {
  4422. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to FPrintf NULL file pointer inside valid file handle!\n" );
  4423. return 0;
  4424. }
  4425. */
  4426. char buffer[65535];
  4427. int len = vsnprintf( buffer, sizeof( buffer), pFormat, args );
  4428. len = fh->Write( buffer, len );
  4429. //int len = FS_vfprintf( fh->GetFileHandle() , pFormat, args );
  4430. va_end( args );
  4431. return len;
  4432. }
  4433. //-----------------------------------------------------------------------------
  4434. // Purpose:
  4435. //-----------------------------------------------------------------------------
  4436. void CBaseFileSystem::SetBufferSize( FileHandle_t file, unsigned nBytes )
  4437. {
  4438. CFileHandle *fh = ( CFileHandle *)file;
  4439. if ( !fh )
  4440. {
  4441. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to SetBufferSize NULL file handle!\n" );
  4442. return;
  4443. }
  4444. fh->SetBufferSize( nBytes );
  4445. }
  4446. //-----------------------------------------------------------------------------
  4447. // Purpose:
  4448. //-----------------------------------------------------------------------------
  4449. bool CBaseFileSystem::IsOk( FileHandle_t file )
  4450. {
  4451. CFileHandle *fh = ( CFileHandle *)file;
  4452. if ( !fh )
  4453. {
  4454. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to IsOk NULL file handle!\n" );
  4455. return false;
  4456. }
  4457. return fh->IsOK();
  4458. }
  4459. //-----------------------------------------------------------------------------
  4460. // Purpose:
  4461. //-----------------------------------------------------------------------------
  4462. void CBaseFileSystem::Flush( FileHandle_t file )
  4463. {
  4464. VPROF_BUDGET( "CBaseFileSystem::Flush", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4465. CFileHandle *fh = ( CFileHandle *)file;
  4466. if ( !fh )
  4467. {
  4468. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Flush NULL file handle!\n" );
  4469. return;
  4470. }
  4471. fh->Flush();
  4472. }
  4473. bool CBaseFileSystem::Precache( const char *pFileName, const char *pPathID)
  4474. {
  4475. CHECK_DOUBLE_SLASHES( pFileName );
  4476. // Allow for UNC-type syntax to specify the path ID.
  4477. char tempPathID[MAX_PATH];
  4478. ParsePathID( pFileName, pPathID, tempPathID );
  4479. Assert( pPathID );
  4480. // Really simple, just open, the file, read it all in and close it.
  4481. // We probably want to use file mapping to do this eventually.
  4482. FileHandle_t f = Open( pFileName, "rb", pPathID );
  4483. if ( !f )
  4484. return false;
  4485. // not for consoles, the read discard is a negative benefit, slow and clobbers small drive caches
  4486. if ( IsPC() )
  4487. {
  4488. char buffer[16384];
  4489. while( sizeof(buffer) == Read(buffer,sizeof(buffer),f) );
  4490. }
  4491. Close( f );
  4492. return true;
  4493. }
  4494. //-----------------------------------------------------------------------------
  4495. // Purpose:
  4496. //-----------------------------------------------------------------------------
  4497. char *CBaseFileSystem::ReadLine( char *pOutput, int maxChars, FileHandle_t file )
  4498. {
  4499. VPROF_BUDGET( "CBaseFileSystem::ReadLine", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4500. CFileHandle *fh = ( CFileHandle *)file;
  4501. if ( !fh )
  4502. {
  4503. FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to ReadLine NULL file handle!\n" );
  4504. return NULL;
  4505. }
  4506. m_Stats.nReads++;
  4507. int nRead = 0;
  4508. // Read up to maxchars:
  4509. while( nRead < ( maxChars - 1 ) )
  4510. {
  4511. // Are we at the end of the file?
  4512. if( 1 != fh->Read( pOutput + nRead, 1 ) )
  4513. break;
  4514. // Translate for text mode files:
  4515. if( fh->m_type == FT_PACK_TEXT && pOutput[nRead] == '\r' )
  4516. {
  4517. // Ignore \r
  4518. continue;
  4519. }
  4520. // We're done when we hit a '\n'
  4521. if( pOutput[nRead] == '\n' )
  4522. {
  4523. nRead++;
  4524. break;
  4525. }
  4526. // Get outta here if we find a NULL.
  4527. if( pOutput[nRead] == '\0' )
  4528. {
  4529. pOutput[nRead] = '\n';
  4530. nRead++;
  4531. break;
  4532. }
  4533. nRead++;
  4534. }
  4535. if( nRead < maxChars )
  4536. pOutput[nRead] = '\0';
  4537. m_Stats.nBytesRead += nRead;
  4538. return ( nRead ) ? pOutput : NULL;
  4539. }
  4540. //-----------------------------------------------------------------------------
  4541. // Purpose:
  4542. // Input : *pFileName -
  4543. // Output : long
  4544. //-----------------------------------------------------------------------------
  4545. long CBaseFileSystem::GetFileTime( const char *pFileName, const char *pPathID )
  4546. {
  4547. VPROF_BUDGET( "CBaseFileSystem::GetFileTime", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4548. CHECK_DOUBLE_SLASHES( pFileName );
  4549. CSearchPathsIterator iter( this, &pFileName, pPathID );
  4550. char tempFileName[MAX_PATH];
  4551. Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) );
  4552. Q_FixSlashes( tempFileName );
  4553. #ifdef _WIN32
  4554. Q_strlower( tempFileName );
  4555. #endif
  4556. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  4557. {
  4558. long ft = FastFileTime( pSearchPath, tempFileName );
  4559. if ( ft != 0L )
  4560. {
  4561. if ( !pSearchPath->GetPackFile() && m_LogFuncs.Count() )
  4562. {
  4563. char pTmpFileName[ MAX_FILEPATH ];
  4564. if ( strchr( tempFileName, ':' ) ) //
  4565. {
  4566. Q_strncpy( pTmpFileName, tempFileName, sizeof( pTmpFileName ) );
  4567. }
  4568. else
  4569. {
  4570. Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), tempFileName );
  4571. }
  4572. Q_FixSlashes( pTmpFileName );
  4573. LogAccessToFile( "filetime", pTmpFileName, "" );
  4574. }
  4575. return ft;
  4576. }
  4577. }
  4578. return 0L;
  4579. }
  4580. long CBaseFileSystem::GetPathTime( const char *pFileName, const char *pPathID )
  4581. {
  4582. VPROF_BUDGET( "CBaseFileSystem::GetFileTime", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  4583. CSearchPathsIterator iter( this, &pFileName, pPathID );
  4584. char tempFileName[MAX_PATH];
  4585. Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) );
  4586. Q_FixSlashes( tempFileName );
  4587. #ifdef _WIN32
  4588. Q_strlower( tempFileName );
  4589. #endif
  4590. long pathTime = 0L;
  4591. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  4592. {
  4593. long ft = FastFileTime( pSearchPath, tempFileName );
  4594. if ( ft > pathTime )
  4595. pathTime = ft;
  4596. if ( ft != 0L )
  4597. {
  4598. if ( !pSearchPath->GetPackFile() && m_LogFuncs.Count() )
  4599. {
  4600. char pTmpFileName[ MAX_FILEPATH ];
  4601. if ( strchr( tempFileName, ':' ) )
  4602. {
  4603. Q_strncpy( pTmpFileName, tempFileName, sizeof( pTmpFileName ) );
  4604. }
  4605. else
  4606. {
  4607. Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), tempFileName );
  4608. }
  4609. Q_FixSlashes( pTmpFileName );
  4610. LogAccessToFile( "filetime", pTmpFileName, "" );
  4611. }
  4612. }
  4613. }
  4614. return pathTime;
  4615. }
  4616. bool CBaseFileSystem::ShouldGameReloadFile( const char *pFilename )
  4617. {
  4618. if ( IsGameConsole() )
  4619. {
  4620. return false;
  4621. }
  4622. if ( V_IsAbsolutePath( pFilename ) )
  4623. {
  4624. if ( m_WhitelistSpewFlags & WHITELIST_SPEW_RELOAD_FILES )
  4625. {
  4626. Msg( "Whitelist - reload (absolute path) %s\n", pFilename );
  4627. }
  4628. // They should be checking with relative filenames, but this is easy to remedy if we need to.
  4629. // Easy enough to remedy if we want.. just strip off the path ID prefixes until we find the file.
  4630. Assert( false );
  4631. return true;
  4632. }
  4633. CFileInfo *fileInfos[256];
  4634. int nFileInfos = m_FileTracker.GetFileInfos( fileInfos, ARRAYSIZE( fileInfos ), pFilename );
  4635. if ( nFileInfos == 0 )
  4636. {
  4637. // Ain't heard of this file. It probably came from a BSP or a pak file.
  4638. if ( m_WhitelistSpewFlags & WHITELIST_SPEW_DONT_RELOAD_FILES )
  4639. {
  4640. Msg( "Whitelist - don't reload (unheard-of-file) %s\n", pFilename );
  4641. }
  4642. return false;
  4643. }
  4644. // Note: This might be null, in which case all files are allowed to come off disk.
  4645. bool bFileAllowedToComeFromDisk = true;
  4646. CWhitelistSpecs *pWhitelist = m_FileWhitelist.GetInMainThread();
  4647. if ( pWhitelist )
  4648. bFileAllowedToComeFromDisk = pWhitelist->m_pAllowFromDiskList->IsFileInList( pFilename );
  4649. // Since we don't require the game to specify which path ID it's interested in here, there's a small amount
  4650. // of ambiguity here (because 2 files with the same name could have been loaded from different path IDs).
  4651. // This case should be extremely rare, and we error on the side of simplicity (don't require the game to
  4652. // remember which path ID its files were opened from).
  4653. //
  4654. // In the case where there are multiple files, we error on the side of reloading the file - if any of the
  4655. // files here would require a reload, then we tell the game to reload this file even if it might not
  4656. // have strictly needed to reload it.
  4657. bool bRet = false;
  4658. for ( int i=0; i < nFileInfos; i++ )
  4659. {
  4660. CFileInfo *pFileInfo = fileInfos[i];
  4661. // See comments above k_eFileFlagsFailedToLoadLastTime for info about this case.
  4662. if ( bFileAllowedToComeFromDisk && (pFileInfo->m_Flags & k_eFileFlagsFailedToLoadLastTime) )
  4663. {
  4664. bRet = true;
  4665. break;
  4666. }
  4667. if ( pFileInfo->m_Flags & k_eFileFlagsLoadedFromSteam )
  4668. {
  4669. if ( (pFileInfo->m_Flags & k_eFileFlagsForcedLoadFromSteam) && bFileAllowedToComeFromDisk )
  4670. {
  4671. // So.. the last time we loaded this file, we forced it to come from Steam, but the new whitelist says it's ok if this
  4672. // file is loaded off disk. So reload it.
  4673. //
  4674. // TODO: we could optimize this by checking if there even IS a file on disk that would override the Steam one,
  4675. // and in that case, don't tell the game to reload it.
  4676. //
  4677. // We could also optimize it if we remembered whether we told Steam to allow loads off disk or not.
  4678. // If we did allow loads and Steam still got the file from Steam, then we know that there isn't
  4679. // a file on disk here, and therefore we wouldn't have to reload it now.
  4680. bRet = true;
  4681. break;
  4682. }
  4683. else
  4684. {
  4685. // So we loaded the file from Steam last time, and the new whitelist only allows it to come from Steam, so we're ok.
  4686. //return false;
  4687. }
  4688. }
  4689. else
  4690. {
  4691. if ( bFileAllowedToComeFromDisk )
  4692. {
  4693. // No need to reload. The new whitelist says this file can come off disk, and the last time we loaded it, it was off disk.
  4694. // The client will still verify the CRC of the file with the server to make sure its file is legit..
  4695. //return false;
  4696. }
  4697. else
  4698. {
  4699. // The file was loaded off disk but the server won't allow that now.
  4700. bRet = true;
  4701. break;
  4702. }
  4703. }
  4704. }
  4705. if ( (m_WhitelistSpewFlags & WHITELIST_SPEW_RELOAD_FILES) && bRet )
  4706. Msg( "Whitelist - reload %s\n", pFilename );
  4707. if ( (m_WhitelistSpewFlags & WHITELIST_SPEW_DONT_RELOAD_FILES) && !bRet )
  4708. Msg( "Whitelist - don't reload %s\n", pFilename );
  4709. return bRet;
  4710. }
  4711. void CBaseFileSystem::MarkAllCRCsUnverified()
  4712. {
  4713. if ( IsGameConsole() )
  4714. {
  4715. return;
  4716. }
  4717. m_FileTracker2.MarkAllCRCsUnverified();
  4718. }
  4719. void CBaseFileSystem::CacheFileCRCs( const char *pPathname, ECacheCRCType eType, IFileList *pFilter )
  4720. {
  4721. if ( IsGameConsole() )
  4722. {
  4723. return;
  4724. }
  4725. // Get a list of the unique search path names (mod, game, platform, etc).
  4726. CUtlDict<int,int> searchPathNames;
  4727. m_SearchPathsMutex.Lock();
  4728. for ( int i = 0; i < m_SearchPaths.Count(); i++ )
  4729. {
  4730. CSearchPath *pSearchPath = &m_SearchPaths[i];
  4731. if ( searchPathNames.Find( pSearchPath->GetPathIDString() ) == searchPathNames.InvalidIndex() )
  4732. searchPathNames.Insert( pSearchPath->GetPathIDString() );
  4733. }
  4734. m_SearchPathsMutex.Unlock();
  4735. CacheFileCRCs_R( pPathname, eType, pFilter, searchPathNames );
  4736. }
  4737. void CBaseFileSystem::CacheFileCRCs_R( const char *pPathname, ECacheCRCType eType, IFileList *pFilter, CUtlDict<int,int> &searchPathNames )
  4738. {
  4739. if ( IsGameConsole() )
  4740. {
  4741. return;
  4742. }
  4743. char searchStr[MAX_PATH];
  4744. bool bRecursive = false;
  4745. if ( eType == k_eCacheCRCType_SingleFile )
  4746. {
  4747. V_snprintf( searchStr, sizeof( searchStr ), "%s", pPathname );
  4748. }
  4749. else if ( eType == k_eCacheCRCType_Directory )
  4750. {
  4751. V_ComposeFileName( pPathname, "*.*", searchStr, sizeof( searchStr ) );
  4752. }
  4753. else if ( eType == k_eCacheCRCType_Directory_Recursive )
  4754. {
  4755. V_ComposeFileName( pPathname, "*.*", searchStr, sizeof( searchStr ) );
  4756. bRecursive = true;
  4757. }
  4758. // Get the path we're searching in.
  4759. char pathDirectory[MAX_PATH];
  4760. V_strncpy( pathDirectory, searchStr, sizeof( pathDirectory ) );
  4761. V_StripLastDir( pathDirectory, sizeof( pathDirectory ) );
  4762. /*
  4763. Note: This is tricky because the client could check different path IDs with the same filename and we'd either
  4764. have the same file or a different file depending on these two cases:
  4765. a) they have one file : hl2\blah.txt
  4766. (in this case, checking the GAME and MOD path IDs for blah.txt return the same file CRC)
  4767. b) they have two files: hl2\blah.txt AND hl2mp\blah.txt
  4768. (in this case, checking the GAME and MOD path IDs for blah.txt return different file CRCs)
  4769. */
  4770. CUtlDict< CUtlVector<CStoreIDEntry>* ,int> filesByStoreID; // key=filename, value=list of store IDs this filename was found in
  4771. for ( int i=searchPathNames.First(); i != searchPathNames.InvalidIndex(); i = searchPathNames.Next( i ) )
  4772. {
  4773. // Now find all the files..
  4774. int foundStoreID;
  4775. const char *pPathIDStr = searchPathNames.GetElementName( i );
  4776. FileFindHandle_t findHandle;
  4777. const char *pFilename = FindFirstHelper( searchStr, pPathIDStr, &findHandle, &foundStoreID );
  4778. while ( pFilename )
  4779. {
  4780. if ( pFilename[0] != '.' )
  4781. {
  4782. char relativeName[MAX_PATH];
  4783. V_ComposeFileName( pathDirectory, pFilename, relativeName, sizeof( relativeName ) );
  4784. if ( FindIsDirectory( findHandle ) )
  4785. {
  4786. if ( bRecursive )
  4787. CacheFileCRCs_R( relativeName, eType, pFilter, searchPathNames );
  4788. }
  4789. else
  4790. {
  4791. if ( pFilter->IsFileInList( relativeName ) )
  4792. {
  4793. CStoreIDEntry *pPrevRecord = FindPrevFileByStoreID( filesByStoreID, pFilename, pPathIDStr, foundStoreID );
  4794. if ( pPrevRecord )
  4795. {
  4796. // Ok, we already found this file in an earlier search path with the same storeID (i.e. the exact same disk path)
  4797. // so rather than recalculate the CRC redundantly, just copy the CRC from the previous one into a record with the new path ID string.
  4798. // This saves a lot of redundant CRC calculations since logdir, default_write_path, game, and mod all share directories.
  4799. m_FileTracker.CacheFileCRC_Copy( pPathIDStr, relativeName, pPrevRecord->m_PathIDString.String() );
  4800. }
  4801. else
  4802. {
  4803. // Ok, we want the CRC for this file.
  4804. m_FileTracker.CacheFileCRC( pPathIDStr, relativeName );
  4805. }
  4806. }
  4807. }
  4808. }
  4809. FindData_t *pFindData = &m_FindData[findHandle];
  4810. if ( !FindNextFileHelper( pFindData, &foundStoreID ) )
  4811. break;
  4812. pFilename = pFindData->findData.cFileName;
  4813. }
  4814. FindClose( findHandle );
  4815. }
  4816. filesByStoreID.PurgeAndDeleteElements();
  4817. }
  4818. EFileCRCStatus CBaseFileSystem::CheckCachedFileHash( const char *pPathID, const char *pRelativeFilename, int nFileFraction, FileHash_t *pFileHash )
  4819. {
  4820. return m_FileTracker2.CheckCachedFileHash( pPathID, pRelativeFilename, nFileFraction, pFileHash );
  4821. }
  4822. void CBaseFileSystem::EnableWhitelistFileTracking( bool bEnable, bool bCacheAllVPKHashes, bool bRecalculateAndCheckHashes )
  4823. {
  4824. if ( IsGameConsole() )
  4825. {
  4826. m_WhitelistFileTrackingEnabled = false;
  4827. return;
  4828. }
  4829. if ( m_WhitelistFileTrackingEnabled != -1 )
  4830. {
  4831. Error( "CBaseFileSystem::EnableWhitelistFileTracking called more than once." );
  4832. }
  4833. m_WhitelistFileTrackingEnabled = bEnable;
  4834. if ( m_WhitelistFileTrackingEnabled && bCacheAllVPKHashes )
  4835. {
  4836. CacheAllVPKFileHashes( bCacheAllVPKHashes, bRecalculateAndCheckHashes );
  4837. }
  4838. }
  4839. void CBaseFileSystem::CacheAllVPKFileHashes( bool bCacheAllVPKHashes, bool bRecalculateAndCheckHashes )
  4840. {
  4841. #ifdef SUPPORT_VPK
  4842. for( int i = 0 ; i < m_VPKFiles.Count(); i++ )
  4843. {
  4844. if ( !m_VPKFiles[i]->BTestDirectoryHash() )
  4845. {
  4846. Msg( "VPK dir file hash does not match. File corrupted or modified.\n" );
  4847. }
  4848. if ( !m_VPKFiles[i]->BTestMasterChunkHash() )
  4849. {
  4850. Msg( "VPK chunk hash hash does not match. File corrupted or modified.\n" );
  4851. }
  4852. CUtlVector<ChunkHashFraction_t> &vecChunkHash = m_VPKFiles[i]->AccessPackFileHashes();
  4853. CPackedStoreFileHandle fhandle = m_VPKFiles[i]->GetHandleForHashingFiles();
  4854. CUtlVector<ChunkHashFraction_t> vecChunkHashFractionCopy;
  4855. if ( bRecalculateAndCheckHashes )
  4856. {
  4857. CUtlString sPackFileErrors;
  4858. m_VPKFiles[i]->GetPackFileLoadErrorSummary( sPackFileErrors );
  4859. if ( sPackFileErrors.Length() )
  4860. {
  4861. Msg( "Errors occured loading files.\n" );
  4862. Msg( "%s", sPackFileErrors.String() );
  4863. Msg( "Verify integrity of your game files, perform memory and disk diagnostics on your system.\n" );
  4864. }
  4865. else
  4866. Msg( "No VPK Errors occured loading files.\n" );
  4867. Msg( "Recomputing all VPK file hashes.\n" );
  4868. vecChunkHashFractionCopy.Swap( vecChunkHash );
  4869. }
  4870. int cFailures = 0;
  4871. if ( vecChunkHash.Count() == 0 )
  4872. {
  4873. if ( vecChunkHashFractionCopy.Count() == 0 )
  4874. Msg( "File hash information not found: Hashing all VPK files for pure server operation.\n" );
  4875. m_VPKFiles[i]->HashAllChunkFiles();
  4876. if ( vecChunkHashFractionCopy.Count() != 0 )
  4877. {
  4878. if ( vecChunkHash.Count() != vecChunkHashFractionCopy.Count() )
  4879. {
  4880. Msg( "VPK hash count does not match. VPK content may be corrupt.\n" );
  4881. }
  4882. else if ( Q_memcmp( vecChunkHash.Base(), vecChunkHashFractionCopy.Base(), vecChunkHash.Count()*sizeof(vecChunkHash[0])) != 0 )
  4883. {
  4884. Msg( "VPK hashes do not match. VPK content may be corrupt.\n" );
  4885. // find the actual mismatch
  4886. FOR_EACH_VEC( vecChunkHashFractionCopy, iHash )
  4887. {
  4888. if ( Q_memcmp( vecChunkHashFractionCopy[iHash].m_md5contents.bits, vecChunkHash[iHash].m_md5contents.bits, sizeof( vecChunkHashFractionCopy[iHash].m_md5contents.bits ) ) != 0 )
  4889. {
  4890. Msg( "VPK hash for file %d failure at offset %x.\n", vecChunkHashFractionCopy[iHash].m_nPackFileNumber, vecChunkHashFractionCopy[iHash].m_nFileFraction );
  4891. cFailures++;
  4892. }
  4893. }
  4894. }
  4895. }
  4896. }
  4897. if ( bCacheAllVPKHashes )
  4898. {
  4899. Msg( "Loading VPK file hashes for pure server operation.\n" );
  4900. FOR_EACH_VEC( vecChunkHash, i )
  4901. {
  4902. m_FileTracker2.AddFileHashForVPKFile( vecChunkHash[i].m_nPackFileNumber, vecChunkHash[i].m_nFileFraction, vecChunkHash[i].m_cbChunkLen, vecChunkHash[i].m_md5contents, fhandle );
  4903. }
  4904. }
  4905. else
  4906. {
  4907. if ( cFailures == 0 && vecChunkHash.Count() == vecChunkHashFractionCopy.Count() )
  4908. Msg( "File hashes checked. %d matches. no failures.\n", vecChunkHash.Count() );
  4909. else
  4910. Msg( "File hashes checked. %d matches. %d failures.\n", vecChunkHash.Count(), cFailures );
  4911. }
  4912. }
  4913. #endif
  4914. }
  4915. bool CBaseFileSystem::CheckVPKFileHash( int PackFileID, int nPackFileNumber, int nFileFraction, MD5Value_t &md5Value )
  4916. {
  4917. #ifdef SUPPORT_VPK
  4918. for( int i = 0 ; i < m_VPKFiles.Count(); i++ )
  4919. {
  4920. if ( m_VPKFiles[i]->m_PackFileID == PackFileID )
  4921. {
  4922. ChunkHashFraction_t fileHashFraction;
  4923. if ( m_VPKFiles[i]->FindFileHashFraction( nPackFileNumber, nFileFraction, fileHashFraction ) )
  4924. {
  4925. CPackedStoreFileHandle fhandle = m_VPKFiles[i]->GetHandleForHashingFiles();
  4926. fhandle.m_nFileNumber = nPackFileNumber;
  4927. char szFileName[MAX_PATH];
  4928. m_VPKFiles[i]->GetPackFileName( fhandle, szFileName, sizeof(szFileName) );
  4929. char hex[ 34 ];
  4930. Q_memset( hex, 0, sizeof( hex ) );
  4931. Q_binarytohex( (const byte *)md5Value.bits, sizeof( md5Value.bits ), hex, sizeof( hex ) );
  4932. char hex2[ 34 ];
  4933. Q_memset( hex2, 0, sizeof( hex2 ) );
  4934. Q_binarytohex( (const byte *)fileHashFraction.m_md5contents.bits, sizeof( fileHashFraction.m_md5contents.bits ), hex2, sizeof( hex2 ) );
  4935. if ( Q_memcmp( fileHashFraction.m_md5contents.bits, md5Value.bits, sizeof(md5Value.bits) ) != 0 )
  4936. {
  4937. Msg( "File %s offset %x hash %s does not match ( should be %s ) \n", szFileName, nFileFraction, hex, hex2 );
  4938. return false;
  4939. }
  4940. else
  4941. {
  4942. return true;
  4943. }
  4944. }
  4945. }
  4946. }
  4947. return false;
  4948. #endif
  4949. }
  4950. void CBaseFileSystem::RegisterFileWhitelist( IFileList *pWantCRCList, IFileList *pAllowFromDiskList, IFileList **pFilesToReload )
  4951. {
  4952. if ( IsGameConsole() )
  4953. {
  4954. return;
  4955. }
  4956. CWhitelistSpecs *pOldList = m_FileWhitelist.GetInMainThread();
  4957. if ( pOldList )
  4958. {
  4959. m_FileWhitelist.ReleaseRef( pOldList ); // Get rid of our reference to it so it can be freed.
  4960. m_FileWhitelist.ResetWhenNoRemainingReferences( NULL ); // Wait for everyone else to stop hanging onto it, then free it.
  4961. // Free the old ones (other threads shouldn't have access to these anymore because
  4962. pOldList->m_pAllowFromDiskList->Release();
  4963. pOldList->m_pWantCRCList->Release();
  4964. }
  4965. if ( pAllowFromDiskList && pWantCRCList )
  4966. {
  4967. CWhitelistSpecs *pNewList = new CWhitelistSpecs;
  4968. pNewList->m_pAllowFromDiskList = pAllowFromDiskList;
  4969. pNewList->m_pWantCRCList = pWantCRCList;
  4970. m_FileWhitelist.Init( pNewList );
  4971. }
  4972. }
  4973. int CBaseFileSystem::GetUnverifiedFileHashes( CUnverifiedFileHash *pFiles, int nMaxFiles )
  4974. {
  4975. return m_FileTracker2.GetUnverifiedFileHashes( pFiles, nMaxFiles );
  4976. }
  4977. int CBaseFileSystem::GetWhitelistSpewFlags()
  4978. {
  4979. return m_WhitelistSpewFlags;
  4980. }
  4981. void CBaseFileSystem::SetWhitelistSpewFlags( int flags )
  4982. {
  4983. m_WhitelistSpewFlags = flags;
  4984. }
  4985. //-----------------------------------------------------------------------------
  4986. // Purpose:
  4987. // Input : *pString -
  4988. // maxCharsIncludingTerminator -
  4989. // fileTime -
  4990. //-----------------------------------------------------------------------------
  4991. void CBaseFileSystem::FileTimeToString( char *pString, int maxCharsIncludingTerminator, long fileTime )
  4992. {
  4993. if ( IsGameConsole() )
  4994. {
  4995. char szTemp[ 256 ];
  4996. time_t time = fileTime;
  4997. V_strncpy( szTemp, ctime( &time ), sizeof( szTemp ) );
  4998. char *pFinalColon = Q_strrchr( szTemp, ':' );
  4999. if ( pFinalColon )
  5000. *pFinalColon = '\0';
  5001. // Clip off the day of the week
  5002. V_strncpy( pString, szTemp + 4, maxCharsIncludingTerminator );
  5003. }
  5004. else
  5005. {
  5006. time_t time = fileTime;
  5007. V_strncpy( pString, ctime( &time ), maxCharsIncludingTerminator );
  5008. // We see a linefeed at the end of these strings...if there is one, gobble it up
  5009. int len = V_strlen( pString );
  5010. if ( pString[ len - 1 ] == '\n' )
  5011. {
  5012. pString[ len - 1 ] = '\0';
  5013. }
  5014. pString[maxCharsIncludingTerminator-1] = '\0';
  5015. }
  5016. }
  5017. //-----------------------------------------------------------------------------
  5018. // Purpose:
  5019. // Input : *pFileName -
  5020. // Output : Returns true on success, false on failure.
  5021. //-----------------------------------------------------------------------------
  5022. bool CBaseFileSystem::FileExists( const char *pFileName, const char *pPathID )
  5023. {
  5024. VPROF_BUDGET( "CBaseFileSystem::FileExists", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  5025. NoteIO();
  5026. CHECK_DOUBLE_SLASHES( pFileName );
  5027. CSearchPathsIterator iter( this, &pFileName, pPathID );
  5028. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  5029. {
  5030. int size = FastFindFile( pSearchPath, pFileName );
  5031. if ( size >= 0 )
  5032. {
  5033. return true;
  5034. }
  5035. }
  5036. return false;
  5037. }
  5038. bool CBaseFileSystem::IsFileWritable( char const *pFileName, char const *pPathID /*=0*/ )
  5039. {
  5040. CHECK_DOUBLE_SLASHES( pFileName );
  5041. struct _stat buf;
  5042. char tempPathID[MAX_PATH];
  5043. ParsePathID( pFileName, pPathID, tempPathID );
  5044. if ( Q_IsAbsolutePath( pFileName ) )
  5045. {
  5046. bool bFixed = false;
  5047. char fixedFATXFilename[MAX_PATH];
  5048. if ( IsX360() )
  5049. {
  5050. bFixed = FixupFATXFilename( pFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5051. }
  5052. if ( FS_stat( bFixed ? fixedFATXFilename : pFileName, &buf ) != -1 )
  5053. {
  5054. #ifdef WIN32
  5055. if ( buf.st_mode & _S_IWRITE )
  5056. #elif defined( _PS3 )
  5057. if( buf.st_mode & S_IWUSR )
  5058. #elif POSIX
  5059. if ( buf.st_mode & S_IWRITE )
  5060. #else
  5061. if ( buf.st_mode & S_IWRITE )
  5062. #endif
  5063. {
  5064. return true;
  5065. }
  5066. }
  5067. return false;
  5068. }
  5069. CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLPACK );
  5070. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  5071. {
  5072. char tempFileName[ MAX_FILEPATH ];
  5073. Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFileName );
  5074. Q_FixSlashes( tempFileName );
  5075. bool bFixed = false;
  5076. char fixedFATXFilename[MAX_PATH];
  5077. if ( IsX360() )
  5078. {
  5079. bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5080. }
  5081. if ( FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1 )
  5082. {
  5083. #ifdef WIN32
  5084. if ( buf.st_mode & _S_IWRITE )
  5085. #elif defined( _PS3 )
  5086. if( buf.st_mode & S_IWUSR )
  5087. #elif POSIX
  5088. if ( buf.st_mode & S_IWRITE )
  5089. #else
  5090. if ( buf.st_mode & S_IWRITE )
  5091. #endif
  5092. {
  5093. return true;
  5094. }
  5095. }
  5096. }
  5097. return false;
  5098. }
  5099. bool CBaseFileSystem::SetFileWritable( char const *pFileName, bool writable, const char *pPathID /*= 0*/ )
  5100. {
  5101. CHECK_DOUBLE_SLASHES( pFileName );
  5102. #ifdef _WIN32
  5103. int pmode = writable ? ( _S_IWRITE | _S_IREAD ) : ( _S_IREAD );
  5104. #elif defined( _PS3 )
  5105. int pmode = writable ? ( S_IWUSR | S_IRUSR ) : ( S_IRUSR );
  5106. #else
  5107. int pmode = writable ? ( S_IWRITE | S_IREAD ) : ( S_IREAD );
  5108. #endif
  5109. char tempPathID[MAX_PATH];
  5110. ParsePathID( pFileName, pPathID, tempPathID );
  5111. if ( Q_IsAbsolutePath( pFileName ) )
  5112. {
  5113. bool bFixed = false;
  5114. char fixedFATXFilename[MAX_PATH];
  5115. if ( IsX360() )
  5116. {
  5117. bFixed = FixupFATXFilename( pFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5118. }
  5119. return ( FS_chmod( bFixed ? fixedFATXFilename : pFileName, pmode ) == 0 );
  5120. }
  5121. CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLPACK );
  5122. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  5123. {
  5124. char tempFilename[ MAX_FILEPATH ];
  5125. Q_snprintf( tempFilename, sizeof( tempFilename ), "%s%s", pSearchPath->GetPathString(), pFileName );
  5126. Q_FixSlashes( tempFilename );
  5127. bool bFixed = false;
  5128. char fixedFATXFilename[MAX_PATH];
  5129. if ( IsX360() )
  5130. {
  5131. bFixed = FixupFATXFilename( tempFilename, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5132. }
  5133. if ( FS_chmod( bFixed ? fixedFATXFilename : tempFilename, pmode ) == 0 )
  5134. {
  5135. return true;
  5136. }
  5137. }
  5138. // Failure
  5139. return false;
  5140. }
  5141. //-----------------------------------------------------------------------------
  5142. // Purpose:
  5143. // Input : *pFileName -
  5144. // Output : Returns true on success, false on failure.
  5145. //-----------------------------------------------------------------------------
  5146. bool CBaseFileSystem::IsDirectory( const char *pFileName, const char *pathID )
  5147. {
  5148. CHECK_DOUBLE_SLASHES( pFileName );
  5149. // Allow for UNC-type syntax to specify the path ID.
  5150. struct _stat buf;
  5151. char pTempBuf[MAX_PATH];
  5152. Q_strncpy( pTempBuf, pFileName, sizeof(pTempBuf) );
  5153. Q_StripTrailingSlash( pTempBuf );
  5154. pFileName = pTempBuf;
  5155. char tempPathID[MAX_PATH];
  5156. ParsePathID( pFileName, pathID, tempPathID );
  5157. if ( Q_IsAbsolutePath( pFileName ) )
  5158. {
  5159. bool bFixed = false;
  5160. char fixedFATXFilename[MAX_PATH];
  5161. if ( IsX360() )
  5162. {
  5163. bFixed = FixupFATXFilename( pFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5164. }
  5165. if ( FS_stat( bFixed ? fixedFATXFilename : pFileName, &buf ) != -1 )
  5166. {
  5167. if ( buf.st_mode & _S_IFDIR )
  5168. return true;
  5169. }
  5170. return false;
  5171. }
  5172. CSearchPathsIterator iter( this, &pFileName, pathID, FILTER_CULLPACK );
  5173. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  5174. {
  5175. char tempFileName[ MAX_FILEPATH ];
  5176. Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFileName );
  5177. Q_FixSlashes( tempFileName );
  5178. bool bFixed = false;
  5179. char fixedFATXFilename[MAX_PATH];
  5180. if ( IsX360() )
  5181. {
  5182. bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5183. }
  5184. if ( FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1 )
  5185. {
  5186. if ( buf.st_mode & _S_IFDIR )
  5187. return true;
  5188. }
  5189. }
  5190. #ifdef SUPPORT_VPK
  5191. //
  5192. // Let's see if the directory exists in the VPK file structure
  5193. //
  5194. if ( 0 == m_VPKDirectories.Count() )
  5195. {
  5196. // Populate the directory list
  5197. CUtlStringList dirNames;
  5198. CUtlStringList fileNames;
  5199. for( int i = 0 ; i < m_VPKFiles.Count(); i++ )
  5200. {
  5201. m_VPKFiles[i]->GetFileAndDirLists( dirNames, fileNames, true );
  5202. }
  5203. FOR_EACH_VEC( dirNames, j )
  5204. {
  5205. m_VPKDirectories.Insert( dirNames[j], 0 );
  5206. }
  5207. }
  5208. // If the dir isn't part of the VPK structure then game over
  5209. char szPathWithCorrectSlashes[MAX_PATH];
  5210. V_strncpy( szPathWithCorrectSlashes, pFileName, sizeof( szPathWithCorrectSlashes ) );
  5211. V_FixSlashes( szPathWithCorrectSlashes, '/' );
  5212. return ( -1 != m_VPKDirectories.Find( szPathWithCorrectSlashes ) );
  5213. #else
  5214. return ( false );
  5215. #endif
  5216. }
  5217. //-----------------------------------------------------------------------------
  5218. // Purpose:
  5219. // Input : *path -
  5220. //-----------------------------------------------------------------------------
  5221. void CBaseFileSystem::CreateDirHierarchy( const char *pRelativePath, const char *pathID )
  5222. {
  5223. CHECK_DOUBLE_SLASHES( pRelativePath );
  5224. // Allow for UNC-type syntax to specify the path ID.
  5225. char tempPathID[MAX_PATH];
  5226. ParsePathID( pRelativePath, pathID, tempPathID );
  5227. char szScratchFileName[MAX_PATH];
  5228. if ( !Q_IsAbsolutePath( pRelativePath ) )
  5229. {
  5230. Assert( pathID );
  5231. ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pRelativePath, pathID );
  5232. }
  5233. else
  5234. {
  5235. Q_strncpy( szScratchFileName, pRelativePath, sizeof(szScratchFileName) );
  5236. }
  5237. Q_FixSlashes( szScratchFileName );
  5238. int len = strlen( szScratchFileName ) + 1;
  5239. char *end = szScratchFileName + len;
  5240. char *s = szScratchFileName;
  5241. while( s < end )
  5242. {
  5243. if ( PATHSEPARATOR( *s ) &&
  5244. s != szScratchFileName &&
  5245. ( IsLinux() || IsPS3() || *( s - 1 ) != ':' )
  5246. )
  5247. {
  5248. char save = *s;
  5249. *s = '\0';
  5250. #if defined( _WIN32 )
  5251. _mkdir( szScratchFileName );
  5252. #elif defined( _PS3 )
  5253. CellFsStat status;
  5254. //Only create is the path doesn't exist already - Jawad.
  5255. if ( cellFsStat( szScratchFileName, &status ) != CELL_FS_SUCCEEDED )
  5256. cellFsMkdir( szScratchFileName, CELL_FS_DEFAULT_CREATE_MODE_1 );
  5257. #elif defined( POSIX )
  5258. mkdir( szScratchFileName, S_IRWXU | S_IRGRP | S_IROTH );// owner has rwx, rest have r
  5259. #endif
  5260. *s = save;
  5261. }
  5262. s++;
  5263. }
  5264. #if defined( _WIN32 )
  5265. _mkdir( szScratchFileName );
  5266. #elif defined( _PS3 )
  5267. CellFsStat status;
  5268. if ( cellFsStat( szScratchFileName, &status ) != CELL_FS_SUCCEEDED )
  5269. cellFsMkdir( szScratchFileName, CELL_FS_DEFAULT_CREATE_MODE_1 );
  5270. #elif defined( POSIX )
  5271. mkdir( szScratchFileName, S_IRWXU | S_IRGRP | S_IROTH );
  5272. #endif
  5273. }
  5274. //-----------------------------------------------------------------------------
  5275. // Purpose: Given an absolute path, do a find first find next on it and build
  5276. // a list of files. Physical file system only
  5277. //-----------------------------------------------------------------------------
  5278. void CBaseFileSystem::FindFileAbsoluteListHelper( CUtlVector< CUtlString > &outAbsolutePathNames, FindData_t &findData, const char *pAbsoluteFindName )
  5279. {
  5280. // TODO: figure out what PS3 does without VPKs
  5281. #ifndef _PS3
  5282. bool bFixed = false;
  5283. char fixedFATXFilename[MAX_PATH];
  5284. if ( IsX360() )
  5285. {
  5286. bFixed = FixupFATXFilename( pAbsoluteFindName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5287. }
  5288. char path[MAX_PATH];
  5289. V_strncpy( path, pAbsoluteFindName, sizeof(path) );
  5290. V_StripFilename( path );
  5291. findData.findHandle = FS_FindFirstFile( bFixed ? fixedFATXFilename : pAbsoluteFindName, &findData.findData );
  5292. while ( findData.findHandle != INVALID_HANDLE_VALUE )
  5293. {
  5294. char result[MAX_PATH];
  5295. V_ComposeFileName( path, findData.findData.cFileName, result, sizeof(result) );
  5296. outAbsolutePathNames.AddToTail( result );
  5297. if ( !FS_FindNextFile( findData.findHandle, &findData.findData ) )
  5298. {
  5299. FS_FindClose( findData.findHandle );
  5300. findData.findHandle = INVALID_HANDLE_VALUE;
  5301. }
  5302. }
  5303. #else
  5304. Error( "Not implemented!\n" );
  5305. #endif
  5306. }
  5307. //-----------------------------------------------------------------------------
  5308. // Purpose: Searches for a file in all paths and results absolute path names
  5309. // for the file, works in pack files (zip and vpk) too. Lets you search for
  5310. // something like sound/sound.cache and get a list of every sound cache
  5311. //-----------------------------------------------------------------------------
  5312. void CBaseFileSystem::FindFileAbsoluteList( CUtlVector< CUtlString > &outAbsolutePathNames, const char *pWildCard, const char *pPathID )
  5313. {
  5314. // TODO: figure out what PS3 does without VPKs
  5315. #ifndef _PS3
  5316. VPROF_BUDGET( "CBaseFileSystem::FindFileAbsoluteList", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  5317. outAbsolutePathNames.Purge();
  5318. FindData_t findData;
  5319. if ( pPathID )
  5320. {
  5321. findData.m_FilterPathID = g_PathIDTable.AddString( pPathID );
  5322. }
  5323. int maxlen = strlen( pWildCard ) + 1;
  5324. findData.wildCardString.AddMultipleToTail( maxlen );
  5325. Q_strncpy( findData.wildCardString.Base(), pWildCard, maxlen );
  5326. Q_FixSlashes( findData.wildCardString.Base() );
  5327. findData.findHandle = INVALID_HANDLE_VALUE;
  5328. if ( Q_IsAbsolutePath( pWildCard ) )
  5329. {
  5330. FindFileAbsoluteListHelper( outAbsolutePathNames, findData, pWildCard );
  5331. }
  5332. else
  5333. {
  5334. int c = m_SearchPaths.Count();
  5335. for ( findData.currentSearchPathID = 0;
  5336. findData.currentSearchPathID < c;
  5337. findData.currentSearchPathID++ )
  5338. {
  5339. CSearchPath *pSearchPath = &m_SearchPaths[findData.currentSearchPathID];
  5340. if ( pSearchPath->GetPackFile() ) // We're going to search pack files second
  5341. continue;
  5342. if ( FilterByPathID( pSearchPath, findData.m_FilterPathID ) )
  5343. continue;
  5344. // already visited this path
  5345. if ( findData.m_VisitedSearchPaths.MarkVisit( *pSearchPath ) )
  5346. continue;
  5347. char tempFileName[ MAX_FILEPATH ];
  5348. Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), findData.wildCardString.Base() );
  5349. Q_FixSlashes( tempFileName );
  5350. FindFileAbsoluteListHelper( outAbsolutePathNames, findData, tempFileName );
  5351. }
  5352. }
  5353. //TODO: zips!
  5354. #if defined( SUPPORT_VPK )
  5355. //
  5356. // Now that we have searched the filesystem let's look in the VPK files
  5357. //
  5358. FOR_EACH_VEC( m_VPKFiles, i )
  5359. {
  5360. CUtlStringList dirMatchesFromVPK, fileMatchesFromVPK;
  5361. m_VPKFiles[i]->GetFileAndDirLists( pWildCard, dirMatchesFromVPK, fileMatchesFromVPK, true );
  5362. FOR_EACH_VEC( dirMatchesFromVPK, j )
  5363. {
  5364. char result[MAX_PATH];
  5365. V_ComposeFileName( m_VPKFiles[i]->FullPathName(), dirMatchesFromVPK[j], result, sizeof(result) );
  5366. outAbsolutePathNames.AddToTail( result );
  5367. }
  5368. FOR_EACH_VEC( fileMatchesFromVPK, j )
  5369. {
  5370. char result[MAX_PATH];
  5371. V_ComposeFileName( m_VPKFiles[i]->FullPathName(), fileMatchesFromVPK[j], result, sizeof(result) );
  5372. outAbsolutePathNames.AddToTail( result );
  5373. }
  5374. }
  5375. #endif
  5376. #else
  5377. Error( "Not implemented!\n" );
  5378. #endif
  5379. }
  5380. //-----------------------------------------------------------------------------
  5381. // Purpose:
  5382. // Input : *pWildCard -
  5383. // *pHandle -
  5384. // Output : const char
  5385. //-----------------------------------------------------------------------------
  5386. const char *CBaseFileSystem::FindFirstEx( const char *pWildCard, const char *pPathID, FileFindHandle_t *pHandle )
  5387. {
  5388. CHECK_DOUBLE_SLASHES( pWildCard );
  5389. return FindFirstHelper( pWildCard, pPathID, pHandle, NULL );
  5390. }
  5391. const char *CBaseFileSystem::FindFirstHelper( const char *pWildCard, const char *pPathID, FileFindHandle_t *pHandle, int *pFoundStoreID )
  5392. {
  5393. VPROF_BUDGET( "CBaseFileSystem::FindFirst", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  5394. Assert( pWildCard );
  5395. Assert( pHandle );
  5396. FileFindHandle_t hTmpHandle = m_FindData.AddToTail();
  5397. FindData_t *pFindData = &m_FindData[hTmpHandle];
  5398. Assert( pFindData );
  5399. if ( pPathID )
  5400. {
  5401. pFindData->m_FilterPathID = g_PathIDTable.AddString( pPathID );
  5402. }
  5403. int maxlen = strlen( pWildCard ) + 1;
  5404. pFindData->wildCardString.AddMultipleToTail( maxlen );
  5405. Q_strncpy( pFindData->wildCardString.Base(), pWildCard, maxlen );
  5406. Q_FixSlashes( pFindData->wildCardString.Base() );
  5407. pFindData->findHandle = INVALID_HANDLE_VALUE;
  5408. if ( Q_IsAbsolutePath( pWildCard ) )
  5409. {
  5410. bool bFixed = false;
  5411. char fixedFATXFilename[MAX_PATH];
  5412. if ( IsX360() )
  5413. {
  5414. bFixed = FixupFATXFilename( pWildCard, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5415. }
  5416. pFindData->findHandle = FS_FindFirstFile( bFixed ? fixedFATXFilename : pWildCard, &pFindData->findData );
  5417. pFindData->currentSearchPathID = -1;
  5418. }
  5419. else
  5420. {
  5421. int c = m_SearchPaths.Count();
  5422. for ( pFindData->currentSearchPathID = 0;
  5423. pFindData->currentSearchPathID < c;
  5424. pFindData->currentSearchPathID++ )
  5425. {
  5426. CSearchPath *pSearchPath = &m_SearchPaths[pFindData->currentSearchPathID];
  5427. // FIXME: Should findfirst/next work with pak files?
  5428. if ( pSearchPath->GetPackFile() )
  5429. continue;
  5430. if ( FilterByPathID( pSearchPath, pFindData->m_FilterPathID ) )
  5431. continue;
  5432. // already visited this path
  5433. if ( pFindData->m_VisitedSearchPaths.MarkVisit( *pSearchPath ) )
  5434. continue;
  5435. char tempFileName[ MAX_FILEPATH ];
  5436. Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFindData->wildCardString.Base() );
  5437. Q_FixSlashes( tempFileName );
  5438. bool bFixed = false;
  5439. char fixedFATXFilename[MAX_PATH];
  5440. if ( IsX360() )
  5441. {
  5442. bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5443. }
  5444. pFindData->findHandle = FS_FindFirstFile( bFixed ? fixedFATXFilename : tempFileName, &pFindData->findData );
  5445. pFindData->m_CurrentStoreID = pSearchPath->m_storeId;
  5446. if ( pFindData->findHandle != INVALID_HANDLE_VALUE )
  5447. break;
  5448. }
  5449. }
  5450. #ifdef SUPPORT_VPK
  5451. //
  5452. // Now that we have searched the filesystem let's look in the VPK files
  5453. //
  5454. for( int i = 0 ; i < m_VPKFiles.Count(); i++ )
  5455. {
  5456. m_VPKFiles[i]->GetFileAndDirLists( pWildCard, pFindData->m_dirMatchesFromVPK, pFindData->m_fileMatchesFromVPK, true );
  5457. }
  5458. #endif
  5459. // We have a result from the filesystem
  5460. if( pFindData->findHandle != INVALID_HANDLE_VALUE )
  5461. {
  5462. // Remember that we visited this file already.
  5463. pFindData->m_VisitedFiles.Insert( pFindData->findData.cFileName, 0 );
  5464. if ( pFoundStoreID )
  5465. *pFoundStoreID = pFindData->m_CurrentStoreID;
  5466. *pHandle = hTmpHandle;
  5467. return pFindData->findData.cFileName;
  5468. }
  5469. #ifdef SUPPORT_VPK
  5470. // We have a file result from the VPK file but not the filesystem
  5471. else if ( pFindData->m_fileMatchesFromVPK.Count() > 0 )
  5472. {
  5473. // Remember that we visited this file already.
  5474. pFindData->m_VisitedFiles.Insert( V_UnqualifiedFileName( pFindData->m_fileMatchesFromVPK[0] ), 0 );
  5475. *pHandle = hTmpHandle;
  5476. V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_fileMatchesFromVPK[0] ), sizeof( pFindData->findData.cFileName ) );
  5477. pFindData->findData.dwFileAttributes = 0;
  5478. delete pFindData->m_fileMatchesFromVPK.Head();
  5479. pFindData->m_fileMatchesFromVPK.RemoveMultipleFromHead( 1 );
  5480. return pFindData->findData.cFileName;
  5481. }
  5482. // We have a dir result from the VPK file but not the filesystem
  5483. else if ( pFindData->m_dirMatchesFromVPK.Count() > 0 )
  5484. {
  5485. // Remember that we visited this file already.
  5486. pFindData->m_VisitedFiles.Insert( V_UnqualifiedFileName( pFindData->m_dirMatchesFromVPK[0] ), 0 );
  5487. *pHandle = hTmpHandle;
  5488. V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_dirMatchesFromVPK[0] ), sizeof( pFindData->findData.cFileName ) );
  5489. pFindData->findData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
  5490. delete pFindData->m_dirMatchesFromVPK.Head();
  5491. pFindData->m_dirMatchesFromVPK.RemoveMultipleFromHead( 1 );
  5492. return pFindData->findData.cFileName;
  5493. }
  5494. #endif
  5495. // Handle failure here
  5496. pFindData = 0;
  5497. m_FindData.Remove(hTmpHandle);
  5498. *pHandle = -1;
  5499. return NULL;
  5500. }
  5501. const char *CBaseFileSystem::FindFirst( const char *pWildCard, FileFindHandle_t *pHandle )
  5502. {
  5503. return FindFirstEx( pWildCard, NULL, pHandle );
  5504. }
  5505. // Get the next file, trucking through the path. . don't check for duplicates.
  5506. bool CBaseFileSystem::FindNextFileHelper( FindData_t *pFindData, int *pFoundStoreID )
  5507. {
  5508. // PAK files???
  5509. // Try the same search path that we were already searching on.
  5510. if( FS_FindNextFile( pFindData->findHandle, &pFindData->findData ) )
  5511. {
  5512. if ( pFoundStoreID )
  5513. *pFoundStoreID = pFindData->m_CurrentStoreID;
  5514. return true;
  5515. }
  5516. // This happens when we searched a full path
  5517. if ( pFindData->currentSearchPathID < 0 )
  5518. return false;
  5519. pFindData->currentSearchPathID++;
  5520. if ( pFindData->findHandle != INVALID_HANDLE_VALUE )
  5521. {
  5522. FS_FindClose( pFindData->findHandle );
  5523. }
  5524. pFindData->findHandle = INVALID_HANDLE_VALUE;
  5525. int c = m_SearchPaths.Count();
  5526. for( ; pFindData->currentSearchPathID < c; ++pFindData->currentSearchPathID )
  5527. {
  5528. CSearchPath *pSearchPath = &m_SearchPaths[pFindData->currentSearchPathID];
  5529. // FIXME: Should this work with PAK files?
  5530. if ( pSearchPath->GetPackFile() )
  5531. continue;
  5532. if ( FilterByPathID( pSearchPath, pFindData->m_FilterPathID ) )
  5533. continue;
  5534. // already visited this path
  5535. if ( pFindData->m_VisitedSearchPaths.MarkVisit( *pSearchPath ) )
  5536. continue;
  5537. char tempFileName[ MAX_FILEPATH ];
  5538. Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFindData->wildCardString.Base() );
  5539. Q_FixSlashes( tempFileName );
  5540. bool bFixed = false;
  5541. char fixedFATXFilename[MAX_PATH];
  5542. if ( IsX360() )
  5543. {
  5544. bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5545. }
  5546. pFindData->findHandle = FS_FindFirstFile( bFixed ? fixedFATXFilename : tempFileName, &pFindData->findData );
  5547. pFindData->m_CurrentStoreID = pSearchPath->m_storeId;
  5548. if ( pFindData->findHandle != INVALID_HANDLE_VALUE )
  5549. {
  5550. if ( pFoundStoreID )
  5551. *pFoundStoreID = pFindData->m_CurrentStoreID;
  5552. return true;
  5553. }
  5554. }
  5555. #ifdef SUPPORT_VPK
  5556. // Return the next one from the list of VPK matches if there is one
  5557. if ( pFindData->m_fileMatchesFromVPK.Count() > 0 )
  5558. {
  5559. V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_fileMatchesFromVPK[0] ), sizeof( pFindData->findData.cFileName ) );
  5560. pFindData->findData.dwFileAttributes = 0;
  5561. delete pFindData->m_fileMatchesFromVPK.Head();
  5562. pFindData->m_fileMatchesFromVPK.RemoveMultipleFromHead( 1 );
  5563. return true;
  5564. }
  5565. // Return the next one from the list of VPK matches if there is one
  5566. if ( pFindData->m_dirMatchesFromVPK.Count() > 0 )
  5567. {
  5568. V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_dirMatchesFromVPK[0] ), sizeof( pFindData->findData.cFileName ) );
  5569. pFindData->findData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
  5570. delete pFindData->m_dirMatchesFromVPK.Head();
  5571. pFindData->m_dirMatchesFromVPK.RemoveMultipleFromHead( 1 );
  5572. return true;
  5573. }
  5574. #endif
  5575. return false;
  5576. }
  5577. //-----------------------------------------------------------------------------
  5578. // Purpose:
  5579. // Input : handle -
  5580. // Output : const char
  5581. //-----------------------------------------------------------------------------
  5582. const char *CBaseFileSystem::FindNext( FileFindHandle_t handle )
  5583. {
  5584. VPROF_BUDGET( "CBaseFileSystem::FindNext", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
  5585. FindData_t *pFindData = &m_FindData[handle];
  5586. while( 1 )
  5587. {
  5588. if( FindNextFileHelper( pFindData, NULL ) )
  5589. {
  5590. if ( pFindData->m_VisitedFiles.Find( pFindData->findData.cFileName ) == -1 )
  5591. {
  5592. pFindData->m_VisitedFiles.Insert( pFindData->findData.cFileName, 0 );
  5593. return pFindData->findData.cFileName;
  5594. }
  5595. }
  5596. else
  5597. {
  5598. return NULL;
  5599. }
  5600. }
  5601. }
  5602. //-----------------------------------------------------------------------------
  5603. // Purpose:
  5604. // Input : handle -
  5605. // Output : Returns true on success, false on failure.
  5606. //-----------------------------------------------------------------------------
  5607. bool CBaseFileSystem::FindIsDirectory( FileFindHandle_t handle )
  5608. {
  5609. FindData_t *pFindData = &m_FindData[handle];
  5610. return !!( pFindData->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY );
  5611. }
  5612. //-----------------------------------------------------------------------------
  5613. // Purpose:
  5614. // Input : handle -
  5615. //-----------------------------------------------------------------------------
  5616. void CBaseFileSystem::FindClose( FileFindHandle_t handle )
  5617. {
  5618. if ( ( handle < 0 ) || ( !m_FindData.IsInList( handle ) ) )
  5619. return;
  5620. FindData_t *pFindData = &m_FindData[handle];
  5621. Assert(pFindData);
  5622. if ( pFindData->findHandle != INVALID_HANDLE_VALUE)
  5623. {
  5624. FS_FindClose( pFindData->findHandle );
  5625. }
  5626. pFindData->findHandle = INVALID_HANDLE_VALUE;
  5627. pFindData->wildCardString.Purge();
  5628. #ifdef SUPPORT_VPK
  5629. pFindData->m_fileMatchesFromVPK.PurgeAndDeleteElements();
  5630. pFindData->m_dirMatchesFromVPK.PurgeAndDeleteElements();
  5631. #endif
  5632. m_FindData.Remove( handle );
  5633. }
  5634. //-----------------------------------------------------------------------------
  5635. // Purpose:
  5636. // Input : *pFileName -
  5637. //-----------------------------------------------------------------------------
  5638. void CBaseFileSystem::GetLocalCopy( const char *pFileName )
  5639. {
  5640. // do nothing. . everything is local.
  5641. }
  5642. #ifdef SUPPORT_VPK
  5643. //-----------------------------------------------------------------------------
  5644. // Purpose:
  5645. // Input : *pFileName -
  5646. //-----------------------------------------------------------------------------
  5647. CPackedStoreFileHandle CBaseFileSystem::FindFileInVPKs( const char *pFileName )
  5648. {
  5649. CPackedStoreFileHandle retVal;
  5650. // Try to find the path in the mounted VPK files
  5651. for( int i = 0 ; i < m_VPKFiles.Count(); i++ )
  5652. {
  5653. retVal = m_VPKFiles[i]->OpenFile( pFileName );
  5654. if ( retVal )
  5655. {
  5656. break;
  5657. }
  5658. }
  5659. return retVal;
  5660. }
  5661. #endif
  5662. //-----------------------------------------------------------------------------
  5663. // Converts a partial path into a full path
  5664. // Relative paths that are pack based are returned as an absolute path .../zip?.zip/foo
  5665. // A pack absolute path can be sent back in for opening, and the file will be properly
  5666. // detected as pack based and mounted inside the pack.
  5667. //-----------------------------------------------------------------------------
  5668. const char *CBaseFileSystem::RelativePathToFullPath( const char *pFileName, const char *pPathID, char *pFullPath, int fullPathBufferSize, PathTypeFilter_t pathFilter, PathTypeQuery_t *pPathType )
  5669. {
  5670. CHECK_DOUBLE_SLASHES( pFileName );
  5671. struct _stat buf;
  5672. if ( pPathType )
  5673. {
  5674. *pPathType = PATH_IS_NORMAL;
  5675. }
  5676. #ifdef _PS3
  5677. // crush the filename to lowercase
  5678. char lowercasedname[256];
  5679. V_strncpy( lowercasedname, pFileName, 255 );
  5680. V_strnlwr( lowercasedname, 255 );
  5681. pFileName = lowercasedname;
  5682. #endif
  5683. // Fill in the default in case it's not found...
  5684. Q_strncpy( pFullPath, pFileName, fullPathBufferSize );
  5685. if ( IsPC() && pathFilter == FILTER_NONE )
  5686. {
  5687. // X360TBD: PC legacy behavior never returned pack paths
  5688. // do legacy behavior to ensure naive callers don't break
  5689. pathFilter = FILTER_CULLPACK;
  5690. }
  5691. CSearchPathsIterator iter( this, &pFileName, pPathID, pathFilter );
  5692. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  5693. {
  5694. int dummy;
  5695. int64 dummy64;
  5696. CPackFile *pPack = pSearchPath->GetPackFile();
  5697. if ( pPack )
  5698. {
  5699. if ( pPack->FindFile( pFileName, dummy, dummy64, dummy ) )
  5700. {
  5701. if ( pPathType )
  5702. {
  5703. if ( pPack->m_bIsMapPath )
  5704. {
  5705. *pPathType |= PATH_IS_MAPPACKFILE;
  5706. }
  5707. else
  5708. {
  5709. *pPathType |= PATH_IS_PACKFILE;
  5710. }
  5711. if ( pSearchPath->m_bIsDvdDevPath )
  5712. {
  5713. *pPathType |= PATH_IS_DVDDEV;
  5714. }
  5715. }
  5716. // form an encoded absolute path that can be decoded by our FS as pak based
  5717. int len;
  5718. V_strncpy( pFullPath, pPack->m_ZipName.String(), fullPathBufferSize );
  5719. len = strlen( pFullPath );
  5720. V_AppendSlash( pFullPath, fullPathBufferSize - len );
  5721. len = strlen( pFullPath );
  5722. V_strncpy( pFullPath + len, pFileName, fullPathBufferSize - len );
  5723. return pFullPath;
  5724. }
  5725. continue;
  5726. }
  5727. char tempFileName[ MAX_FILEPATH ];
  5728. Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFileName );
  5729. Q_FixSlashes( tempFileName );
  5730. bool bFixed = false;
  5731. char fixedFATXFilename[MAX_PATH];
  5732. if ( IsX360() )
  5733. {
  5734. // do not expose naming convolution to callers
  5735. // callers don't expect the filename to change, only that the path get filled out
  5736. bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) );
  5737. }
  5738. bool bFound = FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1;
  5739. if ( bFound )
  5740. {
  5741. Q_strncpy( pFullPath, tempFileName, fullPathBufferSize );
  5742. if ( pPathType && pSearchPath->m_bIsDvdDevPath )
  5743. {
  5744. *pPathType |= PATH_IS_DVDDEV;
  5745. }
  5746. return pFullPath;
  5747. }
  5748. }
  5749. #ifdef SUPPORT_VPK
  5750. // Try to find the path in the mounted VPK files
  5751. if ( FindFileInVPKs( pFileName ) )
  5752. {
  5753. char pModPath[MAX_PATH];
  5754. GetSearchPath( "MOD", false, pModPath, sizeof( pModPath ) );
  5755. V_snprintf( pFullPath, fullPathBufferSize, "%s%s", pModPath, pFileName );
  5756. return pFullPath;
  5757. }
  5758. #endif
  5759. // not found
  5760. return NULL;
  5761. }
  5762. #if IsGameConsole()
  5763. bool CBaseFileSystem::GetPackFileInfoFromRelativePath( const char *pFileName, const char *pPathID, char *pPackPath, int nPackPathBufferSize, int64 &nPosition, int64 &nLength )
  5764. {
  5765. CHECK_DOUBLE_SLASHES( pFileName );
  5766. #ifdef _PS3
  5767. // crush the filename to lowercase
  5768. char lowercasedname[256];
  5769. V_strncpy( lowercasedname, pFileName, 255 );
  5770. V_strnlwr( lowercasedname, 255 );
  5771. pFileName = lowercasedname;
  5772. #endif
  5773. CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLNONPACK );
  5774. for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
  5775. {
  5776. int nIndex;
  5777. int nLength32;
  5778. CPackFile *pPack = pSearchPath->GetPackFile();
  5779. if ( pPack )
  5780. {
  5781. if ( pPack->FindFile( pFileName, nIndex, nPosition, nLength32 ) )
  5782. {
  5783. // Support all types as long as it is in a pack file.
  5784. // Note that if it is in dvddev, prefetching will not really help (and may not be initialized correctly anyway).
  5785. V_strncpy( pPackPath, pPack->m_ZipName.String(), nPackPathBufferSize );
  5786. nLength = nLength32;
  5787. return true;
  5788. }
  5789. }
  5790. }
  5791. // not found
  5792. pPackPath[0] = '\0';
  5793. nPosition = -1;
  5794. nLength = -1;
  5795. return false;
  5796. }
  5797. #endif
  5798. const char *CBaseFileSystem::GetLocalPath( const char *pFileName, char *pLocalPath, int localPathBufferSize )
  5799. {
  5800. CHECK_DOUBLE_SLASHES( pFileName );
  5801. return RelativePathToFullPath( pFileName, NULL, pLocalPath, localPathBufferSize );
  5802. }
  5803. //-----------------------------------------------------------------------------
  5804. // Returns true on success, otherwise false if it can't be resolved
  5805. //-----------------------------------------------------------------------------
  5806. bool CBaseFileSystem::FullPathToRelativePathEx( const char *pFullPath, const char *pPathId, char *pRelative, int nMaxLen )
  5807. {
  5808. CHECK_DOUBLE_SLASHES( pFullPath );
  5809. int nInlen = Q_strlen( pFullPath );
  5810. if ( nInlen <= 0 )
  5811. {
  5812. pRelative[ 0 ] = 0;
  5813. return false;
  5814. }
  5815. Q_strncpy( pRelative, pFullPath, nMaxLen );
  5816. char pInPath[ MAX_FILEPATH ];
  5817. Q_strncpy( pInPath, pFullPath, sizeof( pInPath ) );
  5818. #ifdef _WIN32
  5819. Q_strlower( pInPath );
  5820. #endif
  5821. Q_FixSlashes( pInPath );
  5822. CUtlSymbol lookup;
  5823. if ( pPathId )
  5824. {
  5825. lookup = g_PathIDTable.AddString( pPathId );
  5826. }
  5827. int c = m_SearchPaths.Count();
  5828. for( int i = 0; i < c; i++ )
  5829. {
  5830. // FIXME: Should this work with embedded pak files?
  5831. if ( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath )
  5832. continue;
  5833. // Skip paths that are not on the specified search path
  5834. if ( FilterByPathID( &m_SearchPaths[i], lookup ) )
  5835. continue;
  5836. char pSearchBase[ MAX_FILEPATH ];
  5837. Q_strncpy( pSearchBase, m_SearchPaths[i].GetPathString(), sizeof( pSearchBase ) );
  5838. #ifdef _WIN32
  5839. Q_strlower( pSearchBase );
  5840. #endif
  5841. Q_FixSlashes( pSearchBase );
  5842. int nSearchLen = Q_strlen( pSearchBase );
  5843. if ( Q_strnicmp( pSearchBase, pInPath, nSearchLen ) )
  5844. continue;
  5845. Q_strncpy( pRelative, &pInPath[ nSearchLen ], nMaxLen );
  5846. return true;
  5847. }
  5848. return false;
  5849. }
  5850. //-----------------------------------------------------------------------------
  5851. // Obsolete version
  5852. //-----------------------------------------------------------------------------
  5853. bool CBaseFileSystem::FullPathToRelativePath( const char *pFullPath, char *pRelative, int nMaxLen )
  5854. {
  5855. return FullPathToRelativePathEx( pFullPath, NULL, pRelative, nMaxLen );
  5856. }
  5857. //-----------------------------------------------------------------------------
  5858. // Deletes a file
  5859. //-----------------------------------------------------------------------------
  5860. void CBaseFileSystem::RemoveFile( char const* pRelativePath, const char *pathID )
  5861. {
  5862. CHECK_DOUBLE_SLASHES( pRelativePath );
  5863. // Allow for UNC-type syntax to specify the path ID.
  5864. char tempPathID[MAX_PATH];
  5865. ParsePathID( pRelativePath, pathID, tempPathID );
  5866. Assert( pathID || !IsGameConsole() );
  5867. // Opening for write or append uses Write Path
  5868. char szScratchFileName[MAX_PATH];
  5869. if ( Q_IsAbsolutePath( pRelativePath ) )
  5870. {
  5871. Q_strncpy( szScratchFileName, pRelativePath, sizeof( szScratchFileName ) );
  5872. }
  5873. else
  5874. {
  5875. ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pRelativePath, pathID );
  5876. }
  5877. int fail = unlink( szScratchFileName );
  5878. if ( fail != 0 )
  5879. {
  5880. FileSystemWarning( FILESYSTEM_WARNING, "Unable to remove %s! (errno %x)\n", szScratchFileName, errno );
  5881. }
  5882. }
  5883. //-----------------------------------------------------------------------------
  5884. // Renames a file
  5885. //-----------------------------------------------------------------------------
  5886. bool CBaseFileSystem::RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID )
  5887. {
  5888. Assert( pOldPath && pNewPath );
  5889. CHECK_DOUBLE_SLASHES( pOldPath );
  5890. CHECK_DOUBLE_SLASHES( pNewPath );
  5891. // Allow for UNC-type syntax to specify the path ID.
  5892. char pPathIdCopy[MAX_PATH];
  5893. const char *pOldPathId = pathID;
  5894. if ( pathID )
  5895. {
  5896. Q_strncpy( pPathIdCopy, pathID, sizeof( pPathIdCopy ) );
  5897. pOldPathId = pPathIdCopy;
  5898. }
  5899. char tempOldPathID[MAX_PATH];
  5900. ParsePathID( pOldPath, pOldPathId, tempOldPathID );
  5901. Assert( pOldPathId );
  5902. // Allow for UNC-type syntax to specify the path ID.
  5903. char tempNewPathID[MAX_PATH];
  5904. ParsePathID( pNewPath, pathID, tempNewPathID );
  5905. Assert( pathID );
  5906. char pNewFileName[ MAX_PATH ];
  5907. char szScratchFileName[MAX_PATH];
  5908. // The source file may be in a fallback directory, so just resolve the actual path, don't assume pathid...
  5909. RelativePathToFullPath( pOldPath, pOldPathId, szScratchFileName, sizeof( szScratchFileName ) );
  5910. // Figure out the dest path
  5911. if ( !Q_IsAbsolutePath( pNewPath ) )
  5912. {
  5913. ComputeFullWritePath( pNewFileName, sizeof( pNewFileName ), pNewPath, pathID );
  5914. }
  5915. else
  5916. {
  5917. Q_strncpy( pNewFileName, pNewPath, sizeof(pNewFileName) );
  5918. }
  5919. // Make sure the directory exitsts, too
  5920. char pPathOnly[ MAX_PATH ];
  5921. Q_strncpy( pPathOnly, pNewFileName, sizeof( pPathOnly ) );
  5922. Q_StripFilename( pPathOnly );
  5923. CreateDirHierarchy( pPathOnly, pathID );
  5924. // Now copy the file over
  5925. int fail = rename( szScratchFileName, pNewFileName );
  5926. if (fail != 0)
  5927. {
  5928. FileSystemWarning( FILESYSTEM_WARNING, "Unable to rename %s to %s!\n", szScratchFileName, pNewFileName );
  5929. return false;
  5930. }
  5931. return true;
  5932. }
  5933. //-----------------------------------------------------------------------------
  5934. // Purpose:
  5935. // Input : **ppdir -
  5936. //-----------------------------------------------------------------------------
  5937. bool CBaseFileSystem::GetCurrentDirectory( char* pDirectory, int maxlen )
  5938. {
  5939. #if defined( _WIN32 ) && !defined( _X360 )
  5940. if ( !::GetCurrentDirectoryA( maxlen, pDirectory ) )
  5941. #elif ( defined( POSIX ) && !defined( _PS3 ) ) || defined( _X360 )
  5942. if ( !getcwd( pDirectory, maxlen ) )
  5943. #endif
  5944. return false;
  5945. Q_FixSlashes(pDirectory);
  5946. // Strip the last slash
  5947. int len = strlen(pDirectory);
  5948. if ( pDirectory[ len-1 ] == CORRECT_PATH_SEPARATOR )
  5949. {
  5950. pDirectory[ len-1 ] = 0;
  5951. }
  5952. return true;
  5953. }
  5954. //-----------------------------------------------------------------------------
  5955. // Purpose:
  5956. // Input : pfnWarning - warning function callback
  5957. //-----------------------------------------------------------------------------
  5958. void CBaseFileSystem::SetWarningFunc( void (*pfnWarning)( const char *fmt, ... ) )
  5959. {
  5960. m_pfnWarning = pfnWarning;
  5961. }
  5962. //-----------------------------------------------------------------------------
  5963. // Purpose:
  5964. // Input : level -
  5965. //-----------------------------------------------------------------------------
  5966. void CBaseFileSystem::SetWarningLevel( FileWarningLevel_t level )
  5967. {
  5968. m_fwLevel = level;
  5969. }
  5970. const FileSystemStatistics *CBaseFileSystem::GetFilesystemStatistics()
  5971. {
  5972. return &m_Stats;
  5973. }
  5974. //-----------------------------------------------------------------------------
  5975. // Purpose: Get VPK file IO stats for OGS reporting
  5976. //-----------------------------------------------------------------------------
  5977. void CBaseFileSystem::GetVPKFileStatisticsKV( KeyValues *pKV )
  5978. {
  5979. if ( pKV && !V_strcmp( pKV->GetName(), "ForceVpkOnlyExtensions" ) )
  5980. {
  5981. AUTO_LOCK( m_OpenedFilesMutex ); // this code will force VPK only extensions
  5982. FOR_EACH_SUBKEY( pKV, kvExt )
  5983. {
  5984. m_NonexistingFilesExtensions[ kvExt->GetName() ] = kvExt->GetBool();
  5985. }
  5986. return;
  5987. }
  5988. for( int i = 0; i < m_VPKFiles.Count(); i++ )
  5989. {
  5990. m_VPKFiles[i]->GetPackFileLoadErrorSummaryKV( pKV );
  5991. }
  5992. }
  5993. //-----------------------------------------------------------------------------
  5994. // Purpose:
  5995. // Input : level -
  5996. // *fmt -
  5997. // ... -
  5998. //-----------------------------------------------------------------------------
  5999. void CBaseFileSystem::FileSystemWarning( FileWarningLevel_t level, const char *fmt, ... )
  6000. {
  6001. #ifdef _CERT
  6002. return;
  6003. #endif
  6004. if ( level > m_fwLevel )
  6005. return;
  6006. if ( ( fs_warning_mode.GetInt() == 1 && !ThreadInMainThread() ) || ( fs_warning_mode.GetInt() == 2 && ThreadInMainThread() ) )
  6007. return;
  6008. va_list argptr;
  6009. char warningtext[ 4096 ];
  6010. va_start( argptr, fmt );
  6011. Q_vsnprintf( warningtext, sizeof( warningtext ), fmt, argptr );
  6012. va_end( argptr );
  6013. // Dump to stdio
  6014. printf( "%s", warningtext );
  6015. if ( m_pfnWarning )
  6016. {
  6017. (*m_pfnWarning)( warningtext );
  6018. }
  6019. else
  6020. {
  6021. #ifdef _WIN32
  6022. Plat_DebugString( warningtext );
  6023. #endif
  6024. }
  6025. }
  6026. //-----------------------------------------------------------------------------
  6027. // Purpose:
  6028. //-----------------------------------------------------------------------------
  6029. CBaseFileSystem::COpenedFile::COpenedFile( void )
  6030. {
  6031. m_pFile = NULL;
  6032. m_pName = NULL;
  6033. }
  6034. //-----------------------------------------------------------------------------
  6035. // Purpose:
  6036. //-----------------------------------------------------------------------------
  6037. CBaseFileSystem::COpenedFile::~COpenedFile( void )
  6038. {
  6039. delete[] m_pName;
  6040. }
  6041. //-----------------------------------------------------------------------------
  6042. // Purpose:
  6043. // Input : src -
  6044. //-----------------------------------------------------------------------------
  6045. CBaseFileSystem::COpenedFile::COpenedFile( const COpenedFile& src )
  6046. {
  6047. m_pFile = src.m_pFile;
  6048. if ( src.m_pName )
  6049. {
  6050. int len = strlen( src.m_pName ) + 1;
  6051. m_pName = new char[ len ];
  6052. Q_strncpy( m_pName, src.m_pName, len );
  6053. }
  6054. else
  6055. {
  6056. m_pName = NULL;
  6057. }
  6058. }
  6059. //-----------------------------------------------------------------------------
  6060. // Purpose:
  6061. // Input : src -
  6062. // Output : Returns true on success, false on failure.
  6063. //-----------------------------------------------------------------------------
  6064. bool CBaseFileSystem::COpenedFile::operator==( const CBaseFileSystem::COpenedFile& src ) const
  6065. {
  6066. return src.m_pFile == m_pFile;
  6067. }
  6068. //-----------------------------------------------------------------------------
  6069. // Purpose:
  6070. // Input : *name -
  6071. //-----------------------------------------------------------------------------
  6072. void CBaseFileSystem::COpenedFile::SetName( char const *name )
  6073. {
  6074. delete[] m_pName;
  6075. int len = strlen( name ) + 1;
  6076. m_pName = new char[ len ];
  6077. Q_strncpy( m_pName, name, len );
  6078. }
  6079. //-----------------------------------------------------------------------------
  6080. // Purpose:
  6081. // Output : char
  6082. //-----------------------------------------------------------------------------
  6083. char const *CBaseFileSystem::COpenedFile::GetName( void )
  6084. {
  6085. return m_pName ? m_pName : "???";
  6086. }
  6087. //-----------------------------------------------------------------------------
  6088. // Purpose:
  6089. //-----------------------------------------------------------------------------
  6090. CBaseFileSystem::CSearchPath::CSearchPath( void )
  6091. {
  6092. m_Path = g_PathIDTable.AddString( "" );
  6093. m_pDebugPath = "";
  6094. m_storeId = 0;
  6095. m_pPackFile = NULL;
  6096. m_pPathIDInfo = NULL;
  6097. m_bIsDvdDevPath = false;
  6098. m_bIsLocalizedPath = false;
  6099. }
  6100. //-----------------------------------------------------------------------------
  6101. // Purpose:
  6102. //-----------------------------------------------------------------------------
  6103. CBaseFileSystem::CSearchPath::~CSearchPath( void )
  6104. {
  6105. if ( m_pPackFile )
  6106. {
  6107. m_pPackFile->Release();
  6108. }
  6109. }
  6110. //-----------------------------------------------------------------------------
  6111. // Purpose:
  6112. //-----------------------------------------------------------------------------
  6113. CBaseFileSystem::CSearchPath *CBaseFileSystem::CSearchPathsIterator::GetFirst()
  6114. {
  6115. if ( m_SearchPaths.Count() )
  6116. {
  6117. m_visits.Reset();
  6118. m_iCurrent = -1;
  6119. m_bExcluded = false;
  6120. return GetNext();
  6121. }
  6122. return &m_EmptySearchPath;
  6123. }
  6124. //-----------------------------------------------------------------------------
  6125. // Purpose:
  6126. //-----------------------------------------------------------------------------
  6127. CBaseFileSystem::CSearchPath *CBaseFileSystem::CSearchPathsIterator::GetNext()
  6128. {
  6129. CSearchPath *pSearchPath = NULL;
  6130. // PURPOSELY!!! split the 360 dvddev logic from the nominal (shipping) case
  6131. // the logic is permuted slightly to do the right kind of filtering in dvddev or strict mode
  6132. // 360 can optionally ignore and exclude a local search path in dvddev mode
  6133. // excluding a local search path falls through to its cloned dvddev cache path
  6134. // map paths are exempt from this exclusion logic
  6135. if ( IsGameConsole() && ( m_DVDMode == DVDMODE_DEV || m_DVDMode == DVDMODE_DEV_VISTA ) && m_Filename[0] )
  6136. {
  6137. for ( m_iCurrent++; m_iCurrent < m_SearchPaths.Count(); m_iCurrent++ )
  6138. {
  6139. pSearchPath = &m_SearchPaths[m_iCurrent];
  6140. if ( m_PathTypeFilter == FILTER_CULLPACK && pSearchPath->GetPackFile() )
  6141. continue;
  6142. if ( ( m_PathTypeFilter == FILTER_CULLLOCALIZED || m_PathTypeFilter == FILTER_CULLLOCALIZED_ANY ) && pSearchPath->m_bIsLocalizedPath )
  6143. {
  6144. continue;
  6145. }
  6146. if ( pSearchPath->m_bIsDvdDevPath && !m_bExcluded )
  6147. {
  6148. // the dvddev cache path is ignored until an exclusion has been matched
  6149. continue;
  6150. }
  6151. else if ( !pSearchPath->m_bIsDvdDevPath && m_bExcluded )
  6152. {
  6153. // an excluded file falls all the way through and can only be in the dvddev cache
  6154. continue;
  6155. }
  6156. if ( CBaseFileSystem::FilterByPathID( pSearchPath, m_pathID ) )
  6157. continue;
  6158. // prevent a duplicate costly check for exclusion
  6159. if ( m_visits.MarkVisit( *pSearchPath ) )
  6160. continue;
  6161. // a path gets ignored/skipped if it matches a dvddev exclusion, thus falling through to the dvddev cache path
  6162. bool bIgnorePath = false;
  6163. if ( !pSearchPath->m_bIsDvdDevPath )
  6164. {
  6165. if ( !pSearchPath->GetPackFile() || !pSearchPath->GetPackFile()->m_bIsMapPath )
  6166. {
  6167. char szExcludeFile[MAX_PATH];
  6168. char szFilename[MAX_PATH];
  6169. V_ComposeFileName( pSearchPath->GetPathString(), m_Filename, szFilename, sizeof( szFilename ) );
  6170. for ( int i = 0; i < m_ExcludeFilePaths.Count(); i++ )
  6171. {
  6172. if ( g_pFullFileSystem->String( m_ExcludeFilePaths[i], szExcludeFile, sizeof( szExcludeFile ) ) )
  6173. {
  6174. if ( !V_stricmp( szFilename, szExcludeFile ) )
  6175. {
  6176. // the file was excluded and should not be fetched from this search path
  6177. // the fallthrough will hit the dvddev cache
  6178. bIgnorePath = true;
  6179. m_bExcluded = true;
  6180. break;
  6181. }
  6182. }
  6183. }
  6184. // only an exclusion match is allowed to inhibit the filtering on non-zip paths, i.e. loose files
  6185. // ignoring loose files is critical to preventing pointless slow presence i/o, i.e. 99% files are expected to be in a zip
  6186. if ( !pSearchPath->GetPackFile() && m_PathTypeFilter == FILTER_CULLNONPACK && !bIgnorePath )
  6187. {
  6188. continue;
  6189. }
  6190. }
  6191. }
  6192. if ( !bIgnorePath )
  6193. {
  6194. break;
  6195. }
  6196. }
  6197. }
  6198. else
  6199. {
  6200. // nominal behavior
  6201. for ( m_iCurrent++; m_iCurrent < m_SearchPaths.Count(); m_iCurrent++ )
  6202. {
  6203. pSearchPath = &m_SearchPaths[m_iCurrent];
  6204. if ( m_PathTypeFilter == FILTER_CULLPACK && pSearchPath->GetPackFile() )
  6205. continue;
  6206. if ( ( m_PathTypeFilter == FILTER_CULLNONPACK || m_PathTypeFilter == FILTER_CULLLOCALIZED ) && !pSearchPath->GetPackFile() )
  6207. continue;
  6208. if ( ( m_PathTypeFilter == FILTER_CULLLOCALIZED || m_PathTypeFilter == FILTER_CULLLOCALIZED_ANY ) && pSearchPath->m_bIsLocalizedPath )
  6209. {
  6210. continue;
  6211. }
  6212. if ( CBaseFileSystem::FilterByPathID( pSearchPath, m_pathID ) )
  6213. continue;
  6214. if ( !m_visits.MarkVisit( *pSearchPath ) )
  6215. break;
  6216. }
  6217. }
  6218. if ( m_iCurrent < m_SearchPaths.Count() )
  6219. {
  6220. return pSearchPath;
  6221. }
  6222. return NULL;
  6223. }
  6224. //-----------------------------------------------------------------------------
  6225. // Purpose: Load/unload a DLL
  6226. //-----------------------------------------------------------------------------
  6227. CSysModule *CBaseFileSystem::LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly )
  6228. {
  6229. CHECK_DOUBLE_SLASHES( pFileName );
  6230. bool bPathIsGameBin = false; bPathIsGameBin; // Touch the var for !Win64 build compiler warnings.
  6231. LogFileAccess( pFileName );
  6232. if ( !pPathID )
  6233. {
  6234. pPathID = "EXECUTABLE_PATH"; // default to the bin dir
  6235. }
  6236. else if ( IsPlatformWindowsPC64() )
  6237. {
  6238. bPathIsGameBin = V_strcmp( "GAMEBIN", pPathID ) == 0;
  6239. }
  6240. #if defined(POSIX) && defined(PLATFORM_64BITS)
  6241. bPathIsGameBin = V_strcmp( "GAMEBIN", pPathID ) == 0;
  6242. #endif
  6243. char tempPathID[ MAX_PATH ];
  6244. ParsePathID( pFileName, pPathID, tempPathID );
  6245. CUtlSymbol lookup = g_PathIDTable.AddString( pPathID );
  6246. // a pathID has been specified, find the first match in the path list
  6247. int c = m_SearchPaths.Count();
  6248. for ( int i = 0; i < c; i++ )
  6249. {
  6250. // pak files don't have modules
  6251. if ( m_SearchPaths[i].GetPackFile() )
  6252. continue;
  6253. if ( FilterByPathID( &m_SearchPaths[i], lookup ) )
  6254. continue;
  6255. Q_snprintf( tempPathID, sizeof(tempPathID), "%s%s", m_SearchPaths[i].GetPathString(), pFileName ); // append the path to this dir.
  6256. CSysModule *pModule = Sys_LoadModule( tempPathID );
  6257. if ( pModule )
  6258. {
  6259. // we found the binary in one of our search paths
  6260. return pModule;
  6261. }
  6262. else if ( IsPlatformWindowsPC64() && bPathIsGameBin )
  6263. {
  6264. Q_snprintf( tempPathID, sizeof( tempPathID ), "%s%s%s%s", m_SearchPaths[ i ].GetPathString(), "x64", CORRECT_PATH_SEPARATOR_S, pFileName ); // append the path to this dir.
  6265. pModule = Sys_LoadModule( tempPathID );
  6266. if ( pModule )
  6267. {
  6268. // we found the binary in a 64-bit location.
  6269. return pModule;
  6270. }
  6271. }
  6272. #if defined(POSIX) && defined(PLATFORM_64BITS)
  6273. else if ( bPathIsGameBin )
  6274. {
  6275. #if defined(LINUX)
  6276. const char* plat_dir = "linux64";
  6277. #else
  6278. const char* plat_dir = "osx64";
  6279. #endif
  6280. Q_snprintf( tempPathID, sizeof( tempPathID ), "%s%s%s%s", m_SearchPaths[ i ].GetPathString(), plat_dir, CORRECT_PATH_SEPARATOR_S, pFileName ); // append the path to this dir.
  6281. pModule = Sys_LoadModule( tempPathID );
  6282. if ( pModule )
  6283. {
  6284. // we found the binary in a 64-bit location.
  6285. return pModule;
  6286. }
  6287. }
  6288. #endif
  6289. }
  6290. // couldn't load it from any of the search paths, let LoadLibrary try
  6291. return Sys_LoadModule( pFileName );
  6292. }
  6293. //-----------------------------------------------------------------------------
  6294. // Purpose:
  6295. //-----------------------------------------------------------------------------
  6296. void CBaseFileSystem::UnloadModule( CSysModule *pModule )
  6297. {
  6298. Sys_UnloadModule( pModule );
  6299. }
  6300. //-----------------------------------------------------------------------------
  6301. // Purpose: Adds a filesystem logging function
  6302. //-----------------------------------------------------------------------------
  6303. void CBaseFileSystem::AddLoggingFunc( FileSystemLoggingFunc_t logFunc )
  6304. {
  6305. Assert(!m_LogFuncs.IsValidIndex(m_LogFuncs.Find(logFunc)));
  6306. m_LogFuncs.AddToTail(logFunc);
  6307. }
  6308. //-----------------------------------------------------------------------------
  6309. // Purpose: Removes a filesystem logging function
  6310. //-----------------------------------------------------------------------------
  6311. void CBaseFileSystem::RemoveLoggingFunc( FileSystemLoggingFunc_t logFunc )
  6312. {
  6313. m_LogFuncs.FindAndRemove(logFunc);
  6314. }
  6315. //-----------------------------------------------------------------------------
  6316. // Make sure that slashes are of the right kind and that there is a slash at the
  6317. // end of the filename.
  6318. // WARNING!!: assumes that you have an extra byte allocated in the case that you need
  6319. // a slash at the end.
  6320. //-----------------------------------------------------------------------------
  6321. static void AddSeperatorAndFixPath( char *str )
  6322. {
  6323. char *lastChar = &str[strlen( str ) - 1];
  6324. if( *lastChar != CORRECT_PATH_SEPARATOR && *lastChar != INCORRECT_PATH_SEPARATOR )
  6325. {
  6326. lastChar[1] = CORRECT_PATH_SEPARATOR;
  6327. lastChar[2] = '\0';
  6328. }
  6329. Q_FixSlashes( str );
  6330. if ( IsGameConsole() )
  6331. {
  6332. // 360 FS won't resolve any path with ../
  6333. V_RemoveDotSlashes( str );
  6334. }
  6335. }
  6336. //-----------------------------------------------------------------------------
  6337. // Purpose:
  6338. // Input : *pFileName -
  6339. // Output : FileNameHandle_t
  6340. //-----------------------------------------------------------------------------
  6341. FileNameHandle_t CBaseFileSystem::FindOrAddFileName( char const *pFileName )
  6342. {
  6343. return m_FileNames.FindOrAddFileName( pFileName );
  6344. }
  6345. FileNameHandle_t CBaseFileSystem::FindFileName( char const *pFileName )
  6346. {
  6347. return m_FileNames.FindFileName( pFileName );
  6348. }
  6349. //-----------------------------------------------------------------------------
  6350. // Purpose:
  6351. // Input : handle -
  6352. // Output : char const
  6353. //-----------------------------------------------------------------------------
  6354. bool CBaseFileSystem::String( const FileNameHandle_t& handle, char *buf, int buflen )
  6355. {
  6356. return m_FileNames.String( handle, buf, buflen );
  6357. }
  6358. int CBaseFileSystem::GetPathIndex( const FileNameHandle_t &handle )
  6359. {
  6360. return m_FileNames.PathIndex(handle);
  6361. }
  6362. CBaseFileSystem::CPathIDInfo* CBaseFileSystem::FindOrAddPathIDInfo( const CUtlSymbol &id, int bByRequestOnly )
  6363. {
  6364. for ( int i=0; i < m_PathIDInfos.Count(); i++ )
  6365. {
  6366. CBaseFileSystem::CPathIDInfo *pInfo = m_PathIDInfos[i];
  6367. if ( pInfo->GetPathID() == id )
  6368. {
  6369. if ( bByRequestOnly != -1 )
  6370. {
  6371. pInfo->m_bByRequestOnly = (bByRequestOnly != 0);
  6372. }
  6373. return pInfo;
  6374. }
  6375. }
  6376. // Add a new one.
  6377. CBaseFileSystem::CPathIDInfo *pInfo = new CBaseFileSystem::CPathIDInfo;
  6378. m_PathIDInfos.AddToTail( pInfo );
  6379. pInfo->SetPathID( id );
  6380. pInfo->m_bByRequestOnly = (bByRequestOnly == 1);
  6381. return pInfo;
  6382. }
  6383. void CBaseFileSystem::MarkPathIDByRequestOnly( const char *pPathID, bool bRequestOnly )
  6384. {
  6385. FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), bRequestOnly );
  6386. }
  6387. bool CBaseFileSystem::IsFileInReadOnlySearchPath(const char *pPath, const char *pathID)
  6388. {
  6389. //TODO: implementme!
  6390. return false;
  6391. }
  6392. #if defined( TRACK_BLOCKING_IO )
  6393. void CBaseFileSystem::EnableBlockingFileAccessTracking( bool state )
  6394. {
  6395. m_bBlockingFileAccessReportingEnabled = state;
  6396. }
  6397. bool CBaseFileSystem::IsBlockingFileAccessEnabled() const
  6398. {
  6399. return m_bBlockingFileAccessReportingEnabled;
  6400. }
  6401. IBlockingFileItemList *CBaseFileSystem::RetrieveBlockingFileAccessInfo()
  6402. {
  6403. Assert( m_pBlockingItems );
  6404. return m_pBlockingItems;
  6405. }
  6406. void CBaseFileSystem::RecordBlockingFileAccess( bool synchronous, const FileBlockingItem& item )
  6407. {
  6408. AUTO_LOCK( m_BlockingFileMutex );
  6409. // Not tracking anything
  6410. if ( !m_bBlockingFileAccessReportingEnabled )
  6411. return;
  6412. if ( synchronous && !m_bAllowSynchronousLogging && ( item.m_ItemType == FILESYSTEM_BLOCKING_SYNCHRONOUS ) )
  6413. return;
  6414. // Track it
  6415. m_pBlockingItems->Add( item );
  6416. }
  6417. bool CBaseFileSystem::SetAllowSynchronousLogging( bool state )
  6418. {
  6419. bool oldState = m_bAllowSynchronousLogging;
  6420. m_bAllowSynchronousLogging = state;
  6421. return oldState;
  6422. }
  6423. void CBaseFileSystem::BlockingFileAccess_EnterCriticalSection()
  6424. {
  6425. m_BlockingFileMutex.Lock();
  6426. }
  6427. void CBaseFileSystem::BlockingFileAccess_LeaveCriticalSection()
  6428. {
  6429. m_BlockingFileMutex.Unlock();
  6430. }
  6431. #endif // TRACK_BLOCKING_IO
  6432. bool CBaseFileSystem::GetFileTypeForFullPath( char const *pFullPath, wchar_t *buf, size_t bufSizeInBytes )
  6433. {
  6434. #if !defined( _X360 ) && !defined( POSIX )
  6435. wchar_t wcharpath[512];
  6436. ::MultiByteToWideChar( CP_UTF8, 0, pFullPath, -1, wcharpath, sizeof( wcharpath ) / sizeof(wchar_t) );
  6437. wcharpath[(sizeof( wcharpath ) / sizeof(wchar_t)) - 1] = L'\0';
  6438. SHFILEINFOW info = { 0 };
  6439. DWORD_PTR dwResult = SHGetFileInfoW(
  6440. wcharpath,
  6441. 0,
  6442. &info,
  6443. sizeof( info ),
  6444. SHGFI_TYPENAME
  6445. );
  6446. if ( dwResult )
  6447. {
  6448. wcsncpy( buf, info.szTypeName, ( bufSizeInBytes / sizeof( wchar_t ) ) );
  6449. buf[( bufSizeInBytes / sizeof( wchar_t ) ) - 1] = L'\0';
  6450. return true;
  6451. }
  6452. else
  6453. #endif
  6454. {
  6455. char ext[32];
  6456. Q_ExtractFileExtension( pFullPath, ext, sizeof( ext ) );
  6457. #ifdef POSIX
  6458. V_snwprintf( buf, ( bufSizeInBytes / sizeof( wchar_t ) ) - 1, L"%s File", V_strupr( ext ) ); // Matches what Windows does
  6459. #else
  6460. V_snwprintf( buf, ( bufSizeInBytes / sizeof( wchar_t ) ) - 1, L".%S", ext );
  6461. #endif
  6462. buf[( bufSizeInBytes / sizeof( wchar_t ) ) - 1] = L'\0';
  6463. }
  6464. return false;
  6465. }
  6466. bool CBaseFileSystem::GetOptimalIOConstraints( FileHandle_t hFile, unsigned *pOffsetAlign, unsigned *pSizeAlign, unsigned *pBufferAlign )
  6467. {
  6468. if ( pOffsetAlign )
  6469. *pOffsetAlign = 1;
  6470. if ( pSizeAlign )
  6471. *pSizeAlign = 1;
  6472. if ( pBufferAlign )
  6473. *pBufferAlign = 1;
  6474. return false;
  6475. }
  6476. //-----------------------------------------------------------------------------
  6477. // This is a DVDDEV misery that needs to convolve loose filenames that exceed the 42 character limit.
  6478. // This is not for files inside zip files or in any other context.
  6479. //-----------------------------------------------------------------------------
  6480. bool CBaseFileSystem::FixupFATXFilename( const char *pFilename, char *pOutFilename, int nOutSize )
  6481. {
  6482. if ( !IsX360() || m_DVDMode != DVDMODE_DEV )
  6483. {
  6484. // xbox dvdev mode only
  6485. return false;
  6486. }
  6487. // back up to isolate the filename
  6488. const char *pFilenameStart = V_UnqualifiedFileName( pFilename );
  6489. int nFilenameLen = strlen( pFilenameStart );
  6490. if ( nFilenameLen <= 42 )
  6491. {
  6492. // already compliant
  6493. return false;
  6494. }
  6495. // only files within the dvddev cache or shadercache would be convolved (via vxconsole)
  6496. if ( !V_stristr( pFilename, "dvddev" ) )
  6497. {
  6498. // not in any of the vxconsole caches, ignore
  6499. return false;
  6500. }
  6501. // deterministically generate a unique suffix
  6502. // this is the same operation that vxconsole does before pushing the files
  6503. // good enough to ensure similar excessive filenames in the same dir won't collide
  6504. char szFixedFilename[MAX_PATH];
  6505. V_strncpy( szFixedFilename, pFilenameStart, sizeof( szFixedFilename ) );
  6506. strlwr( szFixedFilename );
  6507. CRC32_t crc32 = CRC32_ProcessSingleBuffer( szFixedFilename, nFilenameLen );
  6508. crc32 %= 10000;
  6509. char szUnique[32];
  6510. sprintf( szUnique, "~%u", ( uint ) crc32 );
  6511. szFixedFilename[42 - strlen( szUnique )] = '\0';
  6512. V_strncat( szFixedFilename, szUnique, sizeof( szFixedFilename ) );
  6513. if ( pFilenameStart != pFilename )
  6514. {
  6515. // get the initial path (and the slash)
  6516. V_strncpy( pOutFilename, pFilename, MIN( nOutSize, pFilenameStart - pFilename + 1 ) );
  6517. }
  6518. // tack on the fixed filename
  6519. V_strncat( pOutFilename, szFixedFilename, nOutSize );
  6520. return true;
  6521. }
  6522. bool CBaseFileSystem::GetStringFromKVPool( CRC32_t poolKey, unsigned int key, char *pOutBuff, int buflen )
  6523. {
  6524. // xbox only
  6525. if ( !IsGameConsole() )
  6526. {
  6527. Assert( 0 );
  6528. return false;
  6529. }
  6530. AUTO_LOCK( m_SearchPathsMutex );
  6531. int c = m_SearchPaths.Count();
  6532. for ( int i = 0; i < c; i++ )
  6533. {
  6534. CSearchPath *pSearchPath = &m_SearchPaths[i];
  6535. CPackFile *pPackFile = pSearchPath->GetPackFile();
  6536. if ( pPackFile && !pPackFile->m_bIsMapPath )
  6537. {
  6538. if ( poolKey == pPackFile->GetKVPoolKey() )
  6539. {
  6540. return pPackFile->GetStringFromKVPool( key, pOutBuff, buflen );
  6541. }
  6542. }
  6543. }
  6544. return false;
  6545. }
  6546. //-----------------------------------------------------------------------------
  6547. // Purpose: Constructs a file handle
  6548. // Input : base file system handle
  6549. // Output :
  6550. //-----------------------------------------------------------------------------
  6551. CFileHandle::CFileHandle( CBaseFileSystem* fs )
  6552. {
  6553. Init( fs );
  6554. }
  6555. CFileHandle::~CFileHandle()
  6556. {
  6557. Assert( IsValid() );
  6558. delete[] m_pszTrueFileName;
  6559. if ( m_pPackFileHandle )
  6560. {
  6561. delete m_pPackFileHandle;
  6562. m_pPackFileHandle = NULL;
  6563. }
  6564. if ( m_pFile )
  6565. {
  6566. m_fs->Trace_FClose( m_pFile );
  6567. m_pFile = NULL;
  6568. }
  6569. m_nMagic = FREE_MAGIC;
  6570. }
  6571. void CFileHandle::Init( CBaseFileSystem *fs )
  6572. {
  6573. m_nMagic = MAGIC;
  6574. m_pFile = NULL;
  6575. m_nLength = 0;
  6576. m_type = FT_NORMAL;
  6577. m_pPackFileHandle = NULL;
  6578. m_fs = fs;
  6579. m_pszTrueFileName = 0;
  6580. }
  6581. bool CFileHandle::IsValid()
  6582. {
  6583. return ( m_nMagic == MAGIC );
  6584. }
  6585. int CFileHandle::GetSectorSize()
  6586. {
  6587. Assert( IsValid() );
  6588. if ( m_pFile )
  6589. {
  6590. return m_fs->FS_GetSectorSize( m_pFile );
  6591. }
  6592. else if ( m_pPackFileHandle )
  6593. {
  6594. return m_pPackFileHandle->GetSectorSize();
  6595. }
  6596. else
  6597. {
  6598. return -1;
  6599. }
  6600. }
  6601. bool CFileHandle::IsOK()
  6602. {
  6603. #ifdef SUPPORT_VPK
  6604. if ( m_VPKHandle )
  6605. return true;
  6606. #endif
  6607. if ( m_pFile )
  6608. {
  6609. return ( IsValid() && m_fs->FS_ferror( m_pFile ) == 0 );
  6610. }
  6611. else if ( m_pPackFileHandle )
  6612. {
  6613. return IsValid();
  6614. }
  6615. m_fs->FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to IsOk NULL file pointer inside valid file handle!\n" );
  6616. return false;
  6617. }
  6618. void CFileHandle::Flush()
  6619. {
  6620. Assert( IsValid() );
  6621. if ( m_pFile )
  6622. {
  6623. m_fs->FS_fflush( m_pFile );
  6624. }
  6625. }
  6626. void CFileHandle::SetBufferSize( int nBytes )
  6627. {
  6628. Assert( IsValid() );
  6629. if ( m_pFile )
  6630. {
  6631. m_fs->FS_setbufsize( m_pFile, nBytes );
  6632. }
  6633. else if ( m_pPackFileHandle )
  6634. {
  6635. m_pPackFileHandle->SetBufferSize( nBytes );
  6636. }
  6637. }
  6638. int CFileHandle::Read( void* pBuffer, int nLength )
  6639. {
  6640. Assert( IsValid() );
  6641. return Read( pBuffer, -1, nLength );
  6642. }
  6643. int CFileHandle::Read( void* pBuffer, int nDestSize, int nLength )
  6644. {
  6645. Assert( IsValid() );
  6646. #ifdef SUPPORT_VPK
  6647. if ( m_VPKHandle )
  6648. {
  6649. if ( nDestSize >= 0 )
  6650. nLength = MIN( nLength, nDestSize );
  6651. int nLengthRead = m_VPKHandle.Read( pBuffer, nLength );
  6652. m_fs->m_FileTracker2.NotePackFileRead( m_VPKHandle, pBuffer, nLength );
  6653. return nLengthRead;
  6654. }
  6655. #endif
  6656. // Is this a regular file or a pack file?
  6657. if ( m_pFile )
  6658. {
  6659. return m_fs->FS_fread( pBuffer, nDestSize, nLength, m_pFile );
  6660. }
  6661. else if ( m_pPackFileHandle )
  6662. {
  6663. // Pack file handle handles clamping all the reads:
  6664. return m_pPackFileHandle->Read( pBuffer, nDestSize, nLength );
  6665. }
  6666. return 0;
  6667. }
  6668. int CFileHandle::Write( const void* pBuffer, int nLength )
  6669. {
  6670. Assert( IsValid() );
  6671. if ( !m_pFile )
  6672. {
  6673. m_fs->FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Write NULL file pointer inside valid file handle!\n" );
  6674. return 0;
  6675. }
  6676. size_t nBytesWritten = m_fs->FS_fwrite( (void*)pBuffer, nLength, m_pFile );
  6677. m_fs->Trace_FWrite(nBytesWritten,m_pFile);
  6678. return nBytesWritten;
  6679. }
  6680. int CFileHandle::Seek( int64 nOffset, int nWhence )
  6681. {
  6682. Assert( IsValid() );
  6683. #ifdef SUPPORT_VPK
  6684. if ( m_VPKHandle )
  6685. {
  6686. return m_VPKHandle.Seek( nOffset, nWhence );
  6687. }
  6688. #endif
  6689. if ( m_pFile )
  6690. {
  6691. m_fs->FS_fseek( m_pFile, nOffset, nWhence );
  6692. // TODO - FS_fseek should return the resultant offset
  6693. return 0;
  6694. }
  6695. else if ( m_pPackFileHandle )
  6696. {
  6697. return m_pPackFileHandle->Seek( (int)nOffset, nWhence );
  6698. }
  6699. return -1;
  6700. }
  6701. int CFileHandle::Tell()
  6702. {
  6703. Assert( IsValid() );
  6704. #ifdef SUPPORT_VPK
  6705. if ( m_VPKHandle )
  6706. {
  6707. return m_VPKHandle.Tell();
  6708. }
  6709. #endif
  6710. if ( m_pFile )
  6711. {
  6712. return m_fs->FS_ftell( m_pFile );
  6713. }
  6714. else if ( m_pPackFileHandle )
  6715. {
  6716. return m_pPackFileHandle->Tell();
  6717. }
  6718. return -1;
  6719. }
  6720. int CFileHandle::Size()
  6721. {
  6722. Assert( IsValid() );
  6723. int nReturnedSize = -1;
  6724. #ifdef SUPPORT_VPK
  6725. if ( m_VPKHandle )
  6726. {
  6727. return m_VPKHandle.m_nFileSize;
  6728. }
  6729. #endif
  6730. if ( m_pFile )
  6731. {
  6732. nReturnedSize = m_nLength;
  6733. }
  6734. else if ( m_pPackFileHandle )
  6735. {
  6736. nReturnedSize = m_pPackFileHandle->Size();
  6737. }
  6738. return nReturnedSize;
  6739. }
  6740. int64 CFileHandle::AbsoluteBaseOffset()
  6741. {
  6742. Assert( IsValid() );
  6743. if ( m_pFile )
  6744. {
  6745. return 0;
  6746. }
  6747. else
  6748. {
  6749. return m_pPackFileHandle->AbsoluteBaseOffset();
  6750. }
  6751. }
  6752. bool CFileHandle::EndOfFile()
  6753. {
  6754. Assert( IsValid() );
  6755. return ( Tell() >= Size() );
  6756. }
  6757. #ifdef _GAMECONSOLE
  6758. static int s_DLC_Numeric_Supported[] =
  6759. {
  6760. 0, 0, 0, 0, 0, // 5
  6761. 0, 0, 0, 0, 0, // 10
  6762. 0, 0, 0, 0, 0, // 15
  6763. 0, 0, 0, 0, 20, // 20
  6764. 0, 0, 0, 0, 0, // 25
  6765. 0, 0, 0, 0, 0, // 30
  6766. };
  6767. static bool IsDlcNumericSupported( int iDLC )
  6768. {
  6769. #if defined( CSTRIKE15 )
  6770. return ( iDLC >= 1 && iDLC < 31 );
  6771. #else // CSTRIKE15
  6772. for ( int k = 0; k < ARRAYSIZE( s_DLC_Numeric_Supported ); ++ k )
  6773. if ( s_DLC_Numeric_Supported[k] == iDLC )
  6774. return true;
  6775. return false;
  6776. #endif // CSTRIKE15
  6777. }
  6778. #else
  6779. static bool IsDlcNumericSupported( int iDLC )
  6780. {
  6781. return false;
  6782. }
  6783. #endif
  6784. bool CBaseFileSystem::DiscoverDLC( int iController )
  6785. {
  6786. #if !defined( _X360 )
  6787. return false;
  6788. #else
  6789. DevMsg( "Discovering DLC...\n" );
  6790. // clear prior corrupt results
  6791. m_CorruptDLC.Purge();
  6792. CUtlSortVector< DLCContent_t, CDLCLess > dlcResults;
  6793. // development path supports locally mounting the dlc when not using XLAST or XBL
  6794. // this is development only, retail runtime would never have command line dictated DLC
  6795. // command line trumps ANY discovery so we can have a desired exact DLC testing state
  6796. bool bUsingCommandLineDLC = false;
  6797. const char *pCmdLine = CommandLine()->GetCmdLine();
  6798. while( pCmdLine )
  6799. {
  6800. pCmdLine = V_stristr( pCmdLine, "-dlc" );
  6801. if ( !pCmdLine )
  6802. break;
  6803. bUsingCommandLineDLC = true;
  6804. int nDLC = atoi( pCmdLine + 4 );
  6805. if ( nDLC == 0 )
  6806. {
  6807. // malformed command line dev args
  6808. DevWarning( "Bad argument: %s\n", pCmdLine );
  6809. break;
  6810. }
  6811. // get the required dlcflags -dlc<n> 0x<flags>
  6812. // user must supply as the lower word identifies control bits
  6813. char const *szDlcNflags = CommandLine()->ParmValue( CFmtStr( "-dlc%d", nDLC ), "0x0" );
  6814. unsigned int nDLCFlags = 0;
  6815. if ( 1 != sscanf( szDlcNflags, "0x%x", &nDLCFlags ) )
  6816. {
  6817. DevWarning( "Bad DLC flags: -dlc%d %s\n", nDLC, szDlcNflags );
  6818. break;
  6819. }
  6820. // form the license mask
  6821. // identify development DLCN as N.0 in the upper MSW, the retail version is at least N.1
  6822. // the LSW are the control flags
  6823. unsigned int nLicenseMask = ( ( nDLC & 0xFF ) << 24 ) | ( nDLCFlags & 0xFFFF );
  6824. DLCContent_t dlcContent;
  6825. // should be part of test image
  6826. // the real DLC encodes descriptive data we can only spoof
  6827. V_strcpy( dlcContent.m_szVolume, "D" );
  6828. V_strtowcs( CFmtStr( "DLC%d Dev Name (0x%8.8x)", nDLC, nLicenseMask ), -1, dlcContent.m_ContentData.szDisplayName, sizeof( dlcContent.m_ContentData.szDisplayName ) );
  6829. dlcContent.m_nController = 0;
  6830. dlcContent.m_LicenseMask = nLicenseMask;
  6831. dlcContent.m_bMounted = ( nLicenseMask & DLCFLAGS_PRESENCE_ONLY ) != 0;
  6832. dlcResults.Insert( dlcContent );
  6833. // next arg
  6834. pCmdLine += 4;
  6835. }
  6836. if ( bUsingCommandLineDLC )
  6837. {
  6838. for ( int i = 0; i < dlcResults.Count(); i++ )
  6839. {
  6840. // only care about new unique occurring DLC
  6841. // skip over any DLC that we have already discovered
  6842. bool bFound = false;
  6843. for ( int j = 0; j < m_DLCContents.Count() && !bFound; j++ )
  6844. {
  6845. bFound = ( DLC_LICENSE_ID( m_DLCContents[j].m_LicenseMask ) == DLC_LICENSE_ID( dlcResults[i].m_LicenseMask ) );
  6846. }
  6847. if ( !bFound )
  6848. {
  6849. m_DLCContents.Insert( dlcResults[i] );
  6850. }
  6851. }
  6852. }
  6853. else
  6854. {
  6855. CUtlMemory< BYTE > buffer;
  6856. DWORD nNumItems = 0;
  6857. BYTE *pBuffer = NULL;
  6858. // find additional content
  6859. // must have a signed in user, otherwise cannot find enhanced content
  6860. DWORD nBufferSize;
  6861. HANDLE hEnumerator;
  6862. if ( XContentCreateEnumerator(
  6863. iController,
  6864. XCONTENTDEVICE_ANY,
  6865. XCONTENTTYPE_MARKETPLACE,
  6866. 0,
  6867. 100,
  6868. &nBufferSize,
  6869. &hEnumerator ) == ERROR_SUCCESS )
  6870. {
  6871. if ( nBufferSize )
  6872. {
  6873. // get a buffer to capture enumeration results
  6874. buffer.EnsureCapacity( nBufferSize );
  6875. pBuffer = buffer.Base();
  6876. if ( XEnumerate( hEnumerator, pBuffer, nBufferSize, &nNumItems, NULL ) != ERROR_SUCCESS )
  6877. {
  6878. nNumItems = 0;
  6879. }
  6880. }
  6881. }
  6882. ::CloseHandle( hEnumerator );
  6883. if ( !nNumItems )
  6884. {
  6885. return false;
  6886. }
  6887. char szFilename[XCONTENT_MAX_FILENAME_LENGTH+1];
  6888. szFilename[XCONTENT_MAX_FILENAME_LENGTH] = 0;
  6889. XCONTENT_DATA *pContentData;
  6890. // determine all our dlc content
  6891. for ( unsigned int i = 0; i < nNumItems; i++ )
  6892. {
  6893. // filenames are encoded encryptions, useless to anything but the system
  6894. pContentData = (XCONTENT_DATA *)pBuffer + i;
  6895. V_memcpy( szFilename, pContentData->szFileName, XCONTENT_MAX_FILENAME_LENGTH );
  6896. // must mount to get license mask
  6897. // license mask is ONLY available for content downloaded through XBL (not mounted locally)
  6898. DWORD licenseMask = 0;
  6899. DWORD dwStatus = XContentCreate( iController, "DLC", pContentData, XCONTENTFLAG_OPENEXISTING, NULL, &licenseMask, NULL );
  6900. if ( dwStatus != ERROR_SUCCESS )
  6901. {
  6902. // assume corrupt
  6903. DLCCorrupt_t dlcCorrupt;
  6904. dlcCorrupt.m_ContentData = *pContentData;
  6905. m_CorruptDLC.AddToTail( dlcCorrupt );
  6906. continue;
  6907. }
  6908. // always unmount, highest version will get re-mounted
  6909. // as we might rev the DLC without a TU
  6910. XContentClose( "DLC", NULL );
  6911. // DLC N
  6912. int nDlcNumericId = DLC_LICENSE_ID( licenseMask );
  6913. bool bDlcIsSupported = IsDlcNumericSupported( nDlcNumericId );
  6914. // only consider DLC with a valid license mask
  6915. // we DONT/CANT support install-test-locally DLC because DLC lacks license mask to decode
  6916. if ( nDlcNumericId && bDlcIsSupported )
  6917. {
  6918. // insert into ascending sorted list, ensures dlc1..dlcN order
  6919. DLCContent_t dlcContent;
  6920. dlcContent.m_ContentData = *pContentData;
  6921. dlcContent.m_LicenseMask = licenseMask;
  6922. dlcContent.m_nController = iController;
  6923. dlcContent.m_szVolume[0] = '\0';
  6924. dlcContent.m_bMounted = false;
  6925. dlcResults.Insert( dlcContent );
  6926. }
  6927. else
  6928. {
  6929. // assume corrupt
  6930. DLCCorrupt_t dlcCorrupt;
  6931. dlcCorrupt.m_ContentData = *pContentData;
  6932. m_CorruptDLC.AddToTail( dlcCorrupt );
  6933. continue;
  6934. }
  6935. }
  6936. // mount the highest version of each type
  6937. // sorted results order guarantees ascending type/version order
  6938. for ( int i = 0; i < dlcResults.Count(); )
  6939. {
  6940. // iterate ascending list determine highest version of matching type
  6941. DWORD dlcType = DLC_LICENSE_ID( dlcResults[i].m_LicenseMask );
  6942. int nBest = i;
  6943. for ( int j = i+1; j < dlcResults.Count(); j++ )
  6944. {
  6945. if ( dlcType != DLC_LICENSE_ID( dlcResults[j].m_LicenseMask ) )
  6946. {
  6947. // wrong one, due to sort order, no more of this type
  6948. break;
  6949. }
  6950. nBest = j;
  6951. }
  6952. // only care about unique DLC types, can't handle newly discovered sub versions of the same type
  6953. // iterate for a match
  6954. bool bFound = false;
  6955. for ( int j = 0; j < m_DLCContents.Count() && !bFound; j++ )
  6956. {
  6957. bFound = ( DLC_LICENSE_ID( m_DLCContents[j].m_LicenseMask ) == DLC_LICENSE_ID( dlcResults[nBest].m_LicenseMask ) );
  6958. }
  6959. if ( !bFound )
  6960. {
  6961. // mount the highest version of each type only
  6962. DLCContent_t dlcContent = dlcResults[nBest];
  6963. V_strcpy( dlcContent.m_szVolume, CFmtStr( "DLC%d", DLC_LICENSE_ID( dlcContent.m_LicenseMask ) ) );
  6964. DWORD dwResults;
  6965. if ( dlcContent.m_LicenseMask & DLCFLAGS_PRESENCE_ONLY )
  6966. {
  6967. // we have it, that's all that is required
  6968. // what's inside it is never acessed
  6969. dwResults = ERROR_SUCCESS;
  6970. dlcContent.m_bMounted = true;
  6971. }
  6972. else
  6973. {
  6974. dwResults = XContentCreate(
  6975. iController,
  6976. dlcContent.m_szVolume,
  6977. &dlcContent.m_ContentData,
  6978. XCONTENTFLAG_OPENEXISTING,
  6979. NULL,
  6980. NULL,
  6981. NULL );
  6982. }
  6983. if ( dwResults == ERROR_SUCCESS )
  6984. {
  6985. // already handled corrupt errors, so expecting success
  6986. m_DLCContents.Insert( dlcContent );
  6987. }
  6988. }
  6989. // continue with next dlc type
  6990. i = nBest + 1;
  6991. }
  6992. }
  6993. PrintDLCInfo();
  6994. return ( m_DLCContents.Count() != 0 );
  6995. #endif
  6996. }
  6997. // Returns the number of DLC components found
  6998. int CBaseFileSystem::IsAnyDLCPresent( bool *pbDLCSearchPathMounted )
  6999. {
  7000. if ( !IsX360() )
  7001. {
  7002. return 0;
  7003. }
  7004. if ( pbDLCSearchPathMounted )
  7005. {
  7006. // discovered DLC may have added new DLC that have not mounted their search path
  7007. *pbDLCSearchPathMounted = true;
  7008. for ( int i = 0; i < m_DLCContents.Count(); i++ )
  7009. {
  7010. if ( !m_DLCContents[i].m_bMounted )
  7011. {
  7012. // set caller's query to false so they know to trigger the AddDLCSearchPaths()
  7013. *pbDLCSearchPathMounted = false;
  7014. break;
  7015. }
  7016. }
  7017. }
  7018. return m_DLCContents.Count();
  7019. }
  7020. bool CBaseFileSystem::GetAnyDLCInfo( int iDLC, unsigned int *pLicenseMask, wchar_t *pTitleBuff, int nOutTitleSize )
  7021. {
  7022. if ( !IsX360() )
  7023. {
  7024. return false;
  7025. }
  7026. if ( !m_DLCContents.IsValidIndex( iDLC ) )
  7027. {
  7028. return false;
  7029. }
  7030. if ( pLicenseMask )
  7031. {
  7032. *pLicenseMask = m_DLCContents[iDLC].m_LicenseMask;
  7033. }
  7034. if ( pTitleBuff )
  7035. {
  7036. V_wcsncpy( pTitleBuff, m_DLCContents[iDLC].m_ContentData.szDisplayName, nOutTitleSize );
  7037. }
  7038. return true;
  7039. }
  7040. int CBaseFileSystem::IsAnyCorruptDLC()
  7041. {
  7042. if ( !IsX360() )
  7043. {
  7044. return 0;
  7045. }
  7046. return m_CorruptDLC.Count();
  7047. }
  7048. bool CBaseFileSystem::GetAnyCorruptDLCInfo( int iCorruptDLC, wchar_t *pTitleBuff, int nOutTitleSize )
  7049. {
  7050. if ( !IsX360() )
  7051. {
  7052. return false;
  7053. }
  7054. if ( !m_CorruptDLC.IsValidIndex( iCorruptDLC ) )
  7055. {
  7056. return false;
  7057. }
  7058. if ( pTitleBuff )
  7059. {
  7060. V_wcsncpy( pTitleBuff, m_CorruptDLC[iCorruptDLC].m_ContentData.szDisplayName, nOutTitleSize );
  7061. }
  7062. return true;
  7063. }
  7064. bool CBaseFileSystem::IsSpecificDLCPresent( unsigned int nDLCPackage )
  7065. {
  7066. for( int i = 0; i < m_DLCContents.Count(); i++ )
  7067. {
  7068. if ( m_DLCContents[i].m_bMounted && ( DLC_LICENSE_ID( m_DLCContents[i].m_LicenseMask ) == nDLCPackage ) )
  7069. {
  7070. return true;
  7071. }
  7072. }
  7073. return false;
  7074. }
  7075. bool CBaseFileSystem::AddDLCSearchPaths()
  7076. {
  7077. if ( !IsX360() )
  7078. {
  7079. return false;
  7080. }
  7081. if ( !IsAnyDLCPresent() )
  7082. {
  7083. return false;
  7084. }
  7085. AsyncFinishAll();
  7086. // have to add the DLC to achieve desired SP order DLCN..DLC1
  7087. for ( int iDLC = 0; iDLC < m_DLCContents.Count(); iDLC++ )
  7088. {
  7089. if ( m_DLCContents[iDLC].m_bMounted )
  7090. {
  7091. // already procesed this on a prior add, skip now
  7092. // only care about newly discovered unique DLC
  7093. continue;
  7094. }
  7095. unsigned int nDLCType = DLC_LICENSE_ID( m_DLCContents[iDLC].m_LicenseMask );
  7096. char szDLCPath[MAX_PATH];
  7097. V_snprintf( szDLCPath, sizeof( szDLCPath ), "%s:\\csgo_dlc%d", m_DLCContents[iDLC].m_szVolume, nDLCType );
  7098. char szDLCLanguagePath[MAX_PATH];
  7099. const char *pLanguageString = NULL;
  7100. if ( XBX_IsAudioLocalized() )
  7101. {
  7102. pLanguageString = XBX_GetLanguageString();
  7103. // paranoid check
  7104. if ( !V_stricmp( pLanguageString, "english" ) )
  7105. {
  7106. // bad, system is confused
  7107. pLanguageString = NULL;
  7108. }
  7109. }
  7110. if ( pLanguageString )
  7111. {
  7112. V_strncpy( szDLCLanguagePath, CFmtStr( "%s_%s", szDLCPath, pLanguageString ), sizeof( szDLCLanguagePath ) );
  7113. }
  7114. const char *pathTargets[] = { "GAME", "MOD" };
  7115. for ( int i = 0; i < ARRAYSIZE( pathTargets ); i++ )
  7116. {
  7117. // inject dlc path for each target path id, once at top
  7118. for ( int j = 0; j < m_SearchPaths.Count(); j++ )
  7119. {
  7120. if ( m_SearchPaths[j].GetPackFile() && m_SearchPaths[j].GetPackFile()->m_bIsMapPath )
  7121. {
  7122. // skip over any map based search paths
  7123. continue;
  7124. }
  7125. if ( V_stricmp( m_SearchPaths[j].GetPathIDString(), pathTargets[i] ) )
  7126. {
  7127. // skip over any path IDs that we are not interested in
  7128. continue;
  7129. }
  7130. // always after update if present
  7131. int iIndex = j;
  7132. if ( V_stristr( m_SearchPaths[j].GetPathString(), "update" ) )
  7133. {
  7134. // add after update
  7135. iIndex++;
  7136. }
  7137. // scan to find any existing DLC, need to be either before or after
  7138. bool bNoAdd = false;
  7139. for ( int k = iIndex; k < m_SearchPaths.Count(); k++ )
  7140. {
  7141. const char *pCurrentPathString = m_SearchPaths[k].GetPathString();
  7142. const char *pDLCSuffix = V_stristr( pCurrentPathString, "_dlc" );
  7143. if ( !pDLCSuffix )
  7144. {
  7145. break;
  7146. }
  7147. else
  7148. {
  7149. unsigned int nExistingDLC = atoi( pDLCSuffix + 4 );
  7150. if ( !nExistingDLC )
  7151. {
  7152. // uh-oh, can't determine what DLC this is, can't add any other
  7153. DevWarning( "ERROR! Skipping DLC Mount, malformed DLC search path found: %s\n", pCurrentPathString );
  7154. bNoAdd = true;
  7155. break;
  7156. }
  7157. if ( nDLCType > nExistingDLC )
  7158. {
  7159. // stop, we need to be prior to this one
  7160. break;
  7161. }
  7162. else if ( nDLCType == nExistingDLC )
  7163. {
  7164. // uh-oh, we don't add DLCs of the same type
  7165. bNoAdd = true;
  7166. break;
  7167. }
  7168. else
  7169. {
  7170. // the DLC we want to add is smaller, it needs to at least go after this one
  7171. iIndex = k + 1;
  7172. }
  7173. }
  7174. }
  7175. if ( bNoAdd )
  7176. {
  7177. // not adding this DLC
  7178. break;
  7179. }
  7180. if ( pLanguageString )
  7181. {
  7182. // only add dlc language search path, if DLC has it
  7183. struct _stat buf;
  7184. if ( FS_stat( szDLCLanguagePath, &buf ) != -1 )
  7185. {
  7186. AddSearchPathInternal( szDLCLanguagePath, pathTargets[i], PATH_ADD_TO_TAIL_ATINDEX, true, iIndex );
  7187. iIndex++;
  7188. }
  7189. }
  7190. AddSearchPathInternal( szDLCPath, pathTargets[i], PATH_ADD_TO_TAIL_ATINDEX, true, iIndex );
  7191. break;
  7192. }
  7193. }
  7194. // never multiply mount or re-mount again
  7195. m_DLCContents[iDLC].m_bMounted = true;
  7196. }
  7197. PrintSearchPaths();
  7198. return true;
  7199. }
  7200. void CBaseFileSystem::PrintDLCInfo()
  7201. {
  7202. if ( IsX360() )
  7203. {
  7204. if ( m_DLCContents.Count() )
  7205. {
  7206. Msg( "\nDLC:\n" );
  7207. for ( int i = 0; i < m_DLCContents.Count(); i++ )
  7208. {
  7209. char szTitle[MAX_PATH];
  7210. V_UnicodeToUTF8( m_DLCContents[i].m_ContentData.szDisplayName, szTitle, sizeof( szTitle ) );
  7211. Msg( "DLC Found: '%s' License: 0x%8.8x\n", szTitle, m_DLCContents[i].m_LicenseMask );
  7212. }
  7213. }
  7214. if ( m_CorruptDLC.Count() )
  7215. {
  7216. Msg( "\nCorrupt DLC:\n" );
  7217. for ( int i = 0; i < m_CorruptDLC.Count(); i++ )
  7218. {
  7219. char szTitle[MAX_PATH];
  7220. V_UnicodeToUTF8( m_CorruptDLC[i].m_ContentData.szDisplayName, szTitle, sizeof( szTitle ) );
  7221. Msg( "Corrupt: '%s'\n", szTitle );
  7222. }
  7223. }
  7224. }
  7225. }
  7226. bool CBaseFileSystem::AddXLSPUpdateSearchPath( const void *pData, int nSize )
  7227. {
  7228. if ( IsPC() )
  7229. {
  7230. return false;
  7231. }
  7232. const char *targetPathIDs[] = { "PLATFORM", "GAME", "MOD" };
  7233. for ( int i = 0; i <ARRAYSIZE( targetPathIDs ); i++ )
  7234. {
  7235. RemoveSearchPath( "u:\\xlsppatch", targetPathIDs[i] );
  7236. }
  7237. g_XLSPPatchZipBuffer.Purge();
  7238. if ( !pData || !nSize )
  7239. {
  7240. return true;
  7241. }
  7242. g_XLSPPatchZipBuffer.Put( pData, nSize );
  7243. bool bXLSPPatchValid = true;
  7244. for ( int i = 0; i < ARRAYSIZE( targetPathIDs ); i++ )
  7245. {
  7246. // only specific paths get the xlsppatch override explicitly prepended
  7247. const char *pPathIDString = targetPathIDs[i];
  7248. // search path gets added once per allowed pathID
  7249. bool bFoundSearchPath = false;
  7250. for ( int j = 0; j < m_SearchPaths.Count(); j++ )
  7251. {
  7252. CSearchPath *pSearchPath = &m_SearchPaths[j];
  7253. if ( !V_stricmp( pSearchPath->GetPathIDString(), pPathIDString ) && !V_stricmp( pSearchPath->GetPathString(), "u:\\xlsppatch\\" ) )
  7254. {
  7255. bFoundSearchPath = true;
  7256. break;
  7257. }
  7258. }
  7259. if ( !bFoundSearchPath && g_XLSPPatchZipBuffer.TellPut() )
  7260. {
  7261. // find first matching pathID, xlsppatch will be placed before it
  7262. int nIndex = 0;
  7263. for ( int j = 0; j < m_SearchPaths.Count(); j++ )
  7264. {
  7265. CSearchPath *pSearchPath = &m_SearchPaths[j];
  7266. if ( !V_stricmp( pSearchPath->GetPathIDString(), pPathIDString ) )
  7267. {
  7268. nIndex = j;
  7269. break;
  7270. }
  7271. }
  7272. // have xlsppatch blob
  7273. nIndex = m_SearchPaths.InsertBefore( nIndex );
  7274. CSearchPath *sp = &m_SearchPaths[ nIndex ];
  7275. // the path and filename are fake but reserved, they denote this binary resident blob
  7276. // these names ensure they get ignored during post hdd install sp fixup
  7277. const char *pFullpath = "u:\\xlsppatch\\xlsppatch" PLATFORM_EXT ".zip";
  7278. sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathIDString ), -1 );
  7279. sp->m_storeId = g_iNextSearchPathID++;
  7280. sp->SetPath( g_PathIDTable.AddString( "u:\\xlsppatch\\" ) );
  7281. // find and alias existing reference
  7282. CPackFile *pf = NULL;
  7283. for ( int iPackFile = 0; iPackFile < m_ZipFiles.Count(); iPackFile++ )
  7284. {
  7285. if ( !Q_stricmp( m_ZipFiles[iPackFile]->m_ZipName.Get(), pFullpath ) )
  7286. {
  7287. // found
  7288. pf = m_ZipFiles[iPackFile];
  7289. sp->SetPackFile( pf );
  7290. pf->AddRef();
  7291. break;
  7292. }
  7293. }
  7294. if ( !pf )
  7295. {
  7296. // there is no 'file', point to the embedded section instead
  7297. pf = new CZipPackFile( this, g_XLSPPatchZipBuffer.Base() );
  7298. pf->SetPath( sp->GetPath() );
  7299. pf->m_bIsExcluded = false;
  7300. pf->m_ZipName = pFullpath;
  7301. m_ZipFiles.AddToTail( pf );
  7302. sp->SetPackFile( pf );
  7303. pf->m_lPackFileTime = 0;
  7304. pf->m_hPackFileHandleFS = NULL;
  7305. if ( !pf->Prepare( g_XLSPPatchZipBuffer.TellPut() ) )
  7306. {
  7307. bXLSPPatchValid = false;
  7308. break;
  7309. }
  7310. }
  7311. }
  7312. }
  7313. return bXLSPPatchValid;
  7314. }
  7315. void CBaseFileSystem::MarkLocalizedPath( CSearchPath *sp )
  7316. {
  7317. // game console only for now
  7318. #ifdef _GAMECONSOLE
  7319. const char *pPath = g_PathIDTable.String( sp->GetPath() );
  7320. if ( !pPath || !*pPath )
  7321. return;
  7322. if ( !XBX_IsAudioLocalized() )
  7323. {
  7324. return;
  7325. }
  7326. const char *pLanguage = XBX_GetLanguageString();
  7327. if ( !pLanguage || !V_stricmp( pLanguage, "english" ) )
  7328. {
  7329. return;
  7330. }
  7331. int languagelen = V_strlen( pLanguage );
  7332. int pathlen = V_strlen( pPath );
  7333. // ignore trailing slash
  7334. if ( pPath[ pathlen - 1 ] == '\\' || pPath[ pathlen - 1 ] == '/' )
  7335. {
  7336. --pathlen;
  7337. }
  7338. if ( pathlen > languagelen &&
  7339. V_strnicmp( pPath + pathlen - languagelen, pLanguage, languagelen ) == 0 )
  7340. {
  7341. sp->m_bIsLocalizedPath = true;
  7342. }
  7343. #endif
  7344. }
  7345. #ifdef SUPPORT_IODELAY_MONITORING
  7346. class CIODelayAlarmThread : public CThread
  7347. {
  7348. public:
  7349. CIODelayAlarmThread( CBaseFileSystem *pFileSystem );
  7350. void WakeUp( void );
  7351. CBaseFileSystem *m_pFileSystem;
  7352. CThreadEvent m_hThreadEvent;
  7353. volatile bool m_bThreadShouldExit;
  7354. // CThread Overrides
  7355. virtual int Run( void );
  7356. };
  7357. CIODelayAlarmThread::CIODelayAlarmThread( CBaseFileSystem *pFileSystem )
  7358. {
  7359. m_pFileSystem = pFileSystem;
  7360. m_bThreadShouldExit = false;
  7361. }
  7362. void CIODelayAlarmThread::WakeUp( void )
  7363. {
  7364. m_hThreadEvent.Set();
  7365. }
  7366. int CIODelayAlarmThread::Run( void )
  7367. {
  7368. while( ! m_bThreadShouldExit )
  7369. {
  7370. uint32 nWaitTime = 1000;
  7371. float flCurTimeout = m_pFileSystem->m_flDelayLimit;
  7372. if ( flCurTimeout > 0. )
  7373. {
  7374. nWaitTime = ( uint32 )( 1000.0 * flCurTimeout );
  7375. m_hThreadEvent.Wait( nWaitTime );
  7376. }
  7377. // check for overflow
  7378. float flCurTime = Plat_FloatTime();
  7379. if ( flCurTime - m_pFileSystem->m_flLastIOTime > m_pFileSystem->m_flDelayLimit )
  7380. {
  7381. Warning( " %f elapsed w/o i/o\n", flCurTime - m_pFileSystem->m_flLastIOTime );
  7382. DebuggerBreakIfDebugging();
  7383. m_pFileSystem->m_flLastIOTime = MAX( Plat_FloatTime(), m_pFileSystem->m_flLastIOTime );
  7384. }
  7385. }
  7386. return 0;
  7387. }
  7388. #endif // SUPPORT_IODELAY_MONITORING
  7389. void CBaseFileSystem::SetIODelayAlarm( float flTime )
  7390. {
  7391. #ifdef SUPPORT_IODELAY_MONITORING
  7392. m_flDelayLimit = flTime;
  7393. if ( m_flDelayLimit > 0. )
  7394. {
  7395. m_flDelayLimit = flTime;
  7396. if ( ! m_pDelayThread )
  7397. {
  7398. m_pDelayThread = new CIODelayAlarmThread( this );
  7399. m_pDelayThread->Start();
  7400. }
  7401. m_pDelayThread->WakeUp();
  7402. }
  7403. #endif
  7404. }
  7405. IIoStats *CBaseFileSystem::GetIoStats()
  7406. {
  7407. #ifndef _CERT
  7408. return &s_IoStats;
  7409. #else
  7410. return NULL;
  7411. #endif
  7412. }
  7413. CON_COMMAND( fs_dump_open_duplicate_times, "Set fs_report_long_reads 1 before loading to use this. Prints a list of files that were opened more than once and ~how long was spent reading from them." )
  7414. {
  7415. float flTotalTime = 0.0f, flAccumulatedMilliseconds;
  7416. AUTO_LOCK( g_FileOpenDuplicateTimesMutex );
  7417. for ( int nFileOpenDuplicate = 0; nFileOpenDuplicate< g_FileOpenDuplicateTimes.Count(); nFileOpenDuplicate++ )
  7418. {
  7419. FileOpenDuplicateTime_t *pFileOpenDuplicate = g_FileOpenDuplicateTimes[ nFileOpenDuplicate ];
  7420. if ( pFileOpenDuplicate )
  7421. {
  7422. if ( pFileOpenDuplicate->m_nLoadCount > 1 )
  7423. {
  7424. flTotalTime += pFileOpenDuplicate->m_flAccumulatedMicroSeconds;
  7425. flAccumulatedMilliseconds = pFileOpenDuplicate->m_flAccumulatedMicroSeconds / 1000.0f;
  7426. DevMsg( "Times Opened: %3i\t\tAccumulated Time: %10.5fms\t\tAverage Time per Open: %13.8fms\t\tFile: %s\n",
  7427. pFileOpenDuplicate->m_nLoadCount, flAccumulatedMilliseconds, ( flAccumulatedMilliseconds / pFileOpenDuplicate->m_nLoadCount ),
  7428. pFileOpenDuplicate->m_szName );
  7429. }
  7430. }
  7431. }
  7432. DevMsg( "Total Seconds: %.5f\n", flTotalTime / 1000000.0f );
  7433. }
  7434. CON_COMMAND( fs_clear_open_duplicate_times, "Clear the list of files that have been opened." )
  7435. {
  7436. AUTO_LOCK( g_FileOpenDuplicateTimesMutex );
  7437. for ( int nFileOpenDuplicate = 0; nFileOpenDuplicate< g_FileOpenDuplicateTimes.Count(); nFileOpenDuplicate++ )
  7438. {
  7439. FileOpenDuplicateTime_t *pFileOpenDuplicate = g_FileOpenDuplicateTimes[ nFileOpenDuplicate ];
  7440. delete pFileOpenDuplicate;
  7441. g_FileOpenDuplicateTimes[ nFileOpenDuplicate ] = NULL;
  7442. }
  7443. g_FileOpenDuplicateTimes.RemoveAll();
  7444. }
  7445. #if IsPlatformPS3()
  7446. class CFiosAllocator : public cell::fios::allocator
  7447. {
  7448. public:
  7449. void* Allocate(uint32_t size, uint32_t flags, const char* pFile, int line)
  7450. {
  7451. (void) pFile;
  7452. (void) line;
  7453. return memalign(FIOS_ALIGNMENT_FROM_MEMFLAGS(flags), size);
  7454. }
  7455. void Deallocate(void* pMemory, uint32_t flags, const char* pFile, int line)
  7456. {
  7457. (void) flags;
  7458. (void) pFile;
  7459. (void) line;
  7460. free(pMemory);
  7461. }
  7462. void* Reallocate(void* pMemory, uint32_t newSize, uint32_t flags, const char* pFile, int line)
  7463. {
  7464. (void) pMemory;
  7465. (void) newSize;
  7466. (void) flags;
  7467. (void) pFile;
  7468. (void) line;
  7469. return NULL; /* fios does not use Reallocate */
  7470. }
  7471. };
  7472. // Configure FIOS, allows prefetching of whole files of files within pack files.
  7473. class CFiosConfiguration
  7474. {
  7475. public:
  7476. CFiosConfiguration();
  7477. ~CFiosConfiguration();
  7478. bool Setup();
  7479. bool Teardown();
  7480. void FlushCache();
  7481. // These methods are not thread safe.
  7482. bool PrefetchFile( const char * pFileName, int nPriority, bool bPersist );
  7483. bool PrefetchFile( const char * pFileName, int nPriority, bool bPersist, int64 nOffset, int64 nSize );
  7484. void SuspendPrefetches( const char *pWhy );
  7485. void ResumePrefetches( const char *pWhy );
  7486. void PrintPrefetches();
  7487. void PrintPrefetch( int nSlot );
  7488. int ClearFinishedPrefetches();
  7489. const char * GetPathCached() const;
  7490. void CancelAllPrefetches();
  7491. bool IsPrefetchingDone();
  7492. private:
  7493. void CancelAndRespawnPrefetches( bool bPersistent );
  7494. int ResumePrefetchesToZero();
  7495. void SuspendPrefetchesToN( int nNumberOfSuspends );
  7496. CFiosAllocator m_Allocator;
  7497. cell::fios::media * m_SysCacheMedia;
  7498. cell::fios::scheduler * m_SchedulerForHDD;
  7499. cell::fios::schedulercache * m_SchedulerCache;
  7500. cell::fios::media * m_DevBdvdMedia;
  7501. cell::fios::scheduler * m_MainScheduler;
  7502. class CPrefetchInfo
  7503. {
  7504. public:
  7505. CPrefetchInfo( cell::fios::op * pOp, const char * pFileName, int64 nOffset, int64 nSize, const cell::fios::opattr_t & opAttributes, bool bPersistent )
  7506. :
  7507. m_OpAttributes( opAttributes ),
  7508. m_nOffset( nOffset ),
  7509. m_nSize( nSize ),
  7510. m_pOp( pOp ),
  7511. m_bPersitent( bPersistent )
  7512. {
  7513. Assert( pOp != NULL );
  7514. V_strncpy( m_FileName, pFileName, sizeof( m_FileName ) );
  7515. }
  7516. // Clone but use a different cell::fios::op *.
  7517. CPrefetchInfo( cell::fios::op * pOp, const CPrefetchInfo & other )
  7518. :
  7519. m_OpAttributes( other.m_OpAttributes ),
  7520. m_nOffset( other.m_nOffset ),
  7521. m_nSize( other.m_nSize ),
  7522. m_pOp( pOp ),
  7523. m_bPersitent( other.m_bPersitent )
  7524. {
  7525. Assert( pOp != NULL );
  7526. V_strncpy( m_FileName, other.m_FileName, sizeof( m_FileName ) );
  7527. }
  7528. cell::fios::op * GetOp() const
  7529. {
  7530. return m_pOp;
  7531. }
  7532. const char * GetFileName() const
  7533. {
  7534. return m_FileName;
  7535. }
  7536. int64 GetOffset() const
  7537. {
  7538. return m_nOffset;
  7539. }
  7540. int64 GetSize() const
  7541. {
  7542. return m_nSize;
  7543. }
  7544. const cell::fios::opattr_t & GetOpAttributes() const
  7545. {
  7546. return m_OpAttributes;
  7547. }
  7548. bool IsPersistent() const
  7549. {
  7550. return m_bPersitent;
  7551. }
  7552. private:
  7553. char m_FileName[256];
  7554. cell::fios::opattr_t m_OpAttributes;
  7555. int64 m_nOffset;
  7556. int64 m_nSize;
  7557. cell::fios::op * m_pOp;
  7558. bool m_bPersitent;
  7559. };
  7560. CUtlVector< CPrefetchInfo * > m_Prefetches;
  7561. };
  7562. CFiosConfiguration g_FiosConfiguration;
  7563. bool SetupFios()
  7564. {
  7565. if ( fs_fios_enabled.GetBool() )
  7566. {
  7567. return g_FiosConfiguration.Setup();
  7568. }
  7569. else
  7570. {
  7571. return false;
  7572. }
  7573. }
  7574. bool TeardownFios()
  7575. {
  7576. if ( fs_fios_enabled.GetBool() )
  7577. {
  7578. return g_FiosConfiguration.Teardown();
  7579. }
  7580. else
  7581. {
  7582. return false;
  7583. }
  7584. }
  7585. const char * CFiosConfiguration::GetPathCached() const
  7586. {
  7587. return g_pPS3PathInfo->GameImagePath();
  7588. }
  7589. CFiosConfiguration::CFiosConfiguration()
  7590. :
  7591. m_Allocator(),
  7592. m_SysCacheMedia( NULL ),
  7593. m_SchedulerForHDD( NULL ),
  7594. m_SchedulerCache( NULL ),
  7595. m_DevBdvdMedia( NULL ),
  7596. m_MainScheduler( NULL ),
  7597. m_Prefetches( 4, 32 ) // 32 allows us to prefetch 16 high priority prefetches at the same time as one low priority prefetch
  7598. // With the low priority being canceled and restarted every time.
  7599. {
  7600. // Do nothing...
  7601. }
  7602. CFiosConfiguration::~CFiosConfiguration()
  7603. {
  7604. // Do nothing...
  7605. Assert( m_Prefetches.Count() == 0 );
  7606. }
  7607. // SONY overrides new with the types ps3media and schedulercache.
  7608. #include "memdbgoff.h"
  7609. bool CFiosConfiguration::Setup()
  7610. {
  7611. cell::fios::fios_parameters parameters = FIOS_PARAMETERS_INITIALIZER;
  7612. parameters.pAllocator = &m_Allocator;
  7613. parameters.pLargeMemcpy = 0; // Use memcpy
  7614. parameters.pVprintf = 0; // Use vprintf
  7615. // Enable this only to see if the cache is working as expected
  7616. //parameters.profiling = cell::fios::kProfileOps | cell::fios::kProfileCache;
  7617. cell::fios::FIOSInit( &parameters );
  7618. m_DevBdvdMedia = new cell::fios::ps3media( GetPathCached() );
  7619. if ( m_DevBdvdMedia == NULL )
  7620. {
  7621. Warning( "[CFiosConfiguration::Setup] Can't allocate dev bdvd media.\n" );
  7622. return false;
  7623. }
  7624. m_SysCacheMedia = new cell::fios::ps3media( g_pPS3PathInfo->SystemCachePath() );
  7625. if ( m_SysCacheMedia == NULL )
  7626. {
  7627. Warning( "[CFiosConfiguration::Setup] Can't allocate system cache media.\n" );
  7628. return false;
  7629. }
  7630. m_SchedulerForHDD = cell::fios::scheduler::createSchedulerForMedia( m_SysCacheMedia );
  7631. if ( m_SchedulerForHDD == NULL )
  7632. {
  7633. Warning( "[CFiosConfiguration::Setup] Can't create scheduler for media.\n" );
  7634. return false;
  7635. }
  7636. // Use SONY-like configuration:
  7637. const int BLOCK_SIZE = 1024 * 1024; // 1 Mb blocks.
  7638. const int NUMBER_OF_BLOCKS = 1800; // 1800 blocks mean that the cache is going to be 1.8 Gb big.
  7639. // We leave 237 MB for the other usages.
  7640. // In theory, without counting other systems, we can go up to 2037 blocks.
  7641. // To remove one potential cause for a CERT issue, let's remove the usage of this flag (#if 0)
  7642. // This is a speculative fix for crashes that would not exist for the end user (although SONY states that we should not ship with this setting turned on).
  7643. #if defined(_CERT) && 0
  7644. const bool bCheckModification = false; // No need to scan for modifications at release time (faster)
  7645. #else
  7646. const bool bCheckModification = true; // But we want to double-check during development to avoid stale data
  7647. #endif
  7648. m_SchedulerCache = new cell::fios::schedulercache( m_DevBdvdMedia, m_SchedulerForHDD,
  7649. "FILECACHE", // Cache directory
  7650. 0x0123456789ABCDEFll, // diskId. For one disk only title, we can hard-code it
  7651. true, // Use single-file as it is faster.
  7652. bCheckModification, NUMBER_OF_BLOCKS, BLOCK_SIZE);
  7653. if ( m_SchedulerCache == NULL )
  7654. {
  7655. Warning( "[CFiosConfiguration::Setup] Can't allocate m_SchedulerCache.\n" );
  7656. return false;
  7657. }
  7658. m_MainScheduler = cell::fios::scheduler::createSchedulerForMedia( m_SchedulerCache );
  7659. if (m_MainScheduler == NULL )
  7660. {
  7661. Warning( "[CFiosConfiguration::Setup] Can't allocate m_MainScheduler.\n" );
  7662. return false;
  7663. }
  7664. // Starts the prefetches 0.5 second after no disk-usage.
  7665. // This should make the IO faster for normal cases, prefetch will start when sounds / animations have not been needed IO for a while.
  7666. // Except it creates huge IO issues. These functions are not reliable at all, created deadlocks, etc... Re-enable it on the PS4.
  7667. // m_MainScheduler->setPrefetchDelay( cell::fios::FIOSMillisecondsToAbstime( 500 ) );
  7668. // m_MainScheduler->setPrefetchThrottle( cell::fios::FIOSMillisecondsToAbstime( 100 ) ); // One prefetch command every 3 game frames
  7669. // This will make the prefetches two times slower.
  7670. m_MainScheduler->setDefault();
  7671. // Currently we are not creating a RAM cache (this could speed up some small reads).
  7672. return true;
  7673. }
  7674. bool CFiosConfiguration::Teardown()
  7675. {
  7676. // Tearing down, we need to make sure suspend state is reset (FIOS limitation).
  7677. ResumePrefetchesToZero();
  7678. // Ops are taken cared of when we destroy the scheduler, but here we want to make sure PrefetchInfo are also deallocated correctly
  7679. CancelAllPrefetches();
  7680. // We cancel all outstanding operations
  7681. m_MainScheduler->cancelAllOps();
  7682. // Wait for the main scheduler to be idle, so all the operations are effectively canceled (otherwise FIOS may crash in shutdownAndCancelOps).
  7683. while ( m_MainScheduler->isIdle() == false )
  7684. {
  7685. ThreadSleep( 1 );
  7686. }
  7687. // sometimes, files aren't closed by file system, and this will prevent the scheduler from shutting down
  7688. m_MainScheduler->closeAllFiles();
  7689. m_MainScheduler->shutdownAndCancelOps();
  7690. cell::fios::scheduler::destroyScheduler( m_MainScheduler );
  7691. m_MainScheduler = NULL;
  7692. delete m_SchedulerCache;
  7693. m_SchedulerCache = NULL;
  7694. m_SchedulerForHDD->shutdownAndCancelOps();
  7695. cell::fios::scheduler::destroyScheduler( m_SchedulerForHDD );
  7696. m_SchedulerForHDD = NULL;
  7697. delete m_SysCacheMedia;
  7698. m_SysCacheMedia = NULL;
  7699. delete m_DevBdvdMedia;
  7700. m_DevBdvdMedia = NULL;
  7701. cell::fios::FIOSTerminate();
  7702. return true;
  7703. }
  7704. // Turn it back on
  7705. #include "memdbgon.h"
  7706. void CFiosConfiguration::FlushCache()
  7707. {
  7708. m_SchedulerCache->flush();
  7709. }
  7710. bool CFiosConfiguration::PrefetchFile( const char * pFileName, int nPriority, bool bPersist )
  7711. {
  7712. #if 0
  7713. // Because prefetching a big file is going to block all the other prefetches (and mess up the priorities if we need some prefetch done ASAP)
  7714. // We are going to split a big prefetch in smaller pieces.
  7715. // This is not necessary anymore:
  7716. // If we cancel a prefetch in the middle and restart it, all the file portion that have been already prefetched before the cancellation
  7717. // will be directly accounted for. I.e. there is not a lot of overhead to cancel and restart a prefetch.
  7718. int64 nFileSize;
  7719. cell::fios::err_t err = cell::fios::scheduler::getDefaultScheduler()->getFileSizeSync( NULL, pFileName, &nFileSize );
  7720. if ( err < 0 )
  7721. {
  7722. Warning( "Can't retrieve size of file '%s'.\n", pFileName );
  7723. return false;
  7724. }
  7725. const int MAX_PREFETCH_BLOCK_SIZE = 16 * 1024 * 1024; // We prefetch 16 Mb at a time max
  7726. // This is around 2 seconds of prefetching at the BluRay speed.
  7727. // A 1.2 Gb file (like the sound zip file on Portal 2) will use 75 prefetch commands.
  7728. // And will take a little bit more than 2 minutes to be prefetched.
  7729. int64 nOffset = 0;
  7730. int64 nRemainingSize = nFileSize;
  7731. while ( nRemainingSize != 0 )
  7732. {
  7733. int64 nPrefetchSize = MIN( nRemainingSize, MAX_PREFETCH_BLOCK_SIZE );
  7734. if ( PrefetchFile( pFileName, nPriority, bPersist, nOffset, nPrefetchSize ) == false)
  7735. {
  7736. return false;
  7737. }
  7738. nRemainingSize -= nPrefetchSize;
  7739. nOffset += nPrefetchSize;
  7740. }
  7741. return true;
  7742. #else
  7743. return PrefetchFile( pFileName, nPriority, bPersist, 0, FIOS_OFF_T_MAX );
  7744. #endif
  7745. }
  7746. bool CFiosConfiguration::PrefetchFile( const char * pFileName, int nPriority, bool bPersist, int64 nOffset, int64 nSize )
  7747. {
  7748. // Before we prefetch more, let's clear the old prefetches
  7749. ClearFinishedPrefetches();
  7750. cell::fios::opattr_t opattr = FIOS_OPATTR_INITIALIZER;
  7751. opattr.deadline = bPersist ? kDEADLINE_LATER : kDEADLINE_ASAP; // If not persistent, assume that we need this sooner rather than later
  7752. // ASAP maybe a bit high (but still lower than NOW)
  7753. // This is to handle the case where non-persistent files (usually for a given map)
  7754. // are prefetched before persistent files (usually for the game in general).
  7755. // Note that FIOS doe not seem to care about the deadline for prefetches (or the priority). :(
  7756. opattr.priority = nPriority;
  7757. opattr.pCallback = NULL;
  7758. opattr.opflags = bPersist ? cell::fios::kOPF_CACHEPERSIST : 0;
  7759. opattr.pLicense = 0;
  7760. cell::fios::op * pOp = cell::fios::scheduler::getDefaultScheduler()->prefetchFile( &opattr, pFileName, nOffset, nSize );
  7761. if ( pOp == NULL )
  7762. {
  7763. Warning( "FIOS error: Can't prefetch the file '%s'.\n", pFileName );
  7764. return false;
  7765. }
  7766. CPrefetchInfo * pPrefetchInfo = new CPrefetchInfo( pOp, pFileName, nOffset, nSize, opattr, bPersist );
  7767. m_Prefetches.AddToTail( pPrefetchInfo );
  7768. if ( bPersist == false )
  7769. {
  7770. // If the prefetch is not persistent, it is deemed higher priority than persistent prefetch
  7771. // (as they are map specific prefetching, so need to happen sooner rather than later).
  7772. // Due to incorrect priorities in the FIOS prefetching engine, we are going to cancel persistent prefetches
  7773. // And recreate them (they will be at the end of the list and recreated just after, as FIOS seems to execute them in order :().
  7774. // This is a workaround that we hope will work.
  7775. // We can't suspend / resume individual op. We can suspend / resume all prefetches but that will not help us here.
  7776. CancelAndRespawnPrefetches( true );
  7777. }
  7778. return true;
  7779. }
  7780. void CFiosConfiguration::SuspendPrefetches( const char *pWhy )
  7781. {
  7782. if ( fs_fios_spew_prefetches.GetBool() )
  7783. {
  7784. Msg( "[Fios] Suspend prefetches. %s\n", pWhy );
  7785. }
  7786. cell::fios::scheduler::getDefaultScheduler()->suspendPrefetch();
  7787. }
  7788. void CFiosConfiguration::ResumePrefetches( const char *pWhy )
  7789. {
  7790. if ( fs_fios_spew_prefetches.GetBool() )
  7791. {
  7792. Msg( "[Fios] Resume prefetches. %s\n", pWhy );
  7793. }
  7794. cell::fios::scheduler::getDefaultScheduler()->resumePrefetch();
  7795. }
  7796. void CFiosConfiguration::CancelAndRespawnPrefetches( bool bPersistent )
  7797. {
  7798. // This code will add new prefetches, but we are only interested in the prefetch already there
  7799. // Note that this code is pretty inefficient if called several times in a row
  7800. // (like when we prefetch non-persistent file one after the other).
  7801. // But it should not happen often enough to be a big performance issue (we are talking about microseconds, not milliseconds).
  7802. int nSize = m_Prefetches.Count();
  7803. for ( int i = 0 ; i < nSize ; ++i )
  7804. {
  7805. CPrefetchInfo * pPrefetchInfo = m_Prefetches[i];
  7806. if ( pPrefetchInfo->IsPersistent() == false )
  7807. {
  7808. // We want to keep these.
  7809. continue;
  7810. }
  7811. cell::fios::op * pOp = pPrefetchInfo->GetOp();
  7812. if ( pOp->isDone() || pOp->isCancelled() )
  7813. {
  7814. // Already done or canceled, nothing to cancel...
  7815. continue;
  7816. }
  7817. // We cancel it
  7818. pOp->cancel();
  7819. // And recreate it, it will be pretty much pushed at the end of the FIOS stack.
  7820. // If the previous op happened to have finished earlier, re-prefetching it will not do another I/O operation.
  7821. cell::fios::op * pNewOp = cell::fios::scheduler::getDefaultScheduler()->prefetchFile( &pPrefetchInfo->GetOpAttributes(), pPrefetchInfo->GetFileName(),
  7822. pPrefetchInfo->GetOffset(), pPrefetchInfo->GetSize() );
  7823. if ( pNewOp == NULL )
  7824. {
  7825. Warning( "FIOS error: Can't prefetch the file '%s'.\n", pPrefetchInfo->GetFileName() );
  7826. continue; // Not restarting the prefetch is not critical but could reduce the game experience
  7827. // Continue with the other files
  7828. }
  7829. CPrefetchInfo * pNewPrefetchInfo = new CPrefetchInfo( pNewOp, *pPrefetchInfo );
  7830. m_Prefetches.AddToTail( pNewPrefetchInfo );
  7831. }
  7832. // In case the canceled ops are done by now (this will also clear the old CPrefetchInfo).
  7833. ClearFinishedPrefetches();
  7834. }
  7835. void CFiosConfiguration::PrintPrefetches()
  7836. {
  7837. int nSize = m_Prefetches.Count();
  7838. if ( nSize != 0 )
  7839. {
  7840. for (int i = 0 ; i < nSize ; ++i )
  7841. {
  7842. PrintPrefetch( i );
  7843. }
  7844. }
  7845. else
  7846. {
  7847. Msg( "No prefetch in progress.\n" );
  7848. }
  7849. }
  7850. void CFiosConfiguration::PrintPrefetch( int nSlot )
  7851. {
  7852. CPrefetchInfo * pPrefetchInfo = m_Prefetches[nSlot];
  7853. cell::fios::op * pOp = pPrefetchInfo->GetOp();
  7854. int nPriority = pOp->getPriority();
  7855. const float ONE_MEGABYTE = 1024.0f * 1024.0f;
  7856. int64_t nRequestedSize = pOp->getRequestedSize(); // Note that if the file is 1.2 Gb (like the sound zip file in Portal2), the reported size will be incorrect.
  7857. if ( nRequestedSize == FIOS_OFF_T_MAX )
  7858. {
  7859. // We only get the size when we print it (not in normal process).
  7860. int64 nFileSize;
  7861. cell::fios::err_t err = cell::fios::scheduler::getDefaultScheduler()->getFileSizeSync( NULL, pPrefetchInfo->GetFileName(), &nFileSize );
  7862. nRequestedSize = nFileSize - pPrefetchInfo->GetOffset();
  7863. }
  7864. float fRequestedSize = ( nRequestedSize >= 0 ) ? ( ( float )( nRequestedSize ) / ONE_MEGABYTE ) : -1.f; // Sometimes the size is unknown, use -1 in this case
  7865. float fFullfilledSize = ( float )( pOp->getFulfilledSize() ) / ONE_MEGABYTE;
  7866. if ( pOp->isDone() || pOp->isCancelled() )
  7867. {
  7868. cell::fios::err_t nError = pOp->getError();
  7869. if ( nError < 0 )
  7870. {
  7871. Msg( "Slot[%d] File: '%s' - Priority: %d - Requested size: %0.1f Mb - Fulfilled size: %0.1f Mb - Error: 0x%08X.\n",
  7872. nSlot, pPrefetchInfo->GetFileName(), nPriority, fRequestedSize, fFullfilledSize, nError );
  7873. }
  7874. else
  7875. {
  7876. Msg( "Slot[%d] File: '%s' - Priority: %d - Requested size: %0.1f Mb - Fulfilled size: %0.1f Mb - Done.\n",
  7877. nSlot, pPrefetchInfo->GetFileName(), nPriority, fRequestedSize, fFullfilledSize );
  7878. }
  7879. }
  7880. else
  7881. {
  7882. int millisecondsToCompletion = cell::fios::FIOSAbstimeToMilliseconds( pOp->getEstimatedCompletion() );
  7883. float fSecondsToCompletion = (float)millisecondsToCompletion / 1000.0f;
  7884. Msg( "Slot[%d] File: '%s' - Priority: %d - Requested size: %0.1f Mb - Fulfilled size: %0.1f Mb - Estimated completion: %0.3f seconds.\n",
  7885. nSlot, pPrefetchInfo->GetFileName(), nPriority, fRequestedSize, fFullfilledSize, fSecondsToCompletion );
  7886. }
  7887. }
  7888. int CFiosConfiguration::ClearFinishedPrefetches()
  7889. {
  7890. int nCleared = 0;
  7891. int nSize = m_Prefetches.Count();
  7892. // From end to beginning to indices are preserved during the scan
  7893. for ( int i = nSize - 1 ; i >= 0 ; --i )
  7894. {
  7895. CPrefetchInfo * pPrefetchInfo = m_Prefetches[i];
  7896. cell::fios::op * pOp = pPrefetchInfo->GetOp();
  7897. if ( pOp->isDone() || pOp->isCancelled() )
  7898. {
  7899. // Remove it from the list
  7900. delete pPrefetchInfo;
  7901. ++nCleared;
  7902. cell::fios::scheduler::getDefaultScheduler()->deleteOp( pOp );
  7903. m_Prefetches.Remove( i );
  7904. }
  7905. }
  7906. return nCleared;
  7907. }
  7908. void CFiosConfiguration::CancelAllPrefetches()
  7909. {
  7910. int nSize = m_Prefetches.Count();
  7911. for ( int i = 0 ; i < nSize ; ++i )
  7912. {
  7913. CPrefetchInfo * pPrefetchInfo = m_Prefetches[i];
  7914. cell::fios::op * pOp = pPrefetchInfo->GetOp();
  7915. if ( pOp->isDone() || pOp->isCancelled() )
  7916. {
  7917. continue;
  7918. }
  7919. pOp->cancel();
  7920. }
  7921. // The prefetches may actually have been suspended (like during map loading).
  7922. // cancel() may not do anything in this suspended state, so let's resume so they can complete.
  7923. int nNumberOfSuspends = ResumePrefetchesToZero();
  7924. // This is going to be a blocking call until all the ops are considered done (after cancellation).
  7925. // Ops are canceled very quickly though.
  7926. while ( m_Prefetches.Count() != 0 )
  7927. {
  7928. ClearFinishedPrefetches();
  7929. }
  7930. // Once it is over, we re-suspend the same number of times the prefetches (so the state is the same as when we entered the function)
  7931. SuspendPrefetchesToN( nNumberOfSuspends );
  7932. }
  7933. int CFiosConfiguration::ResumePrefetchesToZero()
  7934. {
  7935. uint32 nNumberOfSuspends = cell::fios::scheduler::getDefaultScheduler()->getPrefetchSuspendCount();
  7936. for ( int i = 0 ; i < nNumberOfSuspends ; ++i )
  7937. {
  7938. cell::fios::scheduler::getDefaultScheduler()->resumePrefetch( "Resume so CancelAllPrefetches() can complete." );
  7939. }
  7940. Assert( cell::fios::scheduler::getDefaultScheduler()->getPrefetchSuspendCount() == 0 );
  7941. return ( int )nNumberOfSuspends;
  7942. }
  7943. void CFiosConfiguration::SuspendPrefetchesToN( int nNumberOfSuspends )
  7944. {
  7945. for ( int i = 0 ; i < nNumberOfSuspends ; ++i )
  7946. {
  7947. cell::fios::scheduler::getDefaultScheduler()->suspendPrefetch( "Suspends restored after CancelAllPrefetches() has completed." );
  7948. }
  7949. Assert( cell::fios::scheduler::getDefaultScheduler()->getPrefetchSuspendCount() == nNumberOfSuspends );
  7950. }
  7951. bool CFiosConfiguration::IsPrefetchingDone()
  7952. {
  7953. int nSize = m_Prefetches.Count();
  7954. for ( int i = 0 ; i < nSize ; ++i )
  7955. {
  7956. CPrefetchInfo * pPrefetchInfo = m_Prefetches[i];
  7957. cell::fios::op * pOp = pPrefetchInfo->GetOp();
  7958. if ( ( pOp->isDone() || pOp->isCancelled() ) == false )
  7959. {
  7960. return false;
  7961. }
  7962. }
  7963. return true; // All are done or canceled
  7964. }
  7965. bool CBaseFileSystem::PrefetchFile( const char *pFileName, int nPriority, bool bPersist )
  7966. {
  7967. if ( fs_fios_enabled.GetBool() )
  7968. {
  7969. return g_FiosConfiguration.PrefetchFile( pFileName, nPriority, bPersist );
  7970. }
  7971. else
  7972. {
  7973. return false;
  7974. }
  7975. }
  7976. bool CBaseFileSystem::PrefetchFile( const char *pFileName, int nPriority, bool bPersist, int64 nOffset, int64 nSize )
  7977. {
  7978. if ( fs_fios_enabled.GetBool() )
  7979. {
  7980. return g_FiosConfiguration.PrefetchFile( pFileName, nPriority, bPersist, nOffset, nSize );
  7981. }
  7982. else
  7983. {
  7984. return false;
  7985. }
  7986. }
  7987. void CBaseFileSystem::FlushCache()
  7988. {
  7989. if ( fs_fios_enabled.GetBool() )
  7990. {
  7991. g_FiosConfiguration.FlushCache();
  7992. }
  7993. }
  7994. void CBaseFileSystem::SuspendPrefetches( const char *pWhy )
  7995. {
  7996. if ( fs_fios_enabled.GetBool() )
  7997. {
  7998. g_FiosConfiguration.SuspendPrefetches( pWhy );
  7999. }
  8000. }
  8001. bool g_bUseFiosHddCache = true;
  8002. void CBaseFileSystem::OnSaveStateChanged( bool bSaving )
  8003. {
  8004. static CInterlockedInt nPrefetchesDueToSaving = 0;
  8005. if ( bSaving )
  8006. {
  8007. // If we are saving, we want to reduce the HDD access as much as possible
  8008. // That way, normal IO and saving IO don't compete for the HDD usage.
  8009. // Normal IO will not be slowed down by the saving (we will use the BluRay instead),
  8010. // And hopefully the saving will be faster too.
  8011. SuspendPrefetches( "Saving" );
  8012. ++nPrefetchesDueToSaving;
  8013. g_bUseFiosHddCache = false; // Let's stop using the HDD cache (read and write)
  8014. }
  8015. else
  8016. {
  8017. // In case, OnSaveStateChanged( false ) is not called as many times as OnSaveStateChanged( true ),
  8018. // let's restore the prefetch state (until nPrefetchesDueToSaving == 0).
  8019. // This is a paranoid code, in case we don't have expected parity.
  8020. for ( ; ; )
  8021. {
  8022. int nResult = --nPrefetchesDueToSaving;
  8023. if ( nResult < 0 )
  8024. {
  8025. // We decremented too far
  8026. ++nPrefetchesDueToSaving; // Put back the value of 0
  8027. break; // We are done with the resume prefetches related to the save
  8028. }
  8029. ResumePrefetches( "Save finished" );
  8030. // Let's continue until nPrefetchesDueToSaving == 0
  8031. }
  8032. g_bUseFiosHddCache = true; // Safe to re-use the cache again
  8033. }
  8034. }
  8035. bool CBaseFileSystem::IsPrefetchingDone()
  8036. {
  8037. return g_FiosConfiguration.IsPrefetchingDone( );
  8038. }
  8039. void CBaseFileSystem::ResumePrefetches( const char *pWhy )
  8040. {
  8041. if ( fs_fios_enabled.GetBool() )
  8042. {
  8043. g_FiosConfiguration.ResumePrefetches( pWhy );
  8044. }
  8045. }
  8046. CON_COMMAND( fs_fios_flush_cache, "Flushes the FIOS HDD cache." )
  8047. {
  8048. if ( fs_fios_enabled.GetBool() )
  8049. {
  8050. g_FiosConfiguration.FlushCache();
  8051. Msg( "FIOS cache flushed.\n" );
  8052. }
  8053. }
  8054. CON_COMMAND( fs_fios_print_prefetches, "Displays all the prefetches currently in progress." )
  8055. {
  8056. if ( fs_fios_enabled.GetBool() )
  8057. {
  8058. g_FiosConfiguration.PrintPrefetches();
  8059. // Clear it after displaying the list so the user has a chance to view the one finished
  8060. int nCleared = g_FiosConfiguration.ClearFinishedPrefetches();
  8061. if ( nCleared != 0 )
  8062. {
  8063. Msg( "%d prefetch(s) finished and removed from the list.\n", nCleared );
  8064. }
  8065. }
  8066. }
  8067. CON_COMMAND( fs_fios_prefetch_file, "Prefetches a file: </PS3_GAME/USRDIR/filename.bin>.\nThe preftech is medium priority and persistent." )
  8068. {
  8069. if ( fs_fios_enabled.GetBool() )
  8070. {
  8071. if ( args.ArgC() == 2 )
  8072. {
  8073. // Minimum priority and persistent for full zip file
  8074. // As the assumption is all the data in the zip files have been organized to stay prefetched (like for sounds).
  8075. // We want it persistent so we know after a while it will always stay there, but it is a lower priority prefetch
  8076. // as it may not be used before a while. (short term / non-persistent preftech have a more immediate use).
  8077. bool bSucceeded = g_FiosConfiguration.PrefetchFile( args.Arg( 1 ), -128, true );
  8078. if ( bSucceeded == false )
  8079. {
  8080. Warning( "Prefetch failed. Check if there are other prefetches going on with the command 'fs_fios_print_prefetches'.\n");
  8081. }
  8082. }
  8083. else
  8084. {
  8085. Warning( "Incorrect parameter for the command 'fs_fios_prefetch_file'. Please use a file name (like \"/PS3_GAME/USRDIR/filename.bin\".\n" );
  8086. }
  8087. }
  8088. }
  8089. CON_COMMAND( fs_fios_prefetch_file_in_pack, "Prefetches a file in a pack: <portal2/models/container_ride/fineDebris_part5.ani>.\nThe preftech is medium priority and non-persistent.")
  8090. {
  8091. if ( fs_fios_enabled.GetBool() )
  8092. {
  8093. if ( args.ArgC() == 2 )
  8094. {
  8095. // Higher priority and non-persistent for single file (we don't want the file to pollute the cache, it is just used for short term optimization, like in the next map).
  8096. // But it has to be higher priority than the long term persistent prefetches as there is more chance the prefetch is going to help shortly.
  8097. const char * pFileInPack = args.Arg( 1 );
  8098. char packFileName[256];
  8099. int64 nPosition = 0, nLength = 0;
  8100. if ( g_pBaseFileSystem->GetPackFileInfoFromRelativePath( pFileInPack, "GAME", packFileName, sizeof(packFileName), nPosition, nLength ) )
  8101. {
  8102. const char * pPathCached = g_FiosConfiguration.GetPathCached();
  8103. const int nPathCachedLength = strlen( pPathCached );
  8104. if ( memcmp( packFileName, pPathCached, nPathCachedLength ) == 0 )
  8105. {
  8106. Msg( "Prefetching file: '%s' - Pack file: '%s' - Position: %lld - Size: %lld.\n", pFileInPack, packFileName, nPosition, nLength );
  8107. // We have to skip the path cached as FIOS expects a path relative to that.
  8108. // Priority is set higher than average, just to differentiate with persistent prefetches.
  8109. bool bSucceeded = g_FiosConfiguration.PrefetchFile( packFileName + nPathCachedLength, -127, false, nPosition, nLength );
  8110. if ( bSucceeded == false )
  8111. {
  8112. Warning( "Prefetch failed. Check if there are other prefetches going on with the command 'fs_fios_print_prefetches'.\n" );
  8113. }
  8114. }
  8115. else
  8116. {
  8117. Warning( "Can't prefetch file '%s' in the pack file '%s' as the pack file is not a under the cached path '%s'.\n", pFileInPack, packFileName, pPathCached );
  8118. }
  8119. }
  8120. else
  8121. {
  8122. Warning( "Can't find the corresponding pack file for '%s'.\n", pFileInPack );
  8123. }
  8124. }
  8125. else
  8126. {
  8127. Warning( "Incorrect parameter for the command 'fs_fios_prefetch_file_in_pack'. Please use a file name (like \"portal2/models/container_ride/fineDebris_part5.ani\".\n" );
  8128. }
  8129. }
  8130. }
  8131. CON_COMMAND( fs_fios_cancel_prefetches, "Cancels all the prefetches in progress." )
  8132. {
  8133. if ( fs_fios_enabled.GetBool() )
  8134. {
  8135. g_FiosConfiguration.CancelAllPrefetches();
  8136. }
  8137. }
  8138. #else
  8139. // Fake commands for other platforms...
  8140. CON_COMMAND( fs_fios_flush_cache, "Flushes the FIOS HDD cache." )
  8141. {
  8142. }
  8143. CON_COMMAND( fs_fios_print_prefetches, "Displays all the prefetches currently in progress." )
  8144. {
  8145. }
  8146. CON_COMMAND( fs_fios_prefetch_file, "Prefetches a file: </PS3_GAME/USRDIR/filename.bin>.\nThe preftech is medium priority and persistent." )
  8147. {
  8148. }
  8149. CON_COMMAND( fs_fios_prefetch_file_in_pack, "Prefetches a file in a pack: <portal2/models/container_ride/fineDebris_part5.ani>.\nThe preftech is medium priority and non-persistent.")
  8150. {
  8151. }
  8152. CON_COMMAND( fs_fios_cancel_prefetches, "Cancels all the prefetches in progress." )
  8153. {
  8154. }
  8155. #endif