summaryrefslogtreecommitdiff
path: root/tier2/keyvaluesmacros.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /tier2/keyvaluesmacros.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'tier2/keyvaluesmacros.cpp')
-rw-r--r--tier2/keyvaluesmacros.cpp462
1 files changed, 462 insertions, 0 deletions
diff --git a/tier2/keyvaluesmacros.cpp b/tier2/keyvaluesmacros.cpp
new file mode 100644
index 0000000..11a5c97
--- /dev/null
+++ b/tier2/keyvaluesmacros.cpp
@@ -0,0 +1,462 @@
+//===================== Copyright (c) Valve Corporation. All Rights Reserved. ======================
+//
+//==================================================================================================
+
+
+#include "filesystem.h"
+#include "tier1/KeyValues.h"
+#include "tier2/keyvaluesmacros.h"
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+//--------------------------------------------------------------------------------------------------
+// Returns true if the passed string matches the filename style glob, false otherwise
+// * matches any characters, ? matches any single character, otherwise case insensitive matching
+//--------------------------------------------------------------------------------------------------
+bool GlobMatch( const char *pszGlob, const char *pszString )
+{
+ while ( ( *pszString != '\0' ) && ( *pszGlob != '*' ) )
+ {
+ if ( ( V_strnicmp( pszGlob, pszString, 1 ) != 0 ) && ( *pszGlob != '?' ) )
+ {
+ return false;
+ }
+
+ ++pszGlob;
+ ++pszString;
+ }
+
+ const char *pszGlobTmp = nullptr;
+ const char *pszStringTmp = nullptr;
+
+ while ( *pszString )
+ {
+ if ( *pszGlob == '*' )
+ {
+ ++pszGlob;
+
+ if ( *pszGlob == '\0' )
+ {
+ return true;
+ }
+
+ pszGlobTmp = pszGlob;
+ pszStringTmp = pszString + 1;
+ }
+ else if ( ( V_strnicmp( pszGlob, pszString, 1 ) == 0 ) || ( *pszGlob == '?' ) )
+ {
+ ++pszGlob;
+ ++pszString;
+ }
+ else
+ {
+ pszGlob = pszGlobTmp;
+ pszString = pszStringTmp++;
+ }
+ }
+
+ while ( *pszGlob == '*' )
+ {
+ ++pszGlob;
+ }
+
+ return *pszGlob == '\0';
+}
+
+
+//--------------------------------------------------------------------------------------------------
+// Inserts pkvToInsert after pkvAfter but setting pkvAfter's NextKey to pkvInsert
+//--------------------------------------------------------------------------------------------------
+static void InsertKeyValuesAfter( KeyValues *pkvAfter, KeyValues *pkvToInsert )
+{
+ Assert( pkvAfter );
+ Assert( pkvToInsert );
+
+ pkvToInsert->SetNextKey( pkvAfter->GetNextKey() );
+ pkvAfter->SetNextKey( pkvToInsert );
+}
+
+
+//--------------------------------------------------------------------------------------------------
+//
+//--------------------------------------------------------------------------------------------------
+static KeyValues *ReplaceSubKeyWithCopy( KeyValues *pkvParent, KeyValues *pkvToReplace, KeyValues *pkvReplaceWith )
+{
+ Assert( pkvReplaceWith->GetFirstSubKey() == nullptr );
+
+ KeyValues *pkvCopy = pkvReplaceWith->MakeCopy();
+ Assert( pkvCopy->GetFirstSubKey() == nullptr );
+ Assert( pkvCopy->GetNextKey() == nullptr );
+
+ InsertKeyValuesAfter( pkvToReplace, pkvCopy );
+ pkvParent->RemoveSubKey( pkvToReplace );
+ pkvToReplace->deleteThis();
+
+ return pkvCopy;
+}
+
+
+//--------------------------------------------------------------------------------------------------
+// Handles a KeyValues #insert macro. Replaces the #insert KeyValues with the specified file
+// if it can be loaded. This is not called #include because base KeyValue's already has #include
+// but it has two issues. The #include is relative to the keyvalues, these paths are resolved
+// normally via IFileSystem and #include only works at the top level, #insert works no matter
+// how deep the #insert macro is
+//--------------------------------------------------------------------------------------------------
+static KeyValues *HandleKeyValuesMacro_Insert( KeyValues *pkvInsert, KeyValues *pkvParent )
+{
+ const char *pszName = pkvInsert->GetName();
+
+ if ( V_stricmp( "#insert", pszName ) != 0 )
+ return nullptr;
+
+ // Have an #insert key
+
+ if ( pkvInsert->GetFirstSubKey() )
+ {
+ // Invalid, has sub keys
+ Msg( "Error: #insert on key with subkeys, can only do #insert with simple key/value with string value\n" );
+ return nullptr;
+ }
+
+ if ( pkvInsert->GetDataType() != KeyValues::TYPE_STRING )
+ {
+ // Invalid, value isn't a string
+ Msg( "Error: #insert on key without a data type of string, can only do #insert with simple key/value with string value\n" );
+ return nullptr;
+ }
+
+ const char *pszInsert = pkvInsert->GetString();
+ if ( !pszInsert && *pszInsert == '\0' )
+ {
+ // Invalid, value is empty string
+ Msg( "Error: #insert on key with empty string value, can only do #insert with simple key/value with string value\n" );
+ return nullptr;
+ }
+
+ FileHandle_t f = g_pFullFileSystem->Open( pszInsert, "rb" );
+ if ( !f )
+ {
+ // Invalid, couldn't open #insert file
+ Msg( "Error: #insert couldn't open file: %s\n", pszInsert );
+ return nullptr;
+ }
+
+ uint nFileSize = g_pFullFileSystem->Size( f );
+ if ( nFileSize == 0 )
+ {
+ // Invalid, empty file
+ Msg( "Error: #insert empty file: %s\n", pszInsert );
+ return nullptr;
+ }
+
+ uint nBufSize = g_pFullFileSystem->GetOptimalReadSize( f, nFileSize + 2 /* null termination */ + 8 /* "\"x\"\n{\n}\n" */ );
+ char *pBuf = ( char* )g_pFullFileSystem->AllocOptimalReadBuffer( f, nBufSize );
+ pBuf[0] = '"';
+ pBuf[1] = 'i';
+ pBuf[2] = '"';
+ pBuf[3] = '\n';
+ pBuf[4] = '{';
+ pBuf[5] = '\n';
+
+ bool bRetOK = ( g_pFullFileSystem->ReadEx( pBuf + 6, nBufSize - 6, nFileSize, f ) != 0 );
+
+ g_pFullFileSystem->Close( f );
+
+ KeyValues *pkvNew = nullptr;
+
+ if ( bRetOK )
+ {
+ pBuf[nFileSize + 6 + 0] = '}';
+ pBuf[nFileSize + 6 + 1] = '\n';
+ pBuf[nFileSize + 6 + 2] = '\0';
+ pBuf[nFileSize + 6 + 3] = '\0'; // Double NULL termination
+
+ pkvNew = new KeyValues( pszInsert );
+
+ bRetOK = pkvNew->LoadFromBuffer( pszInsert, pBuf, g_pFullFileSystem );
+ }
+ else
+ {
+ Msg( "Error: #insert couldn't read file: %s\n", pszInsert );
+ }
+
+ g_pFullFileSystem->FreeOptimalReadBuffer( pBuf );
+
+ KeyValues *pkvReturn = nullptr;
+
+ CUtlVector< KeyValues * > newKeyList;
+
+ if ( bRetOK )
+ {
+ KeyValues *pkvInsertAfter = pkvInsert;
+
+ KeyValues *pkvNewSubKey = pkvNew->GetFirstSubKey();
+ pkvReturn = pkvNewSubKey;
+
+ while ( pkvNewSubKey )
+ {
+ KeyValues *pkvNextNewSubKey = pkvNewSubKey->GetNextKey();
+
+ pkvNew->RemoveSubKey( pkvNewSubKey );
+
+ bool bFound = false;
+
+ if ( pkvNewSubKey->GetFirstSubKey() == nullptr )
+ {
+ for ( KeyValues *pkvChild = pkvParent->GetFirstSubKey(); pkvChild; pkvChild = pkvChild->GetNextKey() )
+ {
+ if ( pkvChild == pkvInsert )
+ continue;
+
+ if ( pkvChild->GetNameSymbol() == pkvNewSubKey->GetNameSymbol() )
+ {
+ bFound = true;
+ break;
+ }
+ }
+ }
+
+ if ( !bFound )
+ {
+ InsertKeyValuesAfter( pkvInsertAfter, pkvNewSubKey );
+
+ pkvInsertAfter = pkvNewSubKey;
+
+ newKeyList.AddToTail( pkvNewSubKey );
+ }
+
+ pkvNewSubKey = pkvNextNewSubKey;
+ }
+
+ pkvParent->RemoveSubKey( pkvInsert );
+ pkvInsert->deleteThis();
+ }
+
+ if ( pkvNew )
+ {
+ pkvNew->deleteThis();
+ }
+
+ for ( int i = 0; i < newKeyList.Count(); ++i )
+ {
+ HandleKeyValuesMacros( pkvParent, newKeyList[i] );
+ }
+
+ return pkvReturn;
+}
+
+
+//-----------------------------------------------------------------------------
+// Merge pkvSrc over pkvDst, adding any new keys from src to dst but overwriting
+// existing keys in dst with keys with matching names from src
+//-----------------------------------------------------------------------------
+static void UpdateKeyValuesBlock( KeyValues *pkvDst, KeyValues *pkvUpdate )
+{
+ Assert( pkvDst->GetFirstSubKey() );
+ Assert( pkvUpdate->GetFirstSubKey() );
+
+ for ( KeyValues *pkvUpdateSubKey = pkvUpdate->GetFirstSubKey(); pkvUpdateSubKey; pkvUpdateSubKey = pkvUpdateSubKey->GetNextKey() )
+ {
+ const int nSrcName = pkvUpdateSubKey->GetNameSymbol();
+
+ if ( pkvUpdateSubKey->GetFirstSubKey() )
+ {
+ Msg( "Error: #update has a key with subkeys, only simple key/values are allowed for #update, skipping: %s\n", pkvUpdateSubKey->GetName() );
+ continue;
+ }
+
+ KeyValues *pkvNew = nullptr;
+
+ // Check for an existing key with the same name
+ for ( KeyValues *pkvDstSubKey = pkvDst->GetFirstSubKey(); pkvDstSubKey; pkvDstSubKey = pkvDstSubKey->GetNextKey() )
+ {
+ if ( pkvDstSubKey == pkvUpdate )
+ continue;
+
+ const int nDstName = pkvDstSubKey->GetNameSymbol();
+
+ if ( nSrcName == nDstName )
+ {
+ pkvNew = ReplaceSubKeyWithCopy( pkvDst, pkvDstSubKey, pkvUpdateSubKey );
+ break;
+ }
+ }
+
+ if ( !pkvNew )
+ {
+ // Didn't update an existing key, add a key
+ pkvNew = pkvUpdateSubKey->MakeCopy();
+ pkvDst->AddSubKey( pkvNew ); // TODO: Perhaps add this right after the #update block?
+ }
+
+ Assert( pkvNew );
+
+ // Do inserts right away
+ if ( !V_strcmp( pkvNew->GetName(), "#insert" ) )
+ {
+ while ( pkvNew )
+ {
+ pkvNew = HandleKeyValuesMacros( pkvNew, pkvDst );
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------
+// Handle's #update macros
+//
+// An #update must be a KeyValue block with a KeyValue block as a parent. It will look at sibling
+// KeyValue blocks which match an optional "#glob", or all sibling KeyValue blocks if no "#glob" is
+// specified and will merge all of the #update block's subkeys into each sibling block.
+// overwriting KeyValues if they already exist and adding new KeyValues if they don't.
+//
+// Example:
+//
+// Before:
+//
+// "example"
+// {
+// "wear_level_1"
+// {
+// "one" "one_val"
+// "two" "two_val"
+// }
+// "wear_level_2"
+// {
+// "one" "one_val"
+// "two" "two_val"
+// }
+// "subblock"
+// {
+// "one" "one_val"
+// "two" "two_val"
+// }
+// "#update"
+// {
+// "#glob" "wear_level_*"
+// "one" "updated_one_val"
+// "three" "three_val"
+// }
+// }
+//
+// After:
+//
+// "example"
+// {
+// "wear_level_1"
+// {
+// "one" "updated_one_val"
+// "two" "two_val"
+// "three" "three_val"
+// }
+// "wear_level_2"
+// {
+// "one" "updated_one_val"
+// "two" "two_val"
+// "three" "three_val"
+// }
+// "subblock"
+// {
+// "one" "one_val"
+// "two" "two_val"
+// }
+// }
+//
+//--------------------------------------------------------------------------------------------------
+static KeyValues *HandleKeyValuesMacro_Update( KeyValues *pkvUpdate, KeyValues *pkvParent, bool *pbDidUpdate )
+{
+ const char *pszName = pkvUpdate->GetName();
+
+ if ( V_stricmp( "#update", pszName ) != 0 )
+ return nullptr;
+
+ // Have an #update key
+
+ if ( pkvUpdate->GetFirstSubKey() == nullptr )
+ {
+ // Invalid, has sub keys
+ Msg( "Error: #insert on key without subkeys, can only do #update with a key with subkeys\n" );
+ return nullptr;
+ }
+
+ if ( pkvUpdate->GetDataType() != KeyValues::TYPE_NONE )
+ {
+ // Invalid, value isn't a TYPE_NONE
+ Msg( "Error: #update on key without a data type of NONE, can only do #update with a key with subkeys\n" );
+ return nullptr;
+ }
+
+ const char *pszGlob = nullptr;
+
+ KeyValues *pkvGlob = pkvUpdate->FindKey( "#glob" );
+ if ( !pkvGlob )
+ {
+ pkvGlob = pkvUpdate->FindKey( "glob" );
+ }
+
+ if ( pkvGlob )
+ {
+ pszGlob = pkvGlob->GetString( nullptr, nullptr );
+ pkvUpdate->RemoveSubKey( pkvGlob );
+ }
+
+ for ( KeyValues *pkvParentSubKey = pkvParent->GetFirstSubKey(); pkvParentSubKey; pkvParentSubKey = pkvParentSubKey->GetNextKey() )
+ {
+ if ( pkvParentSubKey == pkvUpdate )
+ continue;
+
+ if ( pszGlob && !GlobMatch( pszGlob, pkvParentSubKey->GetName() ) )
+ continue;
+
+ UpdateKeyValuesBlock( pkvParentSubKey, pkvUpdate );
+ }
+
+ KeyValues *pkvReturn = pkvUpdate->GetNextKey();
+
+ pkvParent->RemoveSubKey( pkvUpdate );
+ pkvUpdate->deleteThis();
+
+ if ( pbDidUpdate )
+ {
+ *pbDidUpdate = true;
+ }
+
+ return pkvReturn;
+}
+
+
+//--------------------------------------------------------------------------------------------------
+// Main external extry point
+//--------------------------------------------------------------------------------------------------
+KeyValues *HandleKeyValuesMacros( KeyValues *kv, KeyValues *pkvParent /* = nullptr */ )
+{
+ KeyValues *pkvNextKey = HandleKeyValuesMacro_Insert( kv, pkvParent );
+ if ( pkvNextKey )
+ {
+ Assert( kv->GetFirstSubKey() == nullptr );
+
+ return pkvNextKey;
+ }
+
+ bool bDidLocalUpdate = false;
+ pkvNextKey = HandleKeyValuesMacro_Update( kv, pkvParent, &bDidLocalUpdate );
+ if ( bDidLocalUpdate )
+ {
+ Assert( kv->GetFirstSubKey() != nullptr );
+
+ return pkvNextKey;
+ }
+
+ KeyValues *pkvSub = kv->GetFirstSubKey();
+ while ( pkvSub )
+ {
+ pkvSub = HandleKeyValuesMacros( pkvSub, kv );
+ }
+
+ return kv->GetNextKey();
+}