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.

440 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "vbsp.h"
  9. #include "UtlBuffer.h"
  10. #include "utlsymbol.h"
  11. #include "utlrbtree.h"
  12. #include "KeyValues.h"
  13. #include "bsplib.h"
  14. #include "materialpatch.h"
  15. #include "tier1/strtools.h"
  16. // case insensitive
  17. static CUtlSymbolTable s_SymbolTable( 0, 32, true );
  18. struct NameTranslationLookup_t
  19. {
  20. CUtlSymbol m_OriginalFileName;
  21. CUtlSymbol m_PatchFileName;
  22. };
  23. static bool NameTranslationLessFunc( NameTranslationLookup_t const& src1,
  24. NameTranslationLookup_t const& src2 )
  25. {
  26. return src1.m_PatchFileName < src2.m_PatchFileName;
  27. }
  28. CUtlRBTree<NameTranslationLookup_t, int> s_MapPatchedMatToOriginalMat( 0, 256, NameTranslationLessFunc );
  29. void AddNewTranslation( const char *pOriginalMaterialName, const char *pNewMaterialName )
  30. {
  31. NameTranslationLookup_t newEntry;
  32. newEntry.m_OriginalFileName = s_SymbolTable.AddString( pOriginalMaterialName );
  33. newEntry.m_PatchFileName = s_SymbolTable.AddString( pNewMaterialName );
  34. s_MapPatchedMatToOriginalMat.Insert( newEntry );
  35. }
  36. const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName )
  37. {
  38. const char *pRetName = NULL;
  39. int id;
  40. NameTranslationLookup_t lookup;
  41. lookup.m_PatchFileName = s_SymbolTable.AddString( pPatchMaterialName );
  42. do
  43. {
  44. id = s_MapPatchedMatToOriginalMat.Find( lookup );
  45. if( id >= 0 )
  46. {
  47. NameTranslationLookup_t &found = s_MapPatchedMatToOriginalMat[id];
  48. lookup.m_PatchFileName = found.m_OriginalFileName;
  49. pRetName = s_SymbolTable.String( found.m_OriginalFileName );
  50. }
  51. } while( id >= 0 );
  52. if( !pRetName )
  53. {
  54. // This isn't a patched material, so just return the original name.
  55. return pPatchMaterialName;
  56. }
  57. return pRetName;
  58. }
  59. void CreateMaterialPatchRecursive( KeyValues *pOriginalKeyValues, KeyValues *pPatchKeyValues, int nKeys, const MaterialPatchInfo_t *pInfo )
  60. {
  61. int i;
  62. for( i = 0; i < nKeys; i++ )
  63. {
  64. const char *pVal = pOriginalKeyValues->GetString( pInfo[i].m_pKey, NULL );
  65. if( !pVal )
  66. continue;
  67. if( pInfo[i].m_pRequiredOriginalValue && Q_stricmp( pVal, pInfo[i].m_pRequiredOriginalValue ) != 0 )
  68. continue;
  69. pPatchKeyValues->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue );
  70. }
  71. KeyValues *pScan;
  72. for( pScan = pOriginalKeyValues->GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() )
  73. {
  74. CreateMaterialPatchRecursive( pScan, pPatchKeyValues->FindKey( pScan->GetName(), true ), nKeys, pInfo );
  75. }
  76. }
  77. //-----------------------------------------------------------------------------
  78. // A version which allows you to patch multiple key values
  79. //-----------------------------------------------------------------------------
  80. void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName,
  81. int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType )
  82. {
  83. char pOldVMTFile[ 512 ];
  84. char pNewVMTFile[ 512 ];
  85. AddNewTranslation( pOriginalMaterialName, pNewMaterialName );
  86. Q_snprintf( pOldVMTFile, 512, "materials/%s.vmt", pOriginalMaterialName );
  87. Q_snprintf( pNewVMTFile, 512, "materials/%s.vmt", pNewMaterialName );
  88. // printf( "Creating material patch file %s which points at %s\n", newVMTFile, oldVMTFile );
  89. KeyValues *kv = new KeyValues( "patch" );
  90. if ( !kv )
  91. {
  92. Error( "Couldn't allocate KeyValues for %s!!!", pNewMaterialName );
  93. }
  94. kv->SetString( "include", pOldVMTFile );
  95. const char *pSectionName = (nPatchType == PATCH_INSERT) ? "insert" : "replace";
  96. KeyValues *section = kv->FindKey( pSectionName, true );
  97. if( nPatchType == PATCH_REPLACE )
  98. {
  99. char name[512];
  100. Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pOriginalMaterialName ) );
  101. KeyValues *origkv = new KeyValues( "blah" );
  102. if ( !origkv->LoadFromFile( g_pFileSystem, name ) )
  103. {
  104. origkv->deleteThis();
  105. Assert( 0 );
  106. return;
  107. }
  108. CreateMaterialPatchRecursive( origkv, section, nKeys, pInfo );
  109. origkv->deleteThis();
  110. }
  111. else
  112. {
  113. for ( int i = 0; i < nKeys; ++i )
  114. {
  115. section->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue );
  116. }
  117. }
  118. // Write patched .vmt into a memory buffer
  119. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  120. kv->RecursiveSaveToFile( buf, 0 );
  121. // Add to pak file for this .bsp
  122. AddBufferToPak( GetPakFile(), pNewVMTFile, (void*)buf.Base(), buf.TellPut(), true );
  123. // Cleanup
  124. kv->deleteThis();
  125. }
  126. //-----------------------------------------------------------------------------
  127. // Patches a single keyvalue in a material
  128. //-----------------------------------------------------------------------------
  129. void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName,
  130. const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType )
  131. {
  132. MaterialPatchInfo_t info;
  133. info.m_pKey = pNewKey;
  134. info.m_pValue = pNewValue;
  135. CreateMaterialPatch( pOriginalMaterialName, pNewMaterialName, 1, &info, nPatchType );
  136. }
  137. //-----------------------------------------------------------------------------
  138. // Scan material + all subsections for key
  139. //-----------------------------------------------------------------------------
  140. static bool DoesMaterialHaveKey( KeyValues *pKeyValues, const char *pKeyName )
  141. {
  142. const char *pVal;
  143. pVal = pKeyValues->GetString( pKeyName, NULL );
  144. if ( pVal != NULL )
  145. return true;
  146. for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() )
  147. {
  148. if ( DoesMaterialHaveKey( pSubKey, pKeyName) )
  149. return true;
  150. }
  151. return false;
  152. }
  153. //-----------------------------------------------------------------------------
  154. // Scan material + all subsections for key/value pair
  155. //-----------------------------------------------------------------------------
  156. static bool DoesMaterialHaveKeyValuePair( KeyValues *pKeyValues, const char *pKeyName, const char *pSearchValue )
  157. {
  158. const char *pVal;
  159. pVal = pKeyValues->GetString( pKeyName, NULL );
  160. if ( pVal != NULL && ( Q_stricmp( pSearchValue, pVal ) == 0 ) )
  161. return true;
  162. for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() )
  163. {
  164. if ( DoesMaterialHaveKeyValuePair( pSubKey, pKeyName, pSearchValue ) )
  165. return true;
  166. }
  167. return false;
  168. }
  169. //-----------------------------------------------------------------------------
  170. // Scan material + all subsections for key
  171. //-----------------------------------------------------------------------------
  172. bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName )
  173. {
  174. char name[512];
  175. Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) );
  176. KeyValues *kv = new KeyValues( "blah" );
  177. if ( !kv->LoadFromFile( g_pFileSystem, name ) )
  178. {
  179. kv->deleteThis();
  180. return NULL;
  181. }
  182. bool retVal = DoesMaterialHaveKey( kv, pKeyName );
  183. kv->deleteThis();
  184. return retVal;
  185. }
  186. //-----------------------------------------------------------------------------
  187. // Scan material + all subsections for key/value pair
  188. //-----------------------------------------------------------------------------
  189. bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue )
  190. {
  191. char name[512];
  192. Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) );
  193. KeyValues *kv = new KeyValues( "blah" );
  194. if ( !kv->LoadFromFile( g_pFileSystem, name ) )
  195. {
  196. kv->deleteThis();
  197. return NULL;
  198. }
  199. bool retVal = DoesMaterialHaveKeyValuePair( kv, pKeyName, pSearchValue );
  200. kv->deleteThis();
  201. return retVal;
  202. }
  203. //-----------------------------------------------------------------------------
  204. // Gets a material value from a material. Ignores all patches
  205. //-----------------------------------------------------------------------------
  206. bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len )
  207. {
  208. char name[512];
  209. Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) );
  210. KeyValues *kv = new KeyValues( "blah" );
  211. if ( !kv->LoadFromFile( g_pFileSystem, name ) )
  212. {
  213. // Assert( 0 );
  214. kv->deleteThis();
  215. return NULL;
  216. }
  217. const char *pTmpValue = kv->GetString( pKey, NULL );
  218. if( pTmpValue )
  219. {
  220. Q_strncpy( pValue, pTmpValue, len );
  221. }
  222. kv->deleteThis();
  223. return ( pTmpValue != NULL );
  224. }
  225. //-----------------------------------------------------------------------------
  226. // Finds the original material associated with a patched material
  227. //-----------------------------------------------------------------------------
  228. MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain )
  229. {
  230. MaterialSystemMaterial_t matID;
  231. matID = FindMaterial( GetOriginalMaterialNameForPatchedMaterial( materialName ), pFound, bComplain );
  232. return matID;
  233. }
  234. //-----------------------------------------------------------------------------
  235. // Load keyvalues from the local pack file, or from a file
  236. //-----------------------------------------------------------------------------
  237. bool LoadKeyValuesFromPackOrFile( const char *pFileName, KeyValues *pKeyValues )
  238. {
  239. CUtlBuffer buf;
  240. if ( ReadFileFromPak( GetPakFile(), pFileName, true, buf ) )
  241. {
  242. return pKeyValues->LoadFromBuffer( pFileName, buf );
  243. }
  244. return pKeyValues->LoadFromFile( g_pFileSystem, pFileName );
  245. }
  246. //-----------------------------------------------------------------------------
  247. // VMT parser
  248. //-----------------------------------------------------------------------------
  249. static void InsertKeyValues( KeyValues &dst, KeyValues& src, bool bCheckForExistence )
  250. {
  251. KeyValues *pSrcVar = src.GetFirstSubKey();
  252. while( pSrcVar )
  253. {
  254. if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) )
  255. {
  256. switch( pSrcVar->GetDataType() )
  257. {
  258. case KeyValues::TYPE_STRING:
  259. dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() );
  260. break;
  261. case KeyValues::TYPE_INT:
  262. dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() );
  263. break;
  264. case KeyValues::TYPE_FLOAT:
  265. dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() );
  266. break;
  267. case KeyValues::TYPE_PTR:
  268. dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() );
  269. break;
  270. }
  271. }
  272. pSrcVar = pSrcVar->GetNextKey();
  273. }
  274. }
  275. static void ExpandPatchFile( KeyValues &keyValues )
  276. {
  277. int nCount = 0;
  278. while( nCount < 10 && stricmp( keyValues.GetName(), "patch" ) == 0 )
  279. {
  280. // WriteKeyValuesToFile( "patch.txt", keyValues );
  281. const char *pIncludeFileName = keyValues.GetString( "include" );
  282. if( !pIncludeFileName )
  283. return;
  284. KeyValues * includeKeyValues = new KeyValues( "vmt" );
  285. int nBufLen = Q_strlen( pIncludeFileName ) + Q_strlen( "materials/.vmt" ) + 1;
  286. char *pFileName = ( char * )stackalloc( nBufLen );
  287. Q_strncpy( pFileName, pIncludeFileName, nBufLen );
  288. bool bSuccess = LoadKeyValuesFromPackOrFile( pFileName, includeKeyValues );
  289. if ( !bSuccess )
  290. {
  291. includeKeyValues->deleteThis();
  292. return;
  293. }
  294. KeyValues *pInsertSection = keyValues.FindKey( "insert" );
  295. if( pInsertSection )
  296. {
  297. InsertKeyValues( *includeKeyValues, *pInsertSection, false );
  298. keyValues = *includeKeyValues;
  299. }
  300. KeyValues *pReplaceSection = keyValues.FindKey( "replace" );
  301. if( pReplaceSection )
  302. {
  303. InsertKeyValues( *includeKeyValues, *pReplaceSection, true );
  304. keyValues = *includeKeyValues;
  305. }
  306. // Could add other commands here, like "delete", "rename", etc.
  307. includeKeyValues->deleteThis();
  308. nCount++;
  309. }
  310. if( nCount >= 10 )
  311. {
  312. Warning( "Infinite recursion in patch file?\n" );
  313. }
  314. }
  315. KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags )
  316. {
  317. // Load the underlying file
  318. KeyValues *kv = new KeyValues( "blah" );
  319. char pFullMaterialName[512];
  320. Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName );
  321. if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) )
  322. {
  323. // Assert( 0 );
  324. kv->deleteThis();
  325. return NULL;
  326. }
  327. if( nFlags & LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH )
  328. {
  329. ExpandPatchFile( *kv );
  330. }
  331. return kv;
  332. }
  333. void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv )
  334. {
  335. char pFullMaterialName[512];
  336. Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName );
  337. // Write patched .vmt into a memory buffer
  338. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  339. kv->RecursiveSaveToFile( buf, 0 );
  340. // Add to pak file for this .bsp
  341. AddBufferToPak( GetPakFile(), pFullMaterialName, (void*)buf.Base(), buf.TellPut(), true );
  342. // Cleanup
  343. kv->deleteThis();
  344. }
  345. //-----------------------------------------------------------------------------
  346. // Gets a keyvalue from a *patched* material
  347. //-----------------------------------------------------------------------------
  348. bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len )
  349. {
  350. // Load the underlying file so that we can check if env_cubemap is in there.
  351. KeyValues *kv = new KeyValues( "blah" );
  352. char pFullMaterialName[512];
  353. Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName );
  354. if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) )
  355. {
  356. // Assert( 0 );
  357. kv->deleteThis();
  358. return NULL;
  359. }
  360. ExpandPatchFile( *kv );
  361. const char *pTmpValue = kv->GetString( pKey, NULL );
  362. if( pTmpValue )
  363. {
  364. Q_strncpy( pValue, pTmpValue, len );
  365. }
  366. kv->deleteThis();
  367. return ( pTmpValue != NULL );
  368. }