source-engine/public/vpklib/packedstore.h

463 lines
13 KiB
C
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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"
2020-11-13 13:42:13 +00:00
//#define VPK_ENABLE_SIGNING
2020-04-22 16:56:21 +00:00
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