diff options
Diffstat (limited to 'public/vpklib/packedstore.h')
| -rw-r--r-- | public/vpklib/packedstore.h | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/public/vpklib/packedstore.h b/public/vpklib/packedstore.h new file mode 100644 index 0000000..1d5c40c --- /dev/null +++ b/public/vpklib/packedstore.h @@ -0,0 +1,462 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#ifndef PACKEDSTORE_H +#define PACKEDSTORE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include <tier0/platform.h> +#include <tier0/threadtools.h> +#include <tier0/tslist.h> +#include <tier2/tier2.h> + +#include "filesystem.h" +#include "tier1/utlintrusivelist.h" +#include "tier1/utlvector.h" +#include "tier1/utllinkedlist.h" +#include "tier1/UtlSortVector.h" +#include "tier1/utlmap.h" +#include "tier1/checksum_md5.h" + +#define VPK_ENABLE_SIGNING + +const int k_nVPKDefaultChunkSize = 200 * 1024 * 1024; + +class CPackedStore; + + +struct ChunkHashFraction_t +{ + int m_nPackFileNumber; + int m_nFileFraction; + int m_cbChunkLen; + MD5Value_t m_md5contents; +}; + +class ChunkHashFractionLess_t +{ +public: + bool Less( const ChunkHashFraction_t& lhs, const ChunkHashFraction_t& rhs, void *pContext ) + { + if ( lhs.m_nPackFileNumber < rhs.m_nPackFileNumber ) + return true; + if ( lhs.m_nPackFileNumber > rhs.m_nPackFileNumber ) + return false; + + if ( lhs.m_nFileFraction < rhs.m_nFileFraction ) + return true; + if ( lhs.m_nFileFraction > rhs.m_nFileFraction ) + return false; + return false; + } +}; + +class CPackedStoreFileHandle +{ +public: + int m_nFileNumber; + int m_nFileOffset; + int m_nFileSize; + int m_nCurrentFileOffset; + void const *m_pMetaData; + uint16 m_nMetaDataSize; + CPackedStore *m_pOwner; + struct CFileHeaderFixedData *m_pHeaderData; + uint8 *m_pDirFileNamePtr; // pointer to basename in dir block + + FORCEINLINE operator bool( void ) const + { + return ( m_nFileNumber != -1 ); + } + + FORCEINLINE int Read( void *pOutData, int nNumBytes ); + + CPackedStoreFileHandle( void ) + { + m_nFileNumber = -1; + } + + int Seek( int nOffset, int nWhence ) + { + switch( nWhence ) + { + case SEEK_CUR: + nOffset = m_nFileOffset + nOffset ; + break; + + case SEEK_END: + nOffset = m_nFileSize + nOffset; + break; + } + m_nCurrentFileOffset = MAX( 0, MIN( m_nFileSize, nOffset ) ); + return m_nCurrentFileOffset; + } + + int Tell( void ) const + { + return m_nCurrentFileOffset; + } + + uint32 GetFileCRCFromHeaderData() const + { + uint32 *pCRC = (uint32 *)m_pHeaderData; + return *pCRC; + } + + FORCEINLINE void GetPackFileName( char *pchFileNameOut, int cchFileNameOut ); + +}; + +#define MAX_ARCHIVE_FILES_TO_KEEP_OPEN_AT_ONCE 512 + +#define PACKEDFILE_EXT_HASH_SIZE 15 + + +#ifdef _WIN32 +typedef HANDLE PackDataFileHandle_t; +#else +typedef FileHandle_t PackDataFileHandle_t; +#endif + +struct FileHandleTracker_t +{ + int m_nFileNumber; + PackDataFileHandle_t m_hFileHandle; + int m_nCurOfs; + CThreadFastMutex m_Mutex; + + FileHandleTracker_t( void ) + { + m_nFileNumber = -1; + } +}; + +enum ePackedStoreAddResultCode +{ + EPADD_NEWFILE, // the file was added and is new + EPADD_ADDSAMEFILE, // the file was already present, and the contents are the same as what you passed. + EPADD_UPDATEFILE, // the file was alreayd present and its contents have been updated + EPADD_ERROR, // some error has resulted +}; + +// Describe a file inside of a VPK file. Is not memory efficient; only used for interface +// purposes and during file building +struct VPKContentFileInfo_t +{ + CUtlString m_sName; + int m_idxChunk; + uint32 m_iTotalSize; + uint32 m_iOffsetInChunk; + uint32 m_iPreloadSize; + const void *m_pPreloadData; + //MD5Value_t m_md5Source; // source content before munging & release optimization. Used for incremental builds + uint32 m_crc; // CRC of actual file contents + + /// Size of the data in the chunk file. (Excludes the preload data size) + uint32 GetSizeInChunkFile() const + { + Assert( m_iTotalSize >= m_iPreloadSize ); + return m_iTotalSize - m_iPreloadSize; + } + + VPKContentFileInfo_t() + { + m_idxChunk = -1; + m_iTotalSize = 0; + m_iOffsetInChunk = 0; + m_iPreloadSize = 0; + m_crc = 0; + m_pPreloadData = NULL; + //memset( m_md5Source.bits, 0, sizeof( m_md5Source.bits ) ); + } +}; + + +// a 1MB chunk of cached VPK data +// For CPackedStoreReadCache +struct CachedVPKRead_t +{ + CachedVPKRead_t() + { + m_nPackFileNumber = 0; + m_nFileFraction = 0; + m_pubBuffer = NULL; + m_cubBuffer = 0; + m_idxLRU = -1; + m_hMD5RequestHandle= 0; + m_cFailedHashes = 0; + } + int m_nPackFileNumber; // identifier + int m_nFileFraction; // identifier + uint8 *m_pubBuffer; // data + int m_cubBuffer; // data + int m_idxLRU; // bookkeeping + int m_hMD5RequestHandle;// bookkeeping + int m_cFailedHashes; // did the MD5 match what it was supposed to? + MD5Value_t m_md5Value; + + static bool Less( const CachedVPKRead_t& lhs, const CachedVPKRead_t& rhs ) + { + if ( lhs.m_nPackFileNumber < rhs.m_nPackFileNumber ) + return true; + if ( lhs.m_nPackFileNumber > rhs.m_nPackFileNumber ) + return false; + if ( lhs.m_nFileFraction < rhs.m_nFileFraction ) + return true; + if ( lhs.m_nFileFraction > rhs.m_nFileFraction ) + return false; + return false; + } + +}; + + +// Read the VPK file in 1MB chunks +// and we hang on to those chunks so we can serve other reads out of the cache +// This sounds great, but is only of secondary importance. +// The primary reason we do this is so that the FileTracker can calculate the +// MD5 of the 1MB chunks asynchronously in another thread - while we hold +// the chunk in cache - making the MD5 calculation "free" +class CPackedStoreReadCache +{ +public: + CPackedStoreReadCache( IBaseFileSystem *pFS ); + + bool ReadCacheLine( FileHandleTracker_t &fHandle, CachedVPKRead_t &cachedVPKRead ); + bool BCanSatisfyFromReadCache( uint8 *pOutData, CPackedStoreFileHandle &handle, FileHandleTracker_t &fHandle, int nDesiredPos, int nNumBytes, int &nRead ); + bool BCanSatisfyFromReadCacheInternal( uint8 *pOutData, CPackedStoreFileHandle &handle, FileHandleTracker_t &fHandle, int nDesiredPos, int nNumBytes, int &nRead ); + bool CheckMd5Result( CachedVPKRead_t &cachedVPKRead ); + int FindBufferToUse(); + void RetryBadCacheLine( CachedVPKRead_t &cachedVPKRead ); + void RetryAllBadCacheLines(); + + + // cache 64 MB total + static const int k_nCacheBuffersToKeep = 4; + static const int k_cubCacheBufferSize = 0x00100000; // 1MB + static const int k_nCacheBufferMask = 0x7FF00000; + + CThreadRWLock m_rwlock; + CUtlRBTree<CachedVPKRead_t> m_treeCachedVPKRead; // all the reads we have done + + CTSQueue<CachedVPKRead_t> m_queueCachedVPKReadsRetry; // all the reads that have failed + CUtlLinkedList<CachedVPKRead_t> m_listCachedVPKReadsFailed; // all the reads that have failed + + // current items in the cache + int m_cItemsInCache; + int m_rgCurrentCacheIndex[k_nCacheBuffersToKeep]; + CInterlockedUInt m_rgLastUsedTime[k_nCacheBuffersToKeep]; + + CPackedStore *m_pPackedStore; + IBaseFileSystem *m_pFileSystem; + IThreadedFileMD5Processor *m_pFileTracker; + // stats + int m_cubReadFromCache; + int m_cReadFromCache; + int m_cDiscardsFromCache; + int m_cAddedToCache; + int m_cCacheMiss; + int m_cubCacheMiss; + int m_cFileErrors; + int m_cFileErrorsCorrected; + int m_cFileResultsDifferent; +}; + +class CPackedStore +{ +public: + CPackedStore( char const *pFileBasename, char *pszFName, IBaseFileSystem *pFS, bool bOpenForWrite = false ); + + void RegisterFileTracker( IThreadedFileMD5Processor *pFileTracker ) { m_pFileTracker = pFileTracker; m_PackedStoreReadCache.m_pFileTracker = pFileTracker; } + + CPackedStoreFileHandle OpenFile( char const *pFile ); + CPackedStoreFileHandle GetHandleForHashingFiles(); + + /// Add/update the given file to the directory. Does not write any chunk files + void AddFileToDirectory( const VPKContentFileInfo_t &info ); + + /// Remove the specified file from the directory. Returns true if removed, false if not found + bool RemoveFileFromDirectory( const char *pszName ); + + /// Add file, writing file data to the end + /// of the current chunk + ePackedStoreAddResultCode AddFile( char const *pFile, uint16 nMetaDataSize, const void *pFileData, uint32 nFullFileSize, bool bMultiChunk, uint32 const *pCrcToUse = NULL ); + + // write out the file directory + void Write( void ); + + int ReadData( CPackedStoreFileHandle &handle, void *pOutData, int nNumBytes ); + + ~CPackedStore( void ); + + FORCEINLINE void *DirectoryData( void ) + { + return m_DirectoryData.Base(); + } + + // Get a list of all the files in the zip You are responsible for freeing the contents of + // outFilenames (call outFilenames.PurgeAndDeleteElements). + int GetFileList( CUtlStringList &outFilenames, bool bFormattedOutput, bool bSortedOutput ); + + // Get a list of all files that match the given wildcard string + int GetFileList( const char *pWildCard, CUtlStringList &outFilenames, bool bFormattedOutput, bool bSortedOutput ); + + /// Get a list of all files that match the given wildcard string, fetching all the details + /// at once + void GetFileList( const char *pWildcard, CUtlVector<VPKContentFileInfo_t> &outVecResults ); + + // Get a list of all directories of the given wildcard + int GetFileAndDirLists( const char *pWildCard, CUtlStringList &outDirnames, CUtlStringList &outFilenames, bool bSortedOutput ); + int GetFileAndDirLists( CUtlStringList &outDirnames, CUtlStringList &outFilenames, bool bSortedOutput ); + + bool IsEmpty( void ) const; + + /// Hash metadata and chunk files + void HashEverything(); + + /// Hash all chunk files. Don't forget to rehash the metadata afterwords! + void HashAllChunkFiles(); + + /// Hash all the metadata. (Everything that's not in the chunk files) + void HashMetadata(); + + /// Re-hash a single chunk file. Don't forget to rehash the metadata afterwords! + void HashChunkFile( int iChunkFileIndex ); + + bool HashEntirePackFile( CPackedStoreFileHandle &handle, int64 &nFileSize, int nFileFraction, int nFractionSize, FileHash_t &fileHash ); + void ComputeDirectoryHash( MD5Value_t &md5Directory ); + void ComputeChunkHash( MD5Value_t &md5ChunkHashes ); + MD5Value_t &GetDirFileMD5Value() { return m_TotalFileMD5; } + bool BTestDirectoryHash(); + bool BTestMasterChunkHash(); + CUtlSortVector<ChunkHashFraction_t, ChunkHashFractionLess_t > &AccessPackFileHashes() { return m_vecChunkHashFraction; } + bool FindFileHashFraction( int nPackFileNumber, int nFileFraction, ChunkHashFraction_t &chunkFileHashFraction ); + void GetPackFileLoadErrorSummary( CUtlString &sErrors ); + + void GetPackFileName( CPackedStoreFileHandle &handle, char *pchFileNameOut, int cchFileNameOut ) const; + void GetDataFileName( char *pchFileNameOut, int cchFileNameOut, int nFileNumber ) const; + + char const *BaseName( void ) + { + return m_pszFileBaseName; + } + + char const *FullPathName( void ) + { + return m_pszFullPathName; + } + + void SetWriteChunkSize( int nWriteChunkSize ) + { + m_nWriteChunkSize = nWriteChunkSize; + } + + int GetWriteChunkSize() const { return m_nWriteChunkSize; } + + int GetHighestChunkFileIndex() { return m_nHighestChunkFileIndex; } + + void DiscardChunkHashes( int iChunkFileIndex ); + + const CUtlVector<uint8> &GetSignaturePublicKey() const { return m_SignaturePublicKey; } + const CUtlVector<uint8> &GetSignature() const { return m_Signature; } + +#ifdef VPK_ENABLE_SIGNING + enum ESignatureCheckResult + { + eSignatureCheckResult_NotSigned, + eSignatureCheckResult_WrongKey, + eSignatureCheckResult_Failed, // IO error, etc + eSignatureCheckResult_InvalidSignature, + eSignatureCheckResult_ValidSignature, + }; + ESignatureCheckResult CheckSignature( int nSignatureSize, const void *pSignature ) const; + + void SetKeysForSigning( int nPrivateKeySize, const void *pPrivateKeyData, int nPublicKeySize, const void *pPublicKeyData ); +#endif + + void SetUseDirFile() { m_bUseDirFile = true; } + + int m_PackFileID; +private: + char m_pszFileBaseName[MAX_PATH]; + char m_pszFullPathName[MAX_PATH]; + int m_nDirectoryDataSize; + int m_nWriteChunkSize; + bool m_bUseDirFile; + + IBaseFileSystem *m_pFileSystem; + IThreadedFileMD5Processor *m_pFileTracker; + CThreadFastMutex m_Mutex; + + CPackedStoreReadCache m_PackedStoreReadCache; + + CUtlIntrusiveList<class CFileExtensionData> m_pExtensionData[PACKEDFILE_EXT_HASH_SIZE]; + + CUtlVector<uint8> m_DirectoryData; + CUtlBlockVector<uint8> m_EmbeddedChunkData; + + CUtlSortVector<ChunkHashFraction_t, ChunkHashFractionLess_t > m_vecChunkHashFraction; + bool BFileContainedHashes() { return m_vecChunkHashFraction.Count() > 0; } + // these are valid if BFileContainedHashes() is true + MD5Value_t m_DirectoryMD5; + MD5Value_t m_ChunkHashesMD5; + MD5Value_t m_TotalFileMD5; + + int m_nHighestChunkFileIndex; + + /// The private key that will be used to sign the directory file. + /// This will be empty for unsigned VPK's, or if we don't know the + /// private key. + CUtlVector<uint8> m_SignaturePrivateKey; + + /// The public key in the VPK. + CUtlVector<uint8> m_SignaturePublicKey; + + /// The signature that was read / computed + CUtlVector<uint8> m_Signature; + + /// The number of bytes in the dir file that were signed + uint32 m_nSizeOfSignedData; + + FileHandleTracker_t m_FileHandles[MAX_ARCHIVE_FILES_TO_KEEP_OPEN_AT_ONCE]; + + void Init( void ); + + struct CFileHeaderFixedData *FindFileEntry( + char const *pDirname, char const *pBaseName, char const *pExtension, + uint8 **pExtBaseOut = NULL, uint8 **pNameBaseOut = NULL ); + + void BuildHashTables( void ); + + FileHandleTracker_t &GetFileHandle( int nFileNumber ); + + void CloseWriteHandle( void ); + + // For cache-ing directory and contents data + CUtlStringList m_directoryList; // The index of this list of directories... + CUtlMap<int, CUtlStringList*> m_dirContents; // ...is the key to this map of filenames + void BuildFindFirstCache(); + + bool InternalRemoveFileFromDirectory( const char *pszName ); + + friend class CPackedStoreReadCache; +}; + +FORCEINLINE int CPackedStoreFileHandle::Read( void *pOutData, int nNumBytes ) +{ + return m_pOwner->ReadData( *this, pOutData, nNumBytes ); +} + +FORCEINLINE void CPackedStoreFileHandle::GetPackFileName( char *pchFileNameOut, int cchFileNameOut ) +{ + m_pOwner->GetPackFileName( *this, pchFileNameOut, cchFileNameOut ); +} + + +#endif // packedtsore_h |