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