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.

2115 lines
69 KiB

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