//===================== 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(); }