|
|
//========= 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 <tier2/tier2.h>
#include "filesystem.h"
#include "tier1/utlintrusivelist.h"
#include "tier1/utlvector.h"
#include "tier1/utlsortvector.h"
#include "tier1/utlmap.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; MD5Value_t m_md5ValueRetry;
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, int &nRead ); 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, MD5Value_t &md5Value ); int FindBufferToUse(); void RereadBadCacheLine( CachedVPKRead_t &cachedVPKRead ); void RecheckBadCacheLine( CachedVPKRead_t &cachedVPKRead ); void RetryAllBadCacheLines();
// cache 8 MB total. Caching more wastes too much memory.
// On dedicated servers this cache is not used.
static const int k_nCacheBuffersToKeep = 8; 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 GetPackFileLoadErrorSummaryKV( KeyValues *pKV );
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
|