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.
462 lines
12 KiB
462 lines
12 KiB
//===================== 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(); |
|
}
|
|
|