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