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
440 lines
13 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
#include "vbsp.h" |
|
#include "UtlBuffer.h" |
|
#include "utlsymbol.h" |
|
#include "utlrbtree.h" |
|
#include "KeyValues.h" |
|
#include "bsplib.h" |
|
#include "materialpatch.h" |
|
#include "tier1/strtools.h" |
|
|
|
// case insensitive |
|
static CUtlSymbolTable s_SymbolTable( 0, 32, true ); |
|
|
|
struct NameTranslationLookup_t |
|
{ |
|
CUtlSymbol m_OriginalFileName; |
|
CUtlSymbol m_PatchFileName; |
|
}; |
|
|
|
static bool NameTranslationLessFunc( NameTranslationLookup_t const& src1, |
|
NameTranslationLookup_t const& src2 ) |
|
{ |
|
return src1.m_PatchFileName < src2.m_PatchFileName; |
|
} |
|
|
|
CUtlRBTree<NameTranslationLookup_t, int> s_MapPatchedMatToOriginalMat( 0, 256, NameTranslationLessFunc ); |
|
|
|
void AddNewTranslation( const char *pOriginalMaterialName, const char *pNewMaterialName ) |
|
{ |
|
NameTranslationLookup_t newEntry; |
|
|
|
newEntry.m_OriginalFileName = s_SymbolTable.AddString( pOriginalMaterialName ); |
|
newEntry.m_PatchFileName = s_SymbolTable.AddString( pNewMaterialName ); |
|
|
|
s_MapPatchedMatToOriginalMat.Insert( newEntry ); |
|
} |
|
|
|
const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ) |
|
{ |
|
const char *pRetName = NULL; |
|
int id; |
|
NameTranslationLookup_t lookup; |
|
lookup.m_PatchFileName = s_SymbolTable.AddString( pPatchMaterialName ); |
|
do |
|
{ |
|
id = s_MapPatchedMatToOriginalMat.Find( lookup ); |
|
if( id >= 0 ) |
|
{ |
|
NameTranslationLookup_t &found = s_MapPatchedMatToOriginalMat[id]; |
|
lookup.m_PatchFileName = found.m_OriginalFileName; |
|
pRetName = s_SymbolTable.String( found.m_OriginalFileName ); |
|
} |
|
} while( id >= 0 ); |
|
if( !pRetName ) |
|
{ |
|
// This isn't a patched material, so just return the original name. |
|
return pPatchMaterialName; |
|
} |
|
return pRetName; |
|
} |
|
|
|
|
|
void CreateMaterialPatchRecursive( KeyValues *pOriginalKeyValues, KeyValues *pPatchKeyValues, int nKeys, const MaterialPatchInfo_t *pInfo ) |
|
{ |
|
int i; |
|
for( i = 0; i < nKeys; i++ ) |
|
{ |
|
const char *pVal = pOriginalKeyValues->GetString( pInfo[i].m_pKey, NULL ); |
|
if( !pVal ) |
|
continue; |
|
if( pInfo[i].m_pRequiredOriginalValue && Q_stricmp( pVal, pInfo[i].m_pRequiredOriginalValue ) != 0 ) |
|
continue; |
|
pPatchKeyValues->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); |
|
} |
|
KeyValues *pScan; |
|
for( pScan = pOriginalKeyValues->GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() ) |
|
{ |
|
CreateMaterialPatchRecursive( pScan, pPatchKeyValues->FindKey( pScan->GetName(), true ), nKeys, pInfo ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// A version which allows you to patch multiple key values |
|
//----------------------------------------------------------------------------- |
|
void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, |
|
int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ) |
|
{ |
|
char pOldVMTFile[ 512 ]; |
|
char pNewVMTFile[ 512 ]; |
|
|
|
AddNewTranslation( pOriginalMaterialName, pNewMaterialName ); |
|
|
|
Q_snprintf( pOldVMTFile, 512, "materials/%s.vmt", pOriginalMaterialName ); |
|
Q_snprintf( pNewVMTFile, 512, "materials/%s.vmt", pNewMaterialName ); |
|
|
|
// printf( "Creating material patch file %s which points at %s\n", newVMTFile, oldVMTFile ); |
|
|
|
KeyValues *kv = new KeyValues( "patch" ); |
|
if ( !kv ) |
|
{ |
|
Error( "Couldn't allocate KeyValues for %s!!!", pNewMaterialName ); |
|
} |
|
|
|
kv->SetString( "include", pOldVMTFile ); |
|
|
|
const char *pSectionName = (nPatchType == PATCH_INSERT) ? "insert" : "replace"; |
|
KeyValues *section = kv->FindKey( pSectionName, true ); |
|
|
|
if( nPatchType == PATCH_REPLACE ) |
|
{ |
|
char name[512]; |
|
Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pOriginalMaterialName ) ); |
|
KeyValues *origkv = new KeyValues( "blah" ); |
|
|
|
if ( !origkv->LoadFromFile( g_pFileSystem, name ) ) |
|
{ |
|
origkv->deleteThis(); |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
CreateMaterialPatchRecursive( origkv, section, nKeys, pInfo ); |
|
origkv->deleteThis(); |
|
} |
|
else |
|
{ |
|
for ( int i = 0; i < nKeys; ++i ) |
|
{ |
|
section->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); |
|
} |
|
} |
|
|
|
// Write patched .vmt into a memory buffer |
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
kv->RecursiveSaveToFile( buf, 0 ); |
|
|
|
// Add to pak file for this .bsp |
|
AddBufferToPak( GetPakFile(), pNewVMTFile, (void*)buf.Base(), buf.TellPut(), true ); |
|
|
|
// Cleanup |
|
kv->deleteThis(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Patches a single keyvalue in a material |
|
//----------------------------------------------------------------------------- |
|
void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, |
|
const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ) |
|
{ |
|
MaterialPatchInfo_t info; |
|
info.m_pKey = pNewKey; |
|
info.m_pValue = pNewValue; |
|
CreateMaterialPatch( pOriginalMaterialName, pNewMaterialName, 1, &info, nPatchType ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Scan material + all subsections for key |
|
//----------------------------------------------------------------------------- |
|
static bool DoesMaterialHaveKey( KeyValues *pKeyValues, const char *pKeyName ) |
|
{ |
|
const char *pVal; |
|
pVal = pKeyValues->GetString( pKeyName, NULL ); |
|
if ( pVal != NULL ) |
|
return true; |
|
|
|
for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) |
|
{ |
|
if ( DoesMaterialHaveKey( pSubKey, pKeyName) ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Scan material + all subsections for key/value pair |
|
//----------------------------------------------------------------------------- |
|
static bool DoesMaterialHaveKeyValuePair( KeyValues *pKeyValues, const char *pKeyName, const char *pSearchValue ) |
|
{ |
|
const char *pVal; |
|
pVal = pKeyValues->GetString( pKeyName, NULL ); |
|
if ( pVal != NULL && ( Q_stricmp( pSearchValue, pVal ) == 0 ) ) |
|
return true; |
|
|
|
for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) |
|
{ |
|
if ( DoesMaterialHaveKeyValuePair( pSubKey, pKeyName, pSearchValue ) ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Scan material + all subsections for key |
|
//----------------------------------------------------------------------------- |
|
bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ) |
|
{ |
|
char name[512]; |
|
Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); |
|
KeyValues *kv = new KeyValues( "blah" ); |
|
|
|
if ( !kv->LoadFromFile( g_pFileSystem, name ) ) |
|
{ |
|
kv->deleteThis(); |
|
return NULL; |
|
} |
|
|
|
bool retVal = DoesMaterialHaveKey( kv, pKeyName ); |
|
|
|
kv->deleteThis(); |
|
return retVal; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Scan material + all subsections for key/value pair |
|
//----------------------------------------------------------------------------- |
|
bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ) |
|
{ |
|
char name[512]; |
|
Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); |
|
KeyValues *kv = new KeyValues( "blah" ); |
|
|
|
if ( !kv->LoadFromFile( g_pFileSystem, name ) ) |
|
{ |
|
kv->deleteThis(); |
|
return NULL; |
|
} |
|
|
|
bool retVal = DoesMaterialHaveKeyValuePair( kv, pKeyName, pSearchValue ); |
|
|
|
kv->deleteThis(); |
|
return retVal; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets a material value from a material. Ignores all patches |
|
//----------------------------------------------------------------------------- |
|
bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) |
|
{ |
|
char name[512]; |
|
Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); |
|
KeyValues *kv = new KeyValues( "blah" ); |
|
|
|
if ( !kv->LoadFromFile( g_pFileSystem, name ) ) |
|
{ |
|
// Assert( 0 ); |
|
kv->deleteThis(); |
|
return NULL; |
|
} |
|
|
|
const char *pTmpValue = kv->GetString( pKey, NULL ); |
|
if( pTmpValue ) |
|
{ |
|
Q_strncpy( pValue, pTmpValue, len ); |
|
} |
|
|
|
kv->deleteThis(); |
|
return ( pTmpValue != NULL ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds the original material associated with a patched material |
|
//----------------------------------------------------------------------------- |
|
MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain ) |
|
{ |
|
MaterialSystemMaterial_t matID; |
|
matID = FindMaterial( GetOriginalMaterialNameForPatchedMaterial( materialName ), pFound, bComplain ); |
|
return matID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Load keyvalues from the local pack file, or from a file |
|
//----------------------------------------------------------------------------- |
|
bool LoadKeyValuesFromPackOrFile( const char *pFileName, KeyValues *pKeyValues ) |
|
{ |
|
CUtlBuffer buf; |
|
if ( ReadFileFromPak( GetPakFile(), pFileName, true, buf ) ) |
|
{ |
|
return pKeyValues->LoadFromBuffer( pFileName, buf ); |
|
} |
|
|
|
return pKeyValues->LoadFromFile( g_pFileSystem, pFileName ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// VMT parser |
|
//----------------------------------------------------------------------------- |
|
static void InsertKeyValues( KeyValues &dst, KeyValues& src, bool bCheckForExistence ) |
|
{ |
|
KeyValues *pSrcVar = src.GetFirstSubKey(); |
|
while( pSrcVar ) |
|
{ |
|
if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) ) |
|
{ |
|
switch( pSrcVar->GetDataType() ) |
|
{ |
|
case KeyValues::TYPE_STRING: |
|
dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() ); |
|
break; |
|
case KeyValues::TYPE_INT: |
|
dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() ); |
|
break; |
|
case KeyValues::TYPE_FLOAT: |
|
dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() ); |
|
break; |
|
case KeyValues::TYPE_PTR: |
|
dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() ); |
|
break; |
|
} |
|
} |
|
pSrcVar = pSrcVar->GetNextKey(); |
|
} |
|
} |
|
|
|
static void ExpandPatchFile( KeyValues &keyValues ) |
|
{ |
|
int nCount = 0; |
|
while( nCount < 10 && stricmp( keyValues.GetName(), "patch" ) == 0 ) |
|
{ |
|
// WriteKeyValuesToFile( "patch.txt", keyValues ); |
|
const char *pIncludeFileName = keyValues.GetString( "include" ); |
|
if( !pIncludeFileName ) |
|
return; |
|
|
|
KeyValues * includeKeyValues = new KeyValues( "vmt" ); |
|
int nBufLen = Q_strlen( pIncludeFileName ) + Q_strlen( "materials/.vmt" ) + 1; |
|
char *pFileName = ( char * )stackalloc( nBufLen ); |
|
Q_strncpy( pFileName, pIncludeFileName, nBufLen ); |
|
bool bSuccess = LoadKeyValuesFromPackOrFile( pFileName, includeKeyValues ); |
|
if ( !bSuccess ) |
|
{ |
|
includeKeyValues->deleteThis(); |
|
return; |
|
} |
|
|
|
KeyValues *pInsertSection = keyValues.FindKey( "insert" ); |
|
if( pInsertSection ) |
|
{ |
|
InsertKeyValues( *includeKeyValues, *pInsertSection, false ); |
|
keyValues = *includeKeyValues; |
|
} |
|
|
|
KeyValues *pReplaceSection = keyValues.FindKey( "replace" ); |
|
if( pReplaceSection ) |
|
{ |
|
InsertKeyValues( *includeKeyValues, *pReplaceSection, true ); |
|
keyValues = *includeKeyValues; |
|
} |
|
|
|
// Could add other commands here, like "delete", "rename", etc. |
|
|
|
includeKeyValues->deleteThis(); |
|
nCount++; |
|
} |
|
|
|
if( nCount >= 10 ) |
|
{ |
|
Warning( "Infinite recursion in patch file?\n" ); |
|
} |
|
} |
|
|
|
KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ) |
|
{ |
|
// Load the underlying file |
|
KeyValues *kv = new KeyValues( "blah" ); |
|
|
|
char pFullMaterialName[512]; |
|
Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); |
|
if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) |
|
{ |
|
// Assert( 0 ); |
|
kv->deleteThis(); |
|
return NULL; |
|
} |
|
|
|
if( nFlags & LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH ) |
|
{ |
|
ExpandPatchFile( *kv ); |
|
} |
|
|
|
return kv; |
|
} |
|
|
|
void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ) |
|
{ |
|
char pFullMaterialName[512]; |
|
Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); |
|
|
|
// Write patched .vmt into a memory buffer |
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
kv->RecursiveSaveToFile( buf, 0 ); |
|
|
|
// Add to pak file for this .bsp |
|
AddBufferToPak( GetPakFile(), pFullMaterialName, (void*)buf.Base(), buf.TellPut(), true ); |
|
|
|
// Cleanup |
|
kv->deleteThis(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets a keyvalue from a *patched* material |
|
//----------------------------------------------------------------------------- |
|
bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) |
|
{ |
|
// Load the underlying file so that we can check if env_cubemap is in there. |
|
KeyValues *kv = new KeyValues( "blah" ); |
|
|
|
char pFullMaterialName[512]; |
|
Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); |
|
if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) |
|
{ |
|
// Assert( 0 ); |
|
kv->deleteThis(); |
|
return NULL; |
|
} |
|
|
|
ExpandPatchFile( *kv ); |
|
|
|
const char *pTmpValue = kv->GetString( pKey, NULL ); |
|
if( pTmpValue ) |
|
{ |
|
Q_strncpy( pValue, pTmpValue, len ); |
|
} |
|
|
|
kv->deleteThis(); |
|
return ( pTmpValue != NULL ); |
|
}
|
|
|