Skip to content

Instantly share code, notes, and snippets.

@xPaw
Last active January 19, 2021 11:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xPaw/47e28b4160e7aa8961ec313df22494bb to your computer and use it in GitHub Desktop.
Save xPaw/47e28b4160e7aa8961ec313df22494bb to your computer and use it in GitHub Desktop.
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#if defined( _WIN32 ) && !defined( _X360 )
#include <windows.h> // for widechartomultibyte and multibytetowidechar
#elif defined(POSIX)
#include <wchar.h> // wcslen()
#define _alloca alloca
#define _wtoi(arg) wcstol(arg, NULL, 10)
#define _wtoi64(arg) wcstoll(arg, NULL, 10)
#endif
#include <keyvalues.h>
#include "filesystem.h"
#include <vstdlib/ikeyvaluessystem.h>
#include <color.h>
#include <stdlib.h>
#include <ctype.h>
#include "tier1/convar.h"
#include "tier0/dbg.h"
#include "tier0/mem.h"
#include "utlvector.h"
#include "utlbuffer.h"
#include "utlhash.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
//////// VPROF? //////////////////
// For an example of how to mark up this file with VPROF nodes, see
// changelist 702984. However, be aware that calls to FindKey and Init
// may occur outside of Vprof's usual hierarchy, which can cause strange
// duplicate KeyValues::FindKey nodes at the root level and other
// confusing effects.
//////////////////////////////////
static char * s_LastFileLoadingFrom = "unknown"; // just needed for error messages
// Statics for the growable string table
int (*KeyValues::s_pfGetSymbolForString)( const char *name, bool bCreate ) = &KeyValues::GetSymbolForStringClassic;
const char *(*KeyValues::s_pfGetStringForSymbol)( int symbol ) = &KeyValues::GetStringForSymbolClassic;
CKeyValuesGrowableStringTable *KeyValues::s_pGrowableStringTable = NULL;
#define KEYVALUES_TOKEN_SIZE (1024 * 32)
#define INTERNALWRITE( pData, len ) InternalWrite( filesystem, f, pBuf, pData, len )
#define MAKE_3_BYTES_FROM_1_AND_2( x1, x2 ) (( (( uint16 )x2) << 8 ) | (uint8)(x1))
#define SPLIT_3_BYTES_INTO_1_AND_2( x1, x2, x3 ) do { x1 = (uint8)(x3); x2 = (uint16)( (x3) >> 8 ); } while( 0 )
CExpressionEvaluator g_ExpressionEvaluator;
// a simple class to keep track of a stack of valid parsed symbols
const int MAX_ERROR_STACK = 64;
class CKeyValuesErrorStack
{
public:
CKeyValuesErrorStack() : m_pFilename("NULL"), m_errorIndex(0), m_maxErrorIndex(0), m_bEncounteredErrors(false) {}
void SetFilename( const char *pFilename )
{
m_pFilename = pFilename;
m_maxErrorIndex = 0;
}
// entering a new keyvalues block, save state for errors
// Not save symbols instead of pointers because the pointers can move!
int Push( int symName )
{
if ( m_errorIndex < MAX_ERROR_STACK )
{
m_errorStack[m_errorIndex] = symName;
}
m_errorIndex++;
m_maxErrorIndex = MAX( m_maxErrorIndex, (m_errorIndex-1) );
return m_errorIndex-1;
}
// exiting block, error isn't in this block, remove.
void Pop()
{
m_errorIndex--;
Assert(m_errorIndex>=0);
}
// Allows you to keep the same stack level, but change the name as you parse peers
void Reset( int stackLevel, int symName )
{
Assert( stackLevel >= 0 && stackLevel < m_errorIndex );
if ( stackLevel < MAX_ERROR_STACK )
m_errorStack[stackLevel] = symName;
}
// Hit an error, report it and the parsing stack for context
void ReportError( const char *pError )
{
Warning( "KeyValues Error: %s in file %s\n", pError, m_pFilename );
for ( int i = 0; i < m_maxErrorIndex; i++ )
{
if ( i < MAX_ERROR_STACK && m_errorStack[i] != INVALID_KEY_SYMBOL )
{
if ( i < m_errorIndex )
{
Warning( "%s, ", KeyValuesSystem()->GetStringForSymbol(m_errorStack[i]) );
}
else
{
Warning( "(*%s*), ", KeyValuesSystem()->GetStringForSymbol(m_errorStack[i]) );
}
}
}
Warning( "\n" );
m_bEncounteredErrors = true;
}
bool EncounteredAnyErrors()
{
return m_bEncounteredErrors;
}
void ClearErrorFlag()
{
m_bEncounteredErrors = false;
}
private:
int m_errorStack[MAX_ERROR_STACK];
const char *m_pFilename;
int m_errorIndex;
int m_maxErrorIndex;
bool m_bEncounteredErrors;
} g_KeyValuesErrorStack;
// This class gets the tokens out of a CUtlBuffer for KeyValues.
// Since KeyValues likes to seek backwards and seeking won't work with a text-mode CUtlStreamBuffer
// (which is what dmserializers uses), this class allows you to seek back one token.
class CKeyValuesTokenReader
{
public:
CKeyValuesTokenReader( KeyValues *pKeyValues, CUtlBuffer &buf );
const char* ReadToken( bool &wasQuoted, bool &wasConditional );
void SeekBackOneToken();
private:
KeyValues *m_pKeyValues;
CUtlBuffer &m_Buffer;
int m_nTokensRead;
bool m_bUsePriorToken;
bool m_bPriorTokenWasQuoted;
bool m_bPriorTokenWasConditional;
static char s_pTokenBuf[KEYVALUES_TOKEN_SIZE];
};
char CKeyValuesTokenReader::s_pTokenBuf[KEYVALUES_TOKEN_SIZE];
CKeyValuesTokenReader::CKeyValuesTokenReader( KeyValues *pKeyValues, CUtlBuffer &buf ) :
m_Buffer( buf )
{
m_pKeyValues = pKeyValues;
m_nTokensRead = 0;
m_bUsePriorToken = false;
}
void CKeyValuesTokenReader::SeekBackOneToken()
{
if ( m_bUsePriorToken )
Plat_FatalError( "CKeyValuesTokenReader::SeekBackOneToken: It is only possible to seek back one token at a time" );
if ( m_nTokensRead == 0 )
Plat_FatalError( "CkeyValuesTokenReader::SeekBackOneToken: No tokens read yet" );
m_bUsePriorToken = true;
}
// a simple helper that creates stack entries as it goes in & out of scope
class CKeyErrorContext
{
public:
~CKeyErrorContext()
{
g_KeyValuesErrorStack.Pop();
}
explicit CKeyErrorContext( int symName )
{
Init( symName );
}
void Reset( int symName )
{
g_KeyValuesErrorStack.Reset( m_stackLevel, symName );
}
int GetStackLevel() const
{
return m_stackLevel;
}
private:
void Init( int symName )
{
m_stackLevel = g_KeyValuesErrorStack.Push( symName );
}
int m_stackLevel;
};
// Uncomment this line to hit the ~CLeakTrack assert to see what's looking like it's leaking
// #define LEAKTRACK
#ifdef LEAKTRACK
class CLeakTrack
{
public:
CLeakTrack()
{
}
~CLeakTrack()
{
if ( keys.Count() != 0 )
{
Assert( 0 );
}
}
struct kve
{
KeyValues *kv;
char name[ 256 ];
};
void AddKv( KeyValues *kv, char const *name )
{
kve k;
V_strncpy( k.name, name ? name : "NULL", sizeof( k.name ) );
k.kv = kv;
keys.AddToTail( k );
}
void RemoveKv( KeyValues *kv )
{
int c = keys.Count();
for ( int i = 0; i < c; i++ )
{
if ( keys[i].kv == kv )
{
keys.Remove( i );
break;
}
}
}
CUtlVector< kve > keys;
};
static CLeakTrack track;
#define TRACK_KV_ADD( ptr, name ) track.AddKv( ptr, name )
#define TRACK_KV_REMOVE( ptr ) track.RemoveKv( ptr )
#else
#define TRACK_KV_ADD( ptr, name )
#define TRACK_KV_REMOVE( ptr )
#endif
//-----------------------------------------------------------------------------
// Purpose: An arbitrarily growable string table for KeyValues key names.
// See the comment in the header for more info.
//-----------------------------------------------------------------------------
class CKeyValuesGrowableStringTable
{
public:
// Constructor
CKeyValuesGrowableStringTable() :
m_vecStrings( 0, 512 * 1024 ),
m_hashLookup( 2048, 0, 0, m_Functor, m_Functor )
{
m_vecStrings.AddToTail( '\0' );
}
// Translates a string to an index
int GetSymbolForString( const char *name, bool bCreate = true )
{
AUTO_LOCK( m_mutex );
// Put the current details into our hash functor
m_Functor.SetCurString( name );
m_Functor.SetCurStringBase( (const char *)m_vecStrings.Base() );
if ( bCreate )
{
bool bInserted = false;
UtlHashHandle_t hElement = m_hashLookup.Insert( -1, &bInserted );
if ( bInserted )
{
int iIndex = m_vecStrings.AddMultipleToTail( V_strlen( name ) + 1, name );
m_hashLookup[ hElement ] = iIndex;
}
return m_hashLookup[ hElement ];
}
else
{
UtlHashHandle_t hElement = m_hashLookup.Find( -1 );
if ( m_hashLookup.IsValidHandle( hElement ) )
return m_hashLookup[ hElement ];
else
return -1;
}
}
// Translates an index back to a string
const char *GetStringForSymbol( int symbol )
{
return (const char *)m_vecStrings.Base() + symbol;
}
private:
// A class plugged into CUtlHash that allows us to change the behavior of the table
// and store only the index in the table.
class CLookupFunctor
{
public:
CLookupFunctor() : m_pchCurString( NULL ), m_pchCurBase( NULL ) {}
// Sets what we are currently inserting or looking for.
void SetCurString( const char *pchCurString ) { m_pchCurString = pchCurString; }
void SetCurStringBase( const char *pchCurBase ) { m_pchCurBase = pchCurBase; }
// The compare function.
bool operator()( int nLhs, int nRhs ) const
{
const char *pchLhs = nLhs > 0 ? m_pchCurBase + nLhs : m_pchCurString;
const char *pchRhs = nRhs > 0 ? m_pchCurBase + nRhs : m_pchCurString;
return ( 0 == V_stricmp( pchLhs, pchRhs ) );
}
// The hash function.
unsigned int operator()( int nItem ) const
{
return HashStringCaseless( m_pchCurString );
}
private:
const char *m_pchCurString;
const char *m_pchCurBase;
};
CThreadFastMutex m_mutex;
CLookupFunctor m_Functor;
CUtlHash<int, CLookupFunctor &, CLookupFunctor &> m_hashLookup;
CUtlVector<char> m_vecStrings;
};
//-----------------------------------------------------------------------------
// Purpose: Sets whether the KeyValues system should use an arbitrarily growable
// string table. See the comment in the header for more info.
//-----------------------------------------------------------------------------
void KeyValues::SetUseGrowableStringTable( bool bUseGrowableTable )
{
if ( bUseGrowableTable )
{
s_pfGetStringForSymbol = &(KeyValues::GetStringForSymbolGrowable);
s_pfGetSymbolForString = &(KeyValues::GetSymbolForStringGrowable);
if ( NULL == s_pGrowableStringTable )
{
s_pGrowableStringTable = new CKeyValuesGrowableStringTable;
}
}
else
{
s_pfGetStringForSymbol = &(KeyValues::GetStringForSymbolClassic);
s_pfGetSymbolForString = &(KeyValues::GetSymbolForStringClassic);
}
}
//-----------------------------------------------------------------------------
// Purpose: Bodys of the function pointers used for interacting with the key
// name string table
//-----------------------------------------------------------------------------
int KeyValues::GetSymbolForStringClassic( const char *name, bool bCreate )
{
return KeyValuesSystem()->GetSymbolForString( name, bCreate );
}
const char *KeyValues::GetStringForSymbolClassic( int symbol )
{
return KeyValuesSystem()->GetStringForSymbol( symbol );
}
int KeyValues::GetSymbolForStringGrowable( const char *name, bool bCreate )
{
return s_pGrowableStringTable->GetSymbolForString( name, bCreate );
}
const char *KeyValues::GetStringForSymbolGrowable( int symbol )
{
return s_pGrowableStringTable->GetStringForSymbol( symbol );
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
KeyValues::KeyValues( const char *setName )
{
TRACK_KV_ADD( this, setName );
Init();
SetName ( setName );
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
KeyValues::KeyValues( const char *setName, const char *firstKey, const char *firstValue )
{
TRACK_KV_ADD( this, setName );
Init();
SetName( setName );
SetString( firstKey, firstValue );
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
KeyValues::KeyValues( const char *setName, const char *firstKey, const wchar_t *firstValue )
{
TRACK_KV_ADD( this, setName );
Init();
SetName( setName );
SetWString( firstKey, firstValue );
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
KeyValues::KeyValues( const char *setName, const char *firstKey, int firstValue )
{
TRACK_KV_ADD( this, setName );
Init();
SetName( setName );
SetInt( firstKey, firstValue );
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
KeyValues::KeyValues( const char *setName, const char *firstKey, const char *firstValue, const char *secondKey, const char *secondValue )
{
TRACK_KV_ADD( this, setName );
Init();
SetName( setName );
SetString( firstKey, firstValue );
SetString( secondKey, secondValue );
}
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
KeyValues::KeyValues( const char *setName, const char *firstKey, int firstValue, const char *secondKey, int secondValue )
{
TRACK_KV_ADD( this, setName );
Init();
SetName( setName );
SetInt( firstKey, firstValue );
SetInt( secondKey, secondValue );
}
//-----------------------------------------------------------------------------
// Purpose: Initialize member variables
//-----------------------------------------------------------------------------
void KeyValues::Init()
{
m_iKeyName = 0;
m_iKeyNameCaseSensitive1 = 0;
m_iKeyNameCaseSensitive2 = 0;
m_iDataType = TYPE_NONE;
m_pSub = NULL;
m_pPeer = NULL;
m_pChain = NULL;
m_sValue = NULL;
m_wsValue = NULL;
m_pValue = NULL;
m_bHasEscapeSequences = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
KeyValues::~KeyValues()
{
TRACK_KV_REMOVE( this );
RemoveEverything();
}
// for backwards compat - we used to need this to force the free to run from the same DLL
// as the alloc
void KeyValues::deleteThis()
{
delete this;
}
//-----------------------------------------------------------------------------
// Purpose: remove everything
//-----------------------------------------------------------------------------
void KeyValues::RemoveEverything()
{
KeyValues *dat;
KeyValues *datNext = NULL;
for ( dat = m_pSub; dat != NULL; dat = datNext )
{
datNext = dat->m_pPeer;
dat->m_pPeer = NULL;
delete dat;
}
for ( dat = m_pPeer; dat && dat != this; dat = datNext )
{
datNext = dat->m_pPeer;
dat->m_pPeer = NULL;
delete dat;
}
delete [] m_sValue;
m_sValue = NULL;
delete [] m_wsValue;
m_wsValue = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *f -
//-----------------------------------------------------------------------------
void KeyValues::RecursiveSaveToFile( CUtlBuffer& buf, int indentLevel )
{
RecursiveSaveToFile( NULL, FILESYSTEM_INVALID_HANDLE, &buf, indentLevel );
}
//-----------------------------------------------------------------------------
// Adds a chain... if we don't find stuff in this keyvalue, we'll look
// in the one we're chained to.
//-----------------------------------------------------------------------------
void KeyValues::ChainKeyValue( KeyValues* pChain )
{
m_pChain = pChain;
}
//-----------------------------------------------------------------------------
// Purpose: Get the name of the current key section
//-----------------------------------------------------------------------------
const char *KeyValues::GetName( void ) const
{
AssertMsg( this, "Member function called on NULL KeyValues" );
return this ? KeyValuesSystem()->GetStringForSymbol( MAKE_3_BYTES_FROM_1_AND_2( m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2 ) ) : "";
}
const char* CKeyValuesTokenReader::ReadToken( bool &wasQuoted, bool &wasConditional )
{
if ( m_bUsePriorToken )
{
m_bUsePriorToken = false;
wasQuoted = m_bPriorTokenWasQuoted;
wasConditional = m_bPriorTokenWasConditional;
return s_pTokenBuf;
}
m_bPriorTokenWasQuoted = wasQuoted = false;
m_bPriorTokenWasConditional = wasConditional = false;
if ( !m_Buffer.IsValid() )
return NULL;
// eating white spaces and remarks loop
while ( true )
{
m_Buffer.EatWhiteSpace();
if ( !m_Buffer.IsValid() )
{
return NULL; // file ends after reading whitespaces
}
// stop if it's not a comment; a new token starts here
if ( !m_Buffer.EatCPPComment() )
break;
}
const char *c = (const char*)m_Buffer.PeekGet( sizeof(char), 0 );
if ( !c )
{
return NULL;
}
// read quoted strings specially
if ( *c == '\"' )
{
m_bPriorTokenWasQuoted = wasQuoted = true;
m_Buffer.GetDelimitedString( m_pKeyValues->m_bHasEscapeSequences ? GetCStringCharConversion() : GetNoEscCharConversion(),
s_pTokenBuf, KEYVALUES_TOKEN_SIZE );
++m_nTokensRead;
return s_pTokenBuf;
}
if ( *c == '{' || *c == '}' || *c == '=' )
{
// it's a control char, just add this one char and stop reading
s_pTokenBuf[0] = *c;
s_pTokenBuf[1] = 0;
m_Buffer.GetChar();
++m_nTokensRead;
return s_pTokenBuf;
}
// read in the token until we hit a whitespace or a control character
bool bReportedError = false;
bool bConditionalStart = false;
int nCount = 0;
while ( 1 )
{
c = (const char*)m_Buffer.PeekGet( sizeof(char), 0 );
// end of file
if ( !c || *c == 0 )
break;
// break if any control character appears in non quoted tokens
if ( *c == '"' || *c == '{' || *c == '}' || *c == '=' )
break;
if ( *c == '[' )
bConditionalStart = true;
if ( *c == ']' && bConditionalStart )
{
m_bPriorTokenWasConditional = wasConditional = true;
bConditionalStart = false;
}
// break on whitespace
if ( V_isspace(*c) && !bConditionalStart )
break;
if (nCount < (KEYVALUES_TOKEN_SIZE-1) )
{
s_pTokenBuf[nCount++] = *c; // add char to buffer
}
else if ( !bReportedError )
{
bReportedError = true;
g_KeyValuesErrorStack.ReportError(" ReadToken overflow" );
}
m_Buffer.GetChar();
}
s_pTokenBuf[ nCount ] = 0;
++m_nTokensRead;
return s_pTokenBuf;
}
//-----------------------------------------------------------------------------
// Purpose: Get the symbol name of the current key section
//-----------------------------------------------------------------------------
int KeyValues::GetNameSymbol() const
{
AssertMsg( this, "Member function called on NULL KeyValues" );
return this ? m_iKeyName : INVALID_KEY_SYMBOL;
}
int KeyValues::GetNameSymbolCaseSensitive() const
{
AssertMsg( this, "Member function called on NULL KeyValues" );
return this ? MAKE_3_BYTES_FROM_1_AND_2( m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2 ) : INVALID_KEY_SYMBOL;
}
//-----------------------------------------------------------------------------
// Purpose: if parser should translate escape sequences ( /n, /t etc), set to true
//-----------------------------------------------------------------------------
void KeyValues::UsesEscapeSequences(bool state)
{
m_bHasEscapeSequences = state;
}
//-----------------------------------------------------------------------------
// Purpose: Load keyValues from disk
//-----------------------------------------------------------------------------
bool KeyValues::LoadFromFile( IBaseFileSystem *filesystem, const char *resourceName, const char *pathID, GetSymbolProc_t pfnEvaluateSymbolProc )
{
//TM_ZONE_FILTERED( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s %s", __FUNCTION__, tmDynamicString( TELEMETRY_LEVEL0, resourceName ) );
FileHandle_t f = filesystem->Open(resourceName, "rb", pathID);
if ( !f )
return false;
s_LastFileLoadingFrom = (char*)resourceName;
// load file into a null-terminated buffer
int fileSize = filesystem->Size( f );
unsigned bufSize = ((IFileSystem *)filesystem)->GetOptimalReadSize( f, fileSize + 2 );
char *buffer = (char*)((IFileSystem *)filesystem)->AllocOptimalReadBuffer( f, bufSize );
Assert( buffer );
// read into local buffer
bool bRetOK = ( ((IFileSystem *)filesystem)->ReadEx( buffer, bufSize, fileSize, f ) != 0 );
filesystem->Close( f ); // close file after reading
if ( bRetOK )
{
buffer[fileSize] = 0; // null terminate file as EOF
buffer[fileSize+1] = 0; // double NULL terminating in case this is a unicode file
bRetOK = LoadFromBuffer( resourceName, buffer, filesystem, pathID, pfnEvaluateSymbolProc );
}
((IFileSystem *)filesystem)->FreeOptimalReadBuffer( buffer );
return bRetOK;
}
//-----------------------------------------------------------------------------
// Purpose: Save the keyvalues to disk
// Creates the path to the file if it doesn't exist
//-----------------------------------------------------------------------------
bool KeyValues::SaveToFile( IBaseFileSystem *filesystem, const char *resourceName, const char *pathID, bool bWriteEmptySubkeys )
{
// create a write file
FileHandle_t f = filesystem->Open(resourceName, "wb", pathID);
if ( f == FILESYSTEM_INVALID_HANDLE )
{
DevMsg( "KeyValues::SaveToFile: couldn't open file \"%s\" in path \"%s\".\n",
resourceName?resourceName:"NULL", pathID?pathID:"NULL" );
return false;
}
RecursiveSaveToFile(filesystem, f, NULL, 0, bWriteEmptySubkeys);
filesystem->Close(f);
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Write out a set of indenting
//-----------------------------------------------------------------------------
void KeyValues::WriteIndents( IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, int indentLevel )
{
for ( int i = 0; i < indentLevel; i++ )
{
INTERNALWRITE( "\t", 1 );
}
}
//-----------------------------------------------------------------------------
// Purpose: Write out a string where we convert the double quotes to backslash double quote
//-----------------------------------------------------------------------------
void KeyValues::WriteConvertedString( IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, const char *pszString )
{
// handle double quote chars within the string
// the worst possible case is that the whole string is quotes
int len = V_strlen(pszString);
char *convertedString = (char *) alloca ((len + 1) * sizeof(char) * 2);
int j=0;
for (int i=0; i <= len; i++)
{
if (pszString[i] == '\"')
{
convertedString[j] = '\\';
j++;
}
else if ( m_bHasEscapeSequences && pszString[i] == '\\' )
{
convertedString[j] = '\\';
j++;
}
convertedString[j] = pszString[i];
j++;
}
INTERNALWRITE(convertedString, V_strlen(convertedString));
}
void KeyValues::InternalWrite( IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, const void *pData, int len )
{
if ( filesystem )
{
filesystem->Write( pData, len, f );
}
if ( pBuf )
{
pBuf->Put( pData, len );
}
}
//-----------------------------------------------------------------------------
// Purpose: Save keyvalues from disk, if subkey values are detected, calls
// itself to save those
//-----------------------------------------------------------------------------
void KeyValues::RecursiveSaveToFile( IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, int indentLevel, bool bWriteEmptySubkeys )
{
// write header
WriteIndents( filesystem, f, pBuf, indentLevel );
INTERNALWRITE("\"", 1);
WriteConvertedString(filesystem, f, pBuf, GetName());
INTERNALWRITE("\"\n", 2);
WriteIndents( filesystem, f, pBuf, indentLevel );
INTERNALWRITE("{\n", 2);
// loop through all our keys writing them to disk
for ( KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer )
{
if ( dat->m_pSub )
{
dat->RecursiveSaveToFile( filesystem, f, pBuf, indentLevel + 1, bWriteEmptySubkeys );
}
else
{
// only write non-empty keys
switch (dat->m_iDataType)
{
case TYPE_NONE:
{
if ( bWriteEmptySubkeys )
{
dat->RecursiveSaveToFile( filesystem, f, pBuf, indentLevel + 1, bWriteEmptySubkeys );
}
break;
}
case TYPE_STRING:
{
if (dat->m_sValue && *(dat->m_sValue))
{
WriteIndents(filesystem, f, pBuf, indentLevel + 1);
INTERNALWRITE("\"", 1);
WriteConvertedString(filesystem, f, pBuf, dat->GetName());
INTERNALWRITE("\"\t\t\"", 4);
WriteConvertedString(filesystem, f, pBuf, dat->m_sValue);
INTERNALWRITE("\"\n", 2);
}
break;
}
case TYPE_WSTRING:
{
if ( dat->m_wsValue )
{
static char buf[KEYVALUES_TOKEN_SIZE];
// make sure we have enough space
int result = V_UnicodeToUTF8( dat->m_wsValue, buf, KEYVALUES_TOKEN_SIZE);
if (result)
{
WriteIndents(filesystem, f, pBuf, indentLevel + 1);
INTERNALWRITE("\"", 1);
INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName()));
INTERNALWRITE("\"\t\t\"", 4);
WriteConvertedString(filesystem, f, pBuf, buf);
INTERNALWRITE("\"\n", 2);
}
}
break;
}
case TYPE_INT:
{
WriteIndents(filesystem, f, pBuf, indentLevel + 1);
INTERNALWRITE("\"", 1);
INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName()));
INTERNALWRITE("\"\t\t\"", 4);
char buf[32];
V_snprintf(buf, sizeof( buf ), "%d", dat->m_iValue);
INTERNALWRITE(buf, V_strlen(buf));
INTERNALWRITE("\"\n", 2);
break;
}
case TYPE_UINT64:
{
WriteIndents(filesystem, f, pBuf, indentLevel + 1);
INTERNALWRITE("\"", 1);
INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName()));
INTERNALWRITE("\"\t\t\"", 4);
char buf[32];
// write "0x" + 16 char 0-padded hex encoded 64 bit value
V_snprintf( buf, sizeof( buf ), "0x%016llX", *( (uint64 *)dat->m_sValue ) );
INTERNALWRITE(buf, V_strlen(buf));
INTERNALWRITE("\"\n", 2);
break;
}
case TYPE_FLOAT:
{
WriteIndents(filesystem, f, pBuf, indentLevel + 1);
INTERNALWRITE("\"", 1);
INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName()));
INTERNALWRITE("\"\t\t\"", 4);
char buf[48];
V_snprintf(buf, sizeof( buf ), "%f", dat->m_flValue);
INTERNALWRITE(buf, V_strlen(buf));
INTERNALWRITE("\"\n", 2);
break;
}
case TYPE_COLOR:
DevMsg( "KeyValues::RecursiveSaveToFile: TODO, missing code for TYPE_COLOR.\n" );
break;
default:
break;
}
}
}
// write tail
WriteIndents(filesystem, f, pBuf, indentLevel);
INTERNALWRITE("}\n", 2);
}
//-----------------------------------------------------------------------------
// Purpose: looks up a key by symbol name
//-----------------------------------------------------------------------------
KeyValues *KeyValues::FindKey(int keySymbol) const
{
AssertMsg( this, "Member function called on NULL KeyValues" );
for (KeyValues *dat = this ? m_pSub : NULL; dat != NULL; dat = dat->m_pPeer)
{
if ( dat->m_iKeyName == (uint32) keySymbol )
return dat;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Find a keyValue, create it if it is not found.
// Set bCreate to true to create the key if it doesn't already exist
// (which ensures a valid pointer will be returned)
//-----------------------------------------------------------------------------
KeyValues *KeyValues::FindKey(const char *keyName, bool bCreate)
{
// Validate NULL == this early out
if ( !this )
{
AssertMsg( false, "KeyValues::FindKey called on NULL pointer!" ); // Undefined behavior. Could blow up on a new platform. Don't do it.
Assert( !bCreate );
return NULL;
}
// return the current key if a NULL subkey is asked for
if (!keyName || !keyName[0])
return this;
// look for '/' characters deliminating sub fields
CUtlVector< char > szBuf;
const char *subStr = strchr(keyName, '/');
const char *searchStr = keyName;
// pull out the substring if it exists
if ( subStr )
{
int size = subStr - keyName;
Assert( size >= 0 );
Assert( size < 1024 * 1024 );
szBuf.EnsureCount( size + 1 );
V_memcpy( szBuf.Base(), keyName, size );
szBuf[size] = 0;
if ( V_strlen( keyName ) > 1 )
{
// If the key name is just '/', we don't treat is as a key with subfields, but use the '/' as a key name directly
searchStr = szBuf.Base();
}
}
// lookup the symbol for the search string,
// we do not need the case-sensitive symbol at this time
// because if the key is found, then it will be found by case-insensitive lookup
// if the key is not found and needs to be created we will pass the actual searchStr
// and have the new KeyValues constructor get/create the case-sensitive symbol
HKeySymbol iSearchStr = KeyValuesSystem()->GetSymbolForString( searchStr, bCreate );
if ( iSearchStr == INVALID_KEY_SYMBOL )
{
// not found, couldn't possibly be in key value list
return NULL;
}
KeyValues *lastItem = NULL;
KeyValues *dat;
// find the searchStr in the current peer list
for (dat = m_pSub; dat != NULL; dat = dat->m_pPeer)
{
lastItem = dat; // record the last item looked at (for if we need to append to the end of the list)
// symbol compare
if ( dat->m_iKeyName == ( uint32 ) iSearchStr )
{
break;
}
}
if ( !dat && m_pChain )
{
dat = m_pChain->FindKey(keyName, false);
}
// make sure a key was found
if (!dat)
{
if (bCreate)
{
// we need to create a new key
dat = new KeyValues( searchStr );
// Assert(dat != NULL);
// insert new key at end of list
if (lastItem)
{
lastItem->m_pPeer = dat;
}
else
{
m_pSub = dat;
}
dat->m_pPeer = NULL;
// a key graduates to be a submsg as soon as it's m_pSub is set
// this should be the only place m_pSub is set
m_iDataType = TYPE_NONE;
}
else
{
return NULL;
}
}
// if we've still got a subStr we need to keep looking deeper in the tree
if ( subStr )
{
// recursively chain down through the paths in the string
return dat->FindKey(subStr + 1, bCreate);
}
return dat;
}
//-----------------------------------------------------------------------------
// Purpose: Create a new key, with an autogenerated name.
// Name is guaranteed to be an integer, of value 1 higher than the highest
// other integer key name
//-----------------------------------------------------------------------------
KeyValues *KeyValues::CreateNewKey()
{
int newID = 1;
// search for any key with higher values
KeyValues *pLastChild = NULL;
for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer)
{
// case-insensitive string compare
int val = atoi(dat->GetName());
if (newID <= val)
{
newID = val + 1;
}
pLastChild = dat;
}
char buf[12];
V_snprintf( buf, sizeof(buf), "%d", newID );
return CreateKeyUsingKnownLastChild( buf, pLastChild );
}
//-----------------------------------------------------------------------------
// Create a key
//-----------------------------------------------------------------------------
KeyValues* KeyValues::CreateKey( const char *keyName )
{
KeyValues *pLastChild = FindLastSubKey();
return CreateKeyUsingKnownLastChild( keyName, pLastChild );
}
//-----------------------------------------------------------------------------
// Create a new sibling key
//-----------------------------------------------------------------------------
KeyValues* KeyValues::CreatePeerKey( const char *keyName )
{
KeyValues* dat = new KeyValues( keyName );
//dat->Internal_SetHasEscapeSequences( Internal_HasEscapeSequences() ); // use same format as peer
dat->m_bHasEscapeSequences = m_bHasEscapeSequences;
// insert into peer linked list after self.
dat->m_pPeer = m_pPeer;
m_pPeer = dat;
return dat;
}
//-----------------------------------------------------------------------------
KeyValues* KeyValues::CreateKeyUsingKnownLastChild( const char *keyName, KeyValues *pLastChild )
{
// Create a new key
KeyValues* dat = new KeyValues( keyName );
dat->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); // use same format as parent does
// add into subkey list
AddSubkeyUsingKnownLastChild( dat, pLastChild );
return dat;
}
//-----------------------------------------------------------------------------
void KeyValues::AddSubkeyUsingKnownLastChild( KeyValues *pSubkey, KeyValues *pLastChild )
{
// Make sure the subkey isn't a child of some other keyvalues
Assert( pSubkey != NULL );
Assert( pSubkey->m_pPeer == NULL );
// Empty child list?
if ( pLastChild == NULL )
{
Assert( m_pSub == NULL );
m_pSub = pSubkey;
}
else
{
Assert( m_pSub != NULL );
Assert( pLastChild->m_pPeer == NULL );
pLastChild->SetNextKey( pSubkey );
}
}
//-----------------------------------------------------------------------------
// Adds a subkey. Make sure the subkey isn't a child of some other keyvalues
//-----------------------------------------------------------------------------
void KeyValues::AddSubKey( KeyValues *pSubkey )
{
// Make sure the subkey isn't a child of some other keyvalues
Assert( pSubkey != NULL );
Assert( pSubkey->m_pPeer == NULL );
// add into subkey list
if ( m_pSub == NULL )
{
m_pSub = pSubkey;
}
else
{
KeyValues *pTempDat = m_pSub;
while ( pTempDat->GetNextKey() != NULL )
{
pTempDat = pTempDat->GetNextKey();
}
pTempDat->SetNextKey( pSubkey );
}
}
//-----------------------------------------------------------------------------
// Purpose: Remove a subkey from the list
//-----------------------------------------------------------------------------
void KeyValues::RemoveSubKey(KeyValues *subKey)
{
if (!subKey)
return;
// check the list pointer
if (m_pSub == subKey)
{
m_pSub = subKey->m_pPeer;
}
else
{
// look through the list
KeyValues *kv = m_pSub;
while (kv->m_pPeer)
{
if (kv->m_pPeer == subKey)
{
kv->m_pPeer = subKey->m_pPeer;
break;
}
kv = kv->m_pPeer;
}
}
subKey->m_pPeer = NULL;
}
void KeyValues::InsertSubKey( int nIndex, KeyValues *pSubKey )
{
// Sub key must be valid and not part of another chain
Assert( pSubKey && pSubKey->m_pPeer == NULL );
if ( nIndex == 0 )
{
pSubKey->m_pPeer = m_pSub;
m_pSub = pSubKey;
return;
}
else
{
int nCurrentIndex = 0;
for ( KeyValues *pIter = GetFirstSubKey(); pIter != NULL; pIter = pIter->GetNextKey() )
{
++ nCurrentIndex;
if ( nCurrentIndex == nIndex)
{
pSubKey->m_pPeer = pIter->m_pPeer;
pIter->m_pPeer = pSubKey;
return;
}
}
// Index is out of range if we get here
Assert( 0 );
return;
}
}
bool KeyValues::ContainsSubKey( KeyValues *pSubKey )
{
for ( KeyValues *pIter = GetFirstSubKey(); pIter != NULL; pIter = pIter->GetNextKey() )
{
if ( pSubKey == pIter )
{
return true;
}
}
return false;
}
void KeyValues::SwapSubKey( KeyValues *pExistingSubkey, KeyValues *pNewSubKey )
{
Assert( pExistingSubkey != NULL && pNewSubKey != NULL );
// Make sure the new sub key isn't a child of some other keyvalues
Assert( pNewSubKey->m_pPeer == NULL );
// Check the list pointer
if ( m_pSub == pExistingSubkey )
{
pNewSubKey->m_pPeer = pExistingSubkey->m_pPeer;
pExistingSubkey->m_pPeer = NULL;
m_pSub = pNewSubKey;
}
else
{
// Look through the list
KeyValues *kv = m_pSub;
while ( kv->m_pPeer )
{
if ( kv->m_pPeer == pExistingSubkey )
{
pNewSubKey->m_pPeer = pExistingSubkey->m_pPeer;
pExistingSubkey->m_pPeer = NULL;
kv->m_pPeer = pNewSubKey;
break;
}
kv = kv->m_pPeer;
}
// Existing sub key should always be found, otherwise it's a bug in the calling code.
Assert( kv->m_pPeer != NULL );
}
}
void KeyValues::ElideSubKey( KeyValues *pSubKey )
{
// This pointer's "next" pointer needs to be fixed up when we elide the key
KeyValues **ppPointerToFix = &m_pSub;
for ( KeyValues *pKeyIter = m_pSub; pKeyIter != NULL; ppPointerToFix = &pKeyIter->m_pPeer, pKeyIter = pKeyIter->GetNextKey() )
{
if ( pKeyIter == pSubKey )
{
if ( pSubKey->m_pSub == NULL )
{
// No children, simply remove the key
*ppPointerToFix = pSubKey->m_pPeer;
delete pSubKey;
}
else
{
*ppPointerToFix = pSubKey->m_pSub;
// Attach the remainder of this chain to the last child of pSubKey
KeyValues *pChildIter = pSubKey->m_pSub;
while ( pChildIter->m_pPeer != NULL )
{
pChildIter = pChildIter->m_pPeer;
}
// Now points to the last child of pSubKey
pChildIter->m_pPeer = pSubKey->m_pPeer;
// Detach the node to be elided
pSubKey->m_pSub = NULL;
pSubKey->m_pPeer = NULL;
delete pSubKey;
}
return;
}
}
// Key not found; that's caller error.
Assert( 0 );
}
//-----------------------------------------------------------------------------
// Purpose: Locate last child. Returns NULL if we have no children
//-----------------------------------------------------------------------------
KeyValues *KeyValues::FindLastSubKey()
{
// No children?
if ( m_pSub == NULL )
return NULL;
// Scan for the last one
KeyValues *pLastChild = m_pSub;
while ( pLastChild->m_pPeer )
pLastChild = pLastChild->m_pPeer;
return pLastChild;
}
//-----------------------------------------------------------------------------
// Purpose: Return the first subkey in the list
//-----------------------------------------------------------------------------
KeyValues *KeyValues::GetFirstSubKey() const
{
AssertMsg( this, "Member function called on NULL KeyValues" );
return this ? m_pSub : NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Return the next subkey
//-----------------------------------------------------------------------------
KeyValues *KeyValues::GetNextKey() const
{
AssertMsg( this, "Member function called on NULL KeyValues" );
return this ? m_pPeer : NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Sets this key's peer to the KeyValues passed in
//-----------------------------------------------------------------------------
void KeyValues::SetNextKey( KeyValues *pDat )
{
m_pPeer = pDat;
}
KeyValues* KeyValues::GetFirstTrueSubKey()
{
AssertMsg( this, "Member function called on NULL KeyValues" );
KeyValues *pRet = this ? m_pSub : NULL;
while ( pRet && pRet->m_iDataType != TYPE_NONE )
pRet = pRet->m_pPeer;
return pRet;
}
KeyValues* KeyValues::GetNextTrueSubKey()
{
AssertMsg( this, "Member function called on NULL KeyValues" );
KeyValues *pRet = this ? m_pPeer : NULL;
while ( pRet && pRet->m_iDataType != TYPE_NONE )
pRet = pRet->m_pPeer;
return pRet;
}
KeyValues* KeyValues::GetFirstValue()
{
AssertMsg( this, "Member function called on NULL KeyValues" );
KeyValues *pRet = this ? m_pSub : NULL;
while ( pRet && pRet->m_iDataType == TYPE_NONE )
pRet = pRet->m_pPeer;
return pRet;
}
KeyValues* KeyValues::GetNextValue()
{
AssertMsg( this, "Member function called on NULL KeyValues" );
KeyValues *pRet = this ? m_pPeer : NULL;
while ( pRet && pRet->m_iDataType == TYPE_NONE )
pRet = pRet->m_pPeer;
return pRet;
}
//-----------------------------------------------------------------------------
// Purpose: Get the integer value of a keyName. Default value is returned
// if the keyName can't be found.
//-----------------------------------------------------------------------------
int KeyValues::GetInt( const char *keyName, int defaultValue )
{
KeyValues *dat = FindKey( keyName, false );
if ( dat )
{
switch ( dat->m_iDataType )
{
case TYPE_STRING:
return atoi(dat->m_sValue);
case TYPE_WSTRING:
return _wtoi(dat->m_wsValue);
case TYPE_FLOAT:
return (int)dat->m_flValue;
case TYPE_UINT64:
// can't convert, since it would lose data
Assert(0);
return 0;
case TYPE_INT:
case TYPE_PTR:
default:
return dat->m_iValue;
};
}
return defaultValue;
}
//-----------------------------------------------------------------------------
// Purpose: Get the integer value of a keyName. Default value is returned
// if the keyName can't be found.
//-----------------------------------------------------------------------------
uint64 KeyValues::GetUint64( const char *keyName, uint64 defaultValue )
{
KeyValues *dat = FindKey( keyName, false );
if ( dat )
{
switch ( dat->m_iDataType )
{
case TYPE_STRING:
{
uint64 uiResult = 0ull;
sscanf( dat->m_sValue, "%lld", &uiResult );
return uiResult;
}
case TYPE_WSTRING:
{
uint64 uiResult = 0ull;
swscanf( dat->m_wsValue, L"%lld", &uiResult );
return uiResult;
}
case TYPE_FLOAT:
return (int)dat->m_flValue;
case TYPE_UINT64:
return *((uint64 *)dat->m_sValue);
case TYPE_PTR:
return (uint64)(uintp)dat->m_pValue;
case TYPE_INT:
default:
return dat->m_iValue;
};
}
return defaultValue;
}
//-----------------------------------------------------------------------------
// Purpose: Get the pointer value of a keyName. Default value is returned
// if the keyName can't be found.
//-----------------------------------------------------------------------------
void *KeyValues::GetPtr( const char *keyName, void *defaultValue )
{
KeyValues *dat = FindKey( keyName, false );
if ( dat )
{
switch ( dat->m_iDataType )
{
case TYPE_PTR:
return dat->m_pValue;
case TYPE_WSTRING:
case TYPE_STRING:
case TYPE_FLOAT:
case TYPE_INT:
case TYPE_UINT64:
default:
return NULL;
};
}
return defaultValue;
}
//-----------------------------------------------------------------------------
// Purpose: Get the float value of a keyName. Default value is returned
// if the keyName can't be found.
//-----------------------------------------------------------------------------
float KeyValues::GetFloat( const char *keyName, float defaultValue )
{
KeyValues *dat = FindKey( keyName, false );
if ( dat )
{
switch ( dat->m_iDataType )
{
case TYPE_STRING:
return (float)atof(dat->m_sValue);
case TYPE_WSTRING:
#ifdef WIN32
return (float) _wtof(dat->m_wsValue); // no wtof
#else
return (float) wcstof( dat->m_wsValue, (wchar_t **)NULL );
#endif
case TYPE_FLOAT:
return dat->m_flValue;
case TYPE_INT:
return (float)dat->m_iValue;
case TYPE_UINT64:
return (float)(*((uint64 *)dat->m_sValue));
case TYPE_PTR:
default:
return 0.0f;
};
}
return defaultValue;
}
//-----------------------------------------------------------------------------
// Purpose: Get the string pointer of a keyName. Default value is returned
// if the keyName can't be found.
//-----------------------------------------------------------------------------
const char *KeyValues::GetString( const char *keyName, const char *defaultValue )
{
KeyValues *dat = FindKey( keyName, false );
if ( dat )
{
// convert the data to string form then return it
char buf[64];
switch ( dat->m_iDataType )
{
case TYPE_FLOAT:
V_snprintf( buf, sizeof( buf ), "%f", dat->m_flValue );
SetString( keyName, buf );
break;
case TYPE_PTR:
V_snprintf( buf, sizeof( buf ), "%lld", CastPtrToInt64( dat->m_pValue ) );
SetString( keyName, buf );
break;
case TYPE_INT:
V_snprintf( buf, sizeof( buf ), "%d", dat->m_iValue );
SetString( keyName, buf );
break;
case TYPE_UINT64:
V_snprintf( buf, sizeof( buf ), "%lld", *((uint64 *)(dat->m_sValue)) );
SetString( keyName, buf );
break;
case TYPE_COLOR:
V_snprintf( buf, sizeof( buf ), "%d %d %d %d", dat->m_Color[0], dat->m_Color[1], dat->m_Color[2], dat->m_Color[3] );
SetString( keyName, buf );
break;
case TYPE_WSTRING:
{
// convert the string to char *, set it for future use, and return it
char wideBuf[512];
int result = V_UnicodeToUTF8(dat->m_wsValue, wideBuf, 512);
if ( result )
{
// note: this will copy wideBuf
SetString( keyName, wideBuf );
}
else
{
return defaultValue;
}
break;
}
case TYPE_STRING:
break;
default:
return defaultValue;
};
return dat->m_sValue;
}
return defaultValue;
}
const wchar_t *KeyValues::GetWString( const char *keyName, const wchar_t *defaultValue)
{
KeyValues *dat = FindKey( keyName, false );
if ( dat )
{
wchar_t wbuf[64];
switch ( dat->m_iDataType )
{
case TYPE_FLOAT:
swprintf(wbuf, Q_ARRAYSIZE(wbuf), L"%f", dat->m_flValue);
SetWString( keyName, wbuf);
break;
case TYPE_PTR:
swprintf( wbuf, Q_ARRAYSIZE(wbuf), L"%lld", (int64)(size_t)dat->m_pValue );
SetWString( keyName, wbuf );
break;
case TYPE_INT:
swprintf( wbuf, Q_ARRAYSIZE(wbuf), L"%d", dat->m_iValue );
SetWString( keyName, wbuf );
break;
case TYPE_UINT64:
{
swprintf( wbuf, Q_ARRAYSIZE(wbuf), L"%lld", *((uint64 *)(dat->m_sValue)) );
SetWString( keyName, wbuf );
}
break;
case TYPE_COLOR:
swprintf( wbuf, Q_ARRAYSIZE(wbuf), L"%d %d %d %d", dat->m_Color[0], dat->m_Color[1], dat->m_Color[2], dat->m_Color[3] );
SetWString( keyName, wbuf );
break;
case TYPE_WSTRING:
break;
case TYPE_STRING:
{
int bufSize = V_strlen(dat->m_sValue) + 1;
wchar_t *pWBuf = new wchar_t[ bufSize ];
int result = V_UTF8ToUnicode(dat->m_sValue, pWBuf, bufSize * sizeof( wchar_t ) );
if ( result >= 0 ) // may be a zero length string
{
SetWString( keyName, pWBuf);
}
else
{
delete [] pWBuf;
return defaultValue;
}
delete [] pWBuf;
break;
}
default:
return defaultValue;
};
return (const wchar_t* )dat->m_wsValue;
}
return defaultValue;
}
//-----------------------------------------------------------------------------
// Purpose: Gets a color
//-----------------------------------------------------------------------------
Color KeyValues::GetColor( const char *keyName , const Color& defaultColor )
{
Color color = defaultColor;
KeyValues *dat = FindKey( keyName , false );
if ( dat )
{
if ( dat->m_iDataType == TYPE_COLOR )
{
color[0] = dat->m_Color[0];
color[1] = dat->m_Color[1];
color[2] = dat->m_Color[2];
color[3] = dat->m_Color[3];
}
else if ( dat->m_iDataType == TYPE_FLOAT )
{
color[0] = (unsigned char)dat->m_flValue;
}
else if ( dat->m_iDataType == TYPE_INT )
{
color[0] = (unsigned char)dat->m_iValue;
}
else if ( dat->m_iDataType == TYPE_STRING )
{
// parse the colors out of the string
float a = 0, b = 0, c = 0, d = 0;
sscanf(dat->m_sValue, "%f %f %f %f", &a, &b, &c, &d);
color[0] = (unsigned char)a;
color[1] = (unsigned char)b;
color[2] = (unsigned char)c;
color[3] = (unsigned char)d;
}
}
return color;
}
//-----------------------------------------------------------------------------
// Purpose: Sets a color
//-----------------------------------------------------------------------------
void KeyValues::SetColor( const char *keyName, Color value)
{
KeyValues *dat = FindKey( keyName, true );
if ( dat )
{
dat->m_iDataType = TYPE_COLOR;
dat->m_Color[0] = value[0];
dat->m_Color[1] = value[1];
dat->m_Color[2] = value[2];
dat->m_Color[3] = value[3];
}
}
void KeyValues::SetStringValue( char const *strValue )
{
// delete the old value
delete [] m_sValue;
// make sure we're not storing the WSTRING - as we're converting over to STRING
delete [] m_wsValue;
m_wsValue = NULL;
if (!strValue)
{
// ensure a valid value
strValue = "";
}
// allocate memory for the new value and copy it in
int len = V_strlen( strValue );
m_sValue = new char[len + 1];
V_memcpy( m_sValue, strValue, len+1 );
m_iDataType = TYPE_STRING;
}
//-----------------------------------------------------------------------------
// Purpose: Set the string value of a keyName.
//-----------------------------------------------------------------------------
void KeyValues::SetString( const char *keyName, const char *value )
{
if ( KeyValues *dat = FindKey( keyName, true ) )
{
dat->SetStringValue( value );
}
}
//-----------------------------------------------------------------------------
// Purpose: Set the string value of a keyName.
//-----------------------------------------------------------------------------
void KeyValues::SetWString( const char *keyName, const wchar_t *value )
{
KeyValues *dat = FindKey( keyName, true );
if ( dat )
{
// delete the old value
delete [] dat->m_wsValue;
// make sure we're not storing the STRING - as we're converting over to WSTRING
delete [] dat->m_sValue;
dat->m_sValue = NULL;
if (!value)
{
// ensure a valid value
value = L"";
}
// allocate memory for the new value and copy it in
int len = V_wcslen( value );
dat->m_wsValue = new wchar_t[len + 1];
V_memcpy( dat->m_wsValue, value, (len+1) * sizeof(wchar_t) );
dat->m_iDataType = TYPE_WSTRING;
}
}
//-----------------------------------------------------------------------------
// Purpose: Set the integer value of a keyName.
//-----------------------------------------------------------------------------
void KeyValues::SetInt( const char *keyName, int value )
{
KeyValues *dat = FindKey( keyName, true );
if ( dat )
{
dat->m_iValue = value;
dat->m_iDataType = TYPE_INT;
}
}
//-----------------------------------------------------------------------------
// Purpose: Set the integer value of a keyName.
//-----------------------------------------------------------------------------
void KeyValues::SetUint64( const char *keyName, uint64 value )
{
KeyValues *dat = FindKey( keyName, true );
if ( dat )
{
// delete the old value
delete [] dat->m_sValue;
// make sure we're not storing the WSTRING - as we're converting over to STRING
delete [] dat->m_wsValue;
dat->m_wsValue = NULL;
dat->m_sValue = new char[sizeof(uint64)];
*((uint64 *)dat->m_sValue) = value;
dat->m_iDataType = TYPE_UINT64;
}
}
//-----------------------------------------------------------------------------
// Purpose: Set the float value of a keyName.
//-----------------------------------------------------------------------------
void KeyValues::SetFloat( const char *keyName, float value )
{
KeyValues *dat = FindKey( keyName, true );
if ( dat )
{
dat->m_flValue = value;
dat->m_iDataType = TYPE_FLOAT;
}
}
void KeyValues::SetName( const char * setName )
{
HKeySymbol hCaseSensitiveKeyName = INVALID_KEY_SYMBOL, hCaseInsensitiveKeyName = INVALID_KEY_SYMBOL;
hCaseSensitiveKeyName = KeyValuesSystem()->GetSymbolForStringCaseSensitive( hCaseInsensitiveKeyName, setName );
m_iKeyName = hCaseInsensitiveKeyName;
SPLIT_3_BYTES_INTO_1_AND_2( m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2, hCaseSensitiveKeyName );
}
//-----------------------------------------------------------------------------
// Purpose: Set the pointer value of a keyName.
//-----------------------------------------------------------------------------
void KeyValues::SetPtr( const char *keyName, void *value )
{
KeyValues *dat = FindKey( keyName, true );
if ( dat )
{
dat->m_pValue = value;
dat->m_iDataType = TYPE_PTR;
}
}
void KeyValues::RecursiveCopyKeyValues( KeyValues& src )
{
// garymcthack - need to check this code for possible buffer overruns.
m_iKeyName = src.m_iKeyName;
m_iKeyNameCaseSensitive1 = src.m_iKeyNameCaseSensitive1;
m_iKeyNameCaseSensitive2 = src.m_iKeyNameCaseSensitive2;
if( !src.m_pSub )
{
m_iDataType = src.m_iDataType;
char buf[256];
switch( src.m_iDataType )
{
case TYPE_NONE:
break;
case TYPE_STRING:
if( src.m_sValue )
{
int len = V_strlen(src.m_sValue) + 1;
m_sValue = new char[len];
V_strncpy( m_sValue, src.m_sValue, len );
}
break;
case TYPE_INT:
{
m_iValue = src.m_iValue;
V_snprintf( buf,sizeof(buf), "%d", m_iValue );
int len = V_strlen(buf) + 1;
m_sValue = new char[len];
V_strncpy( m_sValue, buf, len );
}
break;
case TYPE_FLOAT:
{
m_flValue = src.m_flValue;
V_snprintf( buf,sizeof(buf), "%f", m_flValue );
int len = V_strlen(buf) + 1;
m_sValue = new char[len];
V_strncpy( m_sValue, buf, len );
}
break;
case TYPE_PTR:
{
m_pValue = src.m_pValue;
}
break;
case TYPE_UINT64:
{
m_sValue = new char[sizeof(uint64)];
V_memcpy( m_sValue, src.m_sValue, sizeof(uint64) );
}
break;
case TYPE_COLOR:
{
m_Color[0] = src.m_Color[0];
m_Color[1] = src.m_Color[1];
m_Color[2] = src.m_Color[2];
m_Color[3] = src.m_Color[3];
}
break;
default:
{
// do nothing . .what the heck is this?
Assert( 0 );
}
break;
}
}
#if 0
KeyValues *pDst = this;
for ( KeyValues *pSrc = src.m_pSub; pSrc; pSrc = pSrc->m_pPeer )
{
if ( pSrc->m_pSub )
{
pDst->m_pSub = new KeyValues( pSrc->m_pSub->getName() );
pDst->m_pSub->RecursiveCopyKeyValues( *pSrc->m_pSub );
}
else
{
// copy non-empty keys
if ( pSrc->m_sValue && *(pSrc->m_sValue) )
{
pDst->m_pPeer = new KeyValues(
}
}
}
#endif
// Handle the immediate child
if( src.m_pSub )
{
m_pSub = new KeyValues( NULL );
m_pSub->RecursiveCopyKeyValues( *src.m_pSub );
}
// Handle the immediate peer
if( src.m_pPeer )
{
m_pPeer = new KeyValues( NULL );
m_pPeer->RecursiveCopyKeyValues( *src.m_pPeer );
}
}
KeyValues& KeyValues::operator=( KeyValues& src )
{
RemoveEverything();
Init(); // reset all values
RecursiveCopyKeyValues( src );
return *this;
}
//-----------------------------------------------------------------------------
// Make a new copy of all subkeys, add them all to the passed-in keyvalues
//-----------------------------------------------------------------------------
void KeyValues::CopySubkeys( KeyValues *pParent ) const
{
// recursively copy subkeys
// Also maintain ordering....
KeyValues *pPrev = NULL;
for ( KeyValues *sub = m_pSub; sub != NULL; sub = sub->m_pPeer )
{
// take a copy of the subkey
KeyValues *dat = sub->MakeCopy();
// add into subkey list
if (pPrev)
{
pPrev->m_pPeer = dat;
}
else
{
pParent->m_pSub = dat;
}
dat->m_pPeer = NULL;
pPrev = dat;
}
}
//-----------------------------------------------------------------------------
// Purpose: Makes a copy of the whole key-value pair set
//-----------------------------------------------------------------------------
KeyValues *KeyValues::MakeCopy( void ) const
{
KeyValues *newKeyValue = new KeyValues(GetName());
// copy data
newKeyValue->m_iDataType = m_iDataType;
switch ( m_iDataType )
{
case TYPE_STRING:
{
if ( m_sValue )
{
int len = V_strlen( m_sValue );
Assert( !newKeyValue->m_sValue );
newKeyValue->m_sValue = new char[len + 1];
V_memcpy( newKeyValue->m_sValue, m_sValue, len+1 );
}
}
break;
case TYPE_WSTRING:
{
if ( m_wsValue )
{
int len = V_wcslen( m_wsValue );
newKeyValue->m_wsValue = new wchar_t[len+1];
V_memcpy( newKeyValue->m_wsValue, m_wsValue, (len+1)*sizeof(wchar_t));
}
}
break;
case TYPE_INT:
newKeyValue->m_iValue = m_iValue;
break;
case TYPE_FLOAT:
newKeyValue->m_flValue = m_flValue;
break;
case TYPE_PTR:
newKeyValue->m_pValue = m_pValue;
break;
case TYPE_COLOR:
newKeyValue->m_Color[0] = m_Color[0];
newKeyValue->m_Color[1] = m_Color[1];
newKeyValue->m_Color[2] = m_Color[2];
newKeyValue->m_Color[3] = m_Color[3];
break;
case TYPE_UINT64:
newKeyValue->m_sValue = new char[sizeof(uint64)];
V_memcpy( newKeyValue->m_sValue, m_sValue, sizeof(uint64) );
break;
};
// recursively copy subkeys
CopySubkeys( newKeyValue );
return newKeyValue;
}
//-----------------------------------------------------------------------------
// Purpose: Check if a keyName has no value assigned to it.
//-----------------------------------------------------------------------------
bool KeyValues::IsEmpty(const char *keyName)
{
KeyValues *dat = FindKey(keyName, false);
if (!dat)
return true;
if (dat->m_iDataType == TYPE_NONE && dat->m_pSub == NULL)
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Clear out all subkeys, and the current value
//-----------------------------------------------------------------------------
void KeyValues::Clear( void )
{
delete m_pSub;
m_pSub = NULL;
m_iDataType = TYPE_NONE;
}
//-----------------------------------------------------------------------------
// Purpose: Get the data type of the value stored in a keyName
//-----------------------------------------------------------------------------
KeyValues::types_t KeyValues::GetDataType(const char *keyName)
{
KeyValues *dat = FindKey(keyName, false);
if (dat)
return (types_t)dat->m_iDataType;
return TYPE_NONE;
}
KeyValues::types_t KeyValues::GetDataType( void ) const
{
return (types_t)m_iDataType;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : includedKeys -
//-----------------------------------------------------------------------------
void KeyValues::AppendIncludedKeys( CUtlVector< KeyValues * >& includedKeys )
{
// Append any included keys, too...
int includeCount = includedKeys.Count();
int i;
for ( i = 0; i < includeCount; i++ )
{
KeyValues *kv = includedKeys[ i ];
Assert( kv );
KeyValues *insertSpot = this;
while ( insertSpot->GetNextKey() )
{
insertSpot = insertSpot->GetNextKey();
}
insertSpot->SetNextKey( kv );
}
}
void KeyValues::ParseIncludedKeys( char const *resourceName, const char *filetoinclude,
IBaseFileSystem* pFileSystem, const char *pPathID, CUtlVector< KeyValues * >& includedKeys, GetSymbolProc_t pfnEvaluateSymbolProc )
{
Assert( resourceName );
Assert( filetoinclude );
Assert( pFileSystem );
// Load it...
if ( !pFileSystem )
{
return;
}
// Get relative subdirectory
char fullpath[ 512 ];
V_strncpy( fullpath, resourceName, sizeof( fullpath ) );
// Strip off characters back to start or first /
bool done = false;
int len = V_strlen( fullpath );
while ( !done )
{
if ( len <= 0 )
{
break;
}
if ( fullpath[ len - 1 ] == '\\' ||
fullpath[ len - 1 ] == '/' )
{
break;
}
// zero it
fullpath[ len - 1 ] = 0;
--len;
}
// Append included file
V_strncat( fullpath, filetoinclude, sizeof( fullpath ), COPY_ALL_CHARACTERS );
KeyValues *newKV = new KeyValues( fullpath );
// CUtlSymbol save = s_CurrentFileSymbol; // did that had any use ???
newKV->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); // use same format as parent
if ( newKV->LoadFromFile( pFileSystem, fullpath, pPathID, pfnEvaluateSymbolProc ) )
{
includedKeys.AddToTail( newKV );
}
else
{
DevMsg( "KeyValues::ParseIncludedKeys: Couldn't load included keyvalue file %s\n", fullpath );
delete newKV;
}
// s_CurrentFileSymbol = save;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : baseKeys -
//-----------------------------------------------------------------------------
void KeyValues::MergeBaseKeys( CUtlVector< KeyValues * >& baseKeys )
{
int includeCount = baseKeys.Count();
int i;
for ( i = 0; i < includeCount; i++ )
{
KeyValues *kv = baseKeys[ i ];
Assert( kv );
RecursiveMergeKeyValues( kv );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : baseKV - keyvalues we're basing ourselves on
//-----------------------------------------------------------------------------
void KeyValues::RecursiveMergeKeyValues( KeyValues *baseKV )
{
// Merge ourselves
// we always want to keep our value, so nothing to do here
// Now merge our children
for ( KeyValues *baseChild = baseKV->m_pSub; baseChild != NULL; baseChild = baseChild->m_pPeer )
{
// for each child in base, see if we have a matching kv
bool bFoundMatch = false;
// If we have a child by the same name, merge those keys
for ( KeyValues *newChild = m_pSub; newChild != NULL; newChild = newChild->m_pPeer )
{
if ( !V_strcmp( baseChild->GetName(), newChild->GetName() ) )
{
newChild->RecursiveMergeKeyValues( baseChild );
bFoundMatch = true;
break;
}
}
// If not merged, append this key
if ( !bFoundMatch )
{
KeyValues *dat = baseChild->MakeCopy();
Assert( dat );
AddSubKey( dat );
}
}
}
//-----------------------------------------------------------------------------
// Returns whether a keyvalues conditional expression string evaluates to true or false
//-----------------------------------------------------------------------------
bool KeyValues::EvaluateConditional( const char *pExpressionString, GetSymbolProc_t pfnEvaluateSymbolProc )
{
// evaluate the infix expression, calling the symbol proc to resolve each symbol's value
bool bResult = false;
bool bValid = g_ExpressionEvaluator.Evaluate( bResult, pExpressionString, pfnEvaluateSymbolProc );
if ( !bValid )
{
g_KeyValuesErrorStack.ReportError( "KV Conditional Evaluation Error" );
}
return bResult;
}
// prevent two threads from entering this at the same time and trying to share the global error reporting and parse buffers
static CThreadFastMutex g_KVMutex;
//-----------------------------------------------------------------------------
// Read from a buffer...
//-----------------------------------------------------------------------------
bool KeyValues::LoadFromBuffer( char const *resourceName, CUtlBuffer &buf, IBaseFileSystem* pFileSystem, const char *pPathID, GetSymbolProc_t pfnEvaluateSymbolProc )
{
AUTO_LOCK_FM( g_KVMutex );
if ( IsGameConsole() )
{
// Let's not crash if the buffer is empty
unsigned char *pData = buf.Size() > 0 ? (unsigned char *)buf.PeekGet() : NULL;
if ( pData && (unsigned int)pData[0] == KV_BINARY_POOLED_FORMAT )
{
// skip past binary marker
buf.GetUnsignedChar();
// get the pool identifier, allows the fs to bind the expected string pool
unsigned int poolKey = buf.GetUnsignedInt();
RemoveEverything();
Init();
return ReadAsBinaryPooledFormat( buf, pFileSystem, poolKey, pfnEvaluateSymbolProc );
}
}
KeyValues *pPreviousKey = NULL;
KeyValues *pCurrentKey = this;
CUtlVector< KeyValues * > includedKeys;
CUtlVector< KeyValues * > baseKeys;
bool wasQuoted;
bool wasConditional;
CKeyValuesTokenReader tokenReader( this, buf );
g_KeyValuesErrorStack.SetFilename( resourceName );
do
{
bool bAccepted = true;
// the first thing must be a key
const char *s = tokenReader.ReadToken( wasQuoted, wasConditional );
if ( !buf.IsValid() || !s )
break;
if ( !wasQuoted && *s == '\0' )
{
// non quoted empty strings stop parsing
// quoted empty strings are allowed to support unnnamed KV sections
break;
}
if ( !V_stricmp( s, "#include" ) ) // special include macro (not a key name)
{
s = tokenReader.ReadToken( wasQuoted, wasConditional );
// Name of subfile to load is now in s
if ( !s || *s == 0 )
{
g_KeyValuesErrorStack.ReportError("#include is NULL " );
}
else
{
ParseIncludedKeys( resourceName, s, pFileSystem, pPathID, includedKeys, pfnEvaluateSymbolProc );
}
continue;
}
else if ( !V_stricmp( s, "#base" ) )
{
s = tokenReader.ReadToken( wasQuoted, wasConditional );
// Name of subfile to load is now in s
if ( !s || *s == 0 )
{
g_KeyValuesErrorStack.ReportError("#base is NULL " );
}
else
{
ParseIncludedKeys( resourceName, s, pFileSystem, pPathID, baseKeys, pfnEvaluateSymbolProc );
}
continue;
}
if ( !pCurrentKey )
{
pCurrentKey = new KeyValues( s );
Assert( pCurrentKey );
pCurrentKey->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); // same format has parent use
if ( pPreviousKey )
{
pPreviousKey->SetNextKey( pCurrentKey );
}
}
else
{
pCurrentKey->SetName( s );
}
// get the '{'
s = tokenReader.ReadToken( wasQuoted, wasConditional );
if ( wasConditional )
{
bAccepted = EvaluateConditional( s, pfnEvaluateSymbolProc );
// Now get the '{'
s = tokenReader.ReadToken( wasQuoted, wasConditional );
}
if ( s && *s == '{' && !wasQuoted )
{
// header is valid so load the file
pCurrentKey->RecursiveLoadFromBuffer( resourceName, tokenReader, pfnEvaluateSymbolProc );
}
else
{
g_KeyValuesErrorStack.ReportError("LoadFromBuffer: missing {" );
}
if ( !bAccepted )
{
if ( pPreviousKey )
{
pPreviousKey->SetNextKey( NULL );
}
pCurrentKey->Clear();
}
else
{
pPreviousKey = pCurrentKey;
pCurrentKey = NULL;
}
} while ( buf.IsValid() );
AppendIncludedKeys( includedKeys );
{
// delete included keys!
int i;
for ( i = includedKeys.Count() - 1; i > 0; i-- )
{
KeyValues *kv = includedKeys[ i ];
delete kv;
}
}
MergeBaseKeys( baseKeys );
{
// delete base keys!
int i;
for ( i = baseKeys.Count() - 1; i >= 0; i-- )
{
KeyValues *kv = baseKeys[ i ];
delete kv;
}
}
bool bErrors = g_KeyValuesErrorStack.EncounteredAnyErrors();
g_KeyValuesErrorStack.SetFilename( "" );
g_KeyValuesErrorStack.ClearErrorFlag();
return !bErrors;
}
//-----------------------------------------------------------------------------
// Read from a buffer...
//-----------------------------------------------------------------------------
bool KeyValues::LoadFromBuffer( char const *resourceName, const char *pBuffer, IBaseFileSystem* pFileSystem, const char *pPathID, GetSymbolProc_t pfnEvaluateSymbolProc )
{
if ( !pBuffer )
return true;
if ( IsGameConsole() && (unsigned int)((unsigned char *)pBuffer)[0] == KV_BINARY_POOLED_FORMAT )
{
// bad, got a binary compiled KV file through an unexpected text path
// not all paths support binary compiled kv, needs to get fixed
// need to have caller supply buffer length (strlen not valid), this interface change was never plumbed
Warning( "ERROR! Binary compiled KV '%s' in an unexpected handler\n", resourceName );
Assert( 0 );
return false;
}
int nLen = V_strlen( pBuffer );
CUtlBuffer buf( pBuffer, nLen, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER );
// Translate Unicode files into UTF-8 before proceeding
if ( nLen > 2 && (uint8)pBuffer[0] == 0xFF && (uint8)pBuffer[1] == 0xFE )
{
int nUTF8Len = V_UnicodeToUTF8( (wchar_t*)(pBuffer+2), NULL, 0 );
char *pUTF8Buf = new char[nUTF8Len];
V_UnicodeToUTF8( (wchar_t*)(pBuffer+2), pUTF8Buf, nUTF8Len );
buf.AssumeMemory( pUTF8Buf, nUTF8Len, nUTF8Len, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER );
}
return LoadFromBuffer( resourceName, buf, pFileSystem, pPathID, pfnEvaluateSymbolProc );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void KeyValues::RecursiveLoadFromBuffer( char const *resourceName, CKeyValuesTokenReader &tokenReader, GetSymbolProc_t pfnEvaluateSymbolProc )
{
CKeyErrorContext errorReport( GetNameSymbolCaseSensitive() );
bool wasQuoted;
bool wasConditional;
if ( errorReport.GetStackLevel() > 100 )
{
g_KeyValuesErrorStack.ReportError( "RecursiveLoadFromBuffer: recursion overflow" );
return;
}
// keep this out of the stack until a key is parsed
CKeyErrorContext errorKey( INVALID_KEY_SYMBOL );
// Locate the last child. (Almost always, we will not have any children.)
// We maintain the pointer to the last child here, so we don't have to re-locate
// it each time we append the next subkey, which causes O(N^2) time
KeyValues *pLastChild = FindLastSubKey();
// Keep parsing until we hit the closing brace which terminates this block, or a parse error
while ( 1 )
{
bool bAccepted = true;
// get the key name
const char * name = tokenReader.ReadToken( wasQuoted, wasConditional );
if ( !name ) // EOF stop reading
{
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got EOF instead of keyname" );
break;
}
if ( !*name ) // empty token, maybe "" or EOF
{
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got empty keyname" );
break;
}
if ( *name == '}' && !wasQuoted ) // top level closed, stop reading
break;
// Always create the key; note that this could potentially
// cause some duplication, but that's what we want sometimes
KeyValues *dat = CreateKeyUsingKnownLastChild( name, pLastChild );
errorKey.Reset( dat->GetNameSymbolCaseSensitive() );
// get the value
const char * value = tokenReader.ReadToken( wasQuoted, wasConditional );
bool bFoundConditional = wasConditional;
if ( wasConditional && value )
{
bAccepted = EvaluateConditional( value, pfnEvaluateSymbolProc );
// get the real value
value = tokenReader.ReadToken( wasQuoted, wasConditional );
}
if ( !value )
{
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got NULL key" );
break;
}
// support the '=' as an assignment, makes multiple-keys-on-one-line easier to read in a keyvalues file
if ( *value == '=' && !wasQuoted )
{
// just skip over it
value = tokenReader.ReadToken( wasQuoted, wasConditional );
bFoundConditional = wasConditional;
if ( wasConditional && value )
{
bAccepted = EvaluateConditional( value, pfnEvaluateSymbolProc );
// get the real value
value = tokenReader.ReadToken( wasQuoted, wasConditional );
}
if ( bFoundConditional && bAccepted )
{
// if there is a conditional key see if we already have the key defined and blow it away, last one in the list wins
KeyValues *pExistingKey = this->FindKey( dat->GetNameSymbol() );
if ( pExistingKey && pExistingKey != dat )
{
this->RemoveSubKey( pExistingKey );
pExistingKey->deleteThis();
}
}
}
if ( !value )
{
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got NULL key" );
break;
}
if ( *value == '}' && !wasQuoted )
{
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got } in key" );
break;
}
if ( *value == '{' && !wasQuoted )
{
// this isn't a key, it's a section
errorKey.Reset( INVALID_KEY_SYMBOL );
// sub value list
dat->RecursiveLoadFromBuffer( resourceName, tokenReader, pfnEvaluateSymbolProc );
}
else
{
if ( wasConditional )
{
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got conditional between key and value" );
break;
}
if (dat->m_sValue)
{
delete[] dat->m_sValue;
dat->m_sValue = NULL;
}
int len = V_strlen( value );
// Here, let's determine if we got a float or an int....
char* pIEnd; // pos where int scan ended
char* pFEnd; // pos where float scan ended
const char* pSEnd = value + len ; // pos where token ends
long lval = strtol( value, &pIEnd, 10 );
float fval = (float)strtod( value, &pFEnd );
bool bOverflow = ( lval == LONG_MAX || lval == LONG_MIN ) && errno == ERANGE;
#ifdef POSIX
// strtod supports hex representation in strings under posix but we DON'T
// want that support in keyvalues, so undo it here if needed
if ( len > 1 && tolower(value[1]) == 'x' )
{
fval = 0.0f;
pFEnd = (char *)value;
}
#endif
if ( *value == 0 )
{
dat->m_iDataType = TYPE_STRING;
}
else if ( ( 18 == len ) && ( value[0] == '0' ) && ( value[1] == 'x' ) )
{
// an 18-byte value prefixed with "0x" (followed by 16 hex digits) is an int64 value
int64 retVal = 0;
for( int i=2; i < 2 + 16; i++ )
{
char digit = value[i];
if ( digit >= 'a' )
digit -= 'a' - ( '9' + 1 );
else
if ( digit >= 'A' )
digit -= 'A' - ( '9' + 1 );
retVal = ( retVal * 16 ) + ( digit - '0' );
}
dat->m_sValue = new char[sizeof(uint64)];
*((uint64 *)dat->m_sValue) = retVal;
dat->m_iDataType = TYPE_UINT64;
}
else if ( (pFEnd > pIEnd) && (pFEnd == pSEnd) )
{
dat->m_flValue = fval;
dat->m_iDataType = TYPE_FLOAT;
}
else if (pIEnd == pSEnd && !bOverflow)
{
dat->m_iValue = size_cast< int >( lval );
dat->m_iDataType = TYPE_INT;
}
else
{
dat->m_iDataType = TYPE_STRING;
}
if (dat->m_iDataType == TYPE_STRING)
{
// copy in the string information
dat->m_sValue = new char[len+1];
V_memcpy( dat->m_sValue, value, len+1 );
}
// Look ahead one token for a conditional tag
const char *peek = tokenReader.ReadToken( wasQuoted, wasConditional );
if ( wasConditional )
{
bAccepted = EvaluateConditional( peek, pfnEvaluateSymbolProc );
}
else
{
tokenReader.SeekBackOneToken();
}
}
Assert( dat->m_pPeer == NULL );
if ( bAccepted )
{
Assert( pLastChild == NULL || pLastChild->m_pPeer == dat );
pLastChild = dat;
}
else
{
//this->RemoveSubKey( dat );
if ( pLastChild == NULL )
{
Assert( this->m_pSub == dat );
this->m_pSub = NULL;
}
else
{
Assert( pLastChild->m_pPeer == dat );
pLastChild->m_pPeer = NULL;
}
delete dat;
dat = NULL;
}
}
}
// writes KeyValue as binary data to buffer
bool KeyValues::WriteAsBinary( CUtlBuffer &buffer ) const
{
if ( buffer.IsText() ) // must be a binary buffer
return false;
if ( !buffer.IsValid() ) // must be valid, no overflows etc
return false;
// Write subkeys:
// loop through all our peers
for ( const KeyValues *dat = this; dat != NULL; dat = dat->m_pPeer )
{
// write type
buffer.PutUnsignedChar( dat->m_iDataType );
// write name
buffer.PutString( dat->GetName() );
// write type
switch (dat->m_iDataType)
{
case TYPE_NONE:
{
dat->m_pSub->WriteAsBinary( buffer );
break;
}
case TYPE_STRING:
{
if (dat->m_sValue && *(dat->m_sValue))
{
buffer.PutString( dat->m_sValue );
}
else
{
buffer.PutString( "" );
}
break;
}
case TYPE_WSTRING:
{
int nLength = dat->m_wsValue ? V_wcslen( dat->m_wsValue ) : 0;
buffer.PutShort( nLength );
for( int k = 0; k < nLength; ++ k )
{
buffer.PutShort( ( unsigned short ) dat->m_wsValue[k] );
}
break;
}
case TYPE_INT:
{
buffer.PutInt( dat->m_iValue );
break;
}
case TYPE_UINT64:
{
buffer.PutInt64( *((int64 *)dat->m_sValue) );
break;
}
case TYPE_FLOAT:
{
buffer.PutFloat( dat->m_flValue );
break;
}
case TYPE_COLOR:
{
buffer.PutUnsignedChar( dat->m_Color[0] );
buffer.PutUnsignedChar( dat->m_Color[1] );
buffer.PutUnsignedChar( dat->m_Color[2] );
buffer.PutUnsignedChar( dat->m_Color[3] );
break;
}
case TYPE_PTR:
{
#if defined( PLATFORM_64BITS )
// We only put an int here, because 32-bit clients do not expect 64 bits. It'll cause them to read the wrong
// amount of data and then crash. Longer term, we may bump this up in size on all platforms, but short term
// we don't really have much of a choice other than sticking in something that appears to not be NULL.
if ( dat->m_pValue != 0 && ( ( (int)(intp)dat->m_pValue ) == 0 ) )
buffer.PutInt( 31337 ); // Put not 0, but not a valid number. Yuck.
else
buffer.PutInt( ( (int)(intp)dat->m_pValue ) );
#else
buffer.PutPtr( dat->m_pValue );
#endif
break;
}
default:
break;
}
}
// write tail, marks end of peers
buffer.PutUnsignedChar( TYPE_NUMTYPES );
return buffer.IsValid();
}
// read KeyValues from binary buffer, returns true if parsing was successful
bool KeyValues::ReadAsBinary( CUtlBuffer &buffer, int nStackDepth )
{
if ( buffer.IsText() ) // must be a binary buffer
return false;
if ( !buffer.IsValid() ) // must be valid, no overflows etc
return false;
RemoveEverything(); // remove current content
Init(); // reset
if ( nStackDepth > 100 )
{
AssertMsgOnce( false, "KeyValues::ReadAsBinary() stack depth > 100\n" );
return false;
}
KeyValues *dat = this;
types_t type = (types_t)buffer.GetUnsignedChar();
// loop through all our peers
while ( true )
{
if ( type == TYPE_NUMTYPES )
break; // no more peers
dat->m_iDataType = type;
{
char token[ KEYVALUES_TOKEN_SIZE ];
buffer.GetString( token, KEYVALUES_TOKEN_SIZE - 1 );
token[ KEYVALUES_TOKEN_SIZE - 1 ] = 0;
dat->SetName( token );
}
switch ( type )
{
case TYPE_NONE:
{
dat->m_pSub = new KeyValues("");
dat->m_pSub->ReadAsBinary( buffer, nStackDepth + 1 );
break;
}
case TYPE_STRING:
{
char token[ KEYVALUES_TOKEN_SIZE ];
buffer.GetString( token, KEYVALUES_TOKEN_SIZE-1 );
token[KEYVALUES_TOKEN_SIZE-1] = 0;
int len = V_strlen( token );
dat->m_sValue = new char[len + 1];
V_memcpy( dat->m_sValue, token, len+1 );
break;
}
case TYPE_WSTRING:
{
int nLength = buffer.GetShort();
dat->m_wsValue = new wchar_t[nLength + 1];
for( int k = 0; k < nLength; ++ k )
{
dat->m_wsValue[k] = buffer.GetShort();
}
dat->m_wsValue[ nLength ] = 0;
break;
}
case TYPE_INT:
{
dat->m_iValue = buffer.GetInt();
break;
}
case TYPE_UINT64:
{
dat->m_sValue = new char[sizeof(uint64)];
*((uint64 *)dat->m_sValue) = buffer.GetInt64();
break;
}
case TYPE_FLOAT:
{
dat->m_flValue = buffer.GetFloat();
break;
}
case TYPE_COLOR:
{
dat->m_Color[0] = buffer.GetUnsignedChar();
dat->m_Color[1] = buffer.GetUnsignedChar();
dat->m_Color[2] = buffer.GetUnsignedChar();
dat->m_Color[3] = buffer.GetUnsignedChar();
break;
}
case TYPE_PTR:
{
#if defined( PLATFORM_64BITS )
// We need to ensure we only read 32 bits out of the stream because 32 bit clients only wrote
// 32 bits of data there. The actual pointer is irrelevant, all that we really care about here
// contractually is whether the pointer is zero or not zero.
dat->m_pValue = ( void* )( intp )buffer.GetInt();
#else
dat->m_pValue = buffer.GetPtr();
#endif
break;
}
default:
break;
}
if ( !buffer.IsValid() ) // error occured
return false;
type = (types_t)buffer.GetUnsignedChar();
if ( type == TYPE_NUMTYPES )
break;
// new peer follows
dat->m_pPeer = new KeyValues("");
dat = dat->m_pPeer;
}
return buffer.IsValid();
}
// writes KeyValue as binary data to buffer
// removes empty keys
bool KeyValues::WriteAsBinaryFiltered( CUtlBuffer &buffer )
{
if ( buffer.IsText() ) // must be a binary buffer
return false;
if ( !buffer.IsValid() ) // must be valid, no overflows etc
return false;
// Write header
buffer.PutString( GetName() );
// loop through all our keys writing them to buffer
for ( KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer )
{
if ( dat->m_pSub )
{
buffer.PutUnsignedChar( TYPE_NONE );
dat->WriteAsBinaryFiltered( buffer );
}
else
{
if ( dat->m_iDataType == TYPE_NONE )
{
continue; // None with no subs will be filtered
}
// write type and name
buffer.PutUnsignedChar( dat->m_iDataType );
buffer.PutString( dat->GetName() );
// write type
switch (dat->m_iDataType)
{
case TYPE_STRING:
if (dat->m_sValue && *(dat->m_sValue))
{
buffer.PutString( dat->m_sValue );
}
else
{
buffer.PutString( "" );
}
break;
case TYPE_WSTRING:
{
int nLength = dat->m_wsValue ? Q_wcslen( dat->m_wsValue ) : 0;
buffer.PutShort( nLength );
for( int k = 0; k < nLength; ++ k )
{
buffer.PutShort( ( unsigned short ) dat->m_wsValue[k] );
}
break;
}
case TYPE_INT:
{
buffer.PutInt( dat->m_iValue );
break;
}
case TYPE_UINT64:
{
buffer.PutInt64( *((int64 *)dat->m_sValue) );
break;
}
case TYPE_FLOAT:
{
buffer.PutFloat( dat->m_flValue );
break;
}
case TYPE_COLOR:
{
buffer.PutUnsignedChar( dat->m_Color[0] );
buffer.PutUnsignedChar( dat->m_Color[1] );
buffer.PutUnsignedChar( dat->m_Color[2] );
buffer.PutUnsignedChar( dat->m_Color[3] );
break;
}
case TYPE_PTR:
{
#if defined( PLATFORM_64BITS )
// We only put an int here, because 32-bit clients do not expect 64 bits. It'll cause them to read the wrong
// amount of data and then crash. Longer term, we may bump this up in size on all platforms, but short term
// we don't really have much of a choice other than sticking in something that appears to not be NULL.
if ( dat->m_pValue != 0 && ( ( (int)(intp)dat->m_pValue ) == 0 ) )
buffer.PutInt( 31337 ); // Put not 0, but not a valid number. Yuck.
else
buffer.PutInt( ( (int)(intp)dat->m_pValue ) );
#else
buffer.PutPtr( dat->m_pValue );
#endif
break;
}
default:
break;
}
}
}
// write tail, marks end of peers
buffer.PutUnsignedChar( TYPE_NUMTYPES );
return buffer.IsValid();
}
// read KeyValues from binary buffer, returns true if parsing was successful
bool KeyValues::ReadAsBinaryFiltered( CUtlBuffer &buffer, int nStackDepth )
{
if ( buffer.IsText() ) // must be a binary buffer
return false;
if ( !buffer.IsValid() ) // must be valid, no overflows etc
return false;
RemoveEverything(); // remove current content
Init(); // reset
if ( nStackDepth > 100 )
{
AssertMsgOnce( false, "KeyValues::ReadAsBinaryFiltered() stack depth > 100\n" );
return false;
}
char name[KEYVALUES_TOKEN_SIZE];
// Read header
buffer.GetString( name, KEYVALUES_TOKEN_SIZE-1 );
name[KEYVALUES_TOKEN_SIZE-1] = 0;
SetName( name );
// loop through all our peers
while ( true )
{
types_t type = (types_t)buffer.GetUnsignedChar();
if ( type == TYPE_NUMTYPES )
break;
if ( type == TYPE_NONE )
{
KeyValues *newKey = CreateKey("");
newKey->ReadAsBinaryFiltered( buffer, nStackDepth + 1 );
}
else
{
buffer.GetString( name, KEYVALUES_TOKEN_SIZE-1 );
name[KEYVALUES_TOKEN_SIZE-1] = 0;
switch ( type )
{
case TYPE_STRING:
{
char token[KEYVALUES_TOKEN_SIZE];
buffer.GetString( token, KEYVALUES_TOKEN_SIZE-1 );
token[KEYVALUES_TOKEN_SIZE-1] = 0;
SetString( name, token );
}
break;
case TYPE_WSTRING:
{
int nLength = buffer.GetShort();
wchar_t *wsValue = new wchar_t[nLength + 1];
for( int k = 0; k < nLength; ++ k )
{
wsValue[k] = buffer.GetShort();
}
wsValue[ nLength ] = 0;
SetWString( name, wsValue );
delete[] wsValue;
}
break;
case TYPE_INT:
{
int value = buffer.GetInt();
SetInt( name, value );
}
break;
case TYPE_UINT64:
{
uint64 value = buffer.GetInt64();
SetUint64( name, value );
}
break;
case TYPE_FLOAT:
{
float value = buffer.GetFloat();
SetFloat( name, value );
}
break;
case TYPE_COLOR:
{
unsigned char c0 = buffer.GetUnsignedChar();
unsigned char c1 = buffer.GetUnsignedChar();
unsigned char c2 = buffer.GetUnsignedChar();
unsigned char c3 = buffer.GetUnsignedChar();
SetColor( name, Color( c0, c1, c2, c3 ) );
}
break;
case TYPE_PTR:
{
#if defined( PLATFORM_64BITS )
// We need to ensure we only read 32 bits out of the stream because 32 bit clients only wrote
// 32 bits of data there. The actual pointer is irrelevant, all that we really care about here
// contractually is whether the pointer is zero or not zero.
void* value = ( void* )( intp )buffer.GetInt();
#else
void* value = buffer.GetPtr();
#endif
SetPtr( name, value );
}
break;
default:
break;
}
}
if ( !buffer.IsValid() ) // error occured
return false;
}
return buffer.IsValid();
}
//-----------------------------------------------------------------------------
// Purpose: memory allocator
//-----------------------------------------------------------------------------
bool KeyValues::ReadAsBinaryPooledFormat( CUtlBuffer &buffer, IBaseFileSystem *pFileSystem, unsigned int poolKey, GetSymbolProc_t pfnEvaluateSymbolProc )
{
// xbox only support
if ( !IsGameConsole() )
{
Assert( 0 );
return false;
}
if ( buffer.IsText() ) // must be a binary buffer
return false;
if ( !buffer.IsValid() ) // must be valid, no overflows etc
return false;
char token[KEYVALUES_TOKEN_SIZE];
KeyValues *dat = this;
types_t type = (types_t)buffer.GetUnsignedChar();
// loop through all our peers
while ( true )
{
if ( type == TYPE_NUMTYPES )
break; // no more peers
dat->m_iDataType = type;
unsigned int stringKey = buffer.GetUnsignedInt();
if ( !((IFileSystem*)pFileSystem)->GetStringFromKVPool( poolKey, stringKey, token, sizeof( token ) ) )
return false;
dat->SetName( token );
switch ( type )
{
case TYPE_NONE:
{
dat->m_pSub = new KeyValues( "" );
if ( !dat->m_pSub->ReadAsBinaryPooledFormat( buffer, pFileSystem, poolKey, pfnEvaluateSymbolProc ) )
return false;
break;
}
case TYPE_STRING:
{
stringKey = buffer.GetUnsignedInt();
if ( !((IFileSystem*)pFileSystem)->GetStringFromKVPool( poolKey, stringKey, token, sizeof( token ) ) )
return false;
int len = Q_strlen( token );
dat->m_sValue = new char[len + 1];
Q_memcpy( dat->m_sValue, token, len+1 );
break;
}
case TYPE_WSTRING:
{
int nLength = buffer.GetShort();
dat->m_wsValue = new wchar_t[nLength + 1];
for ( int k = 0; k < nLength; ++k )
{
dat->m_wsValue[k] = buffer.GetShort();
}
dat->m_wsValue[nLength] = 0;
break;
}
case TYPE_INT:
{
dat->m_iValue = buffer.GetInt();
break;
}
case TYPE_UINT64:
{
dat->m_sValue = new char[sizeof(uint64)];
*((uint64 *)dat->m_sValue) = buffer.GetInt64();
break;
}
case TYPE_FLOAT:
{
dat->m_flValue = buffer.GetFloat();
break;
}
case TYPE_COLOR:
{
dat->m_Color[0] = buffer.GetUnsignedChar();
dat->m_Color[1] = buffer.GetUnsignedChar();
dat->m_Color[2] = buffer.GetUnsignedChar();
dat->m_Color[3] = buffer.GetUnsignedChar();
break;
}
case TYPE_PTR:
{
#if defined( PLATFORM_64BITS )
// We need to ensure we only read 32 bits out of the stream because 32 bit clients only wrote
// 32 bits of data there. The actual pointer is irrelevant, all that we really care about here
// contractually is whether the pointer is zero or not zero.
dat->m_pValue = ( void* )( intp )buffer.GetInt();
#else
dat->m_pValue = buffer.GetPtr();
#endif
break;
}
case TYPE_COMPILED_INT_0:
{
// only for dense storage purposes, flip back to preferred internal format
dat->m_iDataType = TYPE_INT;
dat->m_iValue = 0;
break;
}
case TYPE_COMPILED_INT_1:
{
// only for dense storage purposes, flip back to preferred internal format
dat->m_iDataType = TYPE_INT;
dat->m_iValue = 1;
break;
}
case TYPE_COMPILED_INT_BYTE:
{
// only for dense storage purposes, flip back to preferred internal format
dat->m_iDataType = TYPE_INT;
dat->m_iValue = buffer.GetChar();
break;
}
default:
break;
}
if ( !buffer.IsValid() ) // error occured
return false;
if ( !buffer.GetBytesRemaining() )
break;
type = (types_t)buffer.GetUnsignedChar();
if ( type == TYPE_NUMTYPES )
break;
// new peer follows
dat->m_pPeer = new KeyValues("");
dat = dat->m_pPeer;
}
return buffer.IsValid();
}
#include "tier0/memdbgoff.h"
void *KeyValues::operator new( size_t iAllocSize )
{
MEM_ALLOC_CREDIT();
return KeyValuesSystem()->AllocKeyValuesMemory(iAllocSize);
}
void *KeyValues::operator new( size_t iAllocSize, int nBlockUse, const char *pFileName, int nLine )
{
MemAlloc_PushAllocDbgInfo( pFileName, nLine );
void *p = KeyValuesSystem()->AllocKeyValuesMemory(iAllocSize);
MemAlloc_PopAllocDbgInfo();
return p;
}
void KeyValues::operator delete( void *pMem )
{
KeyValuesSystem()->FreeKeyValuesMemory( (KeyValues *)pMem );
}
void KeyValues::operator delete( void *pMem, int nBlockUse, const char *pFileName, int nLine )
{
KeyValuesSystem()->FreeKeyValuesMemory( (KeyValues *)pMem );
}
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
void KeyValues::UnpackIntoStructure( KeyValuesUnpackStructure const *pUnpackTable, void *pDest )
{
uint8 *dest = ( uint8 * ) pDest;
while( pUnpackTable->m_pKeyName )
{
uint8 * dest_field = dest + pUnpackTable->m_nFieldOffset;
KeyValues * find_it = FindKey( pUnpackTable->m_pKeyName );
switch( pUnpackTable->m_eDataType )
{
case UNPACK_TYPE_FLOAT:
{
float default_value = ( pUnpackTable->m_pKeyDefault ) ? atof( pUnpackTable->m_pKeyDefault ) : 0.0;
*( ( float * ) dest_field ) = GetFloat( pUnpackTable->m_pKeyName, default_value );
break;
}
break;
case UNPACK_TYPE_VECTOR:
{
Vector *dest_v = ( Vector * ) dest_field;
char const *src_string =
GetString( pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault );
if ( ( ! src_string ) ||
( sscanf(src_string,"%f %f %f",
& ( dest_v->x ), & ( dest_v->y ), & ( dest_v->z )) != 3 ))
dest_v->Init( 0, 0, 0 );
}
break;
case UNPACK_TYPE_FOUR_FLOATS:
{
float *dest_f = ( float * ) dest_field;
char const *src_string =
GetString( pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault );
if ( ( ! src_string ) ||
( sscanf(src_string,"%f %f %f %f",
dest_f, dest_f + 1, dest_f + 2, dest_f + 3 )) != 4 )
memset( dest_f, 0, 4 * sizeof( float ) );
}
break;
case UNPACK_TYPE_TWO_FLOATS:
{
float *dest_f = ( float * ) dest_field;
char const *src_string =
GetString( pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault );
if ( ( ! src_string ) ||
( sscanf(src_string,"%f %f",
dest_f, dest_f + 1 )) != 2 )
memset( dest_f, 0, 2 * sizeof( float ) );
}
break;
case UNPACK_TYPE_STRING:
{
char *dest_s = ( char * ) dest_field;
char const *pDefault = "";
if ( pUnpackTable->m_pKeyDefault )
{
pDefault = pUnpackTable->m_pKeyDefault;
}
strncpy( dest_s,
GetString( pUnpackTable->m_pKeyName, pDefault ),
pUnpackTable->m_nFieldSize );
}
break;
case UNPACK_TYPE_INT:
{
int *dest_i = ( int * ) dest_field;
int default_int = 0;
if ( pUnpackTable->m_pKeyDefault )
default_int = atoi( pUnpackTable->m_pKeyDefault );
*( dest_i ) = GetInt( pUnpackTable->m_pKeyName, default_int );
}
break;
case UNPACK_TYPE_VECTOR_COLOR:
{
Vector *dest_v = ( Vector * ) dest_field;
if ( find_it )
{
Color c = GetColor( pUnpackTable->m_pKeyName );
dest_v->x = c.r();
dest_v->y = c.g();
dest_v->z = c.b();
}
else
{
if ( pUnpackTable->m_pKeyDefault )
sscanf(pUnpackTable->m_pKeyDefault,"%f %f %f",
& ( dest_v->x ), & ( dest_v->y ), & ( dest_v->z ));
else
dest_v->Init( 0, 0, 0 );
}
*( dest_v ) *= ( 1.0 / 255 );
}
}
pUnpackTable++;
}
}
//-----------------------------------------------------------------------------
// Helper function for processing a keyvalue tree for console resolution support.
// Alters key/values for easier console video resolution support.
// If running SD (640x480), the presence of "???_lodef" creates or slams "???".
// If running HD (1280x720), the presence of "???_hidef" creates or slams "???".
//-----------------------------------------------------------------------------
bool KeyValues::ProcessResolutionKeys( const char *pResString )
{
if ( !pResString )
{
// not for pc, console only
return false;
}
KeyValues *pSubKey = GetFirstSubKey();
if ( !pSubKey )
{
// not a block
return false;
}
for ( ; pSubKey != NULL; pSubKey = pSubKey->GetNextKey() )
{
// recursively descend each sub block
pSubKey->ProcessResolutionKeys( pResString );
// check to see if our substring is present
if ( V_stristr( pSubKey->GetName(), pResString ) != NULL )
{
char normalKeyName[128];
V_strncpy( normalKeyName, pSubKey->GetName(), sizeof( normalKeyName ) );
// substring must match exactly, otherwise keys like "_lodef" and "_lodef_wide" would clash.
char *pString = V_stristr( normalKeyName, pResString );
if ( pString && !V_stricmp( pString, pResString ) )
{
*pString = '\0';
// find and delete the original key (if any)
KeyValues *pKey = FindKey( normalKeyName );
if ( pKey )
{
// remove the key
RemoveSubKey( pKey );
delete pKey;
}
// rename the marked key
pSubKey->SetName( normalKeyName );
}
}
}
return true;
}
//
// KeyValues merge operations
//
void KeyValues::MergeFrom( KeyValues *kvMerge, MergeKeyValuesOp_t eOp /* = MERGE_KV_ALL */ )
{
if ( !this || !kvMerge )
return;
switch ( eOp )
{
case MERGE_KV_ALL:
MergeFrom( kvMerge->FindKey( "update" ), MERGE_KV_UPDATE );
MergeFrom( kvMerge->FindKey( "delete" ), MERGE_KV_DELETE );
MergeFrom( kvMerge->FindKey( "borrow" ), MERGE_KV_BORROW );
return;
case MERGE_KV_UPDATE:
{
for ( KeyValues *sub = kvMerge->GetFirstTrueSubKey(); sub; sub = sub->GetNextTrueSubKey() )
{
char const *szName = sub->GetName();
KeyValues *subStorage = this->FindKey( szName, false );
if ( !subStorage )
{
AddSubKey( sub->MakeCopy() );
}
else
{
subStorage->MergeFrom( sub, eOp );
}
}
for ( KeyValues *val = kvMerge->GetFirstValue(); val; val = val->GetNextValue() )
{
char const *szName = val->GetName();
if ( KeyValues *valStorage = this->FindKey( szName, false ) )
{
this->RemoveSubKey( valStorage );
delete valStorage;
}
this->AddSubKey( val->MakeCopy() );
}
}
return;
case MERGE_KV_BORROW:
{
for ( KeyValues *sub = kvMerge->GetFirstTrueSubKey(); sub; sub = sub->GetNextTrueSubKey() )
{
char const *szName = sub->GetName();
KeyValues *subStorage = this->FindKey( szName, false );
if ( !subStorage )
continue;
subStorage->MergeFrom( sub, eOp );
}
for ( KeyValues *val = kvMerge->GetFirstValue(); val; val = val->GetNextValue() )
{
char const *szName = val->GetName();
if ( KeyValues *valStorage = this->FindKey( szName, false ) )
{
this->RemoveSubKey( valStorage );
delete valStorage;
}
else
continue;
this->AddSubKey( val->MakeCopy() );
}
}
return;
case MERGE_KV_DELETE:
{
for ( KeyValues *sub = kvMerge->GetFirstTrueSubKey(); sub; sub = sub->GetNextTrueSubKey() )
{
char const *szName = sub->GetName();
if ( KeyValues *subStorage = this->FindKey( szName, false ) )
{
subStorage->MergeFrom( sub, eOp );
}
}
for ( KeyValues *val = kvMerge->GetFirstValue(); val; val = val->GetNextValue() )
{
char const *szName = val->GetName();
if ( KeyValues *valStorage = this->FindKey( szName, false ) )
{
this->RemoveSubKey( valStorage );
delete valStorage;
}
}
}
return;
}
}
//
// KeyValues from string parsing
//
static char const * ParseStringToken( char const *szStringVal, char const **ppEndOfParse )
{
// Eat whitespace
while ( V_isspace( *szStringVal ) )
++ szStringVal;
char const *pszResult = szStringVal;
while ( *szStringVal && !V_isspace( *szStringVal ) )
++ szStringVal;
if ( ppEndOfParse )
{
*ppEndOfParse = szStringVal;
}
return pszResult;
}
KeyValues * KeyValues::FromString( char const *szName, char const *szStringVal, char const **ppEndOfParse )
{
if ( !szName )
szName = "";
if ( !szStringVal )
szStringVal = "";
KeyValues *kv = new KeyValues( szName );
if ( !kv )
return NULL;
char chName[256] = {0};
char chValue[1024] = {0};
for ( ; ; )
{
char const *szEnd;
char const *szVarValue = NULL;
char const *szVarName = ParseStringToken( szStringVal, &szEnd );
if ( !*szVarName )
break;
if ( *szVarName == '}' )
{
szStringVal = szVarName + 1;
break;
}
V_strncpy( chName, szVarName, ( int )MIN( sizeof( chName ), szEnd - szVarName + 1 ) );
szVarName = chName;
szStringVal = szEnd;
if ( *szVarName == '{' )
{
szVarName = "";
goto do_sub_key;
}
szVarValue = ParseStringToken( szStringVal, &szEnd );
if ( *szVarValue == '}' )
{
szStringVal = szVarValue + 1;
kv->SetString( szVarName, "" );
break;
}
V_strncpy( chValue, szVarValue, ( int )MIN( sizeof( chValue ), szEnd - szVarValue + 1 ) );
szVarValue = chValue;
szStringVal = szEnd;
if ( *szVarValue == '{' )
{
goto do_sub_key;
}
// Try to recognize some known types
if ( char const *szInt = StringAfterPrefix( szVarValue, "#int#" ) )
{
kv->SetInt( szVarName, atoi( szInt ) );
}
else if ( !V_stricmp( szVarValue, "#empty#" ) )
{
kv->SetString( szVarName, "" );
}
else
{
kv->SetString( szVarName, szVarValue );
}
continue;
do_sub_key:
{
KeyValues *pSubKey = KeyValues::FromString( szVarName, szStringVal, &szEnd );
if ( pSubKey )
{
kv->AddSubKey( pSubKey );
}
szStringVal = szEnd;
continue;
}
}
if ( ppEndOfParse )
{
*ppEndOfParse = szStringVal;
}
return kv;
}
//-----------------------------------------------------------------------------
// Purpose: comparison function for keyvalues
//-----------------------------------------------------------------------------
bool KeyValues::IsEqual( KeyValues *pRHS )
{
if ( !pRHS )
return false;
// check our key
if ( m_iDataType != pRHS->m_iDataType )
return false;
switch ( m_iDataType )
{
case TYPE_STRING:
return V_strcmp( GetString(), pRHS->GetString() ) == 0;
case TYPE_WSTRING:
return V_wcscmp( GetWString(), pRHS->GetWString() ) == 0;
case TYPE_FLOAT:
return m_flValue == pRHS->m_flValue;
case TYPE_UINT64:
return GetUint64() == pRHS->GetUint64();
case TYPE_NONE:
{
// walk through the subkeys - does it in order right now
KeyValues *pkv = GetFirstSubKey();
KeyValues *pkvRHS = pRHS->GetFirstSubKey();
bool bRet = false;
while ( 1 )
{
// ended at the same time, good
if ( !pkv && !pkvRHS )
{
bRet = true;
break;
}
// uneven number of keys, failure
if ( !pkv || !pkvRHS )
break;
// recursively compare
if ( !pkv->IsEqual( pkvRHS ) )
break;
pkv = pkv->GetNextKey();
pkvRHS = pkvRHS->GetNextKey();
}
return bRet;
}
case TYPE_INT:
case TYPE_PTR:
return m_iValue == pRHS->m_iValue;
default:
Assert( false );
}
return true;
}
//
// KeyValues dumping implementation
//
bool KeyValues::Dump( IKeyValuesDumpContext *pDump, int nIndentLevel /* = 0 */ )
{
if ( !pDump->KvBeginKey( this, nIndentLevel ) )
return false;
// Dump values
for ( KeyValues *val = this ? GetFirstValue() : NULL; val; val = val->GetNextValue() )
{
if ( !pDump->KvWriteValue( val, nIndentLevel + 1 ) )
return false;
}
// Dump subkeys
for ( KeyValues *sub = this ? GetFirstTrueSubKey() : NULL; sub; sub = sub->GetNextTrueSubKey() )
{
if ( !sub->Dump( pDump, nIndentLevel + 1 ) )
return false;
}
return pDump->KvEndKey( this, nIndentLevel );
}
bool IKeyValuesDumpContextAsText::KvBeginKey( KeyValues *pKey, int nIndentLevel )
{
if ( pKey )
{
return
KvWriteIndent( nIndentLevel ) &&
KvWriteText( pKey->GetName() ) &&
KvWriteText( " {\n" );
}
else
{
return
KvWriteIndent( nIndentLevel ) &&
KvWriteText( "<< NULL >>\n" );
}
}
bool IKeyValuesDumpContextAsText::KvWriteValue( KeyValues *val, int nIndentLevel )
{
if ( !val )
{
return
KvWriteIndent( nIndentLevel ) &&
KvWriteText( "<< NULL >>\n" );
}
if ( !KvWriteIndent( nIndentLevel ) )
return false;
if ( !KvWriteText( val->GetName() ) )
return false;
if ( !KvWriteText( " " ) )
return false;
switch ( val->GetDataType() )
{
case KeyValues::TYPE_STRING:
{
if ( !KvWriteText( val->GetString() ) )
return false;
}
break;
case KeyValues::TYPE_INT:
{
int n = val->GetInt();
char *chBuffer = ( char * ) stackalloc( 128 );
V_snprintf( chBuffer, 128, "int( %d = 0x%X )", n, n );
if ( !KvWriteText( chBuffer ) )
return false;
}
break;
case KeyValues::TYPE_FLOAT:
{
float fl = val->GetFloat();
char *chBuffer = ( char * ) stackalloc( 128 );
V_snprintf( chBuffer, 128, "float( %f )", fl );
if ( !KvWriteText( chBuffer ) )
return false;
}
break;
case KeyValues::TYPE_PTR:
{
void *ptr = val->GetPtr();
char *chBuffer = ( char * ) stackalloc( 128 );
V_snprintf( chBuffer, 128, "ptr( 0x%p )", ptr );
if ( !KvWriteText( chBuffer ) )
return false;
}
break;
case KeyValues::TYPE_WSTRING:
{
wchar_t const *wsz = val->GetWString();
int nLen = V_wcslen( wsz );
int numBytes = nLen*2 + 64;
char *chBuffer = ( char * ) stackalloc( numBytes );
V_snprintf( chBuffer, numBytes, "%ls [wstring, len = %d]", wsz, nLen );
if ( !KvWriteText( chBuffer ) )
return false;
}
break;
case KeyValues::TYPE_UINT64:
{
uint64 n = val->GetUint64();
char *chBuffer = ( char * ) stackalloc( 128 );
V_snprintf( chBuffer, 128, "u64( %lld = 0x%llX )", n, n );
if ( !KvWriteText( chBuffer ) )
return false;
}
break;
default:
break;
#if 0 // this code was accidentally stubbed out by a mis-integration in CL722860; it hasn't been tested
{
int n = val->GetDataType();
char *chBuffer = ( char * ) stackalloc( 128 );
V_snprintf( chBuffer, 128, "??kvtype[%d]", n );
if ( !KvWriteText( chBuffer ) )
return false;
}
break;
#endif
}
return KvWriteText( "\n" );
}
bool IKeyValuesDumpContextAsText::KvEndKey( KeyValues *pKey, int nIndentLevel )
{
if ( pKey )
{
return
KvWriteIndent( nIndentLevel ) &&
KvWriteText( "}\n" );
}
else
{
return true;
}
}
bool IKeyValuesDumpContextAsText::KvWriteIndent( int nIndentLevel )
{
int numIndentBytes = ( nIndentLevel * 2 + 1 );
char *pchIndent = ( char * ) stackalloc( numIndentBytes );
memset( pchIndent, ' ', numIndentBytes - 1 );
pchIndent[ numIndentBytes - 1 ] = 0;
return KvWriteText( pchIndent );
}
bool CKeyValuesDumpContextAsDevMsg::KvBeginKey( KeyValues *pKey, int nIndentLevel )
{
static ConVarRef r_developer( "developer" );
if ( r_developer.IsValid() && r_developer.GetInt() < m_nDeveloperLevel )
// If "developer" is not the correct level, then avoid evaluating KeyValues tree early
return false;
else
return IKeyValuesDumpContextAsText::KvBeginKey( pKey, nIndentLevel );
}
bool CKeyValuesDumpContextAsDevMsg::KvWriteText( char const *szText )
{
if ( m_nDeveloperLevel > 0 )
{
DevMsg( "%s", szText );
}
else
{
Msg( "%s", szText );
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment