//========= Copyright 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 "tier0/dbg.h" #include "tier0/mem.h" #include "utlvector.h" #include "utlbuffer.h" #include "utlhash.h" #include "UtlSortVector.h" #include "convar.h" // memdbgon must be the last include file in a .cpp file!!! #include static const 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 4096 static char s_pTokenBuf[KEYVALUES_TOKEN_SIZE]; #define INTERNALWRITE( pData, len ) InternalWrite( filesystem, f, pBuf, pData, len ) // 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) {} 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 ); Assert( stackLevel < m_errorIndex ); 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 ( m_errorStack[i] != INVALID_KEY_SYMBOL ) { if ( i < m_errorIndex ) { Warning( "%s, ", KeyValues::CallGetStringForSymbol(m_errorStack[i]) ); } else { Warning( "(*%s*), ", KeyValues::CallGetStringForSymbol(m_errorStack[i]) ); } } } Warning( "\n" ); } private: int m_errorStack[MAX_ERROR_STACK]; const char *m_pFilename; int m_errorIndex; int m_maxErrorIndex; } g_KeyValuesErrorStack; // a simple helper that creates stack entries as it goes in & out of scope class CKeyErrorContext { public: CKeyErrorContext( KeyValues *pKv ) { Init( pKv->GetNameSymbol() ); } ~CKeyErrorContext() { g_KeyValuesErrorStack.Pop(); } CKeyErrorContext( int symName ) { Init( symName ); } void Reset( int symName ) { g_KeyValuesErrorStack.Reset( m_stackLevel, symName ); } 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; Q_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() : #ifdef PLATFORM_64BITS m_vecStrings( 0, 4 * 512 * 1024 ) #else m_vecStrings( 0, 512 * 1024 ) #endif , 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); delete s_pGrowableStringTable; s_pGrowableStringTable = NULL; } } //----------------------------------------------------------------------------- // 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 = INVALID_KEY_SYMBOL; m_iDataType = TYPE_NONE; m_pSub = NULL; m_pPeer = NULL; m_pChain = NULL; m_sValue = NULL; m_wsValue = NULL; m_pValue = NULL; m_bHasEscapeSequences = false; m_bEvaluateConditionals = true; // for future proof memset( unused, 0, sizeof(unused) ); } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- KeyValues::~KeyValues() { TRACK_KV_REMOVE( this ); RemoveEverything(); } //----------------------------------------------------------------------------- // 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, bool sortKeys /*= false*/, bool bAllowEmptyString /*= false*/ ) { RecursiveSaveToFile( NULL, FILESYSTEM_INVALID_HANDLE, &buf, indentLevel, sortKeys, bAllowEmptyString ); } //----------------------------------------------------------------------------- // 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 { return s_pfGetStringForSymbol( m_iKeyName ); } //----------------------------------------------------------------------------- // Purpose: Read a single token from buffer (0 terminated) //----------------------------------------------------------------------------- #ifdef _WIN32 #pragma warning (disable:4706) #endif const char *KeyValues::ReadToken( CUtlBuffer &buf, bool &wasQuoted, bool &wasConditional ) { wasQuoted = false; wasConditional = false; if ( !buf.IsValid() ) return NULL; // eating white spaces and remarks loop while ( true ) { buf.EatWhiteSpace(); if ( !buf.IsValid() ) return NULL; // file ends after reading whitespaces // stop if it's not a comment; a new token starts here if ( !buf.EatCPPComment() ) break; } const char *c = (const char*)buf.PeekGet( sizeof(char), 0 ); if ( !c ) return NULL; // read quoted strings specially if ( *c == '\"' ) { wasQuoted = true; buf.GetDelimitedString( m_bHasEscapeSequences ? GetCStringCharConversion() : GetNoEscCharConversion(), s_pTokenBuf, KEYVALUES_TOKEN_SIZE ); return s_pTokenBuf; } if ( *c == '{' || *c == '}' ) { // it's a control char, just add this one char and stop reading s_pTokenBuf[0] = *c; s_pTokenBuf[1] = 0; buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 1 ); 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 ( ( c = (const char*)buf.PeekGet( sizeof(char), 0 ) ) ) { // end of file if ( *c == 0 ) break; // break if any control character appears in non quoted tokens if ( *c == '"' || *c == '{' || *c == '}' ) break; if ( *c == '[' ) bConditionalStart = true; if ( *c == ']' && bConditionalStart ) { wasConditional = true; } // break on whitespace if ( isspace(*c) ) 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" ); } buf.SeekGet( CUtlBuffer::SEEK_CURRENT, 1 ); } s_pTokenBuf[ nCount ] = 0; return s_pTokenBuf; } #ifdef _WIN32 #pragma warning (default:4706) #endif //----------------------------------------------------------------------------- // Purpose: if parser should translate escape sequences ( /n, /t etc), set to true //----------------------------------------------------------------------------- void KeyValues::UsesEscapeSequences(bool state) { m_bHasEscapeSequences = state; } //----------------------------------------------------------------------------- // Purpose: if parser should evaluate conditional blocks ( [$WINDOWS] etc. ) //----------------------------------------------------------------------------- void KeyValues::UsesConditionals(bool state) { m_bEvaluateConditionals = state; } //----------------------------------------------------------------------------- // Purpose: Load keyValues from disk //----------------------------------------------------------------------------- bool KeyValues::LoadFromFile( IBaseFileSystem *filesystem, const char *resourceName, const char *pathID ) { Assert(filesystem); #ifdef WIN32 Assert( IsX360() || ( IsPC() && _heapchk() == _HEAPOK ) ); #endif 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 ); } ((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 sortKeys /*= false*/, bool bAllowEmptyString /*= false*/ ) { // create a write file FileHandle_t f = filesystem->Open(resourceName, "wb", pathID); if ( f == FILESYSTEM_INVALID_HANDLE ) { DevMsg(1, "KeyValues::SaveToFile: couldn't open file \"%s\" in path \"%s\".\n", resourceName?resourceName:"NULL", pathID?pathID:"NULL" ); return false; } RecursiveSaveToFile(filesystem, f, NULL, 0, sortKeys, bAllowEmptyString ); 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 = Q_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, Q_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 sortKeys, bool bAllowEmptyString ) { // 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 if ( sortKeys ) { CUtlSortVector< KeyValues*, CUtlSortVectorKeyValuesByName > vecSortedKeys; for ( KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer ) { vecSortedKeys.InsertNoSort(dat); } vecSortedKeys.RedoSort(); FOR_EACH_VEC( vecSortedKeys, i ) { SaveKeyToFile( vecSortedKeys[i], filesystem, f, pBuf, indentLevel, sortKeys, bAllowEmptyString ); } } else { for ( KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer ) SaveKeyToFile( dat, filesystem, f, pBuf, indentLevel, sortKeys, bAllowEmptyString ); } // write tail WriteIndents(filesystem, f, pBuf, indentLevel); INTERNALWRITE("}\n", 2); } void KeyValues::SaveKeyToFile( KeyValues *dat, IBaseFileSystem *filesystem, FileHandle_t f, CUtlBuffer *pBuf, int indentLevel, bool sortKeys, bool bAllowEmptyString ) { if ( dat->m_pSub ) { dat->RecursiveSaveToFile( filesystem, f, pBuf, indentLevel + 1, sortKeys, bAllowEmptyString ); } else { // only write non-empty keys switch (dat->m_iDataType) { case TYPE_STRING: { if ( dat->m_sValue && ( bAllowEmptyString || *(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 = Q_UnicodeToUTF8( dat->m_wsValue, buf, KEYVALUES_TOKEN_SIZE); if (result) { WriteIndents(filesystem, f, pBuf, indentLevel + 1); INTERNALWRITE("\"", 1); INTERNALWRITE(dat->GetName(), Q_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(), Q_strlen(dat->GetName())); INTERNALWRITE("\"\t\t\"", 4); char buf[32]; Q_snprintf(buf, sizeof( buf ), "%d", dat->m_iValue); INTERNALWRITE(buf, Q_strlen(buf)); INTERNALWRITE("\"\n", 2); break; } case TYPE_UINT64: { WriteIndents(filesystem, f, pBuf, indentLevel + 1); INTERNALWRITE("\"", 1); INTERNALWRITE(dat->GetName(), Q_strlen(dat->GetName())); INTERNALWRITE("\"\t\t\"", 4); char buf[32]; // write "0x" + 16 char 0-padded hex encoded 64 bit value #ifdef WIN32 Q_snprintf( buf, sizeof( buf ), "0x%016I64X", *( (uint64 *)dat->m_sValue ) ); #else Q_snprintf( buf, sizeof( buf ), "0x%016llX", *( (uint64 *)dat->m_sValue ) ); #endif INTERNALWRITE(buf, Q_strlen(buf)); INTERNALWRITE("\"\n", 2); break; } case TYPE_FLOAT: { WriteIndents(filesystem, f, pBuf, indentLevel + 1); INTERNALWRITE("\"", 1); INTERNALWRITE(dat->GetName(), Q_strlen(dat->GetName())); INTERNALWRITE("\"\t\t\"", 4); char buf[48]; Q_snprintf(buf, sizeof( buf ), "%f", dat->m_flValue); INTERNALWRITE(buf, Q_strlen(buf)); INTERNALWRITE("\"\n", 2); break; } case TYPE_COLOR: DevMsg(1, "KeyValues::RecursiveSaveToFile: TODO, missing code for TYPE_COLOR.\n"); break; default: break; } } } //----------------------------------------------------------------------------- // Purpose: looks up a key by symbol name //----------------------------------------------------------------------------- KeyValues *KeyValues::FindKey(int keySymbol) const { for (KeyValues *dat = m_pSub; dat != NULL; dat = dat->m_pPeer) { if (dat->m_iKeyName == 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) { // return the current key if a NULL subkey is asked for if (!keyName || !keyName[0]) return this; // look for '/' characters deliminating sub fields char szBuf[256]; const char *subStr = strchr(keyName, '/'); const char *searchStr = keyName; // pull out the substring if it exists if (subStr) { int size = subStr - keyName; Q_memcpy( szBuf, keyName, size ); szBuf[size] = 0; searchStr = szBuf; } // lookup the symbol for the search string HKeySymbol iSearchStr = s_pfGetSymbolForString( 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 == 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); dat->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); // use same format as parent dat->UsesConditionals( m_bEvaluateConditionals != 0 ); // 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]; Q_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 ); } //----------------------------------------------------------------------------- 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 dat->UsesConditionals( m_bEvaluateConditionals != 0 ); // 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 ); // // In debug, make sure that they really do know which child is the last one // #ifdef _DEBUG // KeyValues *pTempDat = m_pSub; // while ( pTempDat->GetNextKey() != NULL ) // { // pTempDat = pTempDat->GetNextKey(); // } // Assert( pTempDat == pLastChild ); // #endif 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; } //----------------------------------------------------------------------------- // 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: Sets this key's peer to the KeyValues passed in //----------------------------------------------------------------------------- void KeyValues::SetNextKey( KeyValues *pDat ) { m_pPeer = pDat; } KeyValues* KeyValues::GetFirstTrueSubKey() { KeyValues *pRet = m_pSub; while ( pRet && pRet->m_iDataType != TYPE_NONE ) pRet = pRet->m_pPeer; return pRet; } KeyValues* KeyValues::GetNextTrueSubKey() { KeyValues *pRet = m_pPeer; while ( pRet && pRet->m_iDataType != TYPE_NONE ) pRet = pRet->m_pPeer; return pRet; } KeyValues* KeyValues::GetFirstValue() { KeyValues *pRet = m_pSub; while ( pRet && pRet->m_iDataType == TYPE_NONE ) pRet = pRet->m_pPeer; return pRet; } KeyValues* KeyValues::GetNextValue() { KeyValues *pRet = m_pPeer; 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: return (uint64)Q_atoi64(dat->m_sValue); case TYPE_WSTRING: return _wtoi64(dat->m_wsValue); case TYPE_FLOAT: return (int)dat->m_flValue; case TYPE_UINT64: return *((uint64 *)dat->m_sValue); case TYPE_INT: case TYPE_PTR: 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 Assert( !"impl me" ); return 0.0; #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: Q_snprintf( buf, sizeof( buf ), "%f", dat->m_flValue ); SetString( keyName, buf ); break; case TYPE_PTR: Q_snprintf( buf, sizeof( buf ), "%lld", (int64)(size_t)dat->m_pValue ); SetString( keyName, buf ); break; case TYPE_INT: Q_snprintf( buf, sizeof( buf ), "%d", dat->m_iValue ); SetString( keyName, buf ); break; case TYPE_UINT64: Q_snprintf( buf, sizeof( buf ), "%lld", *((uint64 *)(dat->m_sValue)) ); 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 = Q_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_WSTRING: break; case TYPE_STRING: { int bufSize = Q_strlen(dat->m_sValue) + 1; wchar_t *pWBuf = new wchar_t[ bufSize ]; int result = Q_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: Get a bool interpretation of the key. //----------------------------------------------------------------------------- bool KeyValues::GetBool( const char *keyName, bool defaultValue, bool* optGotDefault ) { if ( FindKey( keyName ) ) { if ( optGotDefault ) (*optGotDefault) = false; return 0 != GetInt( keyName, 0 ); } if ( optGotDefault ) (*optGotDefault) = true; return defaultValue; } //----------------------------------------------------------------------------- // Purpose: Gets a color //----------------------------------------------------------------------------- Color KeyValues::GetColor( const char *keyName ) { Color color(0, 0, 0, 0); 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] = dat->m_flValue; } else if ( dat->m_iDataType == TYPE_INT ) { color[0] = dat->m_iValue; } else if ( dat->m_iDataType == TYPE_STRING ) { // parse the colors out of the string float a = 0.0f, b = 0.0f, c = 0.0f, d = 0.0f; 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 = Q_strlen( strValue ); m_sValue = new char[len + 1]; Q_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 ) { KeyValues *dat = FindKey( keyName, true ); if ( dat ) { if ( dat->m_iDataType == TYPE_STRING && dat->m_sValue == value ) { return; } // 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; if (!value) { // ensure a valid value value = ""; } // allocate memory for the new value and copy it in int len = Q_strlen( value ); dat->m_sValue = new char[len + 1]; Q_memcpy( dat->m_sValue, value, len+1 ); dat->m_iDataType = TYPE_STRING; } } //----------------------------------------------------------------------------- // 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 = Q_wcslen( value ); dat->m_wsValue = new wchar_t[len + 1]; Q_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 ) { m_iKeyName = s_pfGetSymbolForString( setName, true ); } //----------------------------------------------------------------------------- // 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.GetNameSymbol(); 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 = Q_strlen(src.m_sValue) + 1; m_sValue = new char[len]; Q_strncpy( m_sValue, src.m_sValue, len ); } break; case TYPE_INT: { m_iValue = src.m_iValue; Q_snprintf( buf,sizeof(buf), "%d", m_iValue ); int len = Q_strlen(buf) + 1; m_sValue = new char[len]; Q_strncpy( m_sValue, buf, len ); } break; case TYPE_FLOAT: { m_flValue = src.m_flValue; Q_snprintf( buf,sizeof(buf), "%f", m_flValue ); int len = Q_strlen(buf) + 1; m_sValue = new char[len]; Q_strncpy( m_sValue, buf, len ); } break; case TYPE_PTR: { m_pValue = src.m_pValue; } break; case TYPE_UINT64: { m_sValue = new char[sizeof(uint64)]; Q_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()); newKeyValue->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); newKeyValue->UsesConditionals( m_bEvaluateConditionals != 0 ); // copy data newKeyValue->m_iDataType = m_iDataType; switch ( m_iDataType ) { case TYPE_STRING: { if ( m_sValue ) { int len = Q_strlen( m_sValue ); Assert( !newKeyValue->m_sValue ); newKeyValue->m_sValue = new char[len + 1]; Q_memcpy( newKeyValue->m_sValue, m_sValue, len+1 ); } } break; case TYPE_WSTRING: { if ( m_wsValue ) { int len = Q_wcslen( m_wsValue ); newKeyValue->m_wsValue = new wchar_t[len+1]; Q_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)]; Q_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; } //----------------------------------------------------------------------------- // Purpose: Deletion, ensures object gets deleted from correct heap //----------------------------------------------------------------------------- void KeyValues::deleteThis() { delete this; } //----------------------------------------------------------------------------- // Purpose: // Input : includedKeys - //----------------------------------------------------------------------------- void KeyValues::AppendIncludedKeys( CUtlVector< KeyValues * >& includedKeys ) { // Append any included keys, too... KeyValues *insertSpot = this; int includeCount = includedKeys.Count(); for ( int i = 0; i < includeCount; i++ ) { KeyValues *kv = includedKeys[ i ]; Assert( kv ); 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 ) { Assert( resourceName ); Assert( filetoinclude ); Assert( pFileSystem ); // Load it... if ( !pFileSystem ) { return; } // Get relative subdirectory char fullpath[ 512 ]; Q_strncpy( fullpath, resourceName, sizeof( fullpath ) ); // Strip off characters back to start or first / int len = Q_strlen( fullpath ); for (;;) { if ( len <= 0 ) { break; } if ( fullpath[ len - 1 ] == '\\' || fullpath[ len - 1 ] == '/' ) { break; } // zero it fullpath[ len - 1 ] = 0; --len; } // Append included file Q_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 newKV->UsesConditionals( m_bEvaluateConditionals != 0 ); if ( newKV->LoadFromFile( pFileSystem, fullpath, pPathID ) ) { includedKeys.AddToTail( newKV ); } else { DevMsg( "KeyValues::ParseIncludedKeys: Couldn't load included keyvalue file %s\n", fullpath ); newKV->deleteThis(); } // 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 ( !Q_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 evaluates to true or false // Needs more flexibility with conditionals, checking convars would be nice. //----------------------------------------------------------------------------- bool EvaluateConditional( const char *str ) { if ( !str ) return false; if ( *str == '[' ) str++; bool bNot = false; // should we negate this command? if ( *str == '!' ) bNot = true; if ( Q_stristr( str, "$X360" ) ) return IsX360() ^ bNot; if ( Q_stristr( str, "$WIN32" ) ) return IsPC() ^ bNot; // hack hack - for now WIN32 really means IsPC if ( Q_stristr( str, "$WINDOWS" ) ) return IsWindows() ^ bNot; if ( Q_stristr( str, "$OSX" ) ) return IsOSX() ^ bNot; if ( Q_stristr( str, "$LINUX" ) ) return IsLinux() ^ bNot; if ( Q_stristr( str, "$POSIX" ) ) return IsPosix() ^ bNot; return false; } //----------------------------------------------------------------------------- // Read from a buffer... //----------------------------------------------------------------------------- bool KeyValues::LoadFromBuffer( char const *resourceName, CUtlBuffer &buf, IBaseFileSystem* pFileSystem, const char *pPathID ) { KeyValues *pPreviousKey = NULL; KeyValues *pCurrentKey = this; CUtlVector< KeyValues * > includedKeys; CUtlVector< KeyValues * > baseKeys; bool wasQuoted; bool wasConditional; g_KeyValuesErrorStack.SetFilename( resourceName ); do { bool bAccepted = true; // the first thing must be a key const char *s = ReadToken( buf, wasQuoted, wasConditional ); if ( !buf.IsValid() || !s || *s == 0 ) break; if ( !Q_stricmp( s, "#include" ) ) // special include macro (not a key name) { s = ReadToken( buf, 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 ); } continue; } else if ( !Q_stricmp( s, "#base" ) ) { s = ReadToken( buf, 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 ); } continue; } if ( !pCurrentKey ) { pCurrentKey = new KeyValues( s ); Assert( pCurrentKey ); pCurrentKey->UsesEscapeSequences( m_bHasEscapeSequences != 0 ); // same format has parent use pCurrentKey->UsesConditionals( m_bEvaluateConditionals != 0 ); if ( pPreviousKey ) { pPreviousKey->SetNextKey( pCurrentKey ); } } else { pCurrentKey->SetName( s ); } // get the '{' s = ReadToken( buf, wasQuoted, wasConditional ); if ( wasConditional ) { bAccepted = !m_bEvaluateConditionals || EvaluateConditional( s ); // Now get the '{' s = ReadToken( buf, wasQuoted, wasConditional ); } if ( s && *s == '{' && !wasQuoted ) { // header is valid so load the file pCurrentKey->RecursiveLoadFromBuffer( resourceName, buf ); } 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 ]; kv->deleteThis(); } } MergeBaseKeys( baseKeys ); { // delete base keys! int i; for ( i = baseKeys.Count() - 1; i >= 0; i-- ) { KeyValues *kv = baseKeys[ i ]; kv->deleteThis(); } } g_KeyValuesErrorStack.SetFilename( "" ); return true; } //----------------------------------------------------------------------------- // Read from a buffer... //----------------------------------------------------------------------------- bool KeyValues::LoadFromBuffer( char const *resourceName, const char *pBuffer, IBaseFileSystem* pFileSystem, const char *pPathID ) { if ( !pBuffer ) return true; int nLen = Q_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 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void KeyValues::RecursiveLoadFromBuffer( char const *resourceName, CUtlBuffer &buf ) { CKeyErrorContext errorReport(this); bool wasQuoted; bool wasConditional; // 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 = ReadToken( buf, 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->GetNameSymbol() ); // get the value const char * value = ReadToken( buf, wasQuoted, wasConditional ); if ( wasConditional && value ) { bAccepted = !m_bEvaluateConditionals || EvaluateConditional( value ); // get the real value value = ReadToken( buf, wasQuoted, wasConditional ); } 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, buf ); } 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 = Q_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 int ival = strtol( value, &pIEnd, 10 ); float fval = (float)strtod( value, &pFEnd ); bool bOverflow = ( ival == LONG_MAX || ival == 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 = ival; 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]; Q_memcpy( dat->m_sValue, value, len+1 ); } // Look ahead one token for a conditional tag int prevPos = buf.TellGet(); const char *peek = ReadToken( buf, wasQuoted, wasConditional ); if ( wasConditional ) { bAccepted = !m_bEvaluateConditionals || EvaluateConditional( peek ); } else { buf.SeekGet( CUtlBuffer::SEEK_HEAD, prevPos ); } } Assert( dat->m_pPeer == NULL ); if ( bAccepted ) { Assert( pLastChild == NULL || pLastChild->m_pPeer == dat ); pLastChild = dat; } else { //this->RemoveSubKey( dat ); if ( pLastChild == NULL ) { Assert( m_pSub == dat ); m_pSub = NULL; } else { Assert( pLastChild->m_pPeer == dat ); pLastChild->m_pPeer = NULL; } dat->deleteThis(); dat = NULL; } } } // writes KeyValue as binary data to buffer bool KeyValues::WriteAsBinary( CUtlBuffer &buffer ) { 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 ( 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: { Assert( !"TYPE_WSTRING" ); break; } case TYPE_INT: { buffer.PutInt( dat->m_iValue ); break; } case TYPE_UINT64: { buffer.PutDouble( *((double *)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: { buffer.PutUnsignedInt( (int)dat->m_pValue ); } 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 = Q_strlen( token ); dat->m_sValue = new char[len + 1]; Q_memcpy( dat->m_sValue, token, len+1 ); break; } case TYPE_WSTRING: { Assert( !"TYPE_WSTRING" ); 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: { dat->m_pValue = (void*)buffer.GetUnsignedInt(); } 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(); } #include "tier0/memdbgoff.h" //----------------------------------------------------------------------------- // Purpose: memory allocator //----------------------------------------------------------------------------- void *KeyValues::operator new( size_t iAllocSize ) { MEM_ALLOC_CREDIT(); return KeyValuesSystem()->AllocKeyValuesMemory( (int)iAllocSize ); } void *KeyValues::operator new( size_t iAllocSize, int nBlockUse, const char *pFileName, int nLine ) { MemAlloc_PushAllocDbgInfo( pFileName, nLine ); void *p = KeyValuesSystem()->AllocKeyValuesMemory( (int)iAllocSize ); MemAlloc_PopAllocDbgInfo(); return p; } //----------------------------------------------------------------------------- // Purpose: deallocator //----------------------------------------------------------------------------- void KeyValues::operator delete( void *pMem ) { KeyValuesSystem()->FreeKeyValuesMemory(pMem); } void KeyValues::operator delete( void *pMem, int nBlockUse, const char *pFileName, int nLine ) { KeyValuesSystem()->FreeKeyValuesMemory(pMem); } void KeyValues::UnpackIntoStructure( KeyValuesUnpackStructure const *pUnpackTable, void *pDest, size_t DestSizeInBytes ) { #ifdef DBGFLAG_ASSERT void *pDestEnd = ( char * )pDest + DestSizeInBytes + 1; #endif 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: { Assert( dest_field + sizeof( float ) < pDestEnd ); 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: { Assert( dest_field + sizeof( Vector ) < pDestEnd ); 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: { Assert( dest_field + sizeof( float ) * 4 < pDestEnd ); 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: { Assert( dest_field + sizeof( float ) * 2 < pDestEnd ); 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: { Assert( dest_field + pUnpackTable->m_nFieldSize < pDestEnd ); char *dest_s=(char *) dest_field; strncpy( dest_s, GetString( pUnpackTable->m_pKeyName, pUnpackTable->m_pKeyDefault ), pUnpackTable->m_nFieldSize ); } break; case UNPACK_TYPE_INT: { Assert( dest_field + sizeof( int ) < pDestEnd ); 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: { Assert( dest_field + sizeof( Vector ) < pDestEnd ); 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 ( Q_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 = Q_stristr( normalKeyName, pResString ); if ( pString && !Q_stricmp( pString, pResString ) ) { *pString = '\0'; // find and delete the original key (if any) KeyValues *pKey = FindKey( normalKeyName ); if ( pKey ) { // remove the key RemoveSubKey( pKey ); } // rename the marked key pSubKey->SetName( normalKeyName ); } } } 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; { int n = val->GetDataType(); char *chBuffer = ( char * ) stackalloc( 128 ); V_snprintf( chBuffer, 128, "??kvtype[%d]", n ); if ( !KvWriteText( chBuffer ) ) return false; } break; } 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( m_nDeveloperLevel, "%s", szText ); } else { Msg( "%s", szText ); } return true; }