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.

536 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "sceneimage.h"
  9. #include "choreoscene.h"
  10. #include "iscenetokenprocessor.h"
  11. #include "scenefilecache/SceneImageFile.h"
  12. #include "lzma/lzma.h"
  13. #include "tier1/utlbuffer.h"
  14. #include "tier1/UtlStringMap.h"
  15. #include "tier1/utlvector.h"
  16. #include "tier1/UtlSortVector.h"
  17. #include "scriplib.h"
  18. #include "cmdlib.h"
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include "tier0/memdbgon.h"
  21. class CSceneImage : public ISceneImage
  22. {
  23. public:
  24. virtual bool CreateSceneImageFile( CUtlBuffer &targetBuffer, char const *pchModPath, bool bLittleEndian, bool bQuiet, ISceneCompileStatus *Status );
  25. };
  26. static CSceneImage g_SceneImage;
  27. ISceneImage *g_pSceneImage = &g_SceneImage;
  28. struct SceneFile_t
  29. {
  30. SceneFile_t()
  31. {
  32. msecs = 0;
  33. }
  34. CUtlString fileName;
  35. CUtlBuffer compiledBuffer;
  36. unsigned int msecs;
  37. CUtlVector< short > soundList;
  38. };
  39. CUtlVector< SceneFile_t > g_SceneFiles;
  40. //-----------------------------------------------------------------------------
  41. // Helper for parsing scene data file
  42. //-----------------------------------------------------------------------------
  43. class CSceneTokenProcessor : public ISceneTokenProcessor
  44. {
  45. public:
  46. const char *CurrentToken( void )
  47. {
  48. return token;
  49. }
  50. bool GetToken( bool crossline )
  51. {
  52. return ::GetToken( crossline ) ? true : false;
  53. }
  54. bool TokenAvailable( void )
  55. {
  56. return ::TokenAvailable() ? true : false;
  57. }
  58. void Error( const char *fmt, ... )
  59. {
  60. char string[2048];
  61. va_list argptr;
  62. va_start( argptr, fmt );
  63. Q_vsnprintf( string, sizeof(string), fmt, argptr );
  64. va_end( argptr );
  65. Warning( "%s", string );
  66. Assert( 0 );
  67. }
  68. };
  69. static CSceneTokenProcessor g_SceneTokenProcessor;
  70. ISceneTokenProcessor *tokenprocessor = &g_SceneTokenProcessor;
  71. // a simple case insensitive string pool
  72. // the final pool contains all the unique strings seperated by a null
  73. class CChoreoStringPool : public IChoreoStringPool
  74. {
  75. public:
  76. CChoreoStringPool() : m_StringMap( true )
  77. {
  78. m_nOffset = 0;
  79. }
  80. // Returns a valid id into the string table
  81. virtual short FindOrAddString( const char *pString )
  82. {
  83. int stringId = m_StringMap.Find( pString );
  84. if ( stringId != m_StringMap.InvalidIndex() )
  85. {
  86. // found in pool
  87. return stringId;
  88. }
  89. int &nOffset = m_StringMap[pString];
  90. nOffset = m_nOffset;
  91. // advance by string and null
  92. m_nOffset += strlen( pString ) + 1;
  93. stringId = m_StringMap.Find( pString );
  94. Assert( stringId >= 0 && stringId <= 32767 );
  95. return stringId;
  96. }
  97. virtual bool GetString( short stringId, char *buff, int buffSize )
  98. {
  99. if ( stringId < 0 || stringId >= m_StringMap.GetNumStrings() )
  100. {
  101. V_strncpy( buff, "", buffSize );
  102. return false;
  103. }
  104. V_strncpy( buff, m_StringMap.String( stringId ), buffSize );
  105. return true;
  106. }
  107. int GetNumStrings()
  108. {
  109. return m_StringMap.GetNumStrings();
  110. }
  111. unsigned int GetPoolSize()
  112. {
  113. return m_nOffset;
  114. }
  115. // build the final pool
  116. void GetTableAndPool( CUtlVector< unsigned int > &offsets, CUtlBuffer &buffer )
  117. {
  118. offsets.Purge();
  119. buffer.Purge();
  120. offsets.EnsureCapacity( m_StringMap.GetNumStrings() );
  121. buffer.EnsureCapacity( m_nOffset );
  122. unsigned int currentOffset = 0;
  123. for ( int i = 0; i < m_StringMap.GetNumStrings(); i++ )
  124. {
  125. offsets.AddToTail( currentOffset );
  126. const char *pString = m_StringMap.String( i );
  127. buffer.Put( pString, strlen( pString ) + 1 );
  128. currentOffset += strlen( pString ) + 1;
  129. }
  130. Assert( currentOffset == m_nOffset );
  131. // align string pool to end on dword boundary
  132. while ( buffer.TellMaxPut() & 0x03 )
  133. {
  134. buffer.PutChar( '\0' );
  135. m_nOffset++;
  136. }
  137. }
  138. void DumpPool()
  139. {
  140. for ( int i = 0; i < m_StringMap.GetNumStrings(); i++ )
  141. {
  142. const char *pString = m_StringMap.String( i );
  143. Msg( "%s\n", pString );
  144. }
  145. }
  146. void Reset()
  147. {
  148. m_StringMap.Purge();
  149. m_nOffset = 0;
  150. }
  151. private:
  152. CUtlStringMap< int > m_StringMap;
  153. unsigned int m_nOffset;
  154. };
  155. CChoreoStringPool g_ChoreoStringPool;
  156. //-----------------------------------------------------------------------------
  157. // Helper for crawling events to determine sounds
  158. //-----------------------------------------------------------------------------
  159. void FindSoundsInEvent( CChoreoEvent *pEvent, CUtlVector< short >& soundList )
  160. {
  161. if ( !pEvent || pEvent->GetType() != CChoreoEvent::SPEAK )
  162. return;
  163. unsigned short stringId = g_ChoreoStringPool.FindOrAddString( pEvent->GetParameters() );
  164. if ( soundList.Find( stringId ) == soundList.InvalidIndex() )
  165. {
  166. soundList.AddToTail( stringId );
  167. }
  168. if ( pEvent->GetCloseCaptionType() == CChoreoEvent::CC_MASTER )
  169. {
  170. char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ];
  171. if ( pEvent->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) )
  172. {
  173. stringId = g_ChoreoStringPool.FindOrAddString( tok );
  174. if ( soundList.Find( stringId ) == soundList.InvalidIndex() )
  175. {
  176. soundList.AddToTail( stringId );
  177. }
  178. }
  179. }
  180. }
  181. //-----------------------------------------------------------------------------
  182. // Create binary compiled version of VCD. Stores to a dictionary for later
  183. // post processing
  184. //-----------------------------------------------------------------------------
  185. bool CreateTargetFile_VCD( const char *pSourceName, const char *pTargetName, bool bWriteToZip, bool bLittleEndian )
  186. {
  187. CUtlBuffer sourceBuf;
  188. if ( !scriptlib->ReadFileToBuffer( pSourceName, sourceBuf ) )
  189. {
  190. return false;
  191. }
  192. CRC32_t crcSource;
  193. CRC32_Init( &crcSource );
  194. CRC32_ProcessBuffer( &crcSource, sourceBuf.Base(), sourceBuf.TellMaxPut() );
  195. CRC32_Final( &crcSource );
  196. ParseFromMemory( (char *)sourceBuf.Base(), sourceBuf.TellMaxPut() );
  197. CChoreoScene *pChoreoScene = ChoreoLoadScene( pSourceName, NULL, &g_SceneTokenProcessor, Msg );
  198. if ( !pChoreoScene )
  199. {
  200. return false;
  201. }
  202. int iScene = g_SceneFiles.AddToTail();
  203. g_SceneFiles[iScene].fileName.Set( pSourceName );
  204. // Walk all events looking for SPEAK events
  205. CChoreoEvent *pEvent;
  206. for ( int i = 0; i < pChoreoScene->GetNumEvents(); ++i )
  207. {
  208. pEvent = pChoreoScene->GetEvent( i );
  209. FindSoundsInEvent( pEvent, g_SceneFiles[iScene].soundList );
  210. }
  211. // calc duration
  212. g_SceneFiles[iScene].msecs = (unsigned int)( pChoreoScene->FindStopTime() * 1000.0f + 0.5f );
  213. // compile to binary buffer
  214. g_SceneFiles[iScene].compiledBuffer.SetBigEndian( !bLittleEndian );
  215. pChoreoScene->SaveToBinaryBuffer( g_SceneFiles[iScene].compiledBuffer, crcSource, &g_ChoreoStringPool );
  216. unsigned int compressedSize;
  217. unsigned char *pCompressedBuffer = LZMA_OpportunisticCompress( (unsigned char *)g_SceneFiles[iScene].compiledBuffer.Base(),
  218. g_SceneFiles[iScene].compiledBuffer.TellMaxPut(),
  219. &compressedSize );
  220. if ( pCompressedBuffer )
  221. {
  222. // replace the compiled buffer with the compressed version
  223. g_SceneFiles[iScene].compiledBuffer.Purge();
  224. g_SceneFiles[iScene].compiledBuffer.EnsureCapacity( compressedSize );
  225. g_SceneFiles[iScene].compiledBuffer.Put( pCompressedBuffer, compressedSize );
  226. free( pCompressedBuffer );
  227. }
  228. delete pChoreoScene;
  229. return true;
  230. }
  231. class CSceneImageEntryLessFunc
  232. {
  233. public:
  234. bool Less( const SceneImageEntry_t &entryLHS, const SceneImageEntry_t &entryRHS, void *pCtx )
  235. {
  236. return entryLHS.crcFilename < entryRHS.crcFilename;
  237. }
  238. };
  239. //-----------------------------------------------------------------------------
  240. // A Scene image file contains all the compiled .XCD
  241. //-----------------------------------------------------------------------------
  242. bool CSceneImage::CreateSceneImageFile( CUtlBuffer &targetBuffer, char const *pchModPath, bool bLittleEndian, bool bQuiet, ISceneCompileStatus *pStatus )
  243. {
  244. CUtlVector<fileList_t> vcdFileList;
  245. CUtlSymbolTable vcdSymbolTable( 0, 32, true );
  246. Msg( "\n" );
  247. // get all the VCD files according to the seacrh paths
  248. char searchPaths[512];
  249. g_pFullFileSystem->GetSearchPath( "GAME", false, searchPaths, sizeof( searchPaths ) );
  250. char *pPath = strtok( searchPaths, ";" );
  251. while ( pPath )
  252. {
  253. int currentCount = vcdFileList.Count();
  254. char szPath[MAX_PATH];
  255. V_ComposeFileName( pPath, "scenes/*.vcd", szPath, sizeof( szPath ) );
  256. scriptlib->FindFiles( szPath, true, vcdFileList );
  257. Msg( "Scenes: Searching '%s' - Found %d scenes.\n", szPath, vcdFileList.Count() - currentCount );
  258. pPath = strtok( NULL, ";" );
  259. }
  260. if ( !vcdFileList.Count() )
  261. {
  262. Msg( "Scenes: No Scene Files found!\n" );
  263. return false;
  264. }
  265. // iterate and convert all the VCD files
  266. bool bGameIsTF = V_stristr( pchModPath, "\\tf" ) != NULL;
  267. for ( int i=0; i<vcdFileList.Count(); i++ )
  268. {
  269. const char *pFilename = vcdFileList[i].fileName.String();
  270. const char *pSceneName = V_stristr( pFilename, "scenes\\" );
  271. if ( !pSceneName )
  272. {
  273. continue;
  274. }
  275. if ( !bLittleEndian && bGameIsTF && V_stristr( pSceneName, "high\\" ) )
  276. {
  277. continue;
  278. }
  279. // process files in order they would be found in search paths
  280. // i.e. skipping later processed files that match an earlier conversion
  281. UtlSymId_t symbol = vcdSymbolTable.Find( pSceneName );
  282. if ( symbol == UTL_INVAL_SYMBOL )
  283. {
  284. vcdSymbolTable.AddString( pSceneName );
  285. pStatus->UpdateStatus( pFilename, bQuiet, i, vcdFileList.Count() );
  286. if ( !CreateTargetFile_VCD( pFilename, "", false, bLittleEndian ) )
  287. {
  288. Error( "CreateSceneImageFile: Failed on '%s' conversion!\n", pFilename );
  289. }
  290. }
  291. }
  292. if ( !g_SceneFiles.Count() )
  293. {
  294. // nothing to do
  295. return true;
  296. }
  297. Msg( "Scenes: Finalizing %d unique scenes.\n", g_SceneFiles.Count() );
  298. // get the string pool
  299. CUtlVector< unsigned int > stringOffsets;
  300. CUtlBuffer stringPool;
  301. g_ChoreoStringPool.GetTableAndPool( stringOffsets, stringPool );
  302. if ( !bQuiet )
  303. {
  304. Msg( "Scenes: String Table: %llu bytes\n", (uint64)(stringOffsets.Count() * sizeof( int )) );
  305. Msg( "Scenes: String Pool: %d bytes\n", stringPool.TellMaxPut() );
  306. }
  307. // first header, then lookup table, then string pool blob
  308. int stringPoolStart = sizeof( SceneImageHeader_t ) + stringOffsets.Count() * sizeof( int );
  309. // then directory
  310. int sceneEntryStart = stringPoolStart + stringPool.TellMaxPut();
  311. // then variable sized summaries
  312. int sceneSummaryStart = sceneEntryStart + g_SceneFiles.Count() * sizeof( SceneImageEntry_t );
  313. // then variable sized compiled binary scene data
  314. int sceneDataStart = 0;
  315. // construct header
  316. SceneImageHeader_t imageHeader = { 0 };
  317. imageHeader.nId = SCENE_IMAGE_ID;
  318. imageHeader.nVersion = SCENE_IMAGE_VERSION;
  319. imageHeader.nNumScenes = g_SceneFiles.Count();
  320. imageHeader.nNumStrings = stringOffsets.Count();
  321. imageHeader.nSceneEntryOffset = sceneEntryStart;
  322. if ( !bLittleEndian )
  323. {
  324. imageHeader.nId = BigLong( imageHeader.nId );
  325. imageHeader.nVersion = BigLong( imageHeader.nVersion );
  326. imageHeader.nNumScenes = BigLong( imageHeader.nNumScenes );
  327. imageHeader.nNumStrings = BigLong( imageHeader.nNumStrings );
  328. imageHeader.nSceneEntryOffset = BigLong( imageHeader.nSceneEntryOffset );
  329. }
  330. targetBuffer.Put( &imageHeader, sizeof( imageHeader ) );
  331. // header is immediately followed by string table and pool
  332. for ( int i = 0; i < stringOffsets.Count(); i++ )
  333. {
  334. unsigned int offset = stringPoolStart + stringOffsets[i];
  335. if ( !bLittleEndian )
  336. {
  337. offset = BigLong( offset );
  338. }
  339. targetBuffer.PutInt( offset );
  340. }
  341. Assert( stringPoolStart == targetBuffer.TellMaxPut() );
  342. targetBuffer.Put( stringPool.Base(), stringPool.TellMaxPut() );
  343. // construct directory
  344. CUtlSortVector< SceneImageEntry_t, CSceneImageEntryLessFunc > imageDirectory;
  345. imageDirectory.EnsureCapacity( g_SceneFiles.Count() );
  346. // build directory
  347. // directory is linear sorted by filename checksum for later binary search
  348. for ( int i = 0; i < g_SceneFiles.Count(); i++ )
  349. {
  350. SceneImageEntry_t imageEntry = { 0 };
  351. // name needs to be normalized for determinstic later CRC name calc
  352. // calc crc based on scenes\anydir\anyscene.vcd
  353. char szCleanName[MAX_PATH];
  354. V_strncpy( szCleanName, g_SceneFiles[i].fileName.String(), sizeof( szCleanName ) );
  355. V_strlower( szCleanName );
  356. V_FixSlashes( szCleanName );
  357. char *pName = V_stristr( szCleanName, "scenes\\" );
  358. if ( !pName )
  359. {
  360. // must have scenes\ in filename
  361. Error( "CreateSceneImageFile: Unexpected lack of scenes prefix on %s\n", g_SceneFiles[i].fileName.String() );
  362. }
  363. CRC32_t crcFilename = CRC32_ProcessSingleBuffer( pName, strlen( pName ) );
  364. imageEntry.crcFilename = crcFilename;
  365. // temp store an index to its file, fixup later, necessary to access post sort
  366. imageEntry.nDataOffset = i;
  367. if ( imageDirectory.Find( imageEntry ) != imageDirectory.InvalidIndex() )
  368. {
  369. // filename checksums must be unique or runtime binary search would be bogus
  370. Error( "CreateSceneImageFile: Unexpected filename checksum collision!\n" );
  371. }
  372. imageDirectory.Insert( imageEntry );
  373. }
  374. // determine sort order and start of data after dynamic summaries
  375. CUtlVector< int > writeOrder;
  376. writeOrder.EnsureCapacity( g_SceneFiles.Count() );
  377. sceneDataStart = sceneSummaryStart;
  378. for ( int i = 0; i < imageDirectory.Count(); i++ )
  379. {
  380. // reclaim offset, indicates write order of scene file
  381. int iScene = imageDirectory[i].nDataOffset;
  382. writeOrder.AddToTail( iScene );
  383. // march past each variable sized summary to determine start of scene data
  384. int numSounds = g_SceneFiles[iScene].soundList.Count();
  385. sceneDataStart += sizeof( SceneImageSummary_t ) + ( numSounds - 1 ) * sizeof( int );
  386. }
  387. // finalize and write directory
  388. Assert( sceneEntryStart == targetBuffer.TellMaxPut() );
  389. int nSummaryOffset = sceneSummaryStart;
  390. int nDataOffset = sceneDataStart;
  391. for ( int i = 0; i < imageDirectory.Count(); i++ )
  392. {
  393. int iScene = writeOrder[i];
  394. imageDirectory[i].nDataOffset = nDataOffset;
  395. imageDirectory[i].nDataLength = g_SceneFiles[iScene].compiledBuffer.TellMaxPut();
  396. imageDirectory[i].nSceneSummaryOffset = nSummaryOffset;
  397. if ( !bLittleEndian )
  398. {
  399. imageDirectory[i].crcFilename = BigLong( imageDirectory[i].crcFilename );
  400. imageDirectory[i].nDataOffset = BigLong( imageDirectory[i].nDataOffset );
  401. imageDirectory[i].nDataLength = BigLong( imageDirectory[i].nDataLength );
  402. imageDirectory[i].nSceneSummaryOffset = BigLong( imageDirectory[i].nSceneSummaryOffset );
  403. }
  404. targetBuffer.Put( &imageDirectory[i], sizeof( SceneImageEntry_t ) );
  405. int numSounds = g_SceneFiles[iScene].soundList.Count();
  406. nSummaryOffset += sizeof( SceneImageSummary_t ) + (numSounds - 1) * sizeof( int );
  407. nDataOffset += g_SceneFiles[iScene].compiledBuffer.TellMaxPut();
  408. }
  409. // finalize and write summaries
  410. Assert( sceneSummaryStart == targetBuffer.TellMaxPut() );
  411. for ( int i = 0; i < imageDirectory.Count(); i++ )
  412. {
  413. int iScene = writeOrder[i];
  414. int msecs = g_SceneFiles[iScene].msecs;
  415. int soundCount = g_SceneFiles[iScene].soundList.Count();
  416. if ( !bLittleEndian )
  417. {
  418. msecs = BigLong( msecs );
  419. soundCount = BigLong( soundCount );
  420. }
  421. targetBuffer.PutInt( msecs );
  422. targetBuffer.PutInt( soundCount );
  423. for ( int j = 0; j < g_SceneFiles[iScene].soundList.Count(); j++ )
  424. {
  425. int soundId = g_SceneFiles[iScene].soundList[j];
  426. if ( !bLittleEndian )
  427. {
  428. soundId = BigLong( soundId );
  429. }
  430. targetBuffer.PutInt( soundId );
  431. }
  432. }
  433. // finalize and write data
  434. Assert( sceneDataStart == targetBuffer.TellMaxPut() );
  435. for ( int i = 0; i < imageDirectory.Count(); i++ )
  436. {
  437. int iScene = writeOrder[i];
  438. targetBuffer.Put( g_SceneFiles[iScene].compiledBuffer.Base(), g_SceneFiles[iScene].compiledBuffer.TellMaxPut() );
  439. }
  440. if ( !bQuiet )
  441. {
  442. Msg( "Scenes: Final size: %.2f MB\n", targetBuffer.TellMaxPut() / (1024.0f * 1024.0f ) );
  443. }
  444. // cleanup
  445. g_SceneFiles.Purge();
  446. return true;
  447. }