Team Fortress 2 Source Code as on 22/4/2020
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.

2087 lines
68 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "vpklib/packedstore.h"
  7. #include "packedstore_internal.h"
  8. #include "tier1/utlintrusivelist.h"
  9. #include "tier1/generichash.h"
  10. #include "tier1/checksum_crc.h"
  11. #include "tier1/checksum_md5.h"
  12. #include "tier1/utldict.h"
  13. #include "tier2/fileutils.h"
  14. #include "tier1/utlbuffer.h"
  15. #ifdef VPK_ENABLE_SIGNING
  16. #include "crypto.h"
  17. #endif
  18. #ifdef IS_WINDOWS_PC
  19. #include <windows.h>
  20. #endif
  21. // memdbgon must be the last include file in a .cpp file!!!
  22. #include "tier0/memdbgon.h"
  23. typedef uint16 PackFileIndex_t;
  24. #define PACKFILEINDEX_END 0xffff
  25. #pragma pack(1)
  26. struct CFilePartDescr
  27. {
  28. PackFileIndex_t m_nFileNumber;
  29. uint32 m_nFileDataOffset;
  30. uint32 m_nFileDataSize;
  31. };
  32. struct CFileHeaderFixedData
  33. {
  34. uint32 m_nFileCRC;
  35. uint16 m_nMetaDataSize;
  36. CFilePartDescr m_PartDescriptors[1]; // variable length
  37. FORCEINLINE const void *MetaData( void ) const;
  38. FORCEINLINE const CFilePartDescr *FileData( int nPart = 0 ) const;
  39. uint32 TotalDataSize( void ) const
  40. {
  41. return m_nMetaDataSize + m_PartDescriptors[0].m_nFileDataSize;
  42. }
  43. size_t HeaderSizeIncludingMetaData( void ) const
  44. {
  45. size_t nRet = sizeof( *this ) - sizeof( m_PartDescriptors ) + m_nMetaDataSize;
  46. // see how many parts we have and count the size of their descriptors
  47. CFilePartDescr const *pPart = m_PartDescriptors;
  48. while( pPart->m_nFileNumber != PACKFILEINDEX_END )
  49. {
  50. nRet += sizeof( CFilePartDescr );
  51. pPart++;
  52. }
  53. nRet += sizeof( PackFileIndex_t ); // count terminator
  54. return nRet;
  55. }
  56. };
  57. #pragma pack()
  58. #define PACKEDFILE_DIR_HASH_SIZE 43
  59. static int s_FileHeaderSize( char const *pName, int nNumDataParts, int nNumMetaDataBytes )
  60. {
  61. return 1 + strlen( pName ) + // name plus nul
  62. sizeof( uint32 ) + // file crc
  63. sizeof( uint16 ) + // meta data size
  64. nNumMetaDataBytes + // metadata
  65. nNumDataParts * sizeof( CFilePartDescr ) + // part data
  66. sizeof( PackFileIndex_t ); // part data 0xff end marker
  67. }
  68. class CFileDirectoryData
  69. {
  70. public:
  71. CFileDirectoryData *m_pNext;
  72. char const *m_Name;
  73. };
  74. // hash chain for accelerating file lookups. We can find an extension by hash, and find the
  75. // directories containing files with this extension by another hash
  76. class CFileExtensionData
  77. {
  78. public:
  79. CFileExtensionData *m_pNext; // next one that has the same hash
  80. char const *m_Name; // points at extension string within the directory data
  81. // nodes for each directory containing a file of this type
  82. CUtlIntrusiveList<CFileDirectoryData> m_pDirectoryHashTable[PACKEDFILE_DIR_HASH_SIZE];
  83. ~CFileExtensionData( void )
  84. {
  85. for( int i = 0; i < ARRAYSIZE( m_pDirectoryHashTable ); i++ )
  86. {
  87. m_pDirectoryHashTable[i].Purge();
  88. }
  89. }
  90. };
  91. static int SkipFile( char const * &pData ) // returns highest file index
  92. {
  93. int nHighestChunkIndex = -1;
  94. pData += 1 + V_strlen( pData );
  95. pData += sizeof( uint32 );
  96. int nMetaDataSize = *(reinterpret_cast<uint16 const *>( pData ) );
  97. pData += sizeof( uint16 );
  98. while ( *( ( PackFileIndex_t const *) pData ) != PACKFILEINDEX_END )
  99. {
  100. int nIdx = reinterpret_cast<CFilePartDescr const *>(pData)->m_nFileNumber;
  101. if ( nIdx != VPKFILENUMBER_EMBEDDED_IN_DIR_FILE )
  102. nHighestChunkIndex = MAX( nHighestChunkIndex, nIdx );
  103. pData += sizeof( CFilePartDescr );
  104. }
  105. pData += sizeof( PackFileIndex_t );
  106. pData += nMetaDataSize;
  107. return nHighestChunkIndex;
  108. }
  109. static inline int SkipAllFilesInDir( char const * & pData )
  110. {
  111. int nHighestChunkIndex = -1;
  112. pData += 1 + strlen( pData ); // skip dir name
  113. // now, march through all the files
  114. while( *pData ) // until we're out of files to look at
  115. {
  116. int nSkipIndex = SkipFile( pData );
  117. nHighestChunkIndex = MAX( nHighestChunkIndex, nSkipIndex );
  118. }
  119. pData++; // skip end marker
  120. return nHighestChunkIndex;
  121. }
  122. CFileHeaderFixedData *CPackedStore::FindFileEntry( char const *pDirname, char const *pBaseName, char const *pExtension, uint8 **pExtBaseOut , uint8 **pNameBaseOut )
  123. {
  124. if ( pExtBaseOut )
  125. *pExtBaseOut = NULL;
  126. if ( pNameBaseOut )
  127. *pNameBaseOut = NULL;
  128. int nExtensionHash = HashString( pExtension ) % PACKEDFILE_EXT_HASH_SIZE;
  129. CFileExtensionData const *pExt = m_pExtensionData[nExtensionHash].FindNamedNodeCaseSensitive( pExtension );
  130. if ( pExt )
  131. {
  132. int nDirHash = HashString( pDirname ) % PACKEDFILE_DIR_HASH_SIZE;
  133. CFileDirectoryData const *pDir = pExt->m_pDirectoryHashTable[nDirHash].FindNamedNodeCaseSensitive( pDirname );
  134. if ( pDir )
  135. {
  136. if ( pExtBaseOut )
  137. *pExtBaseOut = (uint8 *) pDir;
  138. // we found the right directory. now, sequential search. data is heavily packed, so
  139. // this is a little awkward. See fileformat.txt
  140. char const *pData = pDir->m_Name;
  141. pData += 1 + strlen( pData ); // skip dir name
  142. // now, march through all the files
  143. while( *pData ) // until we're out of files to look at
  144. {
  145. if ( !V_strcmp( pData, pBaseName ) ) // found it?
  146. {
  147. if ( pNameBaseOut )
  148. *pNameBaseOut = (uint8 *) pData;
  149. return ( CFileHeaderFixedData * )( pData + 1 + V_strlen( pData ) ); // return header
  150. }
  151. // this isn't it - skip over it
  152. SkipFile( pData );
  153. }
  154. }
  155. }
  156. return NULL;
  157. }
  158. const void *CFileHeaderFixedData::MetaData( void ) const
  159. {
  160. if ( ! m_nMetaDataSize )
  161. return NULL;
  162. const CFilePartDescr *ret = &( m_PartDescriptors[0] );
  163. while( ret->m_nFileNumber != PACKFILEINDEX_END )
  164. ret++;
  165. return reinterpret_cast<uint8 const *>( ret ) + sizeof( PackFileIndex_t );
  166. }
  167. CFilePartDescr const *CFileHeaderFixedData::FileData( int nPart ) const
  168. {
  169. return m_PartDescriptors + nPart;
  170. }
  171. void CPackedStore::Init( void )
  172. {
  173. m_nHighestChunkFileIndex = -1;
  174. m_bUseDirFile = false;
  175. m_pszFileBaseName[0] = 0;
  176. m_pszFullPathName[0] = 0;
  177. memset( m_pExtensionData, 0, sizeof( m_pExtensionData ) );
  178. m_nDirectoryDataSize = 0;
  179. m_nWriteChunkSize = k_nVPKDefaultChunkSize;
  180. m_nSizeOfSignedData = 0;
  181. m_Signature.Purge();
  182. m_SignaturePrivateKey.Purge();
  183. m_SignaturePublicKey.Purge();
  184. }
  185. void CPackedStore::BuildHashTables( void )
  186. {
  187. m_nHighestChunkFileIndex = -1;
  188. for( int i = 0; i < ARRAYSIZE( m_pExtensionData ) ; i++ )
  189. {
  190. m_pExtensionData[i].Purge();
  191. }
  192. char const *pData = reinterpret_cast< char const *>( DirectoryData() );
  193. while( *pData )
  194. {
  195. // for each extension
  196. int nExtensionHash = HashString( pData ) % PACKEDFILE_EXT_HASH_SIZE;
  197. CFileExtensionData *pNewExt = new CFileExtensionData;
  198. pNewExt->m_Name = pData;
  199. m_pExtensionData[nExtensionHash].AddToHead( pNewExt );
  200. // now, iterate over all directories associated with this extension
  201. pData += 1 + strlen( pData );
  202. while( *pData )
  203. {
  204. int nDirHash = HashString( pData ) % PACKEDFILE_DIR_HASH_SIZE;
  205. CFileDirectoryData *pNewDir = new CFileDirectoryData;
  206. pNewDir->m_Name = pData;
  207. pNewExt->m_pDirectoryHashTable[nDirHash].AddToHead( pNewDir );
  208. int nDirChunk = SkipAllFilesInDir( pData );
  209. m_nHighestChunkFileIndex = MAX( m_nHighestChunkFileIndex, nDirChunk );
  210. }
  211. // step past \0
  212. pData++;
  213. }
  214. }
  215. bool CPackedStore::IsEmpty( void ) const
  216. {
  217. return ( m_DirectoryData.Count() <= 1 );
  218. }
  219. static void StripTrailingString( char *pszBuf, const char *pszStrip )
  220. {
  221. int lBuf = V_strlen( pszBuf );
  222. int lStrip = V_strlen( pszStrip );
  223. if ( lBuf < lStrip )
  224. return;
  225. char *pExpectedPos = pszBuf + lBuf - lStrip;
  226. if ( V_stricmp( pExpectedPos, pszStrip ) == 0 )
  227. *pExpectedPos = '\0';
  228. }
  229. CPackedStore::CPackedStore( char const *pFileBasename, char *pszFName, IBaseFileSystem *pFS, bool bOpenForWrite ):m_PackedStoreReadCache( pFS )
  230. {
  231. Init();
  232. m_pFileSystem = pFS;
  233. m_PackedStoreReadCache.m_pPackedStore = this;
  234. m_DirectoryData.AddToTail( 0 );
  235. if ( pFileBasename )
  236. {
  237. V_strcpy( m_pszFileBaseName, pFileBasename );
  238. StripTrailingString( m_pszFileBaseName, ".vpk" );
  239. StripTrailingString( m_pszFileBaseName, "_dir" );
  240. sprintf( pszFName, "%s_dir.vpk", m_pszFileBaseName );
  241. #ifdef _WIN32
  242. Q_strlower( pszFName );
  243. #endif
  244. CInputFile dirFile( pszFName );
  245. // Try to load the VPK as a standalone (probably an addon) even if the standard _dir name is not present
  246. if ( dirFile.IsOk() )
  247. {
  248. m_bUseDirFile = true;
  249. }
  250. else
  251. {
  252. m_bUseDirFile = false;
  253. sprintf( pszFName, "%s.vpk", m_pszFileBaseName );
  254. dirFile.Open( pszFName );
  255. }
  256. bool bNewFileFormat = false;
  257. if ( dirFile.IsOk() )
  258. {
  259. // first, check if it is the new versioned variant
  260. VPKDirHeader_t dirHeader;
  261. // try to read the header.
  262. if (
  263. ( dirFile.Read( &dirHeader, sizeof( dirHeader ) ) == sizeof( dirHeader ) ) &&
  264. ( dirHeader.m_nHeaderMarker == VPK_HEADER_MARKER ) )
  265. {
  266. if ( dirHeader.m_nVersion == VPK_PREVIOUS_VERSION )
  267. {
  268. // fill in the fields of the new header.
  269. dirHeader.m_nEmbeddedChunkSize = dirFile.Size() - dirHeader.m_nDirectorySize - sizeof( VPKDirHeaderOld_t );
  270. dirHeader.m_nChunkHashesSize = 0;
  271. dirHeader.m_nSelfHashesSize = 0;
  272. dirHeader.m_nSignatureSize = 0;
  273. // pretend we didnt read the extra header
  274. dirFile.Seek( sizeof( VPKDirHeaderOld_t ) );
  275. }
  276. else if ( dirHeader.m_nVersion != VPK_CURRENT_VERSION )
  277. {
  278. Error( "Unknown version %d for vpk %s", dirHeader.m_nVersion, pFileBasename );
  279. }
  280. bNewFileFormat = true;
  281. }
  282. else // its an old file
  283. {
  284. dirFile.Seek( 0 );
  285. // fill in a fake header, zero out garbage we read
  286. dirHeader.m_nDirectorySize = dirFile.Size();
  287. dirHeader.m_nEmbeddedChunkSize = 0;
  288. dirHeader.m_nChunkHashesSize = 0;
  289. dirHeader.m_nSelfHashesSize = 0;
  290. dirHeader.m_nSignatureSize = 0;
  291. }
  292. uint32 nSizeOfHeader = dirFile.Tell();
  293. int nSize = dirHeader.m_nDirectorySize;
  294. m_nDirectoryDataSize = dirHeader.m_nDirectorySize;
  295. m_DirectoryData.SetCount( nSize );
  296. dirFile.MustRead( DirectoryData(), nSize );
  297. // now, if we are opening for write, read the entire contents of the embedded data chunk in the dir into ram
  298. if ( bOpenForWrite && bNewFileFormat )
  299. {
  300. if ( dirHeader.m_nEmbeddedChunkSize )
  301. {
  302. CUtlVector<uint8> readBuffer;
  303. int nRemainingSize = dirHeader.m_nEmbeddedChunkSize;
  304. m_EmbeddedChunkData.EnsureCapacity( dirHeader.m_nEmbeddedChunkSize );
  305. // We'll allocate around half a meg of contiguous memory for the read. Any more and the SDK's VPK
  306. // utility has a higher chance of choking on low-end machines.
  307. readBuffer.SetCount( 524288 );
  308. while ( nRemainingSize > 0 )
  309. {
  310. int nReadSize = MIN( nRemainingSize , 524288 );
  311. dirFile.MustRead( readBuffer.Base(), nReadSize );
  312. for ( int i = 0; i < nReadSize; i++ )
  313. {
  314. m_EmbeddedChunkData.AddToTail( readBuffer[i] );
  315. }
  316. nRemainingSize -= nReadSize;
  317. }
  318. }
  319. }
  320. int cbVecHashes = dirHeader.m_nChunkHashesSize;
  321. int ctHashes = cbVecHashes/sizeof(m_vecChunkHashFraction[0]);
  322. m_vecChunkHashFraction.EnsureCount( ctHashes );
  323. dirFile.MustRead( m_vecChunkHashFraction.Base(), cbVecHashes );
  324. FOR_EACH_VEC( m_vecChunkHashFraction, i )
  325. {
  326. int idxFound = m_vecChunkHashFraction.Find( m_vecChunkHashFraction[i] );
  327. Assert ( idxFound == i ); idxFound;
  328. }
  329. // now read the self hashes
  330. V_memset( m_DirectoryMD5.bits, 0, sizeof(m_DirectoryMD5.bits) );
  331. V_memset( m_ChunkHashesMD5.bits, 0, sizeof(m_ChunkHashesMD5.bits) );
  332. V_memset( m_TotalFileMD5.bits, 0, sizeof(m_TotalFileMD5.bits) );
  333. if ( dirHeader.m_nSelfHashesSize == 3*sizeof(m_DirectoryMD5.bits) )
  334. {
  335. // first is an MD5 of directory data
  336. dirFile.MustRead( m_DirectoryMD5.bits, sizeof(m_DirectoryMD5.bits) );
  337. // next is an MD5 of
  338. dirFile.MustRead( m_ChunkHashesMD5.bits, sizeof(m_ChunkHashesMD5.bits) );
  339. // at this point the filesystem has calculated an MD5 of everything in the file up to this point.
  340. // we could ask it for a snapshot of that MD5 value and then be able to compare it to m_TotalFileMD5
  341. // but we would have to do it *before* we read it
  342. dirFile.MustRead( m_TotalFileMD5.bits, sizeof(m_TotalFileMD5.bits) );
  343. }
  344. // Is there a signature?
  345. m_nSizeOfSignedData = 0;
  346. if ( dirHeader.m_nSignatureSize != 0 )
  347. {
  348. // Everything immediately proceeding it should have been signed.
  349. m_nSizeOfSignedData = dirFile.Tell();
  350. uint32 nExpectedSignedSize = nSizeOfHeader + dirHeader.ComputeSizeofSignedDataAfterHeader();
  351. if ( m_nSizeOfSignedData != nExpectedSignedSize )
  352. {
  353. Error( "Size mismatch determining size of signed data block (%d vs %d)", m_nSizeOfSignedData, nExpectedSignedSize );
  354. }
  355. // Read the public key
  356. uint32 cubPublicKey = 0;
  357. dirFile.MustRead( &cubPublicKey, sizeof(cubPublicKey) );
  358. m_SignaturePublicKey.SetCount( cubPublicKey );
  359. dirFile.MustRead( m_SignaturePublicKey.Base(), cubPublicKey );
  360. // Read the private key
  361. uint32 cubSignature = 0;
  362. dirFile.MustRead( &cubSignature, sizeof(cubSignature) );
  363. m_Signature.SetCount( cubSignature );
  364. dirFile.MustRead( m_Signature.Base(), cubSignature );
  365. }
  366. }
  367. Q_MakeAbsolutePath( m_pszFullPathName, sizeof( m_pszFullPathName ), m_pszFileBaseName );
  368. V_strcat_safe( m_pszFullPathName, ".vpk" );
  369. //Q_strlower( m_pszFullPathName ); // NO! this screws up linux.
  370. Q_FixSlashes( m_pszFullPathName );
  371. }
  372. BuildHashTables();
  373. }
  374. void CPackedStore::GetDataFileName( char *pchFileNameOut, int cchFileNameOut, int nFileNumber ) const
  375. {
  376. if ( nFileNumber == VPKFILENUMBER_EMBEDDED_IN_DIR_FILE )
  377. {
  378. if ( m_bUseDirFile )
  379. {
  380. V_snprintf( pchFileNameOut, cchFileNameOut, "%s_dir.vpk", m_pszFileBaseName );
  381. }
  382. else
  383. {
  384. V_snprintf( pchFileNameOut, cchFileNameOut, "%s.vpk", m_pszFileBaseName );
  385. }
  386. }
  387. else
  388. {
  389. V_snprintf( pchFileNameOut, cchFileNameOut, "%s_%03d.vpk", m_pszFileBaseName, nFileNumber );
  390. }
  391. }
  392. CPackedStore::~CPackedStore( void )
  393. {
  394. for( int i = 0; i < ARRAYSIZE( m_pExtensionData ) ; i++ )
  395. {
  396. m_pExtensionData[i].Purge();
  397. }
  398. for (int i = 0; i < ARRAYSIZE( m_FileHandles ); i++ )
  399. {
  400. if ( m_FileHandles[i].m_nFileNumber != -1 )
  401. {
  402. #ifdef IS_WINDOWS_PC
  403. CloseHandle( m_FileHandles[i].m_hFileHandle );
  404. #else
  405. m_pFileSystem->Close( m_FileHandles[i].m_hFileHandle );
  406. #endif
  407. }
  408. }
  409. // Free the FindFirst cache data
  410. m_directoryList.PurgeAndDeleteElements();
  411. FOR_EACH_MAP( m_dirContents, i )
  412. {
  413. m_dirContents[i]->PurgeAndDeleteElements();
  414. delete m_dirContents[i];
  415. }
  416. }
  417. void SplitFileComponents( char const *pFileName, char *pDirOut, char *pBaseOut, char *pExtOut )
  418. {
  419. char pTmpDirOut[MAX_PATH];
  420. V_ExtractFilePath( pFileName, pTmpDirOut, MAX_PATH );
  421. // now, pTmpDirOut to pDirOut, except when we find more then one '\' in a row, only output one
  422. char *pOutDirPtr = pDirOut;
  423. for( char *pDirInPtr = pTmpDirOut; *pDirInPtr; pDirInPtr++ )
  424. {
  425. char c = *( pDirInPtr );
  426. *( pOutDirPtr++ ) = c;
  427. // if we copied a \, skip all subsequent slashes
  428. while( ( c == '\\' ) && ( pDirInPtr[1] == c ) )
  429. {
  430. pDirInPtr++;
  431. }
  432. }
  433. *( pOutDirPtr ) = 0; // null terminate
  434. if ( !pDirOut[0] )
  435. strcpy( pDirOut, " " ); // blank dir name
  436. V_strcpy( pBaseOut, V_UnqualifiedFileName( pFileName ) );
  437. char *pDot = strrchr( pBaseOut, '.' );
  438. if ( pDot )
  439. {
  440. *pDot = 0;
  441. V_strncpy( pExtOut, pDot+1, MAX_PATH );
  442. }
  443. else
  444. {
  445. pExtOut[0]=' ';
  446. pExtOut[1]=0;
  447. }
  448. V_FixSlashes( pDirOut, '/' );
  449. V_strlower( pDirOut );
  450. // the game sometimes asks for paths like dir1/../dir2/ we will replace this with dir2/. This
  451. // one line of perl code sucks in c++.
  452. for(;;)
  453. {
  454. char *pDotDot = V_strstr( pDirOut + 1, "/../" ); // start at second char. we don't want a beginning /
  455. if (! pDotDot )
  456. {
  457. break;
  458. }
  459. // search backwards from the /.. for the previous directory part
  460. char *pPrevSlash = pDotDot - 1;
  461. while( ( pPrevSlash > pDirOut ) && ( pPrevSlash[0] != '/' ) )
  462. {
  463. pPrevSlash--;
  464. }
  465. // if our path was dir0/dir1/../dir2, we are now pointing at "/dir1".
  466. // is strmove in all compilers? that would be better than this loop
  467. char *pStrIn = pDotDot + 3;
  468. for(;;)
  469. {
  470. *pPrevSlash = *pStrIn;
  471. if ( pStrIn[0] )
  472. {
  473. ++pPrevSlash;
  474. ++pStrIn;
  475. }
  476. else
  477. {
  478. break;
  479. }
  480. }
  481. }
  482. char *pLastDirChar = pDirOut + strlen( pDirOut ) - 1;
  483. if ( ( pLastDirChar[0] == '/' ) || ( pLastDirChar[0] == '\\' ) )
  484. *pLastDirChar = 0; // kill trailing slash
  485. V_strlower( pBaseOut );
  486. V_strlower( pExtOut );
  487. }
  488. CPackedStoreFileHandle CPackedStore::OpenFile( char const *pFileName )
  489. {
  490. char dirName[MAX_PATH];
  491. char baseName[MAX_PATH];
  492. char extName[MAX_PATH];
  493. // Fix up the filename first
  494. char tempFileName[MAX_PATH];
  495. V_strncpy( tempFileName, pFileName, sizeof( tempFileName ) );
  496. V_FixSlashes( tempFileName, CORRECT_PATH_SEPARATOR );
  497. // V_RemoveDotSlashes( tempFileName, CORRECT_PATH_SEPARATOR, true );
  498. V_FixDoubleSlashes( tempFileName );
  499. if ( !V_IsAbsolutePath( tempFileName ) )
  500. {
  501. V_strlower( tempFileName );
  502. }
  503. SplitFileComponents( tempFileName, dirName, baseName, extName );
  504. CPackedStoreFileHandle ret;
  505. CFileHeaderFixedData *pHeader = FindFileEntry( dirName, baseName, extName, NULL, &( ret.m_pDirFileNamePtr ) );
  506. if ( pHeader )
  507. {
  508. ret.m_nFileNumber = pHeader->m_PartDescriptors[0].m_nFileNumber;
  509. ret.m_nFileOffset = pHeader->m_PartDescriptors[0].m_nFileDataOffset;
  510. ret.m_nFileSize = pHeader->m_PartDescriptors[0].m_nFileDataSize + pHeader->m_nMetaDataSize;
  511. ret.m_nCurrentFileOffset = 0;
  512. ret.m_pMetaData = pHeader->MetaData();
  513. ret.m_nMetaDataSize = pHeader->m_nMetaDataSize;
  514. ret.m_pHeaderData = pHeader;
  515. ret.m_pOwner = this;
  516. }
  517. else
  518. {
  519. ret.m_nFileNumber = -1;
  520. ret.m_pOwner = NULL;
  521. }
  522. return ret;
  523. }
  524. CPackedStoreFileHandle CPackedStore::GetHandleForHashingFiles()
  525. {
  526. CPackedStoreFileHandle ret;
  527. ret.m_nFileNumber = 0;
  528. ret.m_nFileOffset = 0;
  529. ret.m_nFileSize = 0;
  530. ret.m_nMetaDataSize = 0;
  531. ret.m_nCurrentFileOffset = 0;
  532. ret.m_pDirFileNamePtr = NULL;
  533. ret.m_pHeaderData = NULL;
  534. ret.m_pMetaData = NULL;
  535. ret.m_pOwner = this;
  536. return ret;
  537. }
  538. void CPackedStore::Write( void )
  539. {
  540. // !KLUDGE!
  541. // Write the whole header into a buffer in memory.
  542. // We do this so we can easily sign it.
  543. CUtlBuffer bufDirFile;
  544. VPKDirHeader_t headerOut;
  545. headerOut.m_nDirectorySize = m_DirectoryData.Count();
  546. headerOut.m_nEmbeddedChunkSize = m_EmbeddedChunkData.Count();
  547. headerOut.m_nChunkHashesSize = m_vecChunkHashFraction.Count()*sizeof(m_vecChunkHashFraction[0]);
  548. headerOut.m_nSelfHashesSize = 3*sizeof(m_DirectoryMD5.bits);
  549. headerOut.m_nSignatureSize = 0;
  550. // Do we plan on signing this thing and writing a signature?
  551. m_Signature.Purge();
  552. uint32 nExpectedSignatureSize = 0;
  553. if ( m_SignaturePrivateKey.Count() > 0 && m_SignaturePublicKey.Count() > 0 )
  554. {
  555. #ifdef VPK_ENABLE_SIGNING
  556. nExpectedSignatureSize = k_cubRSASignature;
  557. headerOut.m_nSignatureSize = sizeof(uint32) + m_SignaturePublicKey.Count() + sizeof(uint32) + nExpectedSignatureSize;
  558. #else
  559. Error( "VPK signing not implemented" );
  560. #endif
  561. }
  562. bufDirFile.Put( &headerOut, sizeof( headerOut ) );
  563. bufDirFile.Put( DirectoryData(), m_DirectoryData.Count() );
  564. if ( m_EmbeddedChunkData.Count() )
  565. {
  566. int nRemainingSize = m_EmbeddedChunkData.Count();
  567. CUtlVector<uint8> writeBuffer;
  568. writeBuffer.SetCount( 524288 );
  569. int nChunkOffset = 0;
  570. while ( nRemainingSize > 0 )
  571. {
  572. // We'll write around half a meg of contiguous memory at once. Any more and the SDK's VPK
  573. // utility has a higher chance of choking on low-end machines.
  574. int nWriteSize = MIN( nRemainingSize, 524288 );
  575. for ( int i = 0; i < nWriteSize; i++ )
  576. {
  577. writeBuffer[i] = m_EmbeddedChunkData[nChunkOffset++];
  578. }
  579. bufDirFile.Put( writeBuffer.Base(), nWriteSize );
  580. nRemainingSize -= nWriteSize;
  581. }
  582. }
  583. // write the chunk hashes out
  584. bufDirFile.Put( m_vecChunkHashFraction.Base(), m_vecChunkHashFraction.Count()*sizeof(m_vecChunkHashFraction[0]) );
  585. // write out the MD5s of the 2 main pieces of data
  586. bufDirFile.Put( m_DirectoryMD5.bits, sizeof( m_DirectoryMD5.bits ) );
  587. bufDirFile.Put( m_ChunkHashesMD5.bits, sizeof( m_ChunkHashesMD5.bits ) );
  588. // compute the final MD5 ( of everything in the file up to this point )
  589. MD5_ProcessSingleBuffer( bufDirFile.Base(), bufDirFile.TellPut(), m_TotalFileMD5 );
  590. bufDirFile.Put( m_TotalFileMD5.bits, sizeof( m_TotalFileMD5.bits ) );
  591. // Should we sign all this stuff?
  592. m_nSizeOfSignedData = 0;
  593. #ifdef VPK_ENABLE_SIGNING
  594. if ( headerOut.m_nSignatureSize > 0 )
  595. {
  596. m_nSizeOfSignedData = bufDirFile.TellPut();
  597. uint32 nExpectedSignedSize = sizeof(headerOut) + headerOut.ComputeSizeofSignedDataAfterHeader();
  598. if ( m_nSizeOfSignedData != nExpectedSignedSize )
  599. {
  600. Error( "Size mismatch determining size of signed data block (%d vs %d)", m_nSizeOfSignedData, nExpectedSignedSize );
  601. }
  602. // Allocate more than enough space to hold the signature
  603. m_Signature.SetCount( nExpectedSignatureSize + 1024 );
  604. // Calcuate the signature
  605. uint32 cubSignature = m_Signature.Count();
  606. if ( !CCrypto::RSASignSHA256( (const uint8 *)bufDirFile.Base(), bufDirFile.TellPut(),
  607. (uint8 *)m_Signature.Base(), &cubSignature,
  608. (const uint8 *)m_SignaturePrivateKey.Base(), m_SignaturePrivateKey.Count() ) )
  609. {
  610. Error( "VPK signing failed. Private key may be corrupt or invalid" );
  611. }
  612. // Confirm that the size was what we expected
  613. if ( cubSignature != nExpectedSignatureSize )
  614. {
  615. Error( "VPK signing produced %d byte signature. Expected size was %d bytes", cubSignature, nExpectedSignatureSize );
  616. }
  617. // Shrink signature to fit
  618. m_Signature.SetCountNonDestructively( cubSignature );
  619. // Now re-check the signature, using the public key that we are about
  620. // to burn into the file, to make sure there's no mismatch.
  621. if ( !CCrypto::RSAVerifySignatureSHA256( (const uint8 *)bufDirFile.Base(), bufDirFile.TellPut(),
  622. (const uint8 *)m_Signature.Base(), cubSignature,
  623. (const uint8 *)m_SignaturePublicKey.Base(), m_SignaturePublicKey.Count() ) )
  624. {
  625. Error( "VPK signature verification failed immediately after signing. The public key might be invalid, or might not match the private key used to generate the signature." );
  626. }
  627. // Write public key which should be used
  628. uint32 cubPublicKey = m_SignaturePublicKey.Count();
  629. bufDirFile.Put( &cubPublicKey, sizeof(cubPublicKey) );
  630. bufDirFile.Put( m_SignaturePublicKey.Base(), cubPublicKey );
  631. // Write signature
  632. bufDirFile.Put( &cubSignature, sizeof(cubSignature) );
  633. bufDirFile.Put( m_Signature.Base(), cubSignature );
  634. }
  635. #endif
  636. char szOutFileName[MAX_PATH];
  637. // Delete any existing header file, either the standalone kind,
  638. // or the _dir kind.
  639. V_sprintf_safe( szOutFileName, "%s.vpk", m_pszFileBaseName );
  640. if ( g_pFullFileSystem->FileExists( szOutFileName ) )
  641. g_pFullFileSystem->RemoveFile( szOutFileName );
  642. V_sprintf_safe( szOutFileName, "%s_dir.vpk", m_pszFileBaseName );
  643. if ( g_pFullFileSystem->FileExists( szOutFileName ) )
  644. g_pFullFileSystem->RemoveFile( szOutFileName );
  645. // Force on multi-chunk mode if we have any files in a chunk
  646. if ( m_nHighestChunkFileIndex >= 0 )
  647. m_bUseDirFile = true;
  648. // Fetch actual name to write
  649. GetDataFileName( szOutFileName, sizeof(szOutFileName), VPKFILENUMBER_EMBEDDED_IN_DIR_FILE );
  650. // Now actually write the data to disk
  651. COutputFile dirFile( szOutFileName );
  652. dirFile.Write( bufDirFile.Base(), bufDirFile.TellPut() );
  653. dirFile.Close();
  654. }
  655. #ifdef VPK_ENABLE_SIGNING
  656. void CPackedStore::SetKeysForSigning( int nPrivateKeySize, const void *pPrivateKeyData, int nPublicKeySize, const void *pPublicKeyData )
  657. {
  658. m_SignaturePrivateKey.SetSize( nPrivateKeySize );
  659. V_memcpy( m_SignaturePrivateKey.Base(), pPrivateKeyData, nPrivateKeySize );
  660. m_SignaturePublicKey.SetSize( nPublicKeySize );
  661. V_memcpy( m_SignaturePublicKey.Base(), pPublicKeyData, nPublicKeySize );
  662. // Discard any existing signature
  663. m_Signature.Purge();
  664. }
  665. CPackedStore::ESignatureCheckResult CPackedStore::CheckSignature( int nSignatureSize, const void *pSignature ) const
  666. {
  667. if ( m_Signature.Count() == 0 )
  668. return eSignatureCheckResult_NotSigned;
  669. Assert( m_nSizeOfSignedData > 0 );
  670. // Confirm correct public key, if they specified one.
  671. if ( nSignatureSize > 0 && pSignature != NULL )
  672. {
  673. if ( m_SignaturePublicKey.Count() != nSignatureSize || V_memcmp( pSignature, m_SignaturePublicKey.Base(), nSignatureSize ) != 0 )
  674. {
  675. return eSignatureCheckResult_WrongKey;
  676. }
  677. }
  678. char szFilename[ MAX_PATH ];
  679. GetDataFileName( szFilename, sizeof( szFilename ), VPKFILENUMBER_EMBEDDED_IN_DIR_FILE );
  680. // Read the data
  681. CUtlBuffer bufSignedData;
  682. if ( !g_pFullFileSystem->ReadFile( szFilename, NULL, bufSignedData, m_nSizeOfSignedData ) )
  683. return eSignatureCheckResult_Failed;
  684. if ( bufSignedData.TellPut() < (int)m_nSizeOfSignedData )
  685. {
  686. Assert( false ); // ?
  687. return eSignatureCheckResult_Failed;
  688. }
  689. // Check the signature
  690. if ( !CCrypto::RSAVerifySignatureSHA256( (const uint8 *)bufSignedData.Base(), m_nSizeOfSignedData,
  691. (const uint8 *)m_Signature.Base(), m_Signature.Count(),
  692. (const uint8 *)m_SignaturePublicKey.Base(), m_SignaturePublicKey.Count() ) )
  693. {
  694. return eSignatureCheckResult_InvalidSignature;
  695. }
  696. return eSignatureCheckResult_ValidSignature;
  697. }
  698. #endif
  699. CPackedStoreReadCache::CPackedStoreReadCache( IBaseFileSystem *pFS ):m_treeCachedVPKRead( CachedVPKRead_t::Less )
  700. {
  701. m_pPackedStore = NULL;
  702. m_cItemsInCache = 0;
  703. m_pFileSystem = pFS;
  704. m_cubReadFromCache = 0;
  705. m_cReadFromCache = 0;
  706. m_cDiscardsFromCache = 0;
  707. m_cAddedToCache = 0;
  708. m_cCacheMiss = 0;
  709. m_cubCacheMiss = 0;
  710. m_cFileErrors = 0;
  711. m_cFileErrorsCorrected = 0;
  712. m_cFileResultsDifferent = 0;
  713. m_pFileTracker = NULL;
  714. }
  715. // check if the read request can be satisfied from the read cache we have in 1MB chunks
  716. bool CPackedStoreReadCache::BCanSatisfyFromReadCache( uint8 *pOutData, CPackedStoreFileHandle &handle, FileHandleTracker_t &fHandle, int nDesiredPos, int nNumBytes, int &nRead )
  717. {
  718. nRead = 0;
  719. int nFileFraction = nDesiredPos & k_nCacheBufferMask;
  720. int nOffset = nDesiredPos - nFileFraction;
  721. int cubReadChunk = nOffset + nNumBytes;
  722. if ( cubReadChunk > k_cubCacheBufferSize )
  723. cubReadChunk = ( k_nCacheBufferMask - nOffset ) & (k_cubCacheBufferSize-1);
  724. else
  725. cubReadChunk = nNumBytes;
  726. // the request might straddle multiple chunks - we make sure we have all of the data, if we are missing any, we fail
  727. while ( nNumBytes )
  728. {
  729. int nReadChunk = 0;
  730. if ( !BCanSatisfyFromReadCacheInternal( pOutData, handle, fHandle, nDesiredPos, cubReadChunk, nReadChunk ) )
  731. {
  732. return false;
  733. }
  734. nNumBytes -= cubReadChunk;
  735. pOutData += cubReadChunk;
  736. nDesiredPos += cubReadChunk;
  737. nRead += nReadChunk;
  738. nFileFraction += k_cubCacheBufferSize;
  739. cubReadChunk = nNumBytes;
  740. if ( cubReadChunk > k_cubCacheBufferSize )
  741. cubReadChunk = k_cubCacheBufferSize;
  742. }
  743. return true;
  744. }
  745. // read a single line into the cache
  746. bool CPackedStoreReadCache::ReadCacheLine( FileHandleTracker_t &fHandle, CachedVPKRead_t &cachedVPKRead )
  747. {
  748. cachedVPKRead.m_cubBuffer = 0;
  749. #ifdef IS_WINDOWS_PC
  750. if ( cachedVPKRead.m_nFileFraction != fHandle.m_nCurOfs )
  751. SetFilePointer ( fHandle.m_hFileHandle, cachedVPKRead.m_nFileFraction, NULL, FILE_BEGIN);
  752. ReadFile( fHandle.m_hFileHandle, cachedVPKRead.m_pubBuffer, k_cubCacheBufferSize, (LPDWORD) &cachedVPKRead.m_cubBuffer, NULL );
  753. SetFilePointer ( fHandle.m_hFileHandle, fHandle.m_nCurOfs, NULL, FILE_BEGIN);
  754. #else
  755. m_pFileSystem->Seek( fHandle.m_hFileHandle, cachedVPKRead.m_nFileFraction, FILESYSTEM_SEEK_HEAD );
  756. cachedVPKRead.m_cubBuffer = m_pFileSystem->Read( cachedVPKRead.m_pubBuffer, k_cubCacheBufferSize, fHandle.m_hFileHandle );
  757. m_pFileSystem->Seek( fHandle.m_hFileHandle, fHandle.m_nCurOfs, FILESYSTEM_SEEK_HEAD );
  758. #endif
  759. Assert( cachedVPKRead.m_hMD5RequestHandle == 0 );
  760. if ( m_pFileTracker ) // file tracker doesn't exist in the VPK command line tool
  761. {
  762. cachedVPKRead.m_hMD5RequestHandle = m_pFileTracker->SubmitThreadedMD5Request( cachedVPKRead.m_pubBuffer, cachedVPKRead.m_cubBuffer, m_pPackedStore->m_PackFileID, cachedVPKRead.m_nPackFileNumber, cachedVPKRead.m_nFileFraction );
  763. }
  764. return cachedVPKRead.m_cubBuffer > 0;
  765. }
  766. // check if the MD5 matches
  767. bool CPackedStoreReadCache::CheckMd5Result( CachedVPKRead_t &cachedVPKRead )
  768. {
  769. ChunkHashFraction_t chunkHashFraction;
  770. if ( !m_pPackedStore->FindFileHashFraction( cachedVPKRead.m_nPackFileNumber, cachedVPKRead.m_nFileFraction, chunkHashFraction ) )
  771. return true;
  772. if ( Q_memcmp( &cachedVPKRead.m_md5Value, &chunkHashFraction.m_md5contents, sizeof( MD5Value_t ) ) != 0 )
  773. {
  774. char szFilename[ 512 ];
  775. m_pPackedStore->GetDataFileName( szFilename, sizeof(szFilename), cachedVPKRead.m_nPackFileNumber );
  776. char szCalculated[ MD5_DIGEST_LENGTH*2 + 4 ];
  777. char szExpected[ MD5_DIGEST_LENGTH*2 + 4 ];
  778. V_binarytohex( cachedVPKRead.m_md5Value.bits, MD5_DIGEST_LENGTH, szCalculated, sizeof(szCalculated) );
  779. V_binarytohex( chunkHashFraction.m_md5contents.bits, MD5_DIGEST_LENGTH, szExpected, sizeof(szExpected) );
  780. Warning(
  781. "Corruption detected in %s\n"
  782. "\n"
  783. "Try verifying the integrity of your game cache.\n"
  784. "https://support.steampowered.com/kb_article.php?ref=2037-QEUH-3335"
  785. "\n"
  786. "Offset %d, expected %s, got %s\n",
  787. szFilename,
  788. cachedVPKRead.m_nFileFraction, szExpected, szCalculated
  789. );
  790. // we got an error reading this chunk, record the error
  791. m_cFileErrors++;
  792. cachedVPKRead.m_cFailedHashes++;
  793. // give a copy to the fail whale
  794. //m_queueCachedVPKReadsRetry.PushItem( cachedVPKRead );
  795. return false;
  796. }
  797. if ( cachedVPKRead.m_cFailedHashes > 0 )
  798. {
  799. m_cFileErrorsCorrected++;
  800. }
  801. return true;
  802. }
  803. int CPackedStoreReadCache::FindBufferToUse()
  804. {
  805. int idxLRU = 0;
  806. int idxToRemove = m_treeCachedVPKRead.InvalidIndex();
  807. uint32 uTimeLowest = (uint32)~0; // MAXINT
  808. // find the oldest item, reuse its buffer
  809. for ( int i = 0; i < m_cItemsInCache; i++ )
  810. {
  811. if ( m_rgLastUsedTime[i] < uTimeLowest )
  812. {
  813. uTimeLowest = m_rgLastUsedTime[i];
  814. idxToRemove = m_rgCurrentCacheIndex[i];
  815. idxLRU = i;
  816. }
  817. int idxCurrent = m_rgCurrentCacheIndex[i];
  818. // while we are here check if the MD5 is done
  819. if ( m_treeCachedVPKRead[idxCurrent].m_hMD5RequestHandle )
  820. {
  821. CachedVPKRead_t &cachedVPKRead = m_treeCachedVPKRead[idxCurrent];
  822. if ( m_pFileTracker->IsMD5RequestComplete( cachedVPKRead.m_hMD5RequestHandle, &cachedVPKRead.m_md5Value ) )
  823. {
  824. // if it is done, check the results
  825. cachedVPKRead.m_hMD5RequestHandle = 0;
  826. // if we got bad data - stop looking, just use this one
  827. if ( !CheckMd5Result( cachedVPKRead ) )
  828. return i;
  829. }
  830. }
  831. }
  832. // if we submitted its MD5 for processing, then wait until that is done
  833. if ( m_treeCachedVPKRead[idxToRemove].m_hMD5RequestHandle )
  834. {
  835. CachedVPKRead_t &cachedVPKRead = m_treeCachedVPKRead[idxToRemove];
  836. m_pFileTracker->BlockUntilMD5RequestComplete( cachedVPKRead.m_hMD5RequestHandle, &cachedVPKRead.m_md5Value );
  837. m_treeCachedVPKRead[idxToRemove].m_hMD5RequestHandle = 0;
  838. // make sure it matches what it is supposed to match
  839. CheckMd5Result( cachedVPKRead );
  840. }
  841. return idxLRU;
  842. }
  843. // manage the cache
  844. bool CPackedStoreReadCache::BCanSatisfyFromReadCacheInternal( uint8 *pOutData, CPackedStoreFileHandle &handle, FileHandleTracker_t &fHandle, int nDesiredPos, int nNumBytes, int &nRead )
  845. {
  846. m_rwlock.LockForRead();
  847. bool bLockedForWrite = false;
  848. CachedVPKRead_t key;
  849. key.m_nPackFileNumber = handle.m_nFileNumber;
  850. key.m_nFileFraction = nDesiredPos & k_nCacheBufferMask;
  851. int idxTrackedVPKFile = m_treeCachedVPKRead.Find( key );
  852. if ( idxTrackedVPKFile == m_treeCachedVPKRead.InvalidIndex() || m_treeCachedVPKRead[idxTrackedVPKFile].m_pubBuffer == NULL )
  853. {
  854. m_rwlock.UnlockRead();
  855. m_rwlock.LockForWrite();
  856. bLockedForWrite = true;
  857. // if we didnt find it, we had to grab the write lock, it may have been added while we waited
  858. idxTrackedVPKFile = m_treeCachedVPKRead.Find( key );
  859. }
  860. if ( idxTrackedVPKFile == m_treeCachedVPKRead.InvalidIndex() )
  861. {
  862. idxTrackedVPKFile = m_treeCachedVPKRead.Insert( key );
  863. }
  864. CachedVPKRead_t &cachedVPKRead = m_treeCachedVPKRead[idxTrackedVPKFile];
  865. // Cache hit?
  866. if ( cachedVPKRead.m_pubBuffer == NULL )
  867. {
  868. // We need to have it locked for write, because we're about to muck with these structures.
  869. if ( !bLockedForWrite )
  870. {
  871. Assert( bLockedForWrite );
  872. return false;
  873. }
  874. Assert( cachedVPKRead.m_idxLRU < 0 );
  875. // Can we add another line to the cache, or should we reuse an existing one?
  876. int idxLRU = -1;
  877. if ( m_cItemsInCache >= k_nCacheBuffersToKeep )
  878. {
  879. // Need to kick out the LRU.
  880. idxLRU = FindBufferToUse();
  881. int idxToRemove = m_rgCurrentCacheIndex[idxLRU];
  882. Assert( m_treeCachedVPKRead[idxToRemove].m_idxLRU == idxLRU );
  883. Assert( m_treeCachedVPKRead[idxToRemove].m_pubBuffer != NULL );
  884. // Transfer ownership of the buffer
  885. cachedVPKRead.m_pubBuffer = m_treeCachedVPKRead[idxToRemove].m_pubBuffer;
  886. m_treeCachedVPKRead[idxToRemove].m_pubBuffer = NULL;
  887. m_treeCachedVPKRead[idxToRemove].m_cubBuffer = 0;
  888. m_treeCachedVPKRead[idxToRemove].m_idxLRU = -1;
  889. m_cDiscardsFromCache++;
  890. }
  891. else
  892. {
  893. // We can add a new one
  894. idxLRU = m_cItemsInCache;
  895. m_cItemsInCache++;
  896. Assert( cachedVPKRead.m_pubBuffer == NULL );
  897. }
  898. m_rgCurrentCacheIndex[idxLRU] = idxTrackedVPKFile;
  899. cachedVPKRead.m_idxLRU = idxLRU;
  900. if ( cachedVPKRead.m_pubBuffer == NULL )
  901. {
  902. cachedVPKRead.m_pubBuffer = (uint8 *)malloc( k_cubCacheBufferSize );
  903. if ( cachedVPKRead.m_pubBuffer == NULL )
  904. Error( "Out of memory" );
  905. }
  906. ReadCacheLine( fHandle, cachedVPKRead );
  907. m_cAddedToCache++;
  908. }
  909. else
  910. {
  911. Assert( cachedVPKRead.m_idxLRU >= 0 );
  912. Assert( m_rgCurrentCacheIndex[cachedVPKRead.m_idxLRU] == idxTrackedVPKFile );
  913. }
  914. // Assume no bytes will be satisfied from cache
  915. bool bSuccess = false;
  916. nRead = 0;
  917. // Can we read at least one byte?
  918. Assert( cachedVPKRead.m_nFileFraction <= nDesiredPos );
  919. int nBufferEnd = cachedVPKRead.m_cubBuffer + cachedVPKRead.m_nFileFraction;
  920. if ( cachedVPKRead.m_pubBuffer != NULL && nBufferEnd > nDesiredPos )
  921. {
  922. nRead = Min( nBufferEnd - nDesiredPos, nNumBytes );
  923. int nOffset = nDesiredPos - cachedVPKRead.m_nFileFraction;
  924. Assert( nOffset >= 0 );
  925. memcpy( pOutData, (uint8 *)&cachedVPKRead.m_pubBuffer[nOffset], nRead );
  926. m_cubReadFromCache += nRead;
  927. m_cReadFromCache ++;
  928. bSuccess = true;
  929. m_rgLastUsedTime[m_treeCachedVPKRead[idxTrackedVPKFile].m_idxLRU] = Plat_MSTime();
  930. }
  931. if ( bLockedForWrite )
  932. m_rwlock.UnlockWrite();
  933. else
  934. m_rwlock.UnlockRead();
  935. return bSuccess;
  936. }
  937. void CPackedStoreReadCache::RetryBadCacheLine( CachedVPKRead_t &cachedVPKRead )
  938. {
  939. ChunkHashFraction_t chunkHashFraction;
  940. m_pPackedStore->FindFileHashFraction( cachedVPKRead.m_nPackFileNumber, cachedVPKRead.m_nFileFraction, chunkHashFraction );
  941. cachedVPKRead.m_pubBuffer = (uint8 *)malloc( k_cubCacheBufferSize );
  942. FileHandleTracker_t &fHandle = m_pPackedStore->GetFileHandle( cachedVPKRead.m_nPackFileNumber );
  943. fHandle.m_Mutex.Lock();
  944. ReadCacheLine( fHandle, cachedVPKRead );
  945. fHandle.m_Mutex.Unlock();
  946. m_pFileTracker->BlockUntilMD5RequestComplete( cachedVPKRead.m_hMD5RequestHandle, &cachedVPKRead.m_md5Value );
  947. cachedVPKRead.m_hMD5RequestHandle = 0;
  948. CheckMd5Result( cachedVPKRead );
  949. cachedVPKRead.m_pubBuffer = NULL;
  950. }
  951. // try reloading anything that failed its md5 check
  952. // this is currently only for gathering information, doesnt do anything to repair the cache
  953. void CPackedStoreReadCache::RetryAllBadCacheLines()
  954. {
  955. // while( m_queueCachedVPKReadsRetry.Count() )
  956. // {
  957. // CachedVPKRead_t cachedVPKRead;
  958. // m_rwlock.LockForWrite();
  959. // if ( m_queueCachedVPKReadsRetry.PopItem( &cachedVPKRead ) )
  960. // {
  961. // // retry anything that didnt match one time
  962. // RetryBadCacheLine( cachedVPKRead );
  963. // m_listCachedVPKReadsFailed.AddToTail( cachedVPKRead );
  964. // // m_listCachedVPKReadsFailed contains all the data about failed reads - for error or OGS reporting
  965. // }
  966. // m_rwlock.UnlockWrite();
  967. // }
  968. }
  969. void CPackedStore::GetPackFileLoadErrorSummary( CUtlString &sErrors )
  970. {
  971. FOR_EACH_LL( m_PackedStoreReadCache.m_listCachedVPKReadsFailed, i )
  972. {
  973. char szDataFileName[MAX_PATH];
  974. CPackedStoreFileHandle fhandle = GetHandleForHashingFiles();
  975. fhandle.m_nFileNumber = m_PackedStoreReadCache.m_listCachedVPKReadsFailed[i].m_nPackFileNumber;
  976. fhandle.GetPackFileName( szDataFileName, sizeof(szDataFileName) );
  977. const char *pszFileName = V_GetFileName( szDataFileName );
  978. CUtlString sTemp;
  979. sTemp.Format( "Pack File %s at offset %x length %x errorcount = %d \n",
  980. pszFileName,
  981. m_PackedStoreReadCache.m_listCachedVPKReadsFailed[i].m_nFileFraction,
  982. m_PackedStoreReadCache.m_listCachedVPKReadsFailed[i].m_cubBuffer,
  983. m_PackedStoreReadCache.m_listCachedVPKReadsFailed[i].m_cFailedHashes );
  984. sErrors += sTemp ;
  985. char hex[sizeof(MD5Value_t)*2 + 1 ];
  986. Q_binarytohex( m_PackedStoreReadCache.m_listCachedVPKReadsFailed[i].m_md5Value.bits,
  987. sizeof(MD5Value_t), hex, sizeof( hex ) );
  988. ChunkHashFraction_t chunkHashFraction;
  989. FindFileHashFraction( m_PackedStoreReadCache.m_listCachedVPKReadsFailed[i].m_nPackFileNumber, m_PackedStoreReadCache.m_listCachedVPKReadsFailed[i].m_nFileFraction, chunkHashFraction );
  990. char hex2[sizeof(MD5Value_t)*2 + 1 ];
  991. Q_binarytohex( chunkHashFraction.m_md5contents.bits,
  992. sizeof(MD5Value_t), hex2, sizeof( hex2 ) );
  993. sTemp.Format( "Last Md5 Value %s Should be %s \n", hex, hex2 );
  994. sErrors += sTemp ;
  995. }
  996. }
  997. int CPackedStore::ReadData( CPackedStoreFileHandle &handle, void *pOutData, int nNumBytes )
  998. {
  999. int nRet = 0;
  1000. // clamp read size to file size
  1001. nNumBytes = MIN( nNumBytes, handle.m_nFileSize - handle.m_nCurrentFileOffset );
  1002. if ( nNumBytes > 0 )
  1003. {
  1004. // first satisfy from the metadata, if we can
  1005. int nNumMetaDataBytes = MIN( nNumBytes, handle.m_nMetaDataSize - handle.m_nCurrentFileOffset );
  1006. if ( nNumMetaDataBytes > 0 )
  1007. {
  1008. memcpy( pOutData, reinterpret_cast<uint8 const *>( handle.m_pMetaData )
  1009. + handle.m_nCurrentFileOffset, nNumMetaDataBytes );
  1010. nRet += nNumMetaDataBytes;
  1011. pOutData = reinterpret_cast<uint8 *>( pOutData ) + nNumMetaDataBytes;
  1012. handle.m_nCurrentFileOffset += nNumMetaDataBytes;
  1013. nNumBytes -= nNumMetaDataBytes;
  1014. }
  1015. // satisfy remaining bytes from file
  1016. if ( nNumBytes > 0 )
  1017. {
  1018. FileHandleTracker_t &fHandle = GetFileHandle( handle.m_nFileNumber );
  1019. int nDesiredPos = handle.m_nFileOffset + handle.m_nCurrentFileOffset - handle.m_nMetaDataSize;
  1020. int nRead;
  1021. fHandle.m_Mutex.Lock();
  1022. if ( handle.m_nFileNumber == VPKFILENUMBER_EMBEDDED_IN_DIR_FILE )
  1023. {
  1024. // for file data in the directory header, all offsets are relative to the size of the dir header.
  1025. nDesiredPos += m_nDirectoryDataSize + sizeof( VPKDirHeader_t );
  1026. }
  1027. if ( m_PackedStoreReadCache.BCanSatisfyFromReadCache( (uint8 *)pOutData, handle, fHandle, nDesiredPos, nNumBytes, nRead ) )
  1028. {
  1029. handle.m_nCurrentFileOffset += nRead;
  1030. }
  1031. else
  1032. {
  1033. #ifdef IS_WINDOWS_PC
  1034. if ( nDesiredPos != fHandle.m_nCurOfs )
  1035. SetFilePointer ( fHandle.m_hFileHandle, nDesiredPos, NULL, FILE_BEGIN);
  1036. ReadFile( fHandle.m_hFileHandle, pOutData, nNumBytes, (LPDWORD) &nRead, NULL );
  1037. #else
  1038. m_pFileSystem->Seek( fHandle.m_hFileHandle, nDesiredPos, FILESYSTEM_SEEK_HEAD );
  1039. nRead = m_pFileSystem->Read( pOutData, nNumBytes, fHandle.m_hFileHandle );
  1040. #endif
  1041. handle.m_nCurrentFileOffset += nRead;
  1042. fHandle.m_nCurOfs = nRead + nDesiredPos;
  1043. }
  1044. Assert( nRead == nNumBytes );
  1045. nRet += nRead;
  1046. fHandle.m_Mutex.Unlock();
  1047. }
  1048. }
  1049. m_PackedStoreReadCache.RetryAllBadCacheLines();
  1050. return nRet;
  1051. }
  1052. bool CPackedStore::HashEntirePackFile( CPackedStoreFileHandle &handle, int64 &nFileSize, int nFileFraction, int nFractionSize, FileHash_t &fileHash )
  1053. {
  1054. #define CRC_CHUNK_SIZE (32*1024)
  1055. unsigned char tempBuf[CRC_CHUNK_SIZE];
  1056. #ifdef COMPUTE_HASH_TIMES
  1057. CFastTimer timer;
  1058. timer.Start();
  1059. #endif
  1060. FileHandleTracker_t &fHandle = GetFileHandle( handle.m_nFileNumber );
  1061. fHandle.m_Mutex.Lock();
  1062. #ifdef IS_WINDOWS_PC
  1063. unsigned int fileSizeHigh;
  1064. unsigned int fileLength = GetFileSize( fHandle.m_hFileHandle, (LPDWORD) &fileSizeHigh );
  1065. #else
  1066. unsigned int fileLength = m_pFileSystem->Size( fHandle.m_hFileHandle );
  1067. #endif
  1068. nFileSize = fileLength;
  1069. MD5Context_t ctx;
  1070. memset(&ctx, 0, sizeof(MD5Context_t));
  1071. MD5Init(&ctx);
  1072. int nDesiredPos = nFileFraction;
  1073. #ifdef IS_WINDOWS_PC
  1074. if ( nDesiredPos != fHandle.m_nCurOfs )
  1075. SetFilePointer ( fHandle.m_hFileHandle, nDesiredPos, NULL, FILE_BEGIN);
  1076. #else
  1077. m_pFileSystem->Seek( fHandle.m_hFileHandle, nDesiredPos, FILESYSTEM_SEEK_HEAD );
  1078. #endif
  1079. int nFractionLength = ( fileLength - nFileFraction );
  1080. if ( nFractionLength > nFractionSize )
  1081. nFractionLength = nFractionSize;
  1082. int nChunks = nFractionLength / CRC_CHUNK_SIZE + 1;
  1083. unsigned int curStartByte = 0;
  1084. for ( int iChunk=0; iChunk < nChunks; iChunk++ )
  1085. {
  1086. int curEndByte = MIN( curStartByte + CRC_CHUNK_SIZE, (uint)nFractionLength );
  1087. int chunkLen = curEndByte - curStartByte;
  1088. if ( chunkLen == 0 )
  1089. break;
  1090. int nRead;
  1091. #ifdef IS_WINDOWS_PC
  1092. ReadFile( fHandle.m_hFileHandle, tempBuf, chunkLen, (LPDWORD) &nRead, NULL );
  1093. #else
  1094. nRead = m_pFileSystem->Read( tempBuf, chunkLen, fHandle.m_hFileHandle );
  1095. #endif
  1096. MD5Update(&ctx, tempBuf, nRead);
  1097. curStartByte += CRC_CHUNK_SIZE;
  1098. }
  1099. MD5Final( fileHash.m_md5contents.bits, &ctx);
  1100. fileHash.m_crcIOSequence = nFractionLength;
  1101. fileHash.m_cbFileLen = nFractionLength;
  1102. fileHash.m_eFileHashType = FileHash_t::k_EFileHashTypeEntireFile;
  1103. fileHash.m_nPackFileNumber = handle.m_nFileNumber;
  1104. fileHash.m_PackFileID = handle.m_pOwner->m_PackFileID;
  1105. // seek back to where it was
  1106. #ifdef IS_WINDOWS_PC
  1107. SetFilePointer ( fHandle.m_hFileHandle, fHandle.m_nCurOfs, NULL, FILE_BEGIN);
  1108. #else
  1109. m_pFileSystem->Seek( fHandle.m_hFileHandle, fHandle.m_nCurOfs, FILESYSTEM_SEEK_HEAD );
  1110. #endif
  1111. fHandle.m_Mutex.Unlock();
  1112. #ifdef COMPUTE_HASH_TIMES
  1113. timer.End();
  1114. int nMicroSec = timer.GetDuration().GetMicroseconds();
  1115. char rgch[256];
  1116. Q_snprintf( rgch, 256, "MD5 Pack File %d %d \n", handle.m_nFileNumber, nMicroSec );
  1117. Plat_DebugString( rgch );
  1118. #endif
  1119. return true;
  1120. }
  1121. void CPackedStore::DiscardChunkHashes( int iChunkFileIndex )
  1122. {
  1123. // Wow, this could be a LOT faster because the list is
  1124. // sorted. Probably not worth optimizing
  1125. FOR_EACH_VEC_BACK( m_vecChunkHashFraction, i )
  1126. {
  1127. if ( m_vecChunkHashFraction[i].m_nPackFileNumber == iChunkFileIndex )
  1128. m_vecChunkHashFraction.Remove( i );
  1129. }
  1130. }
  1131. void CPackedStore::HashChunkFile( int iChunkFileIndex )
  1132. {
  1133. AUTO_LOCK( m_Mutex );
  1134. static const int k_nFileFractionSize = 0x00100000; // 1 MB
  1135. // Purge any hashes we already have for this chunk.
  1136. DiscardChunkHashes( iChunkFileIndex );
  1137. CPackedStoreFileHandle VPKHandle = GetHandleForHashingFiles();
  1138. VPKHandle.m_nFileNumber = iChunkFileIndex;
  1139. int nFileFraction = 0;
  1140. while ( 1 )
  1141. {
  1142. FileHash_t filehash;
  1143. // VPKHandle.m_nFileNumber;
  1144. // nFileFraction;
  1145. int64 fileSize = 0;
  1146. // if we have never hashed this before - do it now
  1147. HashEntirePackFile( VPKHandle, fileSize, nFileFraction, k_nFileFractionSize, filehash );
  1148. ChunkHashFraction_t fileHashFraction;
  1149. fileHashFraction.m_cbChunkLen = filehash.m_cbFileLen;
  1150. fileHashFraction.m_nPackFileNumber = VPKHandle.m_nFileNumber;
  1151. fileHashFraction.m_nFileFraction = nFileFraction;
  1152. Q_memcpy( fileHashFraction.m_md5contents.bits, filehash.m_md5contents.bits, sizeof(fileHashFraction.m_md5contents) );
  1153. m_vecChunkHashFraction.Insert( fileHashFraction );
  1154. // move to next section
  1155. nFileFraction += k_nFileFractionSize;
  1156. // if we are at EOF we are done
  1157. if ( nFileFraction > fileSize )
  1158. break;
  1159. }
  1160. }
  1161. void CPackedStore::HashAllChunkFiles()
  1162. {
  1163. // Rebuild the directory hash tables. The main reason to do this is
  1164. // so that the highest chunk number is correct, in case chunks have
  1165. // been removed.
  1166. BuildHashTables();
  1167. // make brand new hashes
  1168. m_vecChunkHashFraction.Purge();
  1169. for ( int iChunkFileIndex = 0 ; iChunkFileIndex <= GetHighestChunkFileIndex() ; ++iChunkFileIndex )
  1170. HashChunkFile( iChunkFileIndex );
  1171. }
  1172. void CPackedStore::ComputeDirectoryHash( MD5Value_t &md5Directory )
  1173. {
  1174. MD5Context_t ctx;
  1175. memset(&ctx, 0, sizeof(MD5Context_t));
  1176. MD5Init(&ctx);
  1177. MD5Update(&ctx, m_DirectoryData.Base(), m_DirectoryData.Count() );
  1178. MD5Final( md5Directory.bits, &ctx);
  1179. }
  1180. void CPackedStore::ComputeChunkHash( MD5Value_t &md5ChunkHashes )
  1181. {
  1182. MD5Context_t ctx;
  1183. memset(&ctx, 0, sizeof(MD5Context_t));
  1184. MD5Init(&ctx);
  1185. MD5Update(&ctx, (uint8 *)m_vecChunkHashFraction.Base(), m_vecChunkHashFraction.Count()*sizeof(m_vecChunkHashFraction[0]) );
  1186. MD5Final( md5ChunkHashes.bits, &ctx);
  1187. }
  1188. bool CPackedStore::BTestDirectoryHash()
  1189. {
  1190. if ( !BFileContainedHashes() )
  1191. return true;
  1192. MD5Value_t md5Directory;
  1193. ComputeDirectoryHash( md5Directory );
  1194. return Q_memcmp( m_DirectoryMD5.bits, md5Directory.bits, sizeof( md5Directory.bits ) ) == 0;
  1195. }
  1196. bool CPackedStore::BTestMasterChunkHash()
  1197. {
  1198. if ( !BFileContainedHashes() )
  1199. return true;
  1200. MD5Value_t md5ChunkHashes;
  1201. ComputeChunkHash( md5ChunkHashes );
  1202. return Q_memcmp( m_ChunkHashesMD5.bits, md5ChunkHashes.bits, sizeof( md5ChunkHashes.bits ) ) == 0;
  1203. }
  1204. void CPackedStore::HashEverything()
  1205. {
  1206. HashAllChunkFiles();
  1207. HashMetadata();
  1208. }
  1209. void CPackedStore::HashMetadata()
  1210. {
  1211. ComputeDirectoryHash( m_DirectoryMD5 );
  1212. ComputeChunkHash( m_ChunkHashesMD5 );
  1213. }
  1214. bool CPackedStore::FindFileHashFraction( int nPackFileNumber, int nFileFraction, ChunkHashFraction_t &fileHashFraction )
  1215. {
  1216. ChunkHashFraction_t fileHashFractionFind;
  1217. fileHashFractionFind.m_nFileFraction = nFileFraction;
  1218. fileHashFractionFind.m_nPackFileNumber = nPackFileNumber;
  1219. int idx = m_vecChunkHashFraction.Find( fileHashFractionFind );
  1220. if ( idx == m_vecChunkHashFraction.InvalidIndex() )
  1221. {
  1222. Assert( false );
  1223. return false;
  1224. }
  1225. fileHashFraction = m_vecChunkHashFraction[idx];
  1226. return true;
  1227. }
  1228. void CPackedStore::GetPackFileName( CPackedStoreFileHandle &handle, char *pchFileNameOut, int cchFileNameOut ) const
  1229. {
  1230. GetDataFileName( pchFileNameOut, cchFileNameOut, handle.m_nFileNumber );
  1231. }
  1232. FileHandleTracker_t & CPackedStore::GetFileHandle( int nFileNumber )
  1233. {
  1234. AUTO_LOCK( m_Mutex );
  1235. int nFileHandleIdx = nFileNumber % ARRAYSIZE( m_FileHandles );
  1236. if ( m_FileHandles[nFileHandleIdx].m_nFileNumber == nFileNumber )
  1237. {
  1238. return m_FileHandles[nFileHandleIdx];
  1239. }
  1240. else if ( m_FileHandles[nFileHandleIdx].m_nFileNumber == -1 )
  1241. {
  1242. // no luck finding the handle - need a new one
  1243. char pszDataFileName[MAX_PATH];
  1244. GetDataFileName( pszDataFileName, sizeof(pszDataFileName), nFileNumber );
  1245. m_FileHandles[nFileHandleIdx].m_nCurOfs = 0;
  1246. #ifdef IS_WINDOWS_PC
  1247. m_FileHandles[nFileHandleIdx].m_hFileHandle =
  1248. CreateFile( pszDataFileName, // file to open
  1249. GENERIC_READ, // open for reading
  1250. FILE_SHARE_READ, // share for reading
  1251. NULL, // default security
  1252. OPEN_EXISTING, // existing file only
  1253. FILE_ATTRIBUTE_NORMAL, // normal file
  1254. NULL); // no attr. template
  1255. if ( m_FileHandles[nFileHandleIdx].m_hFileHandle != INVALID_HANDLE_VALUE )
  1256. {
  1257. m_FileHandles[nFileHandleIdx].m_nFileNumber = nFileNumber;
  1258. }
  1259. #else
  1260. m_FileHandles[nFileHandleIdx].m_hFileHandle = m_pFileSystem->Open( pszDataFileName, "rb" );
  1261. if ( m_FileHandles[nFileHandleIdx].m_hFileHandle != FILESYSTEM_INVALID_HANDLE )
  1262. {
  1263. m_FileHandles[nFileHandleIdx].m_nFileNumber = nFileNumber;
  1264. }
  1265. #endif
  1266. return m_FileHandles[nFileHandleIdx];
  1267. }
  1268. Error( "Exceeded limit of number of vpk files supported (%d)!\n", MAX_ARCHIVE_FILES_TO_KEEP_OPEN_AT_ONCE );
  1269. static FileHandleTracker_t invalid;
  1270. #ifdef IS_WINDOWS_PC
  1271. invalid.m_hFileHandle = INVALID_HANDLE_VALUE;
  1272. #else
  1273. invalid.m_hFileHandle = FILESYSTEM_INVALID_HANDLE;
  1274. #endif
  1275. return invalid;
  1276. }
  1277. bool CPackedStore::RemoveFileFromDirectory( const char *pszName )
  1278. {
  1279. // Remove it without building hash tables
  1280. if ( !InternalRemoveFileFromDirectory( pszName ) )
  1281. return false;
  1282. // We removed it, we need to rebuild hash tables
  1283. BuildHashTables();
  1284. return true;
  1285. }
  1286. bool CPackedStore::InternalRemoveFileFromDirectory( const char *pszName )
  1287. {
  1288. CPackedStoreFileHandle pData = OpenFile( pszName );
  1289. if ( !pData )
  1290. return false;
  1291. CFileHeaderFixedData *pHeader = pData.m_pHeaderData;
  1292. // delete the old header so we can insert a new one with updated contents
  1293. int nBytesToRemove = ( int )( V_strlen( ( char * ) pData.m_pDirFileNamePtr ) + 1 + pHeader->HeaderSizeIncludingMetaData() );
  1294. m_DirectoryData.RemoveMultiple( pData.m_pDirFileNamePtr - m_DirectoryData.Base(), nBytesToRemove );
  1295. return true;
  1296. }
  1297. void CPackedStore::AddFileToDirectory( const VPKContentFileInfo_t &info )
  1298. {
  1299. // this method is fairly complicated because it has to do inserts into the packed directory
  1300. // data Our strategy is to build out the whole ext _ dir _ file record. if none of this is
  1301. // already present, we will just insert it in the head of the file. If the extension is
  1302. // present, we'll insert the dir+file part. If the extension + dir is present, we just insert
  1303. // the file part at the right place. If everything is present, we just need to return the
  1304. // current record
  1305. // First, remove it if it's already there,
  1306. // without rebuilding the hash tables
  1307. InternalRemoveFileFromDirectory( info.m_sName );
  1308. // let's build out a header
  1309. char pszExt[MAX_PATH];
  1310. char pszBase[MAX_PATH];
  1311. char pszDir[MAX_PATH];
  1312. SplitFileComponents( info.m_sName, pszDir, pszBase, pszExt );
  1313. int nNumDataParts = 1;
  1314. int nFileDataSize = s_FileHeaderSize( pszBase, nNumDataParts, info.m_iPreloadSize );
  1315. int nTotalHeaderSize = ( int )( nFileDataSize + ( 2 + strlen( pszExt ) ) + ( 2 + strlen( pszDir ) ) );
  1316. char *pBuf = ( char * ) stackalloc( nTotalHeaderSize );
  1317. char *pOut = pBuf;
  1318. strcpy( pOut, pszExt );
  1319. pOut += strlen( pszExt );
  1320. *( pOut++ ) = 0; // null on ext name
  1321. strcpy( pOut, pszDir );
  1322. pOut += strlen( pszDir );
  1323. *( pOut++ ) = 0; // null at end of dir name
  1324. strcpy( pOut, pszBase );
  1325. pOut += strlen( pszBase );
  1326. *( pOut++ ) = 0;
  1327. uint32 nCRC = info.m_crc;
  1328. memcpy( pOut, &nCRC, sizeof( nCRC ) );
  1329. pOut += sizeof( int );
  1330. if ( info.m_iPreloadSize > 0xffff )
  1331. Error( "Preload size for '%s' is too big", info.m_sName.String() );
  1332. uint16 nMetaDataSize = (uint16)info.m_iPreloadSize;
  1333. memcpy( pOut, &nMetaDataSize, sizeof( uint16 ) );
  1334. pOut += sizeof( uint16 );
  1335. // now, build file parts.
  1336. CFilePartDescr newPart;
  1337. newPart.m_nFileDataSize = info.GetSizeInChunkFile();
  1338. newPart.m_nFileNumber = ( info.m_idxChunk < 0 ) ? VPKFILENUMBER_EMBEDDED_IN_DIR_FILE : info.m_idxChunk;
  1339. newPart.m_nFileDataOffset = info.m_iOffsetInChunk;
  1340. memcpy( pOut, &newPart, sizeof( newPart ) );
  1341. pOut += sizeof( newPart );
  1342. PackFileIndex_t endOfPartMarker = PACKFILEINDEX_END;
  1343. memcpy( pOut, &endOfPartMarker, sizeof( endOfPartMarker ) );
  1344. pOut += sizeof( PackFileIndex_t );
  1345. if ( nMetaDataSize )
  1346. {
  1347. Assert( info.m_pPreloadData );
  1348. memcpy( pOut, info.m_pPreloadData, nMetaDataSize );
  1349. pOut += nMetaDataSize;
  1350. }
  1351. *( pOut++ ) = 0; // mark no more files in dir
  1352. *( pOut++ ) = 0; // mark no more dirs in extension
  1353. Assert( pOut - pBuf == nTotalHeaderSize );
  1354. // now, we need to insert our header, figuring out how many of the fields are already there
  1355. int nExtensionHash = HashString( pszExt ) % PACKEDFILE_EXT_HASH_SIZE;
  1356. int nInsertOffset = 0;
  1357. CFileExtensionData const *pExt = m_pExtensionData[nExtensionHash].FindNamedNodeCaseSensitive( pszExt );
  1358. char *pHeaderInsertPtr = pBuf;
  1359. if ( pExt )
  1360. {
  1361. // this is not a new extension. we should not insert the extension record
  1362. nTotalHeaderSize -= 2 + strlen( pszExt ); // null + end of dir list marker
  1363. pHeaderInsertPtr += 1 + strlen( pszExt ); // don't insert the name + null
  1364. // now, look for the directory
  1365. int nDirHash = HashString( pszDir ) % PACKEDFILE_DIR_HASH_SIZE;
  1366. CFileDirectoryData const *pDir = pExt->m_pDirectoryHashTable[nDirHash].FindNamedNodeCaseSensitive( pszDir );
  1367. if ( pDir )
  1368. {
  1369. // dir and extension found. all we need to do is insert the file data itself
  1370. nTotalHeaderSize -= 2 + strlen( pszDir ); // null + end of file list marker
  1371. pHeaderInsertPtr += 1 + strlen( pszDir );
  1372. char const *pStartOfDirFileData = pDir->m_Name + 1 + strlen( pDir->m_Name );
  1373. nInsertOffset = pStartOfDirFileData - ( char const * ) ( m_DirectoryData.Base() );
  1374. }
  1375. else
  1376. {
  1377. char const *pStartOfExtFileData = pExt->m_Name + 1 + strlen( pExt->m_Name );
  1378. nInsertOffset = pStartOfExtFileData - ( char const * ) ( m_DirectoryData.Base() );
  1379. }
  1380. }
  1381. m_DirectoryData.InsertMultipleBefore( nInsertOffset, nTotalHeaderSize );
  1382. memcpy( &m_DirectoryData[nInsertOffset], pHeaderInsertPtr, nTotalHeaderSize );
  1383. BuildHashTables();
  1384. }
  1385. ePackedStoreAddResultCode CPackedStore::AddFile( char const *pFile, uint16 nMetaDataSize, const void *pFileData, uint32 nFileTotalSize, bool bMultiChunk, uint32 const *pCrcValue )
  1386. {
  1387. // Calculate CRC if they didn't provide one
  1388. uint32 nCRC;
  1389. if ( pCrcValue )
  1390. {
  1391. nCRC = *pCrcValue;
  1392. }
  1393. else
  1394. {
  1395. nCRC = CRC32_ProcessSingleBuffer( pFileData, nFileTotalSize );
  1396. }
  1397. // Check if it is already here with the same contents
  1398. CPackedStoreFileHandle pData = OpenFile( pFile );
  1399. ePackedStoreAddResultCode nRslt = EPADD_NEWFILE;
  1400. if ( pData ) // already in pack
  1401. {
  1402. CFileHeaderFixedData *pHeader = pData.m_pHeaderData;
  1403. if ( ( nFileTotalSize == pHeader->TotalDataSize() ) && ( pHeader->m_nFileCRC == nCRC ) && ( nMetaDataSize == pHeader->m_nMetaDataSize ) ) // file unchanged?
  1404. {
  1405. return EPADD_ADDSAMEFILE;
  1406. }
  1407. nRslt = EPADD_UPDATEFILE;
  1408. }
  1409. // Build up the directory info into an interface structure
  1410. VPKContentFileInfo_t dirEntry;
  1411. dirEntry.m_sName = pFile;
  1412. dirEntry.m_iTotalSize = nFileTotalSize;
  1413. dirEntry.m_iPreloadSize = Min( (uint32)nMetaDataSize, (uint32)nFileTotalSize ) ;
  1414. dirEntry.m_pPreloadData = ( dirEntry.m_iPreloadSize > 0 ) ? pFileData : NULL;
  1415. dirEntry.m_crc = nCRC;
  1416. uint32 nBytesInChunk = dirEntry.GetSizeInChunkFile();
  1417. const unsigned char *pDataStart = (const unsigned char *)pFileData + dirEntry.m_iPreloadSize;
  1418. if ( bMultiChunk && nBytesInChunk > 0 )
  1419. {
  1420. // Check if we need to start a new chunk
  1421. char szDataFileName[MAX_PATH];
  1422. if ( m_nHighestChunkFileIndex < 0 )
  1423. {
  1424. dirEntry.m_idxChunk = 0;
  1425. dirEntry.m_iOffsetInChunk = 0;
  1426. }
  1427. else
  1428. {
  1429. dirEntry.m_idxChunk = m_nHighestChunkFileIndex;
  1430. // Append to most recent chunk
  1431. GetDataFileName( szDataFileName, sizeof(szDataFileName), m_nHighestChunkFileIndex );
  1432. dirEntry.m_iOffsetInChunk = g_pFullFileSystem->Size( szDataFileName );
  1433. if ( (int)dirEntry.m_iOffsetInChunk <= 0 ) // technical wrong, but we shouldn't have 2GB chunks. (Sort of defeats the whole purpose.)
  1434. {
  1435. // Note, there is one possible failure case. if we have a file whose data
  1436. // is actually all in the preload section, but it is marked as being
  1437. // in a chunk, then we might have a zero byte "chunk." We really should
  1438. // not be assigning any files to "chunks" if they are entirely in the preload
  1439. // area.
  1440. Error( "Error querying %s for file size\n", szDataFileName );
  1441. }
  1442. // Check if we need to start a new chunk
  1443. if ( (int)dirEntry.m_iOffsetInChunk >= m_nWriteChunkSize )
  1444. {
  1445. ++dirEntry.m_idxChunk;
  1446. dirEntry.m_iOffsetInChunk = 0;
  1447. }
  1448. }
  1449. m_nHighestChunkFileIndex = MAX( m_nHighestChunkFileIndex, dirEntry.m_idxChunk );
  1450. // write the actual data
  1451. GetDataFileName( szDataFileName, sizeof(szDataFileName), dirEntry.m_idxChunk );
  1452. FileHandle_t fHandle = m_pFileSystem->Open( szDataFileName, "rb+" );
  1453. if ( !fHandle && dirEntry.m_iOffsetInChunk == 0 )
  1454. fHandle = m_pFileSystem->Open( szDataFileName, "wb" );
  1455. if ( !fHandle )
  1456. Error( "Cannot open %s for writing", szDataFileName );
  1457. m_pFileSystem->Seek( fHandle, dirEntry.m_iOffsetInChunk, FILESYSTEM_SEEK_HEAD );
  1458. m_pFileSystem->Write( pDataStart, nBytesInChunk, fHandle );
  1459. m_pFileSystem->Close( fHandle );
  1460. // Force on the use of the "dir" file
  1461. m_bUseDirFile = true;
  1462. }
  1463. else
  1464. {
  1465. // append to the dir data.
  1466. dirEntry.m_idxChunk = VPKFILENUMBER_EMBEDDED_IN_DIR_FILE;
  1467. dirEntry.m_iOffsetInChunk = m_EmbeddedChunkData.Count();
  1468. m_EmbeddedChunkData.AddMultipleToTail( nBytesInChunk, pDataStart );
  1469. }
  1470. // Update the directory
  1471. AddFileToDirectory( dirEntry );
  1472. return nRslt;
  1473. }
  1474. int CPackedStore::GetFileList( CUtlStringList &outFilenames, bool bFormattedOutput, bool bSortedOutput )
  1475. {
  1476. return GetFileList( NULL, outFilenames, bFormattedOutput, bSortedOutput );
  1477. }
  1478. int CPackedStore::GetFileList( const char *pWildCard, CUtlStringList &outFilenames, bool bFormattedOutput, bool bSortedOutput )
  1479. {
  1480. // Separate the wildcard base from the extension
  1481. char szWildCardPath[MAX_PATH];
  1482. char szWildCardBase[64];
  1483. char szWildCardExt[20];
  1484. bool bNoBaseWildcard = false;
  1485. bool bNoExtWildcard = false;
  1486. szWildCardPath[0] = szWildCardExt[0] = szWildCardBase[0] = NULL;
  1487. // Parse the wildcard string into a base and extension used for string comparisons
  1488. if ( pWildCard )
  1489. {
  1490. V_ExtractFilePath( pWildCard, szWildCardPath, sizeof( szWildCardPath ) );
  1491. V_FixSlashes( szWildCardPath, '/' );
  1492. V_FileBase( pWildCard, szWildCardBase, sizeof( szWildCardBase ) );
  1493. V_ExtractFileExtension( pWildCard, szWildCardExt, sizeof( szWildCardExt ) );
  1494. // Remove '*' from the base and extension strings so that the string comparison calls will match
  1495. char *pcStar = strchr( szWildCardBase, '*' );
  1496. pcStar ? *pcStar = NULL : bNoBaseWildcard = true;
  1497. pcStar = strchr( szWildCardExt, '*' );
  1498. pcStar ? *pcStar = NULL : bNoExtWildcard = true;
  1499. }
  1500. char const *pData = reinterpret_cast< char const *>( DirectoryData() );
  1501. while( *pData )
  1502. {
  1503. // for each extension
  1504. char pszCurExtension[MAX_PATH];
  1505. if ( pData[0] != ' ' )
  1506. sprintf( pszCurExtension, ".%s", pData );
  1507. else
  1508. pszCurExtension[0] = 0;
  1509. // now, iterate over all directories associated with this extension
  1510. pData += 1 + strlen( pData );
  1511. while( *pData )
  1512. {
  1513. char pszCurDir[MAX_PATH];
  1514. if ( pData[0] != ' ' )
  1515. sprintf( pszCurDir, "%s/", pData );
  1516. else
  1517. pszCurDir[0] = 0;
  1518. pData += 1 + strlen( pData ); // skip dir name
  1519. // now, march through all the files
  1520. while( *pData ) // until we're out of files to look at
  1521. {
  1522. char pszFNameOut[MAX_PATH*2];
  1523. if ( bFormattedOutput )
  1524. {
  1525. CFileHeaderFixedData const *pHeader = reinterpret_cast< CFileHeaderFixedData const *>( pData + 1 + strlen( pData ) );
  1526. sprintf( pszFNameOut, "%s%s%s crc=0x%x metadatasz=%d", pszCurDir, pData, pszCurExtension, pHeader->m_nFileCRC, pHeader->m_nMetaDataSize );
  1527. CFilePartDescr const *pPart = &( pHeader->m_PartDescriptors[0] );
  1528. while( pPart->m_nFileNumber != PACKFILEINDEX_END )
  1529. {
  1530. sprintf( pszFNameOut + strlen( pszFNameOut )," fnumber=%d ofs=0x%x sz=%d",
  1531. pPart->m_nFileNumber, pPart->m_nFileDataOffset, pPart->m_nFileDataSize );
  1532. pPart++;
  1533. }
  1534. }
  1535. else
  1536. {
  1537. V_strncpy( pszFNameOut, pszCurDir, sizeof( pszFNameOut ) );
  1538. V_strncat( pszFNameOut, pData, sizeof( pszFNameOut ) );
  1539. V_strncat( pszFNameOut, pszCurExtension, sizeof( pszFNameOut ) );
  1540. }
  1541. SkipFile( pData );
  1542. bool matches = true;
  1543. if ( pWildCard )
  1544. {
  1545. // See if the filename matches the wildcards
  1546. char szFNameOutPath[MAX_PATH];
  1547. char szFNameOutBase[64];
  1548. char szFNameOutExt[20];
  1549. V_ExtractFilePath( pszFNameOut, szFNameOutPath, sizeof( szFNameOutPath ) );
  1550. V_FileBase( pszFNameOut, szFNameOutBase, sizeof( szFNameOutBase ) );
  1551. V_ExtractFileExtension( pszFNameOut, szFNameOutExt, sizeof( szFNameOutExt ) );
  1552. matches = !V_strnicmp( szFNameOutPath, szWildCardPath, sizeof( szWildCardPath ) );
  1553. matches = matches && ( !V_strlen( szWildCardExt ) || bNoExtWildcard ? 0 == V_strnicmp( szFNameOutExt, szWildCardExt, strlen( szWildCardExt ) ) : 0 != V_stristr(szFNameOutExt, szWildCardExt ) );
  1554. matches = matches && ( !V_strlen( szWildCardBase ) || bNoBaseWildcard ? 0 == V_strnicmp( szFNameOutBase, szWildCardBase, strlen( szWildCardBase ) ) : 0 != V_stristr(szFNameOutBase, szWildCardBase ) );
  1555. }
  1556. // Add the file to the output list
  1557. if ( matches )
  1558. {
  1559. char *pFName = new char[1 + strlen( pszFNameOut ) ];
  1560. strcpy( pFName, pszFNameOut );
  1561. outFilenames.AddToTail( pFName );
  1562. }
  1563. }
  1564. pData++; // skip end marker
  1565. }
  1566. pData++; // skip end marker
  1567. }
  1568. if ( bSortedOutput )
  1569. {
  1570. outFilenames.Sort( &CUtlStringList::SortFunc );
  1571. }
  1572. return outFilenames.Count();
  1573. }
  1574. void CPackedStore::GetFileList( const char *pWildcard, CUtlVector<VPKContentFileInfo_t> &outVecResults )
  1575. {
  1576. // !KLUDGE! Get the filenames first, and then "find" them again.
  1577. CUtlStringList vecFilenames;
  1578. GetFileList( vecFilenames, false, false );
  1579. FOR_EACH_VEC( vecFilenames, i )
  1580. {
  1581. // Locate where it is in the existing file
  1582. CPackedStoreFileHandle h = OpenFile( vecFilenames[i] );
  1583. if ( !h )
  1584. Error( "File '%s' was returned by GetFileList, but OpenFile() fails?!", vecFilenames[i] );
  1585. // Convert to output structure
  1586. VPKContentFileInfo_t &f = outVecResults[ outVecResults.AddToTail() ];
  1587. f.m_sName = vecFilenames[i];
  1588. f.m_idxChunk = ( h.m_nFileNumber == VPKFILENUMBER_EMBEDDED_IN_DIR_FILE ) ? -1 : h.m_nFileNumber;
  1589. f.m_iTotalSize = h.m_nFileSize;
  1590. f.m_iOffsetInChunk = h.m_nFileOffset;
  1591. f.m_iPreloadSize = h.m_nMetaDataSize;
  1592. f.m_crc = h.m_pHeaderData->m_nFileCRC;
  1593. f.m_pPreloadData = h.m_pHeaderData->MetaData();
  1594. }
  1595. }
  1596. int CPackedStore::GetFileAndDirLists( CUtlStringList &outDirnames, CUtlStringList &outFilenames, bool bSortedOutput )
  1597. {
  1598. return GetFileAndDirLists( NULL, outDirnames, outFilenames, bSortedOutput );
  1599. }
  1600. void CPackedStore::BuildFindFirstCache()
  1601. {
  1602. CUtlStringList allVPKFiles;
  1603. char szLastDirFound[MAX_PATH];
  1604. // Init
  1605. V_strncpy( szLastDirFound, "$$$$$$$HighlyUnlikelyPathForInitializationPurposes#######", sizeof( szLastDirFound ) );
  1606. m_dirContents.SetLessFunc( DefLessFunc( int ) );
  1607. // Get all files in the VPK
  1608. GetFileList( allVPKFiles, false, true );
  1609. // Add directories to directory list and files into map
  1610. FOR_EACH_VEC( allVPKFiles, i )
  1611. {
  1612. char szFilePath[MAX_PATH];
  1613. V_ExtractFilePath( allVPKFiles[i], szFilePath, sizeof( szFilePath ) );
  1614. Q_StripTrailingSlash( szFilePath );
  1615. // New directory
  1616. if ( V_strnicmp( szFilePath, szLastDirFound, sizeof( szLastDirFound ) ) )
  1617. {
  1618. // Mark the new one as the last one encountered
  1619. V_strncpy( szLastDirFound, szFilePath, sizeof( szFilePath ) );
  1620. // Add it
  1621. m_directoryList.CopyAndAddToTail( szFilePath );
  1622. m_dirContents.Insert( m_directoryList.Count(), new CUtlStringList() ); // Freed in destructor
  1623. }
  1624. unsigned short nIndex = m_dirContents.Find( m_directoryList.Count() );
  1625. CUtlStringList *pList = m_dirContents.Element( nIndex );
  1626. pList->CopyAndAddToTail( V_UnqualifiedFileName( allVPKFiles[i] ) );
  1627. }
  1628. }
  1629. int CPackedStore::GetFileAndDirLists( const char *pWildCard, CUtlStringList &outDirnames, CUtlStringList &outFilenames, bool bSortedOutput )
  1630. {
  1631. // If this is the first time we've called FindFirst on this CPackedStore then let's build the caches
  1632. if ( !m_directoryList.Count() )
  1633. {
  1634. BuildFindFirstCache();
  1635. #ifdef NEVER
  1636. printf("CPackedStore::GetFileAndDirLists - list of directories in VPK files\n");
  1637. FOR_EACH_VEC( m_directoryList, i )
  1638. {
  1639. printf("\t%d : %s\n", i, m_directoryList[i] );
  1640. }
  1641. #endif // NEVER
  1642. }
  1643. // printf("CPackedStore::GetFileAndDirLists - Searching for %s\n", pWildCard? pWildCard: "NULL");
  1644. if ( pWildCard )
  1645. {
  1646. CUtlDict<int,int> AddedDirectories; // Used to remove duplicate paths
  1647. char szWildCardPath[MAX_PATH];
  1648. char szWildCardBase[64];
  1649. char szWildCardExt[20];
  1650. int nLenWildcardPath = 0;
  1651. int nLenWildcardBase = 0;
  1652. int nLenWildcardExt = 0;
  1653. bool bBaseWildcard = true;
  1654. bool bExtWildcard = true;
  1655. szWildCardPath[0] = szWildCardExt[0] = szWildCardBase[0] = '\0';
  1656. //
  1657. // Parse the wildcard string into a base and extension used for string comparisons
  1658. //
  1659. V_ExtractFilePath( pWildCard, szWildCardPath, sizeof( szWildCardPath ) );
  1660. V_FixSlashes( szWildCardPath, '/' );
  1661. V_FileBase( pWildCard, szWildCardBase, sizeof( szWildCardBase ) );
  1662. V_ExtractFileExtension( pWildCard, szWildCardExt, sizeof( szWildCardExt ) );
  1663. // From the pattern, we now have the directory path up to the file pattern, the filename base, and the filename
  1664. // extension.
  1665. // Remove '*' from the base and extension strings so that the string comparison calls will match
  1666. char *pcStar = strchr( szWildCardBase, '*' );
  1667. pcStar ? *pcStar = NULL : bBaseWildcard = false;
  1668. pcStar = strchr( szWildCardExt, '*' );
  1669. pcStar ? *pcStar = NULL : bExtWildcard = false;
  1670. nLenWildcardPath = V_strlen( szWildCardPath );
  1671. nLenWildcardBase = V_strlen( szWildCardBase );
  1672. nLenWildcardExt = V_strlen( szWildCardExt );
  1673. // Generate the list of directories and files that match the wildcard
  1674. //
  1675. //
  1676. // Directories first
  1677. //
  1678. FOR_EACH_VEC( m_directoryList, i )
  1679. {
  1680. // Does this file's path match the wildcard path?
  1681. if ( ( nLenWildcardPath && ( 0 == V_strnicmp( m_directoryList[i], szWildCardPath, nLenWildcardPath ) ) )
  1682. || ( !nLenWildcardPath && ( 0 == V_strlen( m_directoryList[i] ) ) ) )
  1683. {
  1684. // Extract the sub-directory name if there is one
  1685. char szSubDir[64];
  1686. char *szSubDirExtension = NULL; // this is anything after a '.' in szSubDir
  1687. bool bBaseMatch = false;
  1688. bool bExtMatch = false;
  1689. // Copy everything to the right of the root directory
  1690. V_strncpy( szSubDir, &m_directoryList[i][nLenWildcardPath], sizeof( szSubDir ) );
  1691. // Set the next / to NULL and we have our subdirectory
  1692. char *pSlash = strchr( szSubDir, '/' );
  1693. pSlash ? *pSlash = NULL : NULL;
  1694. szSubDirExtension = strchr( szSubDir, '.' );
  1695. if ( szSubDirExtension )
  1696. {
  1697. // Null out the . and move the szSubDirExtension to point to the extension
  1698. *szSubDirExtension = '\0';
  1699. szSubDirExtension++;
  1700. }
  1701. // If we have a base dir name, and we have a szWildCardBase to match against
  1702. if ( bBaseWildcard )
  1703. bBaseMatch = true; // The base is the wildCard ("*"), so whatever we have as the base matches
  1704. else
  1705. bBaseMatch = ( 0 == V_strnicmp( szSubDir, szWildCardBase, nLenWildcardBase ) );
  1706. // If we have an extension and we have a szWildCardExtension to mach against
  1707. if ( bExtWildcard )
  1708. bExtMatch = true; // The extension is the wildcard ("*"), so whatever we have as the extension matches
  1709. else
  1710. bExtMatch = ( NULL == szSubDirExtension && '\0' == *szWildCardExt ) || (( NULL != szSubDirExtension ) && ( 0 == V_strnicmp( szSubDirExtension, szWildCardExt, nLenWildcardExt ) ));
  1711. // If both parts match, then add it to the list of directories that match
  1712. if ( bBaseMatch && bExtMatch )
  1713. {
  1714. char szFullPathToDir[ MAX_PATH ];
  1715. V_strncpy( szFullPathToDir, szWildCardPath, nLenWildcardPath );
  1716. V_strcat_safe( szFullPathToDir, "/" );
  1717. V_strcat_safe( szFullPathToDir, szSubDir );
  1718. // Add the subdirectory to the list if it isn't already there
  1719. if ( -1 == AddedDirectories.Find( szFullPathToDir ) )
  1720. {
  1721. char *pDName = new char[1 + strlen( szFullPathToDir )];
  1722. V_strncpy( pDName, szFullPathToDir, 1 + strlen( szFullPathToDir ) );
  1723. outDirnames.AddToTail( pDName );
  1724. AddedDirectories.Insert( pDName, 0 );
  1725. }
  1726. }
  1727. }
  1728. }
  1729. //
  1730. // Files
  1731. //
  1732. FOR_EACH_VEC( m_directoryList, i )
  1733. {
  1734. // We no longer want the trailing slash
  1735. Q_StripTrailingSlash( szWildCardPath );
  1736. // Find the directory that matches the wildcard path
  1737. if ( !V_strnicmp( szWildCardPath, m_directoryList[i], sizeof( szWildCardPath ) ) )
  1738. {
  1739. CUtlStringList &filesInDirectory = *(m_dirContents.Element( i ));
  1740. // Use the cached list of files in this directory
  1741. FOR_EACH_VEC( filesInDirectory, iFile )
  1742. {
  1743. bool matches = true;
  1744. // See if the filename matches the wildcards
  1745. char szFNameOutBase[64];
  1746. char szFNameOutExt[20];
  1747. V_FileBase( filesInDirectory[iFile], szFNameOutBase, sizeof( szFNameOutBase ) );
  1748. V_ExtractFileExtension( filesInDirectory[iFile], szFNameOutExt, sizeof( szFNameOutExt ) );
  1749. // Since we have a sorted list we can optimize using the return code of the compare
  1750. int c = V_strnicmp( szWildCardBase, szFNameOutBase, nLenWildcardBase );
  1751. if ( c < 0 )
  1752. break;
  1753. if ( c > 0 )
  1754. continue;
  1755. matches = ( (nLenWildcardExt <= 0) || bBaseWildcard ? 0 == V_strnicmp( szFNameOutExt, szWildCardExt, nLenWildcardExt ) : V_stristr( szFNameOutExt, szWildCardExt ) != NULL );
  1756. // Add the file to the output list
  1757. if ( matches )
  1758. {
  1759. bool bFound = false;
  1760. FOR_EACH_VEC( outFilenames, j )
  1761. {
  1762. if ( !V_strncmp( outFilenames[j], filesInDirectory[iFile], V_strlen( filesInDirectory[iFile] ) ) )
  1763. {
  1764. bFound = true;
  1765. break;
  1766. }
  1767. }
  1768. if ( !bFound )
  1769. {
  1770. outFilenames.CopyAndAddToTail( filesInDirectory[iFile] );
  1771. }
  1772. }
  1773. }
  1774. }
  1775. }
  1776. }
  1777. else // Otherwise, simply return the base data
  1778. {
  1779. // Add all the files as well
  1780. FOR_EACH_VEC( m_directoryList, i )
  1781. {
  1782. // Add all directories
  1783. outDirnames.CopyAndAddToTail( m_directoryList[i] );
  1784. // Now add all files
  1785. CUtlStringList &filesInDirectory = *(m_dirContents.Element( i ));
  1786. FOR_EACH_VEC( filesInDirectory, j )
  1787. {
  1788. outFilenames.CopyAndAddToTail( filesInDirectory[j] );
  1789. }
  1790. }
  1791. }
  1792. // Sort the output if requested
  1793. if ( bSortedOutput )
  1794. {
  1795. outDirnames.Sort( &CUtlStringList::SortFunc );
  1796. outFilenames.Sort( &CUtlStringList::SortFunc );
  1797. }
  1798. return outDirnames.Count();
  1799. }