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