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.

2217 lines
69 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. // Let's make sure aserts, etc are enabled for this tool
  7. #define RELEASEASSERTS
  8. #include "tier0/platform.h"
  9. #include "tier0/progressbar.h"
  10. #include "vpklib/packedstore.h"
  11. #include "mathlib/mathlib.h"
  12. #include "tier1/KeyValues.h"
  13. #include "tier2/tier2.h"
  14. #include "tier0/memdbgon.h"
  15. #include "tier2/fileutils.h"
  16. #include "tier1/utldict.h"
  17. #include "tier1/utlbuffer.h"
  18. #ifdef VPK_ENABLE_SIGNING
  19. #include "crypto.h"
  20. #endif
  21. static bool s_bBeVerbose = false;
  22. static bool s_bMakeMultiChunk = false;
  23. static bool s_bUseSteamPipeFriendlyBuilder = false;
  24. static int s_iMultichunkSize = k_nVPKDefaultChunkSize / ( 1024 * 1024 );
  25. const int k_nVPKDefaultChunkAlign = 1;
  26. static int s_iChunkAlign = k_nVPKDefaultChunkAlign;
  27. static CUtlString s_sPrivateKeyFile;
  28. static CUtlString s_sPublicKeyFile;
  29. static void PrintArgSummaryAndExit( int iReturnCode = 1 )
  30. {
  31. fflush(stderr);
  32. printf(
  33. "Usage: vpk [options] <command> <command arguments ...>\n"
  34. " vpk [options] <directory>\n"
  35. " vpk [options] <vpkfile>\n"
  36. "\n"
  37. "CREATE VPK / ADD FILES:\n"
  38. " vpk <dirname>\n"
  39. " Creates a pack file named <dirname>.vpk located\n"
  40. " in the parent of the specified directory.\n"
  41. " vpk a <vpkfile> <filename1> <filename2> ...\n"
  42. " Add file(s).\n"
  43. " vpk a <vpkfile> @<filename>\n"
  44. " Add files listed in a response file.\n"
  45. " vpk k <vpkfile> <keyvalues_filename>\n"
  46. " Add files listed in a keyvalues control file.\n"
  47. " vpk <directory>\n"
  48. " Create VPK from directory structure. (This is invoked when\n"
  49. " a directory is dragged onto the VPK tool.)\n"
  50. "\n"
  51. "EXTRACT FILES:\n"
  52. " vpk x <vpkfile> <filename1> <filename2> ...\n"
  53. " Extract file(s).\n"
  54. " vpk <vpkfile>\n"
  55. " Extract all files from VPK. (This is invoked when\n"
  56. " a .VPK file is dragged onto the VPK tool.)\n"
  57. "\n"
  58. "DISPLAY VPK INFO:\n"
  59. " vpk l <vpkfile>\n"
  60. " List contents of VPK.\n"
  61. " vpk L <vpkfile>\n"
  62. " List contents (detailed) of VPK.\n"
  63. #ifdef VPK_ENABLE_SIGNING
  64. " vpk dumpsig <vpkfile>\n"
  65. " Display signature information of VPK file\n"
  66. "\n"
  67. "VPK INTEGRITY / SECURITY:\n"
  68. " vpk checkhash <vpkfile>\n"
  69. " Check all VPK chunk MD5's and file CRC's.\n"
  70. " vpk checksig <vpkfile>\n"
  71. " Verify signature of specified VPK file.\n"
  72. " Requires -k to specify key file to use.\n"
  73. // " vpk rehash <vpkfile>\n"
  74. // " Recalculate chunk MD5's. (Does not recalculate file CRC's)\n"
  75. // " Can be used with -k to sign an existing unsigned VPK.\n"
  76. "\n"
  77. "MISC:\n"
  78. " vpk generate_keypair <keybasename>\n"
  79. " Generate public/private key file. Output files\n"
  80. " will be named <keybasemame>.publickey.vdf\n"
  81. " and <keybasemame>.privatekey.vdf\n"
  82. " Remember: your private key should be kept private.\n"
  83. #endif
  84. "\n"
  85. "\n"
  86. "Options:\n"
  87. " -v Verbose.\n"
  88. " -M Produce a multi-chunk pack file\n"
  89. " -P Use SteamPipe-friendly incremental build algorithm.\n"
  90. " Use with 'k' command.\n"
  91. " For optimal incremental build performance, the control file used\n"
  92. " for the previous build should exist and be named the same as the\n"
  93. " input control file, with '.bak' appended, and each file entry\n"
  94. " should have an 'md5' value. The 'md5' field need not be the\n"
  95. " actual MD5 of the file contents, it is just a unique identifier\n"
  96. " that will be compared to determine if the file contents has changed\n"
  97. " between builds.\n"
  98. " This option implies -M\n" );
  99. printf(
  100. " -c <size>\n"
  101. " Use specified chunk size (in MB). Default is %d.\n", k_nVPKDefaultChunkSize / ( 1024 * 1024 ) );
  102. printf(
  103. " -a <align>\n"
  104. " Align files within chunk on n-byte boundary. Default is %d.\n", k_nVPKDefaultChunkAlign );
  105. #ifdef VPK_ENABLE_SIGNING
  106. printf(
  107. " -K <private keyfile>\n"
  108. " With commands 'a' or 'k': Sign VPK with specified private key.\n"
  109. " -k <public keyfile>\n"
  110. " With commands 'a' or 'k': Public key that will be distributed\n"
  111. " and used by third parties to verify signatures.\n"
  112. " With command 'checksig': Check signature using specified key file.\n" );
  113. #endif
  114. exit( iReturnCode );
  115. }
  116. bool IsRestrictedFileType( const char *pcFileName )
  117. {
  118. return ( V_stristr( pcFileName, ".bat" ) || V_stristr( pcFileName, ".cmd" ) || V_stristr( pcFileName, ".com" ) || V_stristr( pcFileName, ".dll" ) ||
  119. V_stristr( pcFileName, ".exe" ) || V_stristr( pcFileName, ".msi" ) || V_stristr( pcFileName, ".rar" ) || V_stristr( pcFileName, ".reg" ) ||
  120. V_stristr( pcFileName, ".zip" ) );
  121. }
  122. void ReadFile( char const *pName )
  123. {
  124. FileHandle_t f = g_pFullFileSystem->Open( pName, "rb" );
  125. if ( f )
  126. {
  127. int fileSize = g_pFullFileSystem->Size( f );
  128. unsigned bufSize = ((IFileSystem *)g_pFullFileSystem)->GetOptimalReadSize( f, fileSize );
  129. void *buffer = ((IFileSystem *)g_pFullFileSystem)->AllocOptimalReadBuffer( f, bufSize );
  130. // read into local buffer
  131. ( ((IFileSystem *)g_pFullFileSystem)->ReadEx( buffer, bufSize, fileSize, f ) != 0 );
  132. g_pFullFileSystem->Close( f ); // close file after reading
  133. ((IFileSystem *)g_pFullFileSystem)->FreeOptimalReadBuffer( buffer );
  134. }
  135. }
  136. void BenchMark( CUtlVector<char *> &names )
  137. {
  138. for( int i = 0; i < names.Count(); i++ )
  139. ReadFile( names[i] );
  140. }
  141. static void AddFileToPack( CPackedStore &mypack, char const *pSrcName, int nPreloadSize = 0, char const *pDestName = NULL )
  142. {
  143. // Check to make sure that no restricted file types are being added to the VPK
  144. if ( IsRestrictedFileType( pSrcName ) )
  145. {
  146. printf( "Ignoring %s: unsupported file type.\n", pSrcName );
  147. return;
  148. }
  149. // !FIXME! Make sure they didn't request alignment, because we aren't doing it.
  150. if ( s_iChunkAlign != 1 )
  151. Error( "-a is only supported with -P" );
  152. if ( (! pDestName ) || ( pDestName[0] == 0 ) )
  153. {
  154. pDestName = pSrcName;
  155. }
  156. CRequiredInputFile f( pSrcName );
  157. int fileSize = f.Size();
  158. uint8 *pData = new uint8[fileSize];
  159. f.MustRead( pData, fileSize );
  160. ePackedStoreAddResultCode rslt = mypack.AddFile( pDestName, Min( fileSize, nPreloadSize ), pData, fileSize, s_bMakeMultiChunk );
  161. if ( rslt == EPADD_ERROR )
  162. {
  163. Error( "Error adding %s\n", pSrcName );
  164. }
  165. if ( s_bBeVerbose )
  166. {
  167. switch( rslt )
  168. {
  169. case EPADD_ADDSAMEFILE:
  170. {
  171. if ( s_bBeVerbose )
  172. {
  173. printf( "File %s is already in the archive with the same contents\n", pSrcName );
  174. }
  175. }
  176. break;
  177. case EPADD_UPDATEFILE:
  178. {
  179. if ( s_bBeVerbose )
  180. {
  181. printf( "File %s is already in the archive and has been updated\n", pSrcName );
  182. }
  183. }
  184. break;
  185. case EPADD_NEWFILE:
  186. {
  187. if ( s_bBeVerbose )
  188. {
  189. printf( "Add new file %s\n", pSrcName );
  190. }
  191. }
  192. break;
  193. }
  194. }
  195. delete[] pData;
  196. }
  197. #ifdef VPK_ENABLE_SIGNING
  198. static void LoadKeyFile( const char *pszFilename, const char *pszTag, CUtlVector<uint8> &outBytes )
  199. {
  200. KeyValuesAD kv("key");
  201. if ( !kv->LoadFromFile( g_pFullFileSystem, pszFilename ) )
  202. Error( "Failed to load key file %s", pszFilename );
  203. const char *pszType = kv->GetString( "type", NULL );
  204. if ( pszType == NULL )
  205. Error( "Key file %s is missing 'type'", pszFilename );
  206. if ( V_stricmp( pszType, "rsa" ) != 0 )
  207. Error( "Key type '%s' is not supported", pszType );
  208. const char *pszEncodedBytes = kv->GetString( pszTag, NULL );
  209. if ( pszEncodedBytes == NULL )
  210. Error( "Key file is missing '%s'", pszTag );
  211. uint8 rgubDecodedData[k_nRSAKeyLenMax*2];
  212. uint cubDecodedData = Q_ARRAYSIZE( rgubDecodedData );
  213. if( !CCrypto::HexDecode( pszEncodedBytes, rgubDecodedData, &cubDecodedData ) || cubDecodedData <= 0 )
  214. Error( "Key file contains invalid '%s' value", pszTag );
  215. outBytes.SetSize( cubDecodedData );
  216. V_memcpy( outBytes.Base(), rgubDecodedData, cubDecodedData );
  217. }
  218. #endif
  219. static void CheckLoadKeyFilesForSigning( CPackedStore &mypack )
  220. {
  221. // Not signing?
  222. if ( s_sPrivateKeyFile.IsEmpty() && s_sPublicKeyFile.IsEmpty() )
  223. return;
  224. // Signatures only supported if creating multi-chunk file
  225. if ( !s_bMakeMultiChunk )
  226. {
  227. Error( "Multichunk not specified. Only multi-chunk VPK's support signatures.\n" );
  228. }
  229. // If they specified one, they must specify both
  230. if ( s_sPrivateKeyFile.IsEmpty() || s_sPublicKeyFile.IsEmpty() )
  231. Error( "Must specify both public and private key files in order to sign VPK" );
  232. #ifdef VPK_ENABLE_SIGNING
  233. CUtlVector<uint8> bytesPrivateKey;
  234. LoadKeyFile( s_sPrivateKeyFile, "rsa_private_key", bytesPrivateKey );
  235. printf( "Loaded private key file %s\n", s_sPrivateKeyFile.String() );
  236. CUtlVector<uint8> bytesPublicKey;
  237. LoadKeyFile( s_sPublicKeyFile, "rsa_public_key", bytesPublicKey );
  238. printf( "Loaded public key file %s\n", s_sPublicKeyFile.String() );
  239. mypack.SetKeysForSigning( bytesPrivateKey.Count(), bytesPrivateKey.Base(), bytesPublicKey.Count(), bytesPublicKey.Base() );
  240. #else
  241. Error( "VPK signing not implemented" );
  242. #endif
  243. }
  244. class VPKBuilder
  245. {
  246. public:
  247. VPKBuilder( CPackedStore &packfile );
  248. ~VPKBuilder();
  249. void BuildFromInputKeys()
  250. {
  251. if ( s_bUseSteamPipeFriendlyBuilder )
  252. BuildSteamPipeFriendlyFromInputKeys();
  253. else
  254. BuildOldSchoolFromInputKeys();
  255. }
  256. void SetInputKeys( KeyValues *pInputKeys, const char *pszControlFilename );
  257. void LoadInputKeys( const char *pszControlFilename );
  258. private:
  259. CPackedStore &m_packfile;
  260. struct VPKBuildFile_t
  261. {
  262. VPKBuildFile_t()
  263. {
  264. m_pOld = NULL;
  265. m_pNew = NULL;
  266. m_iOldSortIndex = -1;
  267. m_iNewSortIndex = -1;
  268. m_md5Old.Zero();
  269. m_md5New.Zero();
  270. m_pOldKey = NULL;
  271. m_pNewKey = NULL;
  272. }
  273. VPKContentFileInfo_t *m_pOld;
  274. VPKContentFileInfo_t *m_pNew;
  275. int m_iOldSortIndex;
  276. int m_iNewSortIndex;
  277. KeyValues *m_pOldKey;
  278. KeyValues *m_pNewKey;
  279. MD5Value_t m_md5Old;
  280. MD5Value_t m_md5New;
  281. CUtlString m_sNameOnDisk;
  282. };
  283. static int CompareBuildFileByOldPhysicalPosition( VPKBuildFile_t* const *pa, VPKBuildFile_t* const *pb )
  284. {
  285. const VPKContentFileInfo_t *a = (*pa)->m_pOld;
  286. const VPKContentFileInfo_t *b = (*pb)->m_pOld;
  287. if ( a->m_idxChunk < b->m_idxChunk ) return -1;
  288. if ( a->m_idxChunk > b->m_idxChunk ) return +1;
  289. if ( a->m_iOffsetInChunk < b->m_iOffsetInChunk ) return -1;
  290. if ( a->m_iOffsetInChunk > b->m_iOffsetInChunk ) return +1;
  291. return 0;
  292. }
  293. CUtlString m_sControlFilename;
  294. /// List of all files, past and present, keyed by the name in the VPK.
  295. CUtlDict<VPKBuildFile_t> m_dictFiles;
  296. /// All files as they existed in the old VPK. (Empty if we are building from scratch.)
  297. CUtlVector<VPKContentFileInfo_t> m_vecOldFiles;
  298. /// List of all new files.
  299. CUtlVector<VPKContentFileInfo_t *> m_vecNewFiles;
  300. /// List of new files, in the requested order, only counting those
  301. /// that will actually go into a chunk
  302. CUtlVector<VPKContentFileInfo_t *> m_vecNewFilesInChunkOrder;
  303. /// List of old files that have some content in a chunk file,
  304. /// in the order they currently appear
  305. CUtlVector<VPKBuildFile_t *> m_vecOldFilesInChunkOrder;
  306. int64 m_iNewTotalFileSize;
  307. int64 m_iNewTotalFileSizeInChunkFiles;
  308. /// A group of files that are contiguous in the logical linear
  309. /// file list.
  310. struct VPKInputFileRange_t
  311. {
  312. int m_iFirstInputFile; // index of first input file in the chunk
  313. int m_iLastInputFile; // index of last input file in the chunk
  314. int m_iChunkFilenameIndex;
  315. bool m_bKeepExistingFile;
  316. int64 m_nTotalSizeInChunkFile;
  317. int FileCount() const
  318. {
  319. int iResult = m_iLastInputFile - m_iFirstInputFile + 1;
  320. Assert( iResult > 0 );
  321. return iResult;
  322. }
  323. };
  324. KeyValues *m_pInputKeys;
  325. KeyValues *m_pOldInputKeys;
  326. CUtlLinkedList<VPKInputFileRange_t,int> m_llFileRanges;
  327. CUtlVector<int> m_vecRangeForChunk;
  328. CUtlString m_sReasonToForceWriteDirFile;
  329. void BuildOldSchoolFromInputKeys();
  330. void BuildSteamPipeFriendlyFromInputKeys();
  331. void SanityCheckRanges();
  332. void SplitRangeAt( int iFirstInputFile );
  333. void AddRange( VPKInputFileRange_t range );
  334. void MapRangeToChunk( int idxRange, int iChunkFilenameIndex, bool bKeepExistingFile );
  335. void CalculateRangeTotalSizeInChunkFile( VPKInputFileRange_t &range ) const;
  336. void UnmapAllRangesForChangedChunks();
  337. void CoaleseAllUnmappedRanges();
  338. void PrintRangeDebug();
  339. void MapAllRangesToChunks();
  340. };
  341. VPKBuilder::VPKBuilder( CPackedStore &packfile )
  342. : m_packfile( packfile )
  343. {
  344. CUtlVector<uint8> savePublicKey;
  345. savePublicKey = m_packfile.GetSignaturePublicKey();
  346. CheckLoadKeyFilesForSigning( m_packfile );
  347. if ( savePublicKey.Count() != m_packfile.GetSignaturePublicKey().Count()
  348. || V_memcmp( savePublicKey.Base(), m_packfile.GetSignaturePublicKey().Base(), savePublicKey.Count() ) != 0 )
  349. {
  350. if ( m_packfile.GetSignaturePublicKey().Count() == 0 )
  351. {
  352. m_sReasonToForceWriteDirFile = "Signature removed.";
  353. }
  354. else if ( savePublicKey.Count() == 0 )
  355. {
  356. m_sReasonToForceWriteDirFile = "Signature added.";
  357. }
  358. else
  359. {
  360. m_sReasonToForceWriteDirFile = "Public key used for signing changed.";
  361. }
  362. }
  363. m_pInputKeys = NULL;
  364. m_pOldInputKeys = NULL;
  365. // !FIXME! Check if public key is changing so we know if we need to re-sign!
  366. }
  367. VPKBuilder::~VPKBuilder()
  368. {
  369. if ( m_pInputKeys )
  370. m_pInputKeys->deleteThis();
  371. if ( m_pOldInputKeys )
  372. m_pOldInputKeys->deleteThis();
  373. }
  374. void VPKBuilder::BuildOldSchoolFromInputKeys()
  375. {
  376. // Just add them in order
  377. FOR_EACH_VEC( m_vecNewFiles, i )
  378. {
  379. VPKContentFileInfo_t *f = m_vecNewFiles[ i ];
  380. int idxInDict = m_dictFiles.Find( f->m_sName.String() );
  381. Assert( idxInDict >= 0 );
  382. VPKBuildFile_t *bf = &m_dictFiles[ idxInDict ];
  383. Assert( bf->m_pNew == f );
  384. AddFileToPack( m_packfile, bf->m_sNameOnDisk, f->m_iPreloadSize, f->m_sName );
  385. }
  386. if ( s_bBeVerbose )
  387. printf( "Hashing metadata.\n" );
  388. m_packfile.HashMetadata();
  389. if ( s_bBeVerbose )
  390. printf( "Writing directory file.\n" );
  391. m_packfile.Write();
  392. }
  393. void VPKBuilder::BuildSteamPipeFriendlyFromInputKeys()
  394. {
  395. // Get list of all files already in the VPK
  396. m_packfile.GetFileList( NULL, m_vecOldFiles );
  397. FOR_EACH_VEC( m_vecOldFiles, i )
  398. {
  399. VPKContentFileInfo_t *f = &m_vecOldFiles[i];
  400. char szNameInVPK[ MAX_PATH ];
  401. V_strcpy_safe( szNameInVPK, f->m_sName );
  402. V_FixSlashes( szNameInVPK, '\\' ); // always use Windows slashes in VPK
  403. f->m_sName = szNameInVPK;
  404. // Add it to the dictionary
  405. int idxInDict = m_dictFiles.Find( szNameInVPK );
  406. if ( idxInDict == m_dictFiles.InvalidIndex() )
  407. idxInDict = m_dictFiles.Insert( szNameInVPK );
  408. // Each logical file should only be in a VPK file once
  409. if ( m_dictFiles[ idxInDict ].m_pOld )
  410. Error( "File '%s' is listed in VPK directory multiple times?! Cannot build incrementally.\n", szNameInVPK );
  411. m_dictFiles[ idxInDict ].m_pOld = f;
  412. }
  413. // See if we should build incrementally
  414. bool bIncremental = ( m_vecOldFiles.Count() > 0 ) && !m_sControlFilename.IsEmpty();
  415. if ( bIncremental )
  416. {
  417. printf( "Building incrementally in SteamPipe-friendly manner.\n" );
  418. printf( "Existing pack file contains %d files\n", m_vecOldFiles.Count() );
  419. CUtlString sControlFilenameBak = m_sControlFilename;
  420. sControlFilenameBak += ".bak";
  421. m_pOldInputKeys = new KeyValues( "oldkeys" );
  422. if ( m_pOldInputKeys->LoadFromFile( g_pFullFileSystem, sControlFilenameBak ) )
  423. {
  424. printf( "Loaded %s OK\n", sControlFilenameBak.String() );
  425. printf( "Fetching MD5's and checking that it matches the pack file\n" );
  426. for ( KeyValues *i = m_pOldInputKeys; i; i = i->GetNextKey() )
  427. {
  428. const char *pszNameOnDisk = i->GetString( "srcpath", i->GetName() );
  429. char szNameInVPK[ MAX_PATH ];
  430. V_strcpy_safe( szNameInVPK, i->GetString( "destpath", "" ) );
  431. if ( szNameInVPK[0] == '\0' )
  432. Error( "File '%s' is missing 'destpath' in old KeyValues control file", pszNameOnDisk );
  433. V_FixSlashes( szNameInVPK, '\\' ); // always use Windows slashes in VPK
  434. // Locate file build entry. We should have one in the VPK
  435. int idxInDict = m_dictFiles.Find( szNameInVPK );
  436. if ( idxInDict == m_dictFiles.InvalidIndex() || m_dictFiles[ idxInDict ].m_pOld == NULL )
  437. Error( "File '%s' in old KeyValues control file not found in pack file.\nThat control file was probably not used to build the pack file\n", szNameInVPK );
  438. VPKBuildFile_t &bf = m_dictFiles[ idxInDict ];
  439. if ( bf.m_pOldKey )
  440. Error( "File '%s' appears multiple times in old KeyValues control file.\nThat control file was probably not used to build the pack file\n", szNameInVPK );
  441. bf.m_pOldKey = i;
  442. // Fetch preload size from old KV, clamp to actual file size.
  443. int iPreloadSizeFromControlFile = i->GetInt( "preloadsize", 0 );
  444. iPreloadSizeFromControlFile = Min( iPreloadSizeFromControlFile, (int)bf.m_pOld->m_iTotalSize );
  445. if ( iPreloadSizeFromControlFile != (int)bf.m_pOld->m_iPreloadSize )
  446. Error( "File '%s' preload size mismatch in old KeyValues control file and pack file.\nThat control file was probably not used to build the pack file\n", szNameInVPK );
  447. const char *pszMD5 = i->GetString( "md5", "" );
  448. if ( *pszMD5 )
  449. {
  450. if ( V_strlen( pszMD5 ) != MD5_DIGEST_LENGTH*2 )
  451. Error( "File '%s' has invalid MD5 '%s'", pszNameOnDisk, pszMD5 );
  452. V_hextobinary( pszMD5, MD5_DIGEST_LENGTH*2, bf.m_md5Old.bits, MD5_DIGEST_LENGTH );
  453. }
  454. else
  455. {
  456. printf( "WARNING: Old control file entry '%s' does not have an MD5; we will have to compare file contents for this file.\n", pszNameOnDisk );
  457. }
  458. }
  459. // Now many sure every file in the pack was found in the control file. If not, then
  460. // they probably don't match and we should not trust the MD5's.
  461. FOR_EACH_DICT_FAST( m_dictFiles, idxInDict )
  462. {
  463. VPKBuildFile_t &bf = m_dictFiles[ idxInDict ];
  464. if ( bf.m_pOld && bf.m_pOldKey == NULL )
  465. Error( "File '%s' is in pack but not in old control file %s.\n"
  466. "That control file was probably not used to build the pack file", bf.m_pOld->m_sName.String(), sControlFilenameBak.String() );
  467. }
  468. printf( "%s appears to match VPK file.\nUsing MD5s for incremental building\n", sControlFilenameBak.String() );
  469. }
  470. else
  471. {
  472. printf( "WARNING: %s not present; incremental building will be slow.\n", sControlFilenameBak.String() );
  473. printf( " For best results, provide the control file previously used for building.\n" );
  474. m_pOldInputKeys->deleteThis();
  475. m_pOldInputKeys = NULL;
  476. }
  477. }
  478. else
  479. {
  480. printf( "Building pack file from scratch.\n" );
  481. }
  482. // Dictionary is now complete. Gather up list of files in order
  483. // sorted by where they were in the old pack set
  484. FOR_EACH_DICT_FAST( m_dictFiles, i )
  485. {
  486. VPKBuildFile_t *f = &m_dictFiles[i];
  487. if ( f->m_pOld && f->m_pOld->GetSizeInChunkFile() > 0 )
  488. m_vecOldFilesInChunkOrder.AddToTail( f );
  489. }
  490. m_vecOldFilesInChunkOrder.Sort( CompareBuildFileByOldPhysicalPosition );
  491. FOR_EACH_VEC( m_vecOldFilesInChunkOrder, i )
  492. {
  493. m_vecOldFilesInChunkOrder[i]->m_iOldSortIndex = i;
  494. }
  495. // How many chunks are currently in the VPK. (Might be zero)
  496. int nOldChunkCount = 0;
  497. if ( m_vecOldFilesInChunkOrder.Count() > 0 )
  498. nOldChunkCount = m_vecOldFilesInChunkOrder[ m_vecOldFilesInChunkOrder.Count()-1 ]->m_pOld->m_idxChunk + 1;
  499. // For each chunk filename (_nnn.vpk), remember which block
  500. // of files maps will be used to create it.
  501. // None of the chunks have been assigned a block of files yet
  502. for ( int i = 0 ; i < nOldChunkCount ; ++i )
  503. m_vecRangeForChunk.AddToTail( m_llFileRanges.InvalidIndex() );
  504. // Start by putting all the files into a single range
  505. // with no corresponding chunk
  506. VPKInputFileRange_t rangeAllFiles;
  507. rangeAllFiles.m_iChunkFilenameIndex = -1;
  508. rangeAllFiles.m_iFirstInputFile = 0;
  509. rangeAllFiles.m_iLastInputFile = m_vecNewFilesInChunkOrder.Count()-1;
  510. rangeAllFiles.m_bKeepExistingFile = false;
  511. CalculateRangeTotalSizeInChunkFile( rangeAllFiles );
  512. m_llFileRanges.AddToTail( rangeAllFiles );
  513. SanityCheckRanges();
  514. // Building incrementally?
  515. if ( bIncremental && nOldChunkCount > 0 )
  516. {
  517. printf( "Scanning for unchanged chunk files...\n" );
  518. // For each existing chunk, see if it's totally modified or not.
  519. // In our case, since SteamPipe rewrites an entire file from scratch
  520. // anytime a single byte changes, we don't care how much a chunk
  521. // file changes, we only need to detect if we can carry it forward
  522. // exactly as is or not.
  523. int idxOldFile = 0;
  524. while ( idxOldFile < m_vecOldFilesInChunkOrder.Count() )
  525. {
  526. // What chunk are we in?
  527. VPKBuildFile_t const &firstFile = *m_vecOldFilesInChunkOrder[ idxOldFile ];
  528. int idxChunk = firstFile.m_pOld->m_idxChunk;
  529. char szDataFilename[ MAX_PATH ];
  530. m_packfile.GetDataFileName( szDataFilename, sizeof(szDataFilename), idxChunk );
  531. const char *pszShortDataFilename = V_GetFileName( szDataFilename );
  532. int idxInChunk = 0;
  533. CUtlVector<int> vecFilesToCompareContents;
  534. // Scan to the end of files in this chunk.
  535. CUtlString sReasonCannotReuse;
  536. while ( idxOldFile < m_vecOldFilesInChunkOrder.Count() )
  537. {
  538. VPKBuildFile_t const &f = *m_vecOldFilesInChunkOrder[ idxOldFile ];
  539. Assert( f.m_iOldSortIndex == idxOldFile );
  540. // End of this old chunk?
  541. VPKContentFileInfo_t const *pOld = f.m_pOld;
  542. Assert( pOld );
  543. if ( idxChunk != pOld->m_idxChunk )
  544. break;
  545. Assert( f.m_iOldSortIndex == firstFile.m_iOldSortIndex + idxInChunk );
  546. if ( sReasonCannotReuse.IsEmpty() )
  547. {
  548. VPKContentFileInfo_t const *pNew = f.m_pNew;
  549. int iExpectedSortIndex = firstFile.m_iNewSortIndex + idxInChunk;
  550. const char *pszFilename = pOld->m_sName.String();
  551. if ( pNew == NULL )
  552. {
  553. sReasonCannotReuse.Format( "File '%s' was removed.", pszFilename );
  554. }
  555. else if ( pOld->m_iTotalSize != pNew->m_iTotalSize )
  556. {
  557. sReasonCannotReuse.Format( "File '%s' changed size.", pszFilename );
  558. }
  559. else if ( pOld->m_iPreloadSize != pNew->m_iPreloadSize )
  560. {
  561. sReasonCannotReuse.Format( "File '%s' changed preload size.", pszFilename );
  562. }
  563. else if ( f.m_iNewSortIndex != iExpectedSortIndex )
  564. {
  565. // Files reordered in some way. Try to give an appropriate message
  566. if ( f.m_iNewSortIndex > iExpectedSortIndex && iExpectedSortIndex < m_vecNewFilesInChunkOrder.Count() )
  567. {
  568. VPKContentFileInfo_t const *pInsertedFile = m_vecNewFilesInChunkOrder[ iExpectedSortIndex ];
  569. const char *pszInsertedFilename = pInsertedFile->m_sName.String();
  570. int idxDictInserted = m_dictFiles.Find( pszInsertedFilename );
  571. Assert( idxDictInserted != m_dictFiles.InvalidIndex() );
  572. if ( m_dictFiles[idxDictInserted].m_pOld == NULL )
  573. sReasonCannotReuse.Format( "File '%s' was inserted\n", pszInsertedFilename );
  574. else
  575. sReasonCannotReuse.Format( "Chunk reordered. '%s' listed where '%s' used to be.", pszInsertedFilename, pszFilename );
  576. }
  577. else
  578. {
  579. sReasonCannotReuse.Format( "Chunk was reordered. File '%s' was moved.", pszFilename );
  580. }
  581. }
  582. else if ( f.m_md5Old.IsZero() || f.m_md5New.IsZero() )
  583. {
  584. vecFilesToCompareContents.AddToTail( idxOldFile );
  585. }
  586. else if ( f.m_md5Old != f.m_md5New )
  587. {
  588. sReasonCannotReuse.Format( "File '%s' changed. (Based on MD5s in control file.)", pszFilename );
  589. }
  590. }
  591. ++idxOldFile;
  592. ++idxInChunk;
  593. }
  594. // Check if we need to actually compare any file contents
  595. if ( sReasonCannotReuse.IsEmpty() && vecFilesToCompareContents.Count() > 0 )
  596. {
  597. // We'll have to actually load the source file
  598. // and compare the CRC
  599. printf( "%s: Checking for differences using file CRCs...\n", pszShortDataFilename );
  600. FOR_EACH_VEC( vecFilesToCompareContents, i )
  601. {
  602. VPKBuildFile_t const &f = *m_vecOldFilesInChunkOrder[ vecFilesToCompareContents[i] ];
  603. Assert( f.m_pOld );
  604. // Load the input file
  605. CUtlBuffer buf;
  606. if ( !g_pFullFileSystem->ReadFile( f.m_sNameOnDisk, NULL, buf )
  607. || buf.TellPut() != (int)f.m_pOld->m_iTotalSize )
  608. {
  609. Error( "Error reading %s", f.m_sNameOnDisk.String() );
  610. }
  611. // Calculate the CRC
  612. uint32 crc = CRC32_ProcessSingleBuffer( buf.Base(), f.m_pOld->m_iTotalSize );
  613. // Mismatch?
  614. if ( crc != f.m_pOld->m_crc )
  615. {
  616. sReasonCannotReuse.Format( "File '%s' changed. (CRCs differs from %s.)", f.m_pOld->m_sName.String(), f.m_sNameOnDisk.String() );
  617. break;
  618. }
  619. }
  620. }
  621. // Can we take this file as is?
  622. if ( sReasonCannotReuse.IsEmpty() )
  623. {
  624. printf( "%s could be reused.\n", pszShortDataFilename );
  625. // Map the chunk
  626. VPKInputFileRange_t chunkRange;
  627. chunkRange.m_iChunkFilenameIndex = idxChunk;
  628. chunkRange.m_iFirstInputFile = firstFile.m_iNewSortIndex;
  629. chunkRange.m_iLastInputFile = firstFile.m_iNewSortIndex + idxInChunk - 1;
  630. chunkRange.m_bKeepExistingFile = true;
  631. AddRange( chunkRange );
  632. }
  633. else
  634. {
  635. printf( "%s cannot be reused. %s\n", pszShortDataFilename, sReasonCannotReuse.String() );
  636. }
  637. }
  638. }
  639. // Take file ranges that are not mapped to a chunk, and map them.
  640. MapAllRangesToChunks();
  641. int nNewChunkCount = m_llFileRanges.Count();
  642. printf( "Pack file will contain %d chunk files\n", nNewChunkCount );
  643. // Remove files from directory that have been deleted
  644. int iFilesRemoved = 0;
  645. FOR_EACH_DICT_FAST( m_dictFiles, i )
  646. {
  647. const VPKBuildFile_t &bf = m_dictFiles[i];
  648. if ( bf.m_pOld && !bf.m_pNew )
  649. m_packfile.RemoveFileFromDirectory( bf.m_pOld->m_sName.String() );
  650. }
  651. printf( "Removing %d files from the directory\n", iFilesRemoved );
  652. // Make sure ranges are cool
  653. SanityCheckRanges();
  654. // Grow chunk -> range table as necessary
  655. while ( m_vecRangeForChunk.Count() < nNewChunkCount )
  656. m_vecRangeForChunk.AddToTail( m_llFileRanges.InvalidIndex() );
  657. // OK, at this point, we're ready to assign any ranges that have
  658. // not yet been assigned a range an appropriate range index
  659. int idxChunk = 0;
  660. int iChunksToKeep = 0;
  661. int iFilesToKeep = 0;
  662. int64 iChunkSizeToKeep = 0;
  663. int iChunksToWrite = 0;
  664. int iFilesToWrite = 0;
  665. int64 iChunkSizeToWrite = 0;
  666. FOR_EACH_LL( m_llFileRanges, idxRange )
  667. {
  668. VPKInputFileRange_t &r = m_llFileRanges[ idxRange ];
  669. if ( r.m_iChunkFilenameIndex >= 0 )
  670. {
  671. Assert( r.m_bKeepExistingFile );
  672. iChunksToKeep += 1;
  673. iChunkSizeToKeep += r.m_nTotalSizeInChunkFile;
  674. iFilesToKeep += r.FileCount();
  675. continue;
  676. }
  677. // Range has not been assigned a chunk.
  678. // Locate the next chunk index
  679. // that has not been assigned to a range
  680. while ( m_vecRangeForChunk[idxChunk] != m_llFileRanges.InvalidIndex() )
  681. {
  682. ++idxChunk;
  683. Assert( idxChunk < nNewChunkCount );
  684. }
  685. // Map the range
  686. MapRangeToChunk( idxRange, idxChunk, false );
  687. ++idxChunk;
  688. Assert( idxChunk <= nNewChunkCount );
  689. iChunksToWrite += 1;
  690. iChunkSizeToWrite += r.m_nTotalSizeInChunkFile;
  691. iFilesToWrite += r.FileCount();
  692. }
  693. // Now scan chunks in order, and write and chunks that changed.
  694. bool bNeedToWriteDir = false;
  695. for ( int idxChunk = 0 ; idxChunk < nNewChunkCount ; ++idxChunk )
  696. {
  697. int idxRange = m_vecRangeForChunk[ idxChunk ];
  698. VPKInputFileRange_t &r = m_llFileRanges[ idxRange ];
  699. char szDataFilename[ MAX_PATH ];
  700. m_packfile.GetDataFileName( szDataFilename, sizeof(szDataFilename), idxChunk );
  701. const char *pszShortDataFilename = V_GetFileName( szDataFilename );
  702. // Dump info about the chunk and what we're doing with it
  703. printf(
  704. "%s %s (%d files, %lld bytes)\n",
  705. r.m_bKeepExistingFile ? "Keeping" : "Writing",
  706. pszShortDataFilename,
  707. r.FileCount(),
  708. (long long)r.m_nTotalSizeInChunkFile
  709. );
  710. if ( s_bBeVerbose )
  711. {
  712. printf( " First file: %s\n", m_vecNewFilesInChunkOrder[ r.m_iFirstInputFile ]->m_sName.String() );
  713. printf( " Last file : %s\n", m_vecNewFilesInChunkOrder[ r.m_iLastInputFile ]->m_sName.String() );
  714. }
  715. // Retaining the existing file?
  716. if ( r.m_bKeepExistingFile )
  717. {
  718. // Mark the input files in this chunk as having been assigned to this chunk.
  719. for ( int idxFile = r.m_iFirstInputFile ; idxFile <= r.m_iLastInputFile ; ++idxFile )
  720. {
  721. VPKContentFileInfo_t *f = m_vecNewFilesInChunkOrder[ idxFile ];
  722. f->m_idxChunk = idxChunk;
  723. }
  724. continue;
  725. }
  726. // Create the output file.
  727. FileHandle_t fChunkWrite = g_pFullFileSystem->Open( szDataFilename, "wb" );
  728. if ( !fChunkWrite )
  729. Error( "Can't create %s\n", szDataFilename );
  730. // Scan input files in order.
  731. uint32 iOffsetInChunk = 0;
  732. for ( int idxFile = r.m_iFirstInputFile ; idxFile <= r.m_iLastInputFile ; ++idxFile )
  733. {
  734. VPKContentFileInfo_t *f = m_vecNewFilesInChunkOrder[ idxFile ];
  735. int idxInDict = m_dictFiles.Find( f->m_sName.String() );
  736. Assert( idxInDict >= 0 );
  737. VPKBuildFile_t *bf = &m_dictFiles[ idxInDict ];
  738. Assert( bf->m_pNew == f );
  739. // Load the input file
  740. CUtlBuffer buf;
  741. if ( !g_pFullFileSystem->ReadFile( bf->m_sNameOnDisk, NULL, buf )
  742. || buf.TellPut() != (int)f->m_iTotalSize )
  743. {
  744. Error( "Error reading %s", bf->m_sNameOnDisk.String() );
  745. }
  746. Assert( iOffsetInChunk == g_pFullFileSystem->Tell( fChunkWrite ) );
  747. // Calculate the CRC
  748. f->m_crc = CRC32_ProcessSingleBuffer( buf.Base(), f->m_iTotalSize );
  749. // Finish filling in all of the header
  750. f->m_iOffsetInChunk = iOffsetInChunk;
  751. f->m_idxChunk = idxChunk;
  752. f->m_pPreloadData = buf.Base();
  753. // Update the directory
  754. m_packfile.AddFileToDirectory( *f );
  755. // Write the data
  756. int nBytesToWrite = f->GetSizeInChunkFile();
  757. int nBytesWritten = g_pFullFileSystem->Write( (byte*)buf.Base() + f->m_iPreloadSize, nBytesToWrite, fChunkWrite );
  758. if ( nBytesWritten != nBytesToWrite )
  759. Error( "Error writing %s", szDataFilename );
  760. iOffsetInChunk += nBytesToWrite;
  761. Assert( iOffsetInChunk == g_pFullFileSystem->Tell( fChunkWrite ) );
  762. // Align
  763. Assert( s_iChunkAlign > 0 );
  764. while ( iOffsetInChunk % s_iChunkAlign )
  765. {
  766. unsigned char zero = 0;
  767. g_pFullFileSystem->Write( &zero, 1, fChunkWrite );
  768. ++iOffsetInChunk;
  769. }
  770. // Let's clear this pointer just for grins
  771. f->m_pPreloadData = NULL;
  772. }
  773. g_pFullFileSystem->Close( fChunkWrite );
  774. // While we know the data is sitting in the OS file cache,
  775. // let's immediately re-calc the chunk hashes
  776. m_packfile.HashChunkFile( idxChunk );
  777. // We'll need to re-save the directory
  778. bNeedToWriteDir = true;
  779. }
  780. // Delete any extra chunks that aren't needed anymore
  781. for ( int iChunkToDelete = nNewChunkCount ; iChunkToDelete < nOldChunkCount ; ++iChunkToDelete )
  782. {
  783. char szDataFilename[ MAX_PATH ];
  784. m_packfile.GetDataFileName( szDataFilename, sizeof(szDataFilename), iChunkToDelete );
  785. printf( "Deleting %s.\n", szDataFilename );
  786. g_pFullFileSystem->RemoveFile( szDataFilename );
  787. if ( g_pFullFileSystem->FileExists( szDataFilename ) )
  788. Error( "Failed to delete %s\n", szDataFilename );
  789. m_packfile.DiscardChunkHashes( iChunkToDelete );
  790. // We'll need to re-save the directory
  791. bNeedToWriteDir = true;
  792. }
  793. if ( s_bBeVerbose )
  794. {
  795. printf( "Chunk files: %12s%12s\n", "Retained", "Written" );
  796. printf( " Pack file chunks: %12d%12d\n", iChunksToKeep, iChunksToWrite );
  797. printf( " Data files: %12d%12d\n", iFilesToKeep, iFilesToWrite );
  798. printf( " Bytes in chunk: %12lld%12lld\n", (long long)iChunkSizeToKeep, (long long)iChunkSizeToWrite );
  799. }
  800. // Finally, scan for any files that need to go in the directory,
  801. // but don't have any data in a chunk. (Zero byte files, or all
  802. // data is in the preload area.)
  803. FOR_EACH_DICT( m_dictFiles, idxInDict )
  804. {
  805. VPKBuildFile_t *bf = &m_dictFiles[ idxInDict ];
  806. VPKContentFileInfo_t *pNew = bf->m_pNew;
  807. if ( pNew == NULL || pNew->m_idxChunk >= 0 )
  808. continue;
  809. Assert( pNew->GetSizeInChunkFile() == 0 );
  810. // Check if the file has changed and we need to update the directory
  811. VPKContentFileInfo_t *pOld = bf->m_pOld;
  812. int iNeedToUpdateFile = 1;
  813. if ( pOld )
  814. {
  815. if ( pOld->m_iTotalSize != pNew->m_iTotalSize
  816. || pOld->m_iPreloadSize != pNew->m_iPreloadSize )
  817. {
  818. iNeedToUpdateFile = 1;
  819. }
  820. else if ( !bf->m_md5Old.IsZero() && !bf->m_md5New.IsZero() )
  821. {
  822. // We have hashes and can make the determination purely from the hashes
  823. if ( bf->m_md5Old == bf->m_md5New )
  824. iNeedToUpdateFile = 0;
  825. else
  826. iNeedToUpdateFile = 1;
  827. }
  828. else
  829. {
  830. // Not able to make a determination without loading the file
  831. iNeedToUpdateFile = -1;
  832. }
  833. }
  834. // Might we need to update the file?
  835. if ( iNeedToUpdateFile == 0 )
  836. {
  837. // We were able to determine that the files match, and
  838. // we know that there's no need to load the input file or
  839. // check CRC's
  840. continue;
  841. }
  842. // If we get here, we might need to update the header.
  843. // Load the file
  844. CUtlBuffer buf;
  845. if ( !g_pFullFileSystem->ReadFile( bf->m_sNameOnDisk, NULL, buf )
  846. || buf.TellPut() != (int)pNew->m_iTotalSize )
  847. {
  848. Error( "Error reading %s", bf->m_sNameOnDisk.String() );
  849. }
  850. // Calculate the CRC
  851. pNew->m_crc = CRC32_ProcessSingleBuffer( buf.Base(), pNew->m_iTotalSize );
  852. // Compare CRC's
  853. if ( iNeedToUpdateFile < 0 )
  854. {
  855. Assert( pOld );
  856. if ( pOld->m_crc == pNew->m_crc )
  857. continue;
  858. }
  859. // We need to add the file to the header
  860. if ( pNew->m_iPreloadSize > 0 )
  861. pNew->m_pPreloadData = buf.Base();
  862. else
  863. Assert( pNew->m_iTotalSize == 0 );
  864. // Write the directory entry. This will make a copy of any preload data
  865. m_packfile.AddFileToDirectory( *pNew );
  866. // Let's clear this pointer just for grins
  867. pNew->m_pPreloadData = NULL;
  868. // We'll need to re-save the directory
  869. bNeedToWriteDir = true;
  870. }
  871. // Nothing changed?
  872. if ( !bNeedToWriteDir )
  873. {
  874. if ( m_sReasonToForceWriteDirFile.IsEmpty() )
  875. {
  876. printf( "Nothing changed; not writing directory file.\n" );
  877. return;
  878. }
  879. printf( "VPK contents not changed, but directory needs to be resaved. %s.\n", m_sReasonToForceWriteDirFile.String() );
  880. }
  881. if ( s_bBeVerbose )
  882. printf( "Hashing metadata.\n" );
  883. m_packfile.HashMetadata();
  884. if ( s_bBeVerbose )
  885. printf( "Writing directory file.\n" );
  886. m_packfile.Write();
  887. }
  888. void VPKBuilder::LoadInputKeys( const char *pszControlFilename )
  889. {
  890. KeyValues *pInputKeys = new KeyValues( "packkeys" );
  891. if ( !pInputKeys->LoadFromFile( g_pFullFileSystem, pszControlFilename ) )
  892. Error( "Failed to load %s", pszControlFilename );
  893. SetInputKeys( pInputKeys, pszControlFilename );
  894. }
  895. void VPKBuilder::SetInputKeys( KeyValues *pInputKeys, const char *pszControlFilename )
  896. {
  897. m_pInputKeys = pInputKeys;
  898. m_sControlFilename = pszControlFilename;
  899. m_iNewTotalFileSize = 0;
  900. m_iNewTotalFileSizeInChunkFiles = 0;
  901. int iSortIndex = 0;
  902. for ( KeyValues *i = m_pInputKeys; i; i = i->GetNextKey() )
  903. {
  904. const char *pszNameOnDisk = i->GetString( "srcpath", i->GetName() );
  905. char szNameInVPK[ MAX_PATH ];
  906. V_strcpy_safe( szNameInVPK, i->GetString( "destpath", "" ) );
  907. if ( szNameInVPK[0] == '\0' )
  908. Error( "File '%s' is missing 'destpath' in KeyValues control file", pszNameOnDisk );
  909. V_FixSlashes( szNameInVPK, '\\' ); // always use Windows slashes in VPK
  910. // Fail if passed an absolute path.
  911. if ( szNameInVPK[0] == '\\' )
  912. Error( "destpath '%s' is an absolute path; only relative paths should be used", szNameInVPK );
  913. // Check to make sure that no restricted file types are being added to the VPK.
  914. if ( IsRestrictedFileType( szNameInVPK ) )
  915. {
  916. printf( "WARNING: Control file lists '%s'. We cannot put that type of file in the pack.\n", szNameInVPK );
  917. continue;
  918. }
  919. // Make sure we have a dictionary entry
  920. int idxInDict = m_dictFiles.Find( szNameInVPK );
  921. if ( idxInDict == m_dictFiles.InvalidIndex() )
  922. idxInDict = m_dictFiles.Insert( szNameInVPK );
  923. VPKBuildFile_t &bf = m_dictFiles[ idxInDict ];
  924. if ( !bf.m_sNameOnDisk.IsEmpty() || bf.m_pNew || bf.m_iNewSortIndex >= 0 )
  925. Error( "destpath '%s' in VPK appears multiple times in the KV control file.\n (Source files '%s' and '%s')", szNameInVPK, bf.m_sNameOnDisk.String(), pszNameOnDisk );
  926. bf.m_sNameOnDisk = pszNameOnDisk;
  927. VPKContentFileInfo_t *f = new VPKContentFileInfo_t;
  928. f->m_sName = szNameInVPK;
  929. f->m_iTotalSize = g_pFullFileSystem->Size( pszNameOnDisk );
  930. f->m_iPreloadSize = Min( (uint32)i->GetInt( "preloadsize", 0 ), f->m_iTotalSize );
  931. const char *pszMD5 = i->GetString( "md5", "" );
  932. if ( *pszMD5 )
  933. {
  934. if ( V_strlen( pszMD5 ) != MD5_DIGEST_LENGTH*2 )
  935. Error( "File '%s' has invalid MD5 '%s'", pszNameOnDisk, pszMD5 );
  936. V_hextobinary( pszMD5, MD5_DIGEST_LENGTH*2, bf.m_md5New.bits, MD5_DIGEST_LENGTH );
  937. }
  938. m_vecNewFiles.AddToTail( f );
  939. bf.m_pNew = f;
  940. if ( f->GetSizeInChunkFile() > 0 )
  941. {
  942. bf.m_iNewSortIndex = iSortIndex++;
  943. m_vecNewFilesInChunkOrder.AddToTail( f );
  944. }
  945. m_iNewTotalFileSize += f->m_iTotalSize;
  946. m_iNewTotalFileSizeInChunkFiles += f->GetSizeInChunkFile();
  947. }
  948. printf( "Control file lists %d files\n", m_vecNewFiles.Count() );
  949. printf( " Total file size . . . . : %12lld bytes\n", (long long)m_iNewTotalFileSize );
  950. printf( " Size in preload area . : %12lld bytes\n", (long long)(m_iNewTotalFileSize - m_iNewTotalFileSizeInChunkFiles ) );
  951. printf( " Size in data area . . . : %12lld bytes\n", (long long)m_iNewTotalFileSizeInChunkFiles );
  952. }
  953. void VPKBuilder::MapAllRangesToChunks()
  954. {
  955. //PrintRangeDebug();
  956. // If a range is NOT at least as big as one chunk file, then we will have to merge it
  957. // with an adjacent range --- that is, we will need to unmap an adjacent range.
  958. // So the first step will be to identify which of the currently mapped ranges
  959. // to unmap in order to get rid of any ranges that cannot get mapped to a chunk.
  960. // We might have a choice in the matter, and each range that we unmap means
  961. // another file that will have to be rewritten. So the goal here is to minimize
  962. // the number/size of chunks that we unmap and force to rewrite.
  963. //
  964. // The current state of affairs should be that all mapped regions correspond to chunk
  965. // files that do not need to be rewritten, and there are no two unmapped chunk files in a row.
  966. int64 iSizeTooSmallForAChunk = (int64)m_packfile.GetWriteChunkSize() * 95 / 100;
  967. for (;;)
  968. {
  969. for (;;)
  970. {
  971. // Make sure the problem of small chunks can be solved by unmapping
  972. // a mapped chunk
  973. UnmapAllRangesForChangedChunks();
  974. CoaleseAllUnmappedRanges();
  975. // Find a mapped region next to a region that's too
  976. // small to get its own chunk. If there are multiple,
  977. // we'll choose the "best" one to coalesce according to
  978. // a greedy algorithm.
  979. int idxBestRangeToUnmap = m_llFileRanges.InvalidIndex();
  980. int iBestScore = -1;
  981. int64 iBestSize = -1;
  982. FOR_EACH_LL( m_llFileRanges, idxRange )
  983. {
  984. VPKInputFileRange_t &r = m_llFileRanges[ idxRange ];
  985. if ( r.m_iChunkFilenameIndex < 0 )
  986. continue;
  987. // Check if neighbors exist and are too small
  988. // for their own chunk. Calculate score heuristic
  989. // based on how good of a candidate we are to
  990. // be the one to get combined with our neighbors
  991. int iScore = 0;
  992. int idxPrev = m_llFileRanges.Previous( idxRange );
  993. if ( idxPrev != m_llFileRanges.InvalidIndex() )
  994. {
  995. VPKInputFileRange_t &p = m_llFileRanges[ idxPrev ];
  996. if ( p.m_iChunkFilenameIndex < 0 && p.m_nTotalSizeInChunkFile < iSizeTooSmallForAChunk )
  997. {
  998. ++iScore;
  999. if ( idxPrev == m_llFileRanges.Head() )
  1000. iScore += 3; // Nobody else could fix this, so we need to do it
  1001. }
  1002. }
  1003. int idxNext = m_llFileRanges.Next( idxRange );
  1004. if ( idxNext != m_llFileRanges.InvalidIndex() )
  1005. {
  1006. VPKInputFileRange_t &n = m_llFileRanges[ idxNext ];
  1007. if ( n.m_iChunkFilenameIndex < 0 && n.m_nTotalSizeInChunkFile < iSizeTooSmallForAChunk )
  1008. {
  1009. ++iScore;
  1010. if ( idxNext == m_llFileRanges.Tail() )
  1011. iScore += 3; // Nobody else could fix this, so we need to do it
  1012. }
  1013. }
  1014. // Do we have any reason at all to absorb our neighbors?
  1015. if ( iScore == 0 )
  1016. continue;
  1017. // Check if we're the best one so far to absorb our neighbor
  1018. if ( iScore < iBestScore )
  1019. continue;
  1020. // When choosing which of two neighbors should absorb a new gap, add it to the smaller one.
  1021. // (That will be less to rewrite and also keep the chunk size at a more desirable level.)
  1022. if ( iScore == iBestScore && r.m_nTotalSizeInChunkFile > iBestSize )
  1023. continue;
  1024. // We're the new best
  1025. iBestScore = iScore;
  1026. idxBestRangeToUnmap = idxRange;
  1027. iBestSize = r.m_nTotalSizeInChunkFile;
  1028. }
  1029. // Did we find a range that needed to absorb its neighbor?
  1030. if ( idxBestRangeToUnmap == m_llFileRanges.InvalidIndex() )
  1031. break;
  1032. // Unmap it
  1033. MapRangeToChunk( idxBestRangeToUnmap, -1, false );
  1034. // We'll coalesce the unmapped region with its neighbor(s) and
  1035. // start the whole process over
  1036. }
  1037. // OK, at this point, if there were any ranges that were too small to hold their
  1038. // own chunks, then we should have merged them. (Unless there is exactly one range.)
  1039. // The next step is to split up ranges that are too large for a single chunk.
  1040. SanityCheckRanges();
  1041. FOR_EACH_LL( m_llFileRanges, idxRange )
  1042. {
  1043. VPKInputFileRange_t *r = &m_llFileRanges[ idxRange ];
  1044. // Check how many chunks this
  1045. int iChunks = r->m_nTotalSizeInChunkFile / m_packfile.GetWriteChunkSize();
  1046. if ( iChunks <= 1 )
  1047. continue;
  1048. // If they consistently build with the same chunk size, then
  1049. // we should only hit this for ranges that are going to be rewritten.
  1050. // However, if this chunk is already fine as it, let's leave it alone.
  1051. // There's no reason to split it.
  1052. if ( r->m_iChunkFilenameIndex >= 0 )
  1053. {
  1054. Assert( r->m_bKeepExistingFile );
  1055. printf( "Chunk %d is currently bigger than desired chunk size of %d bytes, but we're not splitting it because the contents have not changed.\n", r->m_iChunkFilenameIndex, m_packfile.GetWriteChunkSize() );
  1056. continue;
  1057. }
  1058. // Try to split off approximately 1 N/th of the data into this chunk.
  1059. // Note that if we have big files inside, we might not have enough granularity to
  1060. // do exactly what they desire and could get caught in a bad state
  1061. int64 iDesiredSize = r->m_nTotalSizeInChunkFile / iChunks;
  1062. Assert( iDesiredSize >= m_packfile.GetWriteChunkSize() );
  1063. int iNewLastInputFile = r->m_iFirstInputFile;
  1064. int64 iNewSize = m_vecNewFilesInChunkOrder[ iNewLastInputFile ]->GetSizeInChunkFile();
  1065. while ( iNewSize < iDesiredSize && iNewLastInputFile < r->m_iLastInputFile )
  1066. {
  1067. ++iNewLastInputFile;
  1068. iNewSize += m_vecNewFilesInChunkOrder[ iNewLastInputFile ]->GetSizeInChunkFile();
  1069. }
  1070. // Do the split
  1071. int iSaveFirstInputFile = r->m_iFirstInputFile;
  1072. SplitRangeAt( iNewLastInputFile+1 );
  1073. r = &m_llFileRanges[ idxRange ]; // ranges may have moved in memory!
  1074. // Here we make an assumption that SplitRangeAt will keep range idxRange
  1075. // modified and link the new range AFTER this range. Verify that assumption.
  1076. Assert( r->m_iFirstInputFile == iSaveFirstInputFile );
  1077. Assert( r->m_iLastInputFile == iNewLastInputFile );
  1078. Assert( r->m_nTotalSizeInChunkFile == iNewSize );
  1079. // We've got this range approximately to the desired size.
  1080. // The next range should be approximately (N-1)/N as big as the original
  1081. // size of this range, and if N>2, then it wil need to be split, too
  1082. }
  1083. // OK, all ranges should now be the appropriate size, and should
  1084. // map to exactly one chunk. We just haven't assigned the chunk
  1085. // numbers yet. The important thing to realize is that the numbers
  1086. // are essentially arbitrary, and if we're going to rewrite a file,
  1087. // it doesn't matter if data moves from one chunk to another with
  1088. // a totally different number. However....leaving a gap is probably
  1089. // a bad idea. We don't know what assumptions existing tools make,
  1090. // and this could be confusing and look like a missing file. So
  1091. // if we have N chunks, we will always number them 0...N-1.
  1092. int nNewChunkCount = m_llFileRanges.Count();
  1093. // Check if the number of chunks has been reduced, and a chunk file
  1094. // that we previously thought we would be able to retain has
  1095. // a file index that won't exist any more, then let's unmap those ranges
  1096. // and start over.
  1097. bool bNeedToStartOver = false;
  1098. for ( int i = m_vecRangeForChunk.Count()-1 ; i >= nNewChunkCount ; --i )
  1099. {
  1100. int idxRange = m_vecRangeForChunk[i];
  1101. if ( idxRange == m_llFileRanges.InvalidIndex() )
  1102. continue;
  1103. Assert( m_llFileRanges[ idxRange ].m_iChunkFilenameIndex == i );
  1104. Assert( m_llFileRanges[ idxRange ].m_bKeepExistingFile );
  1105. MapRangeToChunk( idxRange, -1, false );
  1106. bNeedToStartOver = true;
  1107. }
  1108. if ( !bNeedToStartOver )
  1109. break;
  1110. // We unmapped a chunk because the chunk file is going to
  1111. // get deleted. Start all over!
  1112. }
  1113. }
  1114. void VPKBuilder::UnmapAllRangesForChangedChunks()
  1115. {
  1116. SanityCheckRanges();
  1117. FOR_EACH_LL( m_llFileRanges, idxRange )
  1118. {
  1119. VPKInputFileRange_t &r = m_llFileRanges[ idxRange ];
  1120. // If range was assigned a chunk, but the chunk file will have to be rewitten,
  1121. // then unmap it
  1122. if ( r.m_iChunkFilenameIndex >= 0 && !r.m_bKeepExistingFile )
  1123. MapRangeToChunk( idxRange, -1, false );
  1124. }
  1125. SanityCheckRanges();
  1126. }
  1127. void VPKBuilder::CoaleseAllUnmappedRanges()
  1128. {
  1129. SanityCheckRanges();
  1130. int idxRange = m_llFileRanges.Head();
  1131. for (;;)
  1132. {
  1133. int idxNext = m_llFileRanges.Next( idxRange );
  1134. if ( idxNext == m_llFileRanges.InvalidIndex() )
  1135. break;
  1136. // Grab shortcuts
  1137. VPKInputFileRange_t &ri = m_llFileRanges[ idxRange ];
  1138. VPKInputFileRange_t &rn = m_llFileRanges[ idxNext ];
  1139. // Both chunks unassigned?
  1140. if ( ri.m_iChunkFilenameIndex < 0 && rn.m_iChunkFilenameIndex < 0 )
  1141. {
  1142. // Merge current with next
  1143. ri.m_iLastInputFile = rn.m_iLastInputFile;
  1144. CalculateRangeTotalSizeInChunkFile( ri );
  1145. m_llFileRanges.Remove( idxNext );
  1146. // List should be valid at this point
  1147. SanityCheckRanges();
  1148. }
  1149. else
  1150. {
  1151. // Keep it, advance to the next one
  1152. idxRange = idxNext;
  1153. }
  1154. }
  1155. }
  1156. void VPKBuilder::CalculateRangeTotalSizeInChunkFile( VPKInputFileRange_t &range ) const
  1157. {
  1158. range.m_nTotalSizeInChunkFile = 0;
  1159. for ( int i = range.m_iFirstInputFile ; i <= range.m_iLastInputFile ; ++i )
  1160. {
  1161. range.m_nTotalSizeInChunkFile += m_vecNewFilesInChunkOrder[ i ]->GetSizeInChunkFile();
  1162. }
  1163. }
  1164. void VPKBuilder::SanityCheckRanges()
  1165. {
  1166. int iFileIndex = 0;
  1167. int64 iTotalSizeInChunks = 0;
  1168. FOR_EACH_LL( m_llFileRanges, idxRange )
  1169. {
  1170. VPKInputFileRange_t &r = m_llFileRanges[ idxRange ];
  1171. Assert( r.m_iFirstInputFile == iFileIndex );
  1172. Assert( r.m_iLastInputFile >= r.m_iFirstInputFile );
  1173. iFileIndex = r.m_iLastInputFile + 1;
  1174. iTotalSizeInChunks += r.m_nTotalSizeInChunkFile;
  1175. }
  1176. Assert( iFileIndex == m_vecNewFilesInChunkOrder.Count() );
  1177. Assert( iTotalSizeInChunks == m_iNewTotalFileSizeInChunkFiles );
  1178. }
  1179. void VPKBuilder::PrintRangeDebug()
  1180. {
  1181. FOR_EACH_LL( m_llFileRanges, idxRange )
  1182. {
  1183. VPKInputFileRange_t &r = m_llFileRanges[ idxRange ];
  1184. printf( "Range handle %d:\n", idxRange );
  1185. printf( " File range %d .. %d\n", r.m_iFirstInputFile, r.m_iLastInputFile );
  1186. printf( " Chunk %d%s\n", r.m_iChunkFilenameIndex, r.m_bKeepExistingFile ? " (keep existing file)" : "" );
  1187. printf( " Size %lld\n", (long long)r.m_nTotalSizeInChunkFile );
  1188. }
  1189. }
  1190. void VPKBuilder::AddRange( VPKInputFileRange_t range )
  1191. {
  1192. // Sanity check that ranges are in a valid order
  1193. SanityCheckRanges();
  1194. // Split up the range(s) we overlap so that we will match exactly one range
  1195. SplitRangeAt( range.m_iFirstInputFile );
  1196. SplitRangeAt( range.m_iLastInputFile+1 );
  1197. // Locate the range
  1198. FOR_EACH_LL( m_llFileRanges, idxRange )
  1199. {
  1200. VPKInputFileRange_t *p = &m_llFileRanges[ idxRange ];
  1201. if ( p->m_iLastInputFile < range.m_iFirstInputFile )
  1202. continue;
  1203. // Range should now match exactly
  1204. Assert( p->m_iFirstInputFile == range.m_iFirstInputFile );
  1205. Assert( p->m_iLastInputFile == range.m_iLastInputFile );
  1206. // Assign it to the proper chunk
  1207. MapRangeToChunk( idxRange, range.m_iChunkFilenameIndex, range.m_bKeepExistingFile );
  1208. return;
  1209. }
  1210. // We should have found it
  1211. Assert( false );
  1212. }
  1213. void VPKBuilder::SplitRangeAt( int iFirstInputFile )
  1214. {
  1215. // Sanity check that ranges are in a valid order
  1216. SanityCheckRanges();
  1217. // Now Locate any ranges that we overlap, and split them as appropriate
  1218. FOR_EACH_LL( m_llFileRanges, idxRange )
  1219. {
  1220. VPKInputFileRange_t *p = &m_llFileRanges[ idxRange ];
  1221. // No need to make any changes if split already exists at requested location
  1222. if ( p->m_iFirstInputFile == iFirstInputFile || p->m_iLastInputFile+1 == iFirstInputFile)
  1223. return;
  1224. // Found the range to split?
  1225. Assert( p->m_iFirstInputFile < iFirstInputFile );
  1226. if ( p->m_iLastInputFile >= iFirstInputFile )
  1227. {
  1228. // We should only be spliting up unallocated space
  1229. Assert( p->m_iChunkFilenameIndex < 0 );
  1230. VPKInputFileRange_t newRange = *p;
  1231. p->m_iLastInputFile = iFirstInputFile-1;
  1232. newRange.m_iFirstInputFile = iFirstInputFile;
  1233. CalculateRangeTotalSizeInChunkFile( newRange );
  1234. CalculateRangeTotalSizeInChunkFile( *p );
  1235. m_llFileRanges.InsertAfter( idxRange, newRange );
  1236. // Make sure we didn't screw anything up
  1237. SanityCheckRanges();
  1238. return;
  1239. }
  1240. }
  1241. // We should have found something
  1242. Assert( false );
  1243. }
  1244. void VPKBuilder::MapRangeToChunk( int idxRange, int iChunkFilenameIndex, bool bKeepExistingFile )
  1245. {
  1246. VPKInputFileRange_t *p = &m_llFileRanges[ idxRange ];
  1247. // If range was already mapped to a chunk, unmap it.
  1248. if ( p->m_iChunkFilenameIndex >= 0 )
  1249. {
  1250. Assert( m_vecRangeForChunk[ p->m_iChunkFilenameIndex ] == idxRange );
  1251. m_vecRangeForChunk[ p->m_iChunkFilenameIndex ] = m_llFileRanges.InvalidIndex();
  1252. p->m_iChunkFilenameIndex = -1;
  1253. p->m_bKeepExistingFile = false;
  1254. }
  1255. // Map range to a chunk?
  1256. if ( iChunkFilenameIndex >= 0 )
  1257. {
  1258. Assert( m_vecRangeForChunk[ iChunkFilenameIndex ] == m_llFileRanges.InvalidIndex() );
  1259. p->m_iChunkFilenameIndex = iChunkFilenameIndex;
  1260. p->m_bKeepExistingFile = bKeepExistingFile;
  1261. m_vecRangeForChunk[ iChunkFilenameIndex ] = idxRange;
  1262. }
  1263. else
  1264. {
  1265. Assert( !bKeepExistingFile );
  1266. }
  1267. }
  1268. #ifdef VPK_ENABLE_SIGNING
  1269. void GenerateKeyPair( const char *pszBaseKeyName )
  1270. {
  1271. printf( "Generating RSA public/private keypair...\n" );
  1272. //
  1273. // This code pretty much copied from vsign.cpp
  1274. //
  1275. uint8 rgubPublicKey[k_nRSAKeyLenMax]={0};
  1276. uint cubPublicKey = Q_ARRAYSIZE( rgubPublicKey );
  1277. uint8 rgubPrivateKey[k_nRSAKeyLenMax]={0};
  1278. uint cubPrivateKey = Q_ARRAYSIZE( rgubPrivateKey );
  1279. if( !CCrypto::RSAGenerateKeys( rgubPublicKey, &cubPublicKey, rgubPrivateKey, &cubPrivateKey ) )
  1280. {
  1281. Error( "Failed to generate RSA keypair.\n" );
  1282. }
  1283. char rgchEncodedPublicKey[k_nRSAKeyLenMax*4];
  1284. uint cubEncodedPublicKey = Q_ARRAYSIZE( rgchEncodedPublicKey );
  1285. if( !CCrypto::HexEncode( rgubPublicKey, cubPublicKey, rgchEncodedPublicKey, cubEncodedPublicKey ) )
  1286. {
  1287. Error( "Failed to encode public key.\n" );
  1288. }
  1289. // Don't encrypt
  1290. // uint8 rgubEncryptedPrivateKey[Q_ARRAYSIZE( rgubPrivateKey )*2];
  1291. // uint cubEncryptedPrivateKey = Q_ARRAYSIZE( rgubEncryptedPrivateKey );
  1292. //
  1293. // if( !CCrypto::SymmetricEncrypt( rgubPrivateKey, cubPrivateKey, rgubEncryptedPrivateKey, &cubEncryptedPrivateKey, (uint8 *)rgchPassphrase, k_nSymmetricKeyLen ) )
  1294. // {
  1295. // printf( "ERROR! Failed to encrypt private key.\n" );
  1296. // return false;
  1297. // }
  1298. char rgchEncodedEncryptedPrivateKey[Q_ARRAYSIZE( rgubPrivateKey )*8];
  1299. if( !CCrypto::HexEncode( rgubPrivateKey, cubPrivateKey, rgchEncodedEncryptedPrivateKey, Q_ARRAYSIZE(rgchEncodedEncryptedPrivateKey) ) )
  1300. {
  1301. Error( "Failed to encode private key.\n" );
  1302. }
  1303. // Good Lord. Use fopen, because it will work without any surprising crap or hidden limitations.
  1304. // I just wasted an hour trying to get CUtlBuffer and our filesystem to print a block of text to a file.
  1305. // Save public keyfile
  1306. {
  1307. CUtlString sPubFilename( pszBaseKeyName );
  1308. sPubFilename += ".publickey.vdf";
  1309. FILE *f = fopen( sPubFilename, "wt" );
  1310. if ( f == NULL )
  1311. Error( "Cannot create %s.", sPubFilename.String() );
  1312. // Write public keyfile
  1313. fprintf( f,
  1314. "// Public key file. You can publish this key file and share it with the world.\n"
  1315. "// It can be used by third parties to verify any signatures made with the corresponding private key.\n"
  1316. "public_key\n"
  1317. "{\n"
  1318. "\ttype \"rsa\"\n"
  1319. "\trsa_public_key \"%s\"\n"
  1320. "}\n",
  1321. rgchEncodedPublicKey );
  1322. fclose(f);
  1323. printf( " Saved %s\n", sPubFilename.String() );
  1324. }
  1325. // Save private keyfile
  1326. {
  1327. CUtlString sPrivFilename( pszBaseKeyName );
  1328. sPrivFilename += ".privatekey.vdf";
  1329. FILE *f = fopen( sPrivFilename, "wt" );
  1330. if ( f == NULL )
  1331. Error( "Cannot create %s.", sPrivFilename.String() );
  1332. fprintf( f,
  1333. "// Private key file.\n"
  1334. "// This key can be used to sign files. Third parties can verify your signature by using your public key.\n"
  1335. "//\n"
  1336. "// THIS KEY SHOULD BE KEPT SECRET\n"
  1337. "//\n"
  1338. "// You should share your public key freely, but anyone who has your private key will be able to impersonate you.\n"
  1339. "private_key\n"
  1340. "{\n"
  1341. "\ttype \"rsa\"\n"
  1342. "\trsa_private_key \"%s\"\n"
  1343. "\n"
  1344. "\t// Note: the private key is stored in plaintext. It is not encrypted or protected by a password.\n"
  1345. "\t// Anyone who obtains this key can use it to sign files.\n"
  1346. "\tprivate_key_encrypted 0\n"
  1347. "\n"
  1348. "\t// The public key that corresponds to this private key. The public keyfile you can share with others is\n"
  1349. "\t// saved in another file, but the key data is duplicated here to help you confirm which public key matches\n"
  1350. "\t// with this private key.\n"
  1351. "\tpublic_key\n"
  1352. "\t{\n"
  1353. "\t\ttype \"rsa\"\n"
  1354. "\t\trsa_public_key \"%s\"\n"
  1355. "\t}\n"
  1356. "}\n",
  1357. rgchEncodedEncryptedPrivateKey, rgchEncodedPublicKey );
  1358. fclose(f);
  1359. printf( " Saved %s\n", sPrivFilename.String() );
  1360. }
  1361. printf( "\n" );
  1362. printf( "REMEMBER: Your private key should be kept secret. Don't share it!\n" );
  1363. }
  1364. static void CheckSignature( const char *pszFilename )
  1365. {
  1366. char szActualFileName[MAX_PATH];
  1367. CPackedStore pack( pszFilename, szActualFileName, g_pFullFileSystem );
  1368. // Make sure they didn't make a mistake
  1369. CUtlVector<uint8> bytesPublicKey;
  1370. if ( s_sPublicKeyFile.IsEmpty() )
  1371. {
  1372. if ( !s_sPrivateKeyFile.IsEmpty() )
  1373. Error( "Private keys are not used to verify signatures. Did you mean to use -k instead?" );
  1374. printf(
  1375. "Checking signature using public key in VPK.\n"
  1376. "\n"
  1377. "NOTE: This just confirms that the VPK has a valid signature,\n"
  1378. " not that signature was made by any particular party. Use -k\n"
  1379. " and provide a public key in order to verify that a file was\n"
  1380. " signed by a particular trusted party.\n" );
  1381. }
  1382. else
  1383. {
  1384. LoadKeyFile( s_sPublicKeyFile, "rsa_public_key", bytesPublicKey );
  1385. printf( "Loaded public key file %s\n", s_sPublicKeyFile.String() );
  1386. }
  1387. printf( "\n" );
  1388. fflush( stdout );
  1389. CPackedStore::ESignatureCheckResult result = pack.CheckSignature( bytesPublicKey.Count(), bytesPublicKey.Base() );
  1390. switch (result )
  1391. {
  1392. default:
  1393. case CPackedStore::eSignatureCheckResult_Failed:
  1394. fprintf( stderr, "ERROR: FAILED\n" );
  1395. fflush( stderr );
  1396. printf( "IO error or other generic failure." );
  1397. exit(-1);
  1398. case CPackedStore::eSignatureCheckResult_NotSigned:
  1399. fprintf( stderr, "ERROR: NOT SIGNED\n" );
  1400. fflush( stderr );
  1401. printf( "The VPK does not contain a signature." );
  1402. exit(1);
  1403. case CPackedStore::eSignatureCheckResult_WrongKey:
  1404. fprintf( stderr, "ERROR: KEY MISMATCH\n" );
  1405. fflush( stderr );
  1406. printf(
  1407. "The public key provided does not match the public\n"
  1408. "key contained in the VPK file. The VPK was not\n"
  1409. "signed using the private key corresponding to your\n"
  1410. "public key.\n" );
  1411. exit(2);
  1412. case CPackedStore::eSignatureCheckResult_InvalidSignature:
  1413. fprintf( stderr, "ERROR: INVALID SIGNATURE\n" );
  1414. fflush( stderr );
  1415. printf( "The VPK contains a signature, but it isn't valid." );
  1416. exit(3);
  1417. case CPackedStore::eSignatureCheckResult_ValidSignature:
  1418. printf( "SUCCESS\n" );
  1419. if ( s_sPublicKeyFile.IsEmpty() )
  1420. {
  1421. printf( "VPK contains a valid signature." );
  1422. }
  1423. else
  1424. {
  1425. printf( "VPK signature validated using the specified public key." );
  1426. }
  1427. exit(0);
  1428. }
  1429. }
  1430. static void CheckHashes( const char *pszFilename )
  1431. {
  1432. char szActualFileName[MAX_PATH];
  1433. CPackedStore pack( pszFilename, szActualFileName, g_pFullFileSystem );
  1434. char szChunkFilename[ 256 ];
  1435. printf( "Checking cache line hashes:\n" );
  1436. CUtlSortVector<ChunkHashFraction_t, ChunkHashFractionLess_t > &vecHashes = pack.AccessPackFileHashes();
  1437. CPackedStoreFileHandle handle = pack.GetHandleForHashingFiles();
  1438. handle.m_nFileNumber = -1;
  1439. int nCheckedFractionsOK = 0;
  1440. int nTotalCheckedCacheLines = 0;
  1441. int nTotalErrorCacheLines = 0;
  1442. FOR_EACH_VEC( vecHashes, idx )
  1443. {
  1444. ChunkHashFraction_t frac = vecHashes[idx];
  1445. if ( idx == 0 || frac.m_nPackFileNumber != handle.m_nFileNumber )
  1446. {
  1447. if ( nCheckedFractionsOK > 0 )
  1448. printf( "OK. (%d caches lines)\n", nCheckedFractionsOK );
  1449. handle.m_nFileNumber = frac.m_nPackFileNumber;
  1450. pack.GetPackFileName( handle, szChunkFilename, sizeof(szChunkFilename) );
  1451. printf(" %s: ", szChunkFilename );
  1452. fflush( stdout );
  1453. nCheckedFractionsOK = 0;
  1454. }
  1455. FileHash_t filehash;
  1456. // VPKHandle.m_nFileNumber;
  1457. // nFileFraction;
  1458. int64 fileSize = 0;
  1459. // if we have never hashed this before - do it now
  1460. pack.HashEntirePackFile( handle, fileSize, frac.m_nFileFraction, frac.m_cbChunkLen, filehash );
  1461. ++nTotalCheckedCacheLines;
  1462. if ( filehash.m_cbFileLen != frac.m_cbChunkLen )
  1463. {
  1464. if ( nCheckedFractionsOK >= 0 )
  1465. {
  1466. printf( "\n" );
  1467. fflush( stdout );
  1468. nCheckedFractionsOK = -1;
  1469. }
  1470. fprintf( stderr, " @%d: size mismatch. Stored: %d Computed: %d\n", frac.m_nFileFraction, frac.m_cbChunkLen, filehash.m_cbFileLen );
  1471. fflush( stderr );
  1472. ++nTotalErrorCacheLines;
  1473. }
  1474. else if ( filehash.m_md5contents != frac.m_md5contents )
  1475. {
  1476. if ( nCheckedFractionsOK >= 0 )
  1477. {
  1478. printf( "\n" );
  1479. fflush( stdout );
  1480. nCheckedFractionsOK = -1;
  1481. }
  1482. char szCalculated[ MD5_DIGEST_LENGTH*2 + 4 ];
  1483. char szExpected[ MD5_DIGEST_LENGTH*2 + 4 ];
  1484. V_binarytohex( filehash.m_md5contents.bits, MD5_DIGEST_LENGTH, szCalculated, sizeof(szCalculated) );
  1485. V_binarytohex( frac.m_md5contents.bits, MD5_DIGEST_LENGTH, szExpected, sizeof(szExpected) );
  1486. fprintf( stderr, " @%d: hash mismatch: Got %s, expected %s.\n", frac.m_nFileFraction, szCalculated, szExpected );
  1487. fflush( stderr );
  1488. ++nTotalErrorCacheLines;
  1489. }
  1490. else
  1491. {
  1492. if ( nCheckedFractionsOK >= 0 )
  1493. ++nCheckedFractionsOK;
  1494. }
  1495. }
  1496. if ( nCheckedFractionsOK > 0 )
  1497. printf( "OK. (%d caches lines)\n", nCheckedFractionsOK );
  1498. if ( nTotalErrorCacheLines == 0 )
  1499. {
  1500. printf( "All %d cache lines hashes matched OK\n", nTotalCheckedCacheLines );
  1501. exit(0);
  1502. }
  1503. fprintf( stderr, "%d cache lines failed validation out of %d checked \n", nTotalErrorCacheLines, nTotalCheckedCacheLines );
  1504. exit(1);
  1505. }
  1506. static void PrintBinaryBlob( const CUtlVector<uint8> &blob )
  1507. {
  1508. const int kRowLen = 32;
  1509. for ( int i = 0 ; i < blob.Count() ; i += kRowLen )
  1510. {
  1511. int iEnd = Min( i+kRowLen, blob.Count() );
  1512. const char *pszSep = " ";
  1513. for ( int j = i ; j < iEnd ; ++j )
  1514. {
  1515. printf( "%s%02X", pszSep, blob[j] );
  1516. pszSep = "";
  1517. }
  1518. printf( "\n" );
  1519. }
  1520. }
  1521. static void DumpSignatureInfo( const char *pszFilename )
  1522. {
  1523. char szActualFileName[MAX_PATH];
  1524. CPackedStore pack( pszFilename, szActualFileName, g_pFullFileSystem );
  1525. if ( pack.GetSignature().Count() == 0 )
  1526. {
  1527. printf( "VPK is not signed\n" );
  1528. return;
  1529. }
  1530. printf( "Public key:\n" );
  1531. PrintBinaryBlob( pack.GetSignaturePublicKey() );
  1532. printf( "Signature:\n" );
  1533. PrintBinaryBlob( pack.GetSignature() );
  1534. }
  1535. #endif
  1536. void BuildRecursiveFileList( const char *pcDirName, CUtlStringList &fileList )
  1537. {
  1538. char szDirWildcard[MAX_PATH];
  1539. FileFindHandle_t findHandle;
  1540. V_snprintf( szDirWildcard, sizeof( szDirWildcard ), "%s%c%s", pcDirName, CORRECT_PATH_SEPARATOR, "*.*" );
  1541. char const *pcResult = g_pFullFileSystem->FindFirst( szDirWildcard, &findHandle );
  1542. if ( pcResult )
  1543. {
  1544. do
  1545. {
  1546. char szFullResultPath[MAX_PATH];
  1547. if ( '.' == pcResult[0] )
  1548. {
  1549. pcResult = g_pFullFileSystem->FindNext( findHandle );
  1550. continue;
  1551. }
  1552. // Make a full path to the result
  1553. V_snprintf( szFullResultPath, sizeof( szFullResultPath ), "%s%c%s", pcDirName, CORRECT_PATH_SEPARATOR, pcResult );
  1554. if ( g_pFullFileSystem->IsDirectory( szFullResultPath ) )
  1555. {
  1556. // Recurse
  1557. BuildRecursiveFileList( szFullResultPath, fileList );
  1558. }
  1559. else
  1560. {
  1561. // Add file to the file list
  1562. fileList.CopyAndAddToTail( szFullResultPath );
  1563. }
  1564. pcResult = g_pFullFileSystem->FindNext( findHandle );
  1565. } while ( pcResult );
  1566. g_pFullFileSystem->FindClose( findHandle );
  1567. }
  1568. }
  1569. static void DroppedVpk( const char *pszVpkFilename )
  1570. {
  1571. char szActualFileName[MAX_PATH];
  1572. CPackedStore mypack( pszVpkFilename, szActualFileName, g_pFullFileSystem );
  1573. CUtlStringList fileNames;
  1574. char szVPKParentDir[MAX_PATH];
  1575. V_strncpy( szVPKParentDir, pszVpkFilename, sizeof( szVPKParentDir ) );
  1576. V_SetExtension( szVPKParentDir, "", sizeof( szVPKParentDir ) );
  1577. mypack.GetFileList( fileNames, false, true );
  1578. for( int i = 0 ; i < fileNames.Count(); i++ )
  1579. {
  1580. char szDestFilePath[MAX_PATH];
  1581. CPackedStoreFileHandle pData = mypack.OpenFile( fileNames[i] );
  1582. V_snprintf( szDestFilePath, sizeof( szDestFilePath ), "%s%c%s", szVPKParentDir, CORRECT_PATH_SEPARATOR, fileNames[i] );
  1583. if ( pData )
  1584. {
  1585. char szParentDirectory[MAX_PATH];
  1586. V_ExtractFilePath( szDestFilePath, szParentDirectory, sizeof( szParentDirectory ) );
  1587. V_FixSlashes( szParentDirectory );
  1588. if ( !g_pFullFileSystem->IsDirectory( szParentDirectory ) )
  1589. {
  1590. g_pFullFileSystem->CreateDirHierarchy( szParentDirectory );
  1591. }
  1592. printf( "extracting %s\n", fileNames[i] );
  1593. COutputFile outF( szDestFilePath );
  1594. if ( outF.IsOk() )
  1595. {
  1596. int nBytes = pData.m_nFileSize;
  1597. while( nBytes )
  1598. {
  1599. char cpBuf[65535];
  1600. int nReadSize = MIN( sizeof( cpBuf ), nBytes );
  1601. mypack.ReadData( pData, cpBuf, nReadSize );
  1602. outF.Write( cpBuf, nReadSize );
  1603. nBytes -= nReadSize;
  1604. }
  1605. outF.Close();
  1606. }
  1607. }
  1608. }
  1609. }
  1610. static void DroppedDirectory( const char *pszDirectoryArg )
  1611. {
  1612. // Strip trailing slash, if any
  1613. char szDirectory[MAX_PATH];
  1614. V_strcpy_safe( szDirectory, pszDirectoryArg );
  1615. V_StripTrailingSlash( szDirectory );
  1616. char szVPKPath[MAX_PATH];
  1617. // Construct path to VPK
  1618. V_snprintf( szVPKPath, sizeof( szVPKPath ), "%s.vpk", szDirectory );
  1619. // Delete any existing one at that location
  1620. if ( g_pFullFileSystem->FileExists( szVPKPath ) )
  1621. {
  1622. if ( g_pFullFileSystem->IsFileWritable( szVPKPath ) )
  1623. {
  1624. g_pFullFileSystem->RemoveFile( szVPKPath );
  1625. }
  1626. else
  1627. {
  1628. fprintf( stderr, "Cannot delete file: %s\n", szVPKPath );
  1629. exit(1);
  1630. }
  1631. }
  1632. // Make the VPK
  1633. char szActualFileName[MAX_PATH];
  1634. CPackedStore mypack( szVPKPath, szActualFileName, g_pFullFileSystem, true );
  1635. mypack.SetWriteChunkSize( s_iMultichunkSize * 1024*1024 );
  1636. // !KLUDGE! Create keyvalues object, since that's what the builder uses
  1637. printf( "Finding files and creating temporary control file...\n" );
  1638. CUtlStringList fileList;
  1639. BuildRecursiveFileList( szDirectory, fileList );
  1640. KeyValues *pInputKeys = new KeyValues("packkeys");
  1641. const int nBaseDirLength = V_strlen( szDirectory );
  1642. for( int i = 0 ; i < fileList.Count(); i++ )
  1643. {
  1644. // .... Ug O(n^2)
  1645. KeyValues *pFileKey = pInputKeys->CreateNewKey();
  1646. const char *pszFilename = fileList[i];
  1647. pFileKey->SetString( "srcpath", pszFilename );
  1648. const char *pszDestPath = pszFilename + nBaseDirLength;
  1649. if ( *pszDestPath == '/' || *pszDestPath == '\\' )
  1650. ++pszDestPath;
  1651. pFileKey->SetString( "destpath", pszDestPath );
  1652. }
  1653. VPKBuilder builder( mypack );
  1654. builder.SetInputKeys( pInputKeys->GetFirstSubKey(), "" );
  1655. builder.BuildFromInputKeys();
  1656. }
  1657. int main(int argc, char **argv)
  1658. {
  1659. InitCommandLineProgram( argc, argv );
  1660. int nCurArg = 1;
  1661. //
  1662. // Check for standard usage syntax
  1663. //
  1664. while ( ( nCurArg < argc ) && ( argv[nCurArg][0] == '-' ) )
  1665. {
  1666. switch( argv[nCurArg][1] )
  1667. {
  1668. case '?': // args
  1669. {
  1670. PrintArgSummaryAndExit( 0 ); // return success in this case.
  1671. }
  1672. break;
  1673. case 'M':
  1674. {
  1675. s_bMakeMultiChunk = true;
  1676. }
  1677. break;
  1678. case 'P':
  1679. {
  1680. s_bUseSteamPipeFriendlyBuilder = true;
  1681. s_bMakeMultiChunk = true;
  1682. }
  1683. break;
  1684. case 'v': // verbose
  1685. {
  1686. s_bBeVerbose = true;
  1687. }
  1688. break;
  1689. case 'a':
  1690. {
  1691. nCurArg++;
  1692. if ( nCurArg >= argc )
  1693. {
  1694. fprintf( stderr, "Expected argument after %s\n", argv[nCurArg-1] );
  1695. exit( 1 );
  1696. }
  1697. s_iChunkAlign = V_atoi( argv[nCurArg] );
  1698. if ( s_iChunkAlign <= 0 || s_iChunkAlign > 32*1024 )
  1699. {
  1700. fprintf( stderr, "Invalid alignment value %s\n", argv[nCurArg] );
  1701. exit( 1 );
  1702. }
  1703. }
  1704. break;
  1705. case 'c':
  1706. {
  1707. nCurArg++;
  1708. if ( nCurArg >= argc )
  1709. {
  1710. fprintf( stderr, "Expected argument after %s\n", argv[nCurArg-1] );
  1711. exit( 1 );
  1712. }
  1713. s_iMultichunkSize = V_atoi( argv[nCurArg] );
  1714. if ( s_iMultichunkSize <= 0 || s_iMultichunkSize > 1*1024 )
  1715. {
  1716. fprintf( stderr, "Invalid chunk size %s\n", argv[nCurArg] );
  1717. exit( 1 );
  1718. }
  1719. }
  1720. break;
  1721. case 'K':
  1722. nCurArg++;
  1723. if ( nCurArg >= argc )
  1724. {
  1725. fprintf( stderr, "Expected argument after %s\n", argv[nCurArg-1] );
  1726. exit( 1 );
  1727. }
  1728. s_sPrivateKeyFile = argv[nCurArg];
  1729. break;
  1730. case 'k':
  1731. nCurArg++;
  1732. if ( nCurArg >= argc )
  1733. {
  1734. fprintf( stderr, "Expected argument after %s\n", argv[nCurArg-1] );
  1735. exit( 1 );
  1736. }
  1737. s_sPublicKeyFile = argv[nCurArg];
  1738. break;
  1739. default:
  1740. Error( "Unrecognized option '%s'\n", argv[nCurArg] );
  1741. }
  1742. nCurArg++;
  1743. }
  1744. argc -= ( nCurArg - 1 );
  1745. argv += ( nCurArg - 1 );
  1746. if ( argc < 2 )
  1747. {
  1748. Error( "No command specified. Try 'vpk -?' for info.\n" );
  1749. }
  1750. const char *pszCommand = argv[1];
  1751. if ( V_stricmp( pszCommand, "l" ) == 0 )
  1752. {
  1753. if ( argc != 3 )
  1754. {
  1755. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1756. exit(1);
  1757. }
  1758. // list a file
  1759. char szActualFileName[MAX_PATH];
  1760. CPackedStore mypack( argv[2], szActualFileName, g_pFullFileSystem );
  1761. CUtlStringList fileNames;
  1762. mypack.GetFileList( fileNames, pszCommand[0] == 'L', true );
  1763. for( int i = 0 ; i < fileNames.Count(); i++ )
  1764. {
  1765. printf( "%s\n", fileNames[i] );
  1766. }
  1767. }
  1768. else if ( V_strcmp( pszCommand, "a" ) == 0 )
  1769. {
  1770. if ( argc < 3 )
  1771. {
  1772. fprintf( stderr, "Not enough arguments for '%s' command.\n", pszCommand );
  1773. exit(1);
  1774. }
  1775. char szActualFileName[MAX_PATH];
  1776. CPackedStore mypack( argv[2], szActualFileName, g_pFullFileSystem, true );
  1777. CheckLoadKeyFilesForSigning( mypack );
  1778. for( int i = 3; i < argc; i++ )
  1779. {
  1780. if ( argv[i][0] == '@' )
  1781. {
  1782. // response file?
  1783. CRequiredInputTextFile hResponseFile( argv[i] + 1 );
  1784. CUtlStringList fileList;
  1785. hResponseFile.ReadLines( fileList );
  1786. for( int i = 0 ; i < fileList.Count(); i++ )
  1787. {
  1788. AddFileToPack( mypack, fileList[i] );
  1789. }
  1790. }
  1791. else
  1792. {
  1793. AddFileToPack( mypack, argv[i] );
  1794. }
  1795. }
  1796. mypack.HashEverything();
  1797. mypack.Write();
  1798. }
  1799. else if ( V_strcmp( pszCommand, "k" ) == 0 )
  1800. {
  1801. if ( argc != 4 )
  1802. {
  1803. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1804. exit(1);
  1805. }
  1806. char szActualFileName[MAX_PATH];
  1807. CPackedStore mypack( argv[2], szActualFileName, g_pFullFileSystem, true );
  1808. mypack.SetWriteChunkSize( s_iMultichunkSize * 1024*1024 );
  1809. VPKBuilder builder( mypack );
  1810. builder.LoadInputKeys( argv[3] );
  1811. builder.BuildFromInputKeys();
  1812. }
  1813. else if ( V_strcmp( pszCommand, "x" ) == 0 )
  1814. {
  1815. if ( argc < 3 )
  1816. {
  1817. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1818. exit(1);
  1819. }
  1820. // extract a file
  1821. char szActualFileName[MAX_PATH];
  1822. CPackedStore mypack( argv[2], szActualFileName, g_pFullFileSystem );
  1823. for( int i = 3; i < argc; i++ )
  1824. {
  1825. CPackedStoreFileHandle pData = mypack.OpenFile( argv[i] );
  1826. if ( pData )
  1827. {
  1828. printf( "extracting %s\n", argv[i] );
  1829. COutputFile outF( argv[i] );
  1830. if ( !outF.IsOk() )
  1831. {
  1832. fprintf( stderr, "Unable to create '%s'.\n", argv[i] );
  1833. exit(1);
  1834. }
  1835. int nBytes = pData.m_nFileSize;
  1836. while( nBytes )
  1837. {
  1838. char cpBuf[65535];
  1839. int nReadSize = MIN( sizeof( cpBuf ), nBytes );
  1840. mypack.ReadData( pData, cpBuf, nReadSize );
  1841. outF.Write( cpBuf, nReadSize );
  1842. nBytes -= nReadSize;
  1843. }
  1844. outF.Close();
  1845. }
  1846. else
  1847. {
  1848. printf( "couldn't find file %s\n", argv[i] );
  1849. break;
  1850. }
  1851. }
  1852. }
  1853. else if ( V_strcmp( pszCommand, "B" ) == 0 )
  1854. {
  1855. if ( argc != 4 )
  1856. {
  1857. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1858. exit(1);
  1859. }
  1860. // benchmark
  1861. CRequiredInputTextFile hResponseFile( argv[3] );
  1862. CUtlStringList files;
  1863. hResponseFile.ReadLines( files );
  1864. printf("%d files\n", files.Count() );
  1865. float stime = Plat_FloatTime();
  1866. BenchMark( files );
  1867. printf( " time no pack = %f\n", Plat_FloatTime() - stime );
  1868. //g_pFullFileSystem->AddVPKFile( argv[2] );
  1869. //stime = Plat_FloatTime();
  1870. //BenchMark( files );
  1871. //printf( " time pack = %f\n", Plat_FloatTime() - stime );
  1872. }
  1873. else if ( V_strcmp( pszCommand, "rehash" ) == 0 )
  1874. {
  1875. if ( argc != 3 )
  1876. {
  1877. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1878. exit(1);
  1879. }
  1880. char szActualFileName[MAX_PATH];
  1881. CPackedStore mypack( argv[2], szActualFileName, g_pFullFileSystem, true );
  1882. CheckLoadKeyFilesForSigning( mypack );
  1883. mypack.HashEverything();
  1884. mypack.Write();
  1885. }
  1886. else if ( V_strcmp( pszCommand, "checkhash" ) == 0 )
  1887. {
  1888. if ( argc != 3 )
  1889. {
  1890. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1891. exit(1);
  1892. }
  1893. CheckHashes( argv[2] );
  1894. }
  1895. #ifdef VPK_ENABLE_SIGNING
  1896. else if ( V_strcmp( pszCommand, "generate_keypair" ) == 0 )
  1897. {
  1898. if ( argc != 3 )
  1899. {
  1900. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1901. exit(1);
  1902. }
  1903. GenerateKeyPair( argv[2] );
  1904. }
  1905. else if ( V_strcmp( pszCommand, "checksig" ) == 0 )
  1906. {
  1907. if ( argc != 3 )
  1908. {
  1909. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1910. exit(1);
  1911. }
  1912. CheckSignature( argv[2] );
  1913. }
  1914. else if ( V_strcmp( pszCommand, "dumpsig" ) == 0 )
  1915. {
  1916. if ( argc != 3 )
  1917. {
  1918. fprintf( stderr, "Incorrect number of arguments for '%s' command.\n", pszCommand );
  1919. exit(1);
  1920. }
  1921. DumpSignatureInfo( argv[2] );
  1922. }
  1923. #endif
  1924. else if ( argc == 2 && g_pFullFileSystem->IsDirectory( argv[1] ) )
  1925. {
  1926. DroppedDirectory( argv[1] );
  1927. }
  1928. else if ( argc == 2 && V_GetFileExtension( argv[1] ) && V_stristr( V_GetFileExtension( argv[1] ), "vpk") )
  1929. {
  1930. DroppedVpk( argv[1] );
  1931. }
  1932. else
  1933. {
  1934. Error( "Unknown command '%s'. Try 'vpk -?' for info.\n", pszCommand );
  1935. }
  1936. return 0;
  1937. }