1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
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
|