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.

593 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "MakeGameData.h"
  7. static CUtlSymbolTable g_CriticalPreloadTable( 0, 32, true );
  8. //-----------------------------------------------------------------------------
  9. // Purpose: Compute preload data by file type. Calls into appropriate libraries
  10. // to get the preload info. Libraries would use filename to generate
  11. // the preload if there is a compilation step, otherwise the file buffer
  12. // is a buffer loaded filename.
  13. //-----------------------------------------------------------------------------
  14. static bool GetPreloadBuffer( const char *pFilename, CUtlBuffer &fileBuffer, CUtlBuffer &preloadBuffer )
  15. {
  16. char fileExtension[MAX_PATH];
  17. Q_ExtractFileExtension( pFilename, fileExtension, sizeof( fileExtension ) );
  18. // adding an entire file IS ONLY for files that are expected to be read by the game as a single read
  19. // NOT for files that have any seek pattern
  20. bool bAddEntireFile = false;
  21. // trivial small files, always add
  22. if ( !Q_stricmp( fileExtension, "txt" ) ||
  23. !Q_stricmp( fileExtension, "dat" ) ||
  24. !Q_stricmp( fileExtension, "lst" ) ||
  25. !Q_stricmp( fileExtension, "res" ) ||
  26. !Q_stricmp( fileExtension, "vmt" ) ||
  27. !Q_stricmp( fileExtension, "cfg" ) ||
  28. !Q_stricmp( fileExtension, "bnf" ) ||
  29. !Q_stricmp( fileExtension, "rc" ) ||
  30. !Q_stricmp( fileExtension, "vbf" ) ||
  31. !Q_stricmp( fileExtension, "vfe" ) ||
  32. !Q_stricmp( fileExtension, "pcf" ) ||
  33. !Q_stricmp( fileExtension, "inf" ) )
  34. {
  35. bAddEntireFile = true;
  36. }
  37. // critical resources get blindly added to the preload
  38. if ( !bAddEntireFile && ( g_CriticalPreloadTable.Find( pFilename ) != UTL_INVAL_SYMBOL ) )
  39. {
  40. bAddEntireFile = true;
  41. }
  42. if ( bAddEntireFile && LZMA_IsCompressed( (unsigned char *)fileBuffer.Base() ) )
  43. {
  44. // sorry, not allowed to add entirely to preload if already compressed
  45. // breaks the run-time filesystem due to inability to deliver file as-is
  46. bAddEntireFile = false;
  47. }
  48. if ( bAddEntireFile )
  49. {
  50. if ( fileBuffer.TellMaxPut() >= 1*1024 )
  51. {
  52. // only compress preload files of reasonable size
  53. unsigned int compressedSize = 0;
  54. unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)fileBuffer.Base(),
  55. fileBuffer.TellMaxPut(), &compressedSize );
  56. if ( pCompressedOutput )
  57. {
  58. // add as compressed
  59. preloadBuffer.EnsureCapacity( compressedSize );
  60. preloadBuffer.Put( pCompressedOutput, compressedSize );
  61. free( pCompressedOutput );
  62. return true;
  63. }
  64. }
  65. // add entire file to preload section
  66. preloadBuffer.EnsureCapacity( fileBuffer.TellMaxPut() );
  67. preloadBuffer.Put( fileBuffer.Base(), fileBuffer.TellMaxPut() );
  68. return true;
  69. }
  70. // Each library will fetch the optional preload data into caller's buffer
  71. if ( !Q_stricmp( fileExtension, "wav" ) )
  72. {
  73. return GetPreloadData_WAV( pFilename, fileBuffer, preloadBuffer );
  74. }
  75. else if ( !Q_stricmp( fileExtension, "vtf" ) )
  76. {
  77. return GetPreloadData_VTF( pFilename, fileBuffer, preloadBuffer );
  78. }
  79. else if ( !Q_stricmp( fileExtension, "vcs" ) )
  80. {
  81. return GetPreloadData_VCS( pFilename, fileBuffer, preloadBuffer );
  82. }
  83. else if ( !Q_stricmp( fileExtension, "vhv" ) )
  84. {
  85. return GetPreloadData_VHV( pFilename, fileBuffer, preloadBuffer );
  86. }
  87. else if ( !Q_stricmp( fileExtension, "vtx" ) )
  88. {
  89. return GetPreloadData_VTX( pFilename, fileBuffer, preloadBuffer );
  90. }
  91. else if ( !Q_stricmp( fileExtension, "vvd" ) )
  92. {
  93. return GetPreloadData_VVD( pFilename, fileBuffer, preloadBuffer );
  94. }
  95. // others...
  96. return false;
  97. }
  98. void SetupCriticalPreloadScript( const char *pModPath )
  99. {
  100. characterset_t breakSet;
  101. CharacterSetBuild( &breakSet, "" );
  102. // purge any prior entries
  103. g_CriticalPreloadTable.RemoveAll();
  104. // populate table
  105. char szCriticaList[MAX_PATH];
  106. char szToken[MAX_PATH];
  107. V_ComposeFileName( pModPath, "scripts\\preload_xbox.xsc", szCriticaList, sizeof( szCriticaList ) );
  108. CUtlBuffer criticalListBuffer;
  109. if ( ReadFileToBuffer( szCriticaList, criticalListBuffer, true, true ) )
  110. {
  111. for ( ;; )
  112. {
  113. int nTokenSize = criticalListBuffer.ParseToken( &breakSet, szToken, sizeof( szToken ) );
  114. if ( nTokenSize <= 0 )
  115. {
  116. break;
  117. }
  118. V_strlower( szToken );
  119. V_FixSlashes( szToken, CORRECT_PATH_SEPARATOR );
  120. if ( UTL_INVAL_SYMBOL == g_CriticalPreloadTable.Find( szToken ) )
  121. {
  122. g_CriticalPreloadTable.AddString( szToken );
  123. }
  124. }
  125. }
  126. }
  127. CXZipTool::CXZipTool()
  128. {
  129. m_pZip = NULL;
  130. m_hPreloadFile = INVALID_HANDLE_VALUE;
  131. m_hOutputZipFile = INVALID_HANDLE_VALUE;
  132. m_PreloadFilename[0] = '\0';
  133. }
  134. CXZipTool::~CXZipTool()
  135. {
  136. Reset();
  137. }
  138. void CXZipTool::Reset()
  139. {
  140. if ( m_hOutputZipFile != INVALID_HANDLE_VALUE )
  141. {
  142. CloseHandle( m_hOutputZipFile );
  143. m_hOutputZipFile = INVALID_HANDLE_VALUE;
  144. }
  145. if ( m_hPreloadFile != INVALID_HANDLE_VALUE )
  146. {
  147. CloseHandle( m_hPreloadFile );
  148. m_hPreloadFile = INVALID_HANDLE_VALUE;
  149. }
  150. if ( m_PreloadFilename[0] )
  151. {
  152. DeleteFile( m_PreloadFilename );
  153. m_PreloadFilename[0] = '\0';
  154. }
  155. if ( m_pZip )
  156. {
  157. IZip::ReleaseZip( m_pZip );
  158. m_pZip = NULL;
  159. }
  160. m_ZipPreloadDirectoryEntries.Purge();
  161. m_ZipCRCList.Purge();
  162. m_ZipPreloadRemapEntries.Purge();
  163. }
  164. //-----------------------------------------------------------------------------
  165. // Purpose: Add a file buffer to the zip
  166. //-----------------------------------------------------------------------------
  167. bool CXZipTool::AddBuffer( const char *pFilename, CUtlBuffer &fileBuffer, bool bDoPreload )
  168. {
  169. if ( !m_pZip )
  170. {
  171. return false;
  172. }
  173. // safely strip unecessary prefix, otherise pollutes CRC
  174. if ( !strnicmp( pFilename, ".\\", 2 ) )
  175. {
  176. pFilename += 2;
  177. }
  178. // scan for CRC collision now, not at runtime
  179. CRCEntry_t crcEntry;
  180. crcEntry.fileNameCRC = HashStringCaselessConventional( pFilename );
  181. crcEntry.filename = pFilename;
  182. int idx = m_ZipCRCList.Find( crcEntry );
  183. if ( -1 != idx )
  184. {
  185. if ( !V_stricmp( pFilename, m_ZipCRCList[idx].filename.String() ) )
  186. {
  187. // file has already been added, ignore as succesful
  188. return true;
  189. }
  190. Msg( "ERROR: CRC Collision: '%s' with '%s'\n", pFilename, m_ZipCRCList[idx].filename.String() );
  191. return false;
  192. }
  193. else
  194. {
  195. // add unique entry to lists
  196. // must track filenames in a non-sort manner
  197. m_ZipCRCList.Insert( crcEntry );
  198. }
  199. // default, no preload entry
  200. unsigned short preloadDir = INVALID_PRELOAD_ENTRY;
  201. if ( bDoPreload )
  202. {
  203. CUtlBuffer preloadBuffer;
  204. bool bHasPreload = GetPreloadBuffer( pFilename, fileBuffer, preloadBuffer );
  205. int preloadSize = preloadBuffer.TellMaxPut();
  206. if ( bHasPreload && preloadSize > 0 )
  207. {
  208. if ( m_ZipPreloadDirectoryEntries.Count() >= 65534 )
  209. {
  210. Msg( "ERROR: Preload section FULL!, skipping %s\n", pFilename );
  211. return FALSE;
  212. }
  213. // Initialize the entry header
  214. ZIP_PreloadDirectoryEntry entry;
  215. memset( &entry, 0, sizeof( entry ) );
  216. entry.Length = preloadSize;
  217. entry.DataOffset = SetFilePointer( m_hPreloadFile, 0, NULL, FILE_CURRENT );
  218. // Add the directory entry to the preload table
  219. preloadDir = m_ZipPreloadDirectoryEntries.AddToTail( entry );
  220. // Append the preload data to the preload file
  221. DWORD numBytesWritten;
  222. BOOL bOK = WriteFile( m_hPreloadFile, preloadBuffer.Base(), preloadSize, &numBytesWritten, NULL );
  223. if ( !bOK || preloadSize != numBytesWritten )
  224. {
  225. Msg( "ERROR: writing %d preload bytes of '%s'\n", preloadSize, pFilename );
  226. return false;
  227. }
  228. if ( !g_bQuiet )
  229. {
  230. // Spew it
  231. if ( LZMA_IsCompressed( (unsigned char *)preloadBuffer.Base() ) )
  232. {
  233. unsigned int actualSize = LZMA_GetActualSize( (unsigned char *)preloadBuffer.Base() );
  234. Msg( "Preload: '%s': Compressed:%u Actual:%u\n", pFilename, preloadSize, actualSize );
  235. }
  236. else
  237. {
  238. Msg( "Preload: '%s': Length:%u\n", pFilename, preloadSize );
  239. }
  240. }
  241. }
  242. }
  243. unsigned int fileSize = fileBuffer.TellMaxPut();
  244. if ( fileSize > 0 )
  245. {
  246. // order in zip is sorted, not sequential
  247. m_pZip->AddBufferToZip( pFilename, fileBuffer.Base(), fileSize, false );
  248. // track the file in the zip directory to it's preload entry
  249. // order in preload is sequential as buffers are added
  250. preloadRemap_t remap;
  251. remap.filename = pFilename;
  252. remap.preloadDirIndex = preloadDir;
  253. m_ZipPreloadRemapEntries.AddToTail( remap );
  254. if ( !g_bQuiet )
  255. {
  256. Msg( "File: '%s': Length:%u\n", pFilename, fileSize );
  257. }
  258. }
  259. return true;
  260. }
  261. //-----------------------------------------------------------------------------
  262. // Purpose: Load a file and add it to the zip
  263. //-----------------------------------------------------------------------------
  264. bool CXZipTool::AddFile( const char *pFilename, bool bDoPreload )
  265. {
  266. if ( !m_pZip )
  267. {
  268. return false;
  269. }
  270. FILE* pFile = fopen( pFilename, "rb" );
  271. if( !pFile )
  272. {
  273. Msg( "ERROR: failed to open file: '%s'\n", pFilename );
  274. return false;
  275. }
  276. // Get the length of the file
  277. fseek( pFile, 0, SEEK_END );
  278. unsigned fileSize = ftell( pFile );
  279. fseek( pFile, 0, SEEK_SET);
  280. // read file to buffer
  281. CUtlBuffer fileBuffer;
  282. fileBuffer.EnsureCapacity( fileSize );
  283. fread( fileBuffer.Base(), fileSize, 1, pFile );
  284. fclose( pFile );
  285. fileBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, fileSize );
  286. return AddBuffer( pFilename, fileBuffer, bDoPreload );
  287. }
  288. //-----------------------------------------------------------------------------
  289. // Purpose: Add the preload section and save out the zip file
  290. //-----------------------------------------------------------------------------
  291. bool CXZipTool::End()
  292. {
  293. if ( !m_pZip )
  294. {
  295. return false;
  296. }
  297. // Add the preload section to the zip
  298. if ( m_ZipPreloadDirectoryEntries.Count() )
  299. {
  300. CUtlBuffer sectionBuffer;
  301. CByteswap byteSwap;
  302. // pc tools write 360 native data
  303. byteSwap.ActivateByteSwapping( IsPC() );
  304. // determine the preload data footprint
  305. unsigned int preloadDataSize = SetFilePointer( m_hPreloadFile, 0, NULL, FILE_CURRENT );
  306. // finalize header
  307. m_ZipPreloadHeader.DirectoryEntries = m_ZipPreloadRemapEntries.Count();
  308. m_ZipPreloadHeader.PreloadDirectoryEntries = m_ZipPreloadDirectoryEntries.Count();
  309. // determine the total section size ( treated as a single file inside zip )
  310. unsigned int sectionSize = sizeof( ZIP_PreloadHeader ) +
  311. m_ZipPreloadHeader.PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) +
  312. m_ZipPreloadHeader.DirectoryEntries * sizeof( unsigned short ) +
  313. preloadDataSize;
  314. sectionSize = AlignValue( sectionSize, m_ZipPreloadHeader.Alignment );
  315. sectionBuffer.EnsureCapacity( sectionSize );
  316. // save data in order
  317. // save the header
  318. byteSwap.SwapFieldsToTargetEndian( &m_ZipPreloadHeader );
  319. sectionBuffer.Put( &m_ZipPreloadHeader, sizeof( ZIP_PreloadHeader ) );
  320. // fixup and save the preload directory
  321. for ( int i=0; i<m_ZipPreloadDirectoryEntries.Count(); i++ )
  322. {
  323. ZIP_PreloadDirectoryEntry entry = m_ZipPreloadDirectoryEntries[i];
  324. byteSwap.SwapFieldsToTargetEndian( &entry );
  325. sectionBuffer.Put( &entry, sizeof( ZIP_PreloadDirectoryEntry ) );
  326. }
  327. // generate remap table
  328. char fileName[MAX_PATH];
  329. int fileSize;
  330. int zipIndex = -1;
  331. unsigned short *pRemapTable = (unsigned short *)malloc( m_ZipPreloadRemapEntries.Count() * sizeof( unsigned short ) );
  332. for ( int i=0; i<m_ZipPreloadRemapEntries.Count(); i++ )
  333. {
  334. // zip files get iterated in the same order they are serialized to disk
  335. fileName[0] = '\0';
  336. fileSize = 0;
  337. zipIndex = m_pZip->GetNextFilename( zipIndex, fileName, sizeof( fileName ), fileSize );
  338. // find the file in the preload remap table
  339. bool bFound = false;
  340. int j;
  341. for ( j=0; j<m_ZipPreloadRemapEntries.Count(); j++ )
  342. {
  343. if ( !Q_stricmp( fileName, m_ZipPreloadRemapEntries[j].filename.String() ) )
  344. {
  345. bFound = true;
  346. break;
  347. }
  348. }
  349. if ( !bFound )
  350. {
  351. // shouldn't happen, every file in the zip has a matching preload remap entry, that is valid or marked invalid
  352. Msg( "ERROR: file '%s' was expected to have an entry in preload table\n", fileName );
  353. }
  354. // the remap table is used to go find a file's preload data (if available)
  355. pRemapTable[i] = m_ZipPreloadRemapEntries[j].preloadDirIndex;
  356. }
  357. for ( int i=0; i<m_ZipPreloadRemapEntries.Count(); i++ )
  358. {
  359. unsigned short s = pRemapTable[i];
  360. sectionBuffer.PutShort( BigShort( s ) );
  361. }
  362. free( pRemapTable );
  363. // get and save preload data
  364. void *pPreloadData = malloc( preloadDataSize );
  365. SetFilePointer( m_hPreloadFile, 0, NULL, FILE_BEGIN );
  366. DWORD numBytesRead;
  367. BOOL bOK = ReadFile( m_hPreloadFile, pPreloadData, preloadDataSize, &numBytesRead, NULL );
  368. if ( !bOK || numBytesRead != preloadDataSize )
  369. {
  370. Msg( "ERROR: failed to read %d bytes from temporary preload file\n", preloadDataSize );
  371. }
  372. CloseHandle( m_hPreloadFile );
  373. m_hPreloadFile = INVALID_HANDLE_VALUE;
  374. sectionBuffer.Put( pPreloadData, preloadDataSize );
  375. free( pPreloadData );
  376. // cannot have written more than was pre-calced, unless code was broken
  377. Assert( (unsigned int)sectionBuffer.TellMaxPut() <= sectionSize );
  378. while( (unsigned int)sectionBuffer.TellMaxPut() < sectionSize )
  379. {
  380. // pad to final alignment
  381. sectionBuffer.PutChar( 0 );
  382. }
  383. m_pZip->AddBufferToZip( PRELOAD_SECTION_NAME, sectionBuffer.Base(), sectionBuffer.TellMaxPut(), false );
  384. }
  385. else
  386. {
  387. // Clear the preload section placeholder
  388. m_pZip->RemoveFileFromZip( PRELOAD_SECTION_NAME );
  389. }
  390. m_pZip->SaveToDisk( m_hOutputZipFile );
  391. Reset();
  392. return true;
  393. }
  394. //-----------------------------------------------------------------------------
  395. // Purpose: Create the zip file
  396. //-----------------------------------------------------------------------------
  397. bool CXZipTool::Begin( const char *pZipFileName, unsigned int alignment )
  398. {
  399. // get the volume of the target zip
  400. char drivePath[MAX_PATH];
  401. _splitpath( pZipFileName, drivePath, NULL, NULL, NULL );
  402. m_pZip = IZip::CreateZip( drivePath, true );
  403. if ( alignment )
  404. {
  405. // making an aligned zip that uses an optimized (but incompatible) format
  406. m_pZip->ForceAlignment( true, false, alignment );
  407. }
  408. // Open the output file
  409. m_hOutputZipFile = CreateFile( pZipFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
  410. if ( m_hOutputZipFile == INVALID_HANDLE_VALUE )
  411. {
  412. Msg( "ERROR: failed to create zip file '%s'\n", pZipFileName );
  413. return false;
  414. }
  415. // Create a temporary file for storing the preloaded data
  416. scriptlib->MakeTemporaryFilename( g_szModPath, m_PreloadFilename, sizeof( m_PreloadFilename ) );
  417. m_hPreloadFile = CreateFile( m_PreloadFilename, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
  418. if ( m_hPreloadFile == INVALID_HANDLE_VALUE )
  419. {
  420. Msg( "ERROR: failed to create temporary file '%s' for preload data\n", m_PreloadFilename );
  421. CloseHandle( m_hOutputZipFile );
  422. m_hOutputZipFile = INVALID_HANDLE_VALUE;
  423. return false;
  424. }
  425. memset( &m_ZipPreloadHeader, 0, sizeof( ZIP_PreloadHeader ) );
  426. m_ZipPreloadHeader.Version = PRELOAD_HDR_VERSION;
  427. m_ZipPreloadHeader.Alignment = alignment;
  428. // Add a placeholder for the preload section
  429. m_pZip->AddBufferToZip( PRELOAD_SECTION_NAME, NULL, 0, false );
  430. preloadRemap_t remap;
  431. remap.filename = PRELOAD_SECTION_NAME;
  432. remap.preloadDirIndex = INVALID_PRELOAD_ENTRY;
  433. m_ZipPreloadRemapEntries.AddToTail( remap );
  434. return true;
  435. }
  436. //-----------------------------------------------------------------------------
  437. // Purpose: Dump the preload contents
  438. //-----------------------------------------------------------------------------
  439. void CXZipTool::SpewPreloadInfo( const char *pZipName )
  440. {
  441. IZip *pZip = IZip::CreateZip( NULL, true );
  442. HANDLE hZipFile = pZip->ParseFromDisk( pZipName );
  443. if ( !hZipFile )
  444. {
  445. Msg( "Bad or missing zip file, failed to open '%s'\n", pZipName );
  446. return;
  447. }
  448. CUtlBuffer preloadBuffer;
  449. if ( !pZip->ReadFileFromZip( hZipFile, PRELOAD_SECTION_NAME, false, preloadBuffer ) )
  450. {
  451. Msg( "No preload info for '%s'\n", pZipName );
  452. return;
  453. }
  454. preloadBuffer.ActivateByteSwapping( IsPC() );
  455. ZIP_PreloadHeader header;
  456. preloadBuffer.GetObjects( &header );
  457. // get the dir table
  458. ZIP_PreloadDirectoryEntry *pDir = (ZIP_PreloadDirectoryEntry *)malloc( header.PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) );
  459. preloadBuffer.GetObjects( pDir, header.PreloadDirectoryEntries );
  460. // get the remap table
  461. unsigned short *pRemap = (unsigned short *)malloc( header.DirectoryEntries * sizeof( unsigned short ) );
  462. for ( unsigned int i = 0; i < header.DirectoryEntries; i++ )
  463. {
  464. pRemap[i] = preloadBuffer.GetShort();
  465. }
  466. int zipIndex = -1;
  467. int fileSize;
  468. char fileName[MAX_PATH];
  469. // iterate preload entries sequentially
  470. CUtlDict< unsigned int, int > sizes( true );
  471. for ( unsigned int i = 0; i < header.DirectoryEntries; i++ )
  472. {
  473. fileName[0] = '\0';
  474. fileSize = 0;
  475. zipIndex = pZip->GetNextFilename( zipIndex, fileName, sizeof( fileName ), fileSize );
  476. unsigned short zipPreloadDirIndex = pRemap[i];
  477. if ( zipPreloadDirIndex == INVALID_PRELOAD_ENTRY )
  478. {
  479. continue;
  480. }
  481. Msg( "Offset: 0x%8.8x Length: %5d %s (%d)\n", pDir[zipPreloadDirIndex].DataOffset, pDir[zipPreloadDirIndex].Length, fileName, fileSize );
  482. // total preload sizes by extension
  483. const char *pExt = V_GetFileExtension( fileName );
  484. if ( !pExt )
  485. {
  486. pExt = "???";
  487. }
  488. int iIndex = sizes.Find( pExt );
  489. if ( iIndex == sizes.InvalidIndex() )
  490. {
  491. iIndex = sizes.Insert( pExt );
  492. sizes[iIndex] = 0;
  493. }
  494. sizes[iIndex] += pDir[zipPreloadDirIndex].Length;
  495. }
  496. Msg( "\n" );
  497. Msg( "Preload Size: %.2f MB\n", (float)preloadBuffer.TellMaxPut()/(1024.0f * 1024.0f) );
  498. Msg( "Zip Entries: %d\n", header.DirectoryEntries );
  499. Msg( "Preload Entries: %d\n", header.PreloadDirectoryEntries );
  500. // dump each extension's total size, necessary for debugging who is the largest contributor
  501. for ( int i = 0; i < sizes.Count(); i++ )
  502. {
  503. Msg( "Extension: '%3s' %d bytes (%.2f%s)\n", sizes.GetElementName( i ), sizes[i], (float)sizes[i]/(float)preloadBuffer.TellMaxPut() * 100.0f, "%%" );
  504. }
  505. Msg( "\n" );
  506. free( pRemap );
  507. free( pDir );
  508. IZip::ReleaseZip( pZip );
  509. }