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.

462 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #ifndef PACKEDSTORE_H
  7. #define PACKEDSTORE_H
  8. #ifdef _WIN32
  9. #pragma once
  10. #endif
  11. #include <tier0/platform.h>
  12. #include <tier0/threadtools.h>
  13. #include <tier0/tslist.h>
  14. #include <tier2/tier2.h>
  15. #include "filesystem.h"
  16. #include "tier1/utlintrusivelist.h"
  17. #include "tier1/utlvector.h"
  18. #include "tier1/utllinkedlist.h"
  19. #include "tier1/UtlSortVector.h"
  20. #include "tier1/utlmap.h"
  21. #include "tier1/checksum_md5.h"
  22. #define VPK_ENABLE_SIGNING
  23. const int k_nVPKDefaultChunkSize = 200 * 1024 * 1024;
  24. class CPackedStore;
  25. struct ChunkHashFraction_t
  26. {
  27. int m_nPackFileNumber;
  28. int m_nFileFraction;
  29. int m_cbChunkLen;
  30. MD5Value_t m_md5contents;
  31. };
  32. class ChunkHashFractionLess_t
  33. {
  34. public:
  35. bool Less( const ChunkHashFraction_t& lhs, const ChunkHashFraction_t& rhs, void *pContext )
  36. {
  37. if ( lhs.m_nPackFileNumber < rhs.m_nPackFileNumber )
  38. return true;
  39. if ( lhs.m_nPackFileNumber > rhs.m_nPackFileNumber )
  40. return false;
  41. if ( lhs.m_nFileFraction < rhs.m_nFileFraction )
  42. return true;
  43. if ( lhs.m_nFileFraction > rhs.m_nFileFraction )
  44. return false;
  45. return false;
  46. }
  47. };
  48. class CPackedStoreFileHandle
  49. {
  50. public:
  51. int m_nFileNumber;
  52. int m_nFileOffset;
  53. int m_nFileSize;
  54. int m_nCurrentFileOffset;
  55. void const *m_pMetaData;
  56. uint16 m_nMetaDataSize;
  57. CPackedStore *m_pOwner;
  58. struct CFileHeaderFixedData *m_pHeaderData;
  59. uint8 *m_pDirFileNamePtr; // pointer to basename in dir block
  60. FORCEINLINE operator bool( void ) const
  61. {
  62. return ( m_nFileNumber != -1 );
  63. }
  64. FORCEINLINE int Read( void *pOutData, int nNumBytes );
  65. CPackedStoreFileHandle( void )
  66. {
  67. m_nFileNumber = -1;
  68. }
  69. int Seek( int nOffset, int nWhence )
  70. {
  71. switch( nWhence )
  72. {
  73. case SEEK_CUR:
  74. nOffset = m_nFileOffset + nOffset ;
  75. break;
  76. case SEEK_END:
  77. nOffset = m_nFileSize + nOffset;
  78. break;
  79. }
  80. m_nCurrentFileOffset = MAX( 0, MIN( m_nFileSize, nOffset ) );
  81. return m_nCurrentFileOffset;
  82. }
  83. int Tell( void ) const
  84. {
  85. return m_nCurrentFileOffset;
  86. }
  87. uint32 GetFileCRCFromHeaderData() const
  88. {
  89. uint32 *pCRC = (uint32 *)m_pHeaderData;
  90. return *pCRC;
  91. }
  92. FORCEINLINE void GetPackFileName( char *pchFileNameOut, int cchFileNameOut );
  93. };
  94. #define MAX_ARCHIVE_FILES_TO_KEEP_OPEN_AT_ONCE 512
  95. #define PACKEDFILE_EXT_HASH_SIZE 15
  96. #ifdef _WIN32
  97. typedef HANDLE PackDataFileHandle_t;
  98. #else
  99. typedef FileHandle_t PackDataFileHandle_t;
  100. #endif
  101. struct FileHandleTracker_t
  102. {
  103. int m_nFileNumber;
  104. PackDataFileHandle_t m_hFileHandle;
  105. int m_nCurOfs;
  106. CThreadFastMutex m_Mutex;
  107. FileHandleTracker_t( void )
  108. {
  109. m_nFileNumber = -1;
  110. }
  111. };
  112. enum ePackedStoreAddResultCode
  113. {
  114. EPADD_NEWFILE, // the file was added and is new
  115. EPADD_ADDSAMEFILE, // the file was already present, and the contents are the same as what you passed.
  116. EPADD_UPDATEFILE, // the file was alreayd present and its contents have been updated
  117. EPADD_ERROR, // some error has resulted
  118. };
  119. // Describe a file inside of a VPK file. Is not memory efficient; only used for interface
  120. // purposes and during file building
  121. struct VPKContentFileInfo_t
  122. {
  123. CUtlString m_sName;
  124. int m_idxChunk;
  125. uint32 m_iTotalSize;
  126. uint32 m_iOffsetInChunk;
  127. uint32 m_iPreloadSize;
  128. const void *m_pPreloadData;
  129. //MD5Value_t m_md5Source; // source content before munging & release optimization. Used for incremental builds
  130. uint32 m_crc; // CRC of actual file contents
  131. /// Size of the data in the chunk file. (Excludes the preload data size)
  132. uint32 GetSizeInChunkFile() const
  133. {
  134. Assert( m_iTotalSize >= m_iPreloadSize );
  135. return m_iTotalSize - m_iPreloadSize;
  136. }
  137. VPKContentFileInfo_t()
  138. {
  139. m_idxChunk = -1;
  140. m_iTotalSize = 0;
  141. m_iOffsetInChunk = 0;
  142. m_iPreloadSize = 0;
  143. m_crc = 0;
  144. m_pPreloadData = NULL;
  145. //memset( m_md5Source.bits, 0, sizeof( m_md5Source.bits ) );
  146. }
  147. };
  148. // a 1MB chunk of cached VPK data
  149. // For CPackedStoreReadCache
  150. struct CachedVPKRead_t
  151. {
  152. CachedVPKRead_t()
  153. {
  154. m_nPackFileNumber = 0;
  155. m_nFileFraction = 0;
  156. m_pubBuffer = NULL;
  157. m_cubBuffer = 0;
  158. m_idxLRU = -1;
  159. m_hMD5RequestHandle= 0;
  160. m_cFailedHashes = 0;
  161. }
  162. int m_nPackFileNumber; // identifier
  163. int m_nFileFraction; // identifier
  164. uint8 *m_pubBuffer; // data
  165. int m_cubBuffer; // data
  166. int m_idxLRU; // bookkeeping
  167. int m_hMD5RequestHandle;// bookkeeping
  168. int m_cFailedHashes; // did the MD5 match what it was supposed to?
  169. MD5Value_t m_md5Value;
  170. static bool Less( const CachedVPKRead_t& lhs, const CachedVPKRead_t& rhs )
  171. {
  172. if ( lhs.m_nPackFileNumber < rhs.m_nPackFileNumber )
  173. return true;
  174. if ( lhs.m_nPackFileNumber > rhs.m_nPackFileNumber )
  175. return false;
  176. if ( lhs.m_nFileFraction < rhs.m_nFileFraction )
  177. return true;
  178. if ( lhs.m_nFileFraction > rhs.m_nFileFraction )
  179. return false;
  180. return false;
  181. }
  182. };
  183. // Read the VPK file in 1MB chunks
  184. // and we hang on to those chunks so we can serve other reads out of the cache
  185. // This sounds great, but is only of secondary importance.
  186. // The primary reason we do this is so that the FileTracker can calculate the
  187. // MD5 of the 1MB chunks asynchronously in another thread - while we hold
  188. // the chunk in cache - making the MD5 calculation "free"
  189. class CPackedStoreReadCache
  190. {
  191. public:
  192. CPackedStoreReadCache( IBaseFileSystem *pFS );
  193. bool ReadCacheLine( FileHandleTracker_t &fHandle, CachedVPKRead_t &cachedVPKRead );
  194. bool BCanSatisfyFromReadCache( uint8 *pOutData, CPackedStoreFileHandle &handle, FileHandleTracker_t &fHandle, int nDesiredPos, int nNumBytes, int &nRead );
  195. bool BCanSatisfyFromReadCacheInternal( uint8 *pOutData, CPackedStoreFileHandle &handle, FileHandleTracker_t &fHandle, int nDesiredPos, int nNumBytes, int &nRead );
  196. bool CheckMd5Result( CachedVPKRead_t &cachedVPKRead );
  197. int FindBufferToUse();
  198. void RetryBadCacheLine( CachedVPKRead_t &cachedVPKRead );
  199. void RetryAllBadCacheLines();
  200. // cache 64 MB total
  201. static const int k_nCacheBuffersToKeep = 4;
  202. static const int k_cubCacheBufferSize = 0x00100000; // 1MB
  203. static const int k_nCacheBufferMask = 0x7FF00000;
  204. CThreadRWLock m_rwlock;
  205. CUtlRBTree<CachedVPKRead_t> m_treeCachedVPKRead; // all the reads we have done
  206. CTSQueue<CachedVPKRead_t> m_queueCachedVPKReadsRetry; // all the reads that have failed
  207. CUtlLinkedList<CachedVPKRead_t> m_listCachedVPKReadsFailed; // all the reads that have failed
  208. // current items in the cache
  209. int m_cItemsInCache;
  210. int m_rgCurrentCacheIndex[k_nCacheBuffersToKeep];
  211. CInterlockedUInt m_rgLastUsedTime[k_nCacheBuffersToKeep];
  212. CPackedStore *m_pPackedStore;
  213. IBaseFileSystem *m_pFileSystem;
  214. IThreadedFileMD5Processor *m_pFileTracker;
  215. // stats
  216. int m_cubReadFromCache;
  217. int m_cReadFromCache;
  218. int m_cDiscardsFromCache;
  219. int m_cAddedToCache;
  220. int m_cCacheMiss;
  221. int m_cubCacheMiss;
  222. int m_cFileErrors;
  223. int m_cFileErrorsCorrected;
  224. int m_cFileResultsDifferent;
  225. };
  226. class CPackedStore
  227. {
  228. public:
  229. CPackedStore( char const *pFileBasename, char *pszFName, IBaseFileSystem *pFS, bool bOpenForWrite = false );
  230. void RegisterFileTracker( IThreadedFileMD5Processor *pFileTracker ) { m_pFileTracker = pFileTracker; m_PackedStoreReadCache.m_pFileTracker = pFileTracker; }
  231. CPackedStoreFileHandle OpenFile( char const *pFile );
  232. CPackedStoreFileHandle GetHandleForHashingFiles();
  233. /// Add/update the given file to the directory. Does not write any chunk files
  234. void AddFileToDirectory( const VPKContentFileInfo_t &info );
  235. /// Remove the specified file from the directory. Returns true if removed, false if not found
  236. bool RemoveFileFromDirectory( const char *pszName );
  237. /// Add file, writing file data to the end
  238. /// of the current chunk
  239. ePackedStoreAddResultCode AddFile( char const *pFile, uint16 nMetaDataSize, const void *pFileData, uint32 nFullFileSize, bool bMultiChunk, uint32 const *pCrcToUse = NULL );
  240. // write out the file directory
  241. void Write( void );
  242. int ReadData( CPackedStoreFileHandle &handle, void *pOutData, int nNumBytes );
  243. ~CPackedStore( void );
  244. FORCEINLINE void *DirectoryData( void )
  245. {
  246. return m_DirectoryData.Base();
  247. }
  248. // Get a list of all the files in the zip You are responsible for freeing the contents of
  249. // outFilenames (call outFilenames.PurgeAndDeleteElements).
  250. int GetFileList( CUtlStringList &outFilenames, bool bFormattedOutput, bool bSortedOutput );
  251. // Get a list of all files that match the given wildcard string
  252. int GetFileList( const char *pWildCard, CUtlStringList &outFilenames, bool bFormattedOutput, bool bSortedOutput );
  253. /// Get a list of all files that match the given wildcard string, fetching all the details
  254. /// at once
  255. void GetFileList( const char *pWildcard, CUtlVector<VPKContentFileInfo_t> &outVecResults );
  256. // Get a list of all directories of the given wildcard
  257. int GetFileAndDirLists( const char *pWildCard, CUtlStringList &outDirnames, CUtlStringList &outFilenames, bool bSortedOutput );
  258. int GetFileAndDirLists( CUtlStringList &outDirnames, CUtlStringList &outFilenames, bool bSortedOutput );
  259. bool IsEmpty( void ) const;
  260. /// Hash metadata and chunk files
  261. void HashEverything();
  262. /// Hash all chunk files. Don't forget to rehash the metadata afterwords!
  263. void HashAllChunkFiles();
  264. /// Hash all the metadata. (Everything that's not in the chunk files)
  265. void HashMetadata();
  266. /// Re-hash a single chunk file. Don't forget to rehash the metadata afterwords!
  267. void HashChunkFile( int iChunkFileIndex );
  268. bool HashEntirePackFile( CPackedStoreFileHandle &handle, int64 &nFileSize, int nFileFraction, int nFractionSize, FileHash_t &fileHash );
  269. void ComputeDirectoryHash( MD5Value_t &md5Directory );
  270. void ComputeChunkHash( MD5Value_t &md5ChunkHashes );
  271. MD5Value_t &GetDirFileMD5Value() { return m_TotalFileMD5; }
  272. bool BTestDirectoryHash();
  273. bool BTestMasterChunkHash();
  274. CUtlSortVector<ChunkHashFraction_t, ChunkHashFractionLess_t > &AccessPackFileHashes() { return m_vecChunkHashFraction; }
  275. bool FindFileHashFraction( int nPackFileNumber, int nFileFraction, ChunkHashFraction_t &chunkFileHashFraction );
  276. void GetPackFileLoadErrorSummary( CUtlString &sErrors );
  277. void GetPackFileName( CPackedStoreFileHandle &handle, char *pchFileNameOut, int cchFileNameOut ) const;
  278. void GetDataFileName( char *pchFileNameOut, int cchFileNameOut, int nFileNumber ) const;
  279. char const *BaseName( void )
  280. {
  281. return m_pszFileBaseName;
  282. }
  283. char const *FullPathName( void )
  284. {
  285. return m_pszFullPathName;
  286. }
  287. void SetWriteChunkSize( int nWriteChunkSize )
  288. {
  289. m_nWriteChunkSize = nWriteChunkSize;
  290. }
  291. int GetWriteChunkSize() const { return m_nWriteChunkSize; }
  292. int GetHighestChunkFileIndex() { return m_nHighestChunkFileIndex; }
  293. void DiscardChunkHashes( int iChunkFileIndex );
  294. const CUtlVector<uint8> &GetSignaturePublicKey() const { return m_SignaturePublicKey; }
  295. const CUtlVector<uint8> &GetSignature() const { return m_Signature; }
  296. #ifdef VPK_ENABLE_SIGNING
  297. enum ESignatureCheckResult
  298. {
  299. eSignatureCheckResult_NotSigned,
  300. eSignatureCheckResult_WrongKey,
  301. eSignatureCheckResult_Failed, // IO error, etc
  302. eSignatureCheckResult_InvalidSignature,
  303. eSignatureCheckResult_ValidSignature,
  304. };
  305. ESignatureCheckResult CheckSignature( int nSignatureSize, const void *pSignature ) const;
  306. void SetKeysForSigning( int nPrivateKeySize, const void *pPrivateKeyData, int nPublicKeySize, const void *pPublicKeyData );
  307. #endif
  308. void SetUseDirFile() { m_bUseDirFile = true; }
  309. int m_PackFileID;
  310. private:
  311. char m_pszFileBaseName[MAX_PATH];
  312. char m_pszFullPathName[MAX_PATH];
  313. int m_nDirectoryDataSize;
  314. int m_nWriteChunkSize;
  315. bool m_bUseDirFile;
  316. IBaseFileSystem *m_pFileSystem;
  317. IThreadedFileMD5Processor *m_pFileTracker;
  318. CThreadFastMutex m_Mutex;
  319. CPackedStoreReadCache m_PackedStoreReadCache;
  320. CUtlIntrusiveList<class CFileExtensionData> m_pExtensionData[PACKEDFILE_EXT_HASH_SIZE];
  321. CUtlVector<uint8> m_DirectoryData;
  322. CUtlBlockVector<uint8> m_EmbeddedChunkData;
  323. CUtlSortVector<ChunkHashFraction_t, ChunkHashFractionLess_t > m_vecChunkHashFraction;
  324. bool BFileContainedHashes() { return m_vecChunkHashFraction.Count() > 0; }
  325. // these are valid if BFileContainedHashes() is true
  326. MD5Value_t m_DirectoryMD5;
  327. MD5Value_t m_ChunkHashesMD5;
  328. MD5Value_t m_TotalFileMD5;
  329. int m_nHighestChunkFileIndex;
  330. /// The private key that will be used to sign the directory file.
  331. /// This will be empty for unsigned VPK's, or if we don't know the
  332. /// private key.
  333. CUtlVector<uint8> m_SignaturePrivateKey;
  334. /// The public key in the VPK.
  335. CUtlVector<uint8> m_SignaturePublicKey;
  336. /// The signature that was read / computed
  337. CUtlVector<uint8> m_Signature;
  338. /// The number of bytes in the dir file that were signed
  339. uint32 m_nSizeOfSignedData;
  340. FileHandleTracker_t m_FileHandles[MAX_ARCHIVE_FILES_TO_KEEP_OPEN_AT_ONCE];
  341. void Init( void );
  342. struct CFileHeaderFixedData *FindFileEntry(
  343. char const *pDirname, char const *pBaseName, char const *pExtension,
  344. uint8 **pExtBaseOut = NULL, uint8 **pNameBaseOut = NULL );
  345. void BuildHashTables( void );
  346. FileHandleTracker_t &GetFileHandle( int nFileNumber );
  347. void CloseWriteHandle( void );
  348. // For cache-ing directory and contents data
  349. CUtlStringList m_directoryList; // The index of this list of directories...
  350. CUtlMap<int, CUtlStringList*> m_dirContents; // ...is the key to this map of filenames
  351. void BuildFindFirstCache();
  352. bool InternalRemoveFileFromDirectory( const char *pszName );
  353. friend class CPackedStoreReadCache;
  354. };
  355. FORCEINLINE int CPackedStoreFileHandle::Read( void *pOutData, int nNumBytes )
  356. {
  357. return m_pOwner->ReadData( *this, pOutData, nNumBytes );
  358. }
  359. FORCEINLINE void CPackedStoreFileHandle::GetPackFileName( char *pchFileNameOut, int cchFileNameOut )
  360. {
  361. m_pOwner->GetPackFileName( *this, pchFileNameOut, cchFileNameOut );
  362. }
  363. #endif // packedtsore_h