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