source-engine/tier1/KeyValues.cpp

3247 lines
82 KiB
C++
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#if defined( _WIN32 ) && !defined( _X360 )
#include <windows.h> // for WideCharToMultiByte and MultiByteToWideChar
#elif defined(POSIX)
#include <wchar.h> // wcslen()
#define _alloca alloca
#define _wtoi(arg) wcstol(arg, NULL, 10)
#define _wtoi64(arg) wcstoll(arg, NULL, 10)
#endif
#include <KeyValues.h>
#include "filesystem.h"
#include <vstdlib/IKeyValuesSystem.h>
#include "tier0/icommandline.h"
#include "tier0/vprof_telemetry.h"
#include <Color.h>
#include <stdlib.h>
#include "tier0/dbg.h"
#include "tier0/mem.h"
#include "utlbuffer.h"
#include "utlhash.h"
#include "utlvector.h"
#include "utlqueue.h"
#include "UtlSortVector.h"
#include "convar.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
static const char * s_LastFileLoadingFrom = "unknown"; // just needed for error messages
// Statics for the growable string table
2022-02-23 11:50:30 +00:00
intp (*KeyValues::s_pfGetSymbolForString)( const char *name, bool bCreate ) = &KeyValues::GetSymbolForStringClassic;
const char *(*KeyValues::s_pfGetStringForSymbol)( intp symbol ) = &KeyValues::GetStringForSymbolClassic;
2020-04-22 16:56:21 +00:00
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!
2022-02-23 11:50:30 +00:00
int Push( intp symName )
2020-04-22 16:56:21 +00:00
{
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
2022-02-23 11:50:30 +00:00
void Reset( int stackLevel, intp symName )
2020-04-22 16:56:21 +00:00
{
Assert( stackLevel >= 0 );
Assert( 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 )
{
bool bSpewCR = false;
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, ", KeyValues::CallGetStringForSymbol(m_errorStack[i]) );
}
else
{
Warning( "(*%s*), ", KeyValues::CallGetStringForSymbol(m_errorStack[i]) );
}
bSpewCR = true;
}
}
if ( bSpewCR )
Warning( "\n" );
}
private:
2022-02-23 11:50:30 +00:00
intp m_errorStack[MAX_ERROR_STACK];
2020-04-22 16:56:21 +00:00
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();
}
2022-02-23 11:50:30 +00:00
CKeyErrorContext( intp symName )
2020-04-22 16:56:21 +00:00
{
Init( symName );
}
2022-02-23 11:50:30 +00:00
void Reset( intp symName )
2020-04-22 16:56:21 +00:00
{
g_KeyValuesErrorStack.Reset( m_stackLevel, symName );
}
int GetStackLevel() const
{
return m_stackLevel;
}
private:
2022-02-23 11:50:30 +00:00
void Init( intp symName )
2020-04-22 16:56:21 +00:00
{
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
2022-02-23 11:50:30 +00:00
intp GetSymbolForString( const char *name, bool bCreate = true )
2020-04-22 16:56:21 +00:00
{
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
2022-02-23 11:50:30 +00:00
const char *GetStringForSymbol( intp symbol )
2020-04-22 16:56:21 +00:00
{
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.
2022-02-23 11:50:30 +00:00
bool operator()( intp nLhs, intp nRhs ) const
2020-04-22 16:56:21 +00:00
{
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;
2022-02-23 11:50:30 +00:00
CUtlHash<intp, CLookupFunctor &, CLookupFunctor &> m_hashLookup;
2020-04-22 16:56:21 +00:00
CUtlVector<char> m_vecStrings;
};
//-----------------------------------------------------------------------------
// Purpose: Sets whether the KeyValues system should use an arbitrarily growable
// string table. See the comment in the header for more info.
//-----------------------------------------------------------------------------
void KeyValues::SetUseGrowableStringTable( bool bUseGrowableTable )
{
if ( bUseGrowableTable )
{
s_pfGetStringForSymbol = &(KeyValues::GetStringForSymbolGrowable);
s_pfGetSymbolForString = &(KeyValues::GetSymbolForStringGrowable);
if ( NULL == s_pGrowableStringTable )
{
s_pGrowableStringTable = new CKeyValuesGrowableStringTable;
}
}
else
{
s_pfGetStringForSymbol = &(KeyValues::GetStringForSymbolClassic);
s_pfGetSymbolForString = &(KeyValues::GetSymbolForStringClassic);
delete s_pGrowableStringTable;
s_pGrowableStringTable = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose: Bodys of the function pointers used for interacting with the key
// name string table
//-----------------------------------------------------------------------------
2022-02-23 11:50:30 +00:00
intp KeyValues::GetSymbolForStringClassic( const char *name, bool bCreate )
2020-04-22 16:56:21 +00:00
{
return KeyValuesSystem()->GetSymbolForString( name, bCreate );
}
2022-02-23 11:50:30 +00:00
const char *KeyValues::GetStringForSymbolClassic( intp symbol )
2020-04-22 16:56:21 +00:00
{
return KeyValuesSystem()->GetStringForSymbol( symbol );
}
2022-02-23 11:50:30 +00:00
intp KeyValues::GetSymbolForStringGrowable( const char *name, bool bCreate )
2020-04-22 16:56:21 +00:00
{
return s_pGrowableStringTable->GetSymbolForString( name, bCreate );
}
2022-02-23 11:50:30 +00:00
const char *KeyValues::GetStringForSymbolGrowable( intp symbol )
2020-04-22 16:56:21 +00:00
{
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)
//-----------------------------------------------------------------------------
#pragma warning (disable:4706)
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;
}
#pragma warning (default:4706)
//-----------------------------------------------------------------------------
// 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, bool refreshCache )
{
TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 );
TM_ZONE_DEFAULT_PARAM( TELEMETRY_LEVEL0, resourceName );
Assert(filesystem);
#ifdef WIN32
Assert( IsX360() || ( IsPC() && _heapchk() == _HEAPOK ) );
#endif
#ifdef STAGING_ONLY
static bool s_bCacheEnabled = !!CommandLine()->FindParm( "-enable_keyvalues_cache" );
const bool bUseCache = s_bCacheEnabled && ( s_pfGetSymbolForString == KeyValues::GetSymbolForStringClassic );
#else
/*
People are cheating with the keyvalue cache enabled by doing the below, so disable it.
For example if one is to allow a blue demoman texture on sv_pure they
change it to this, "$basetexture" "temp/demoman_blue". Remember to move the
demoman texture to the temp folder in the materials folder. It will likely
not be there so make a new folder for it. Once the directory in the
demoman_blue vmt is changed to the temp folder and the vtf texture is in
the temp folder itself you are finally done.
I packed my mods into a vpk but I don't think it's required. Once in game
you must create a server via the create server button and select the map
that will load the custom texture before you join a valve server. I suggest
you only do this with player textures and such as they are always loaded.
After you load the map you join the valve server and the textures should
appear and work on valve servers.
This can be done on any sv_pure 1 server but it depends on what is type of
files are allowed. All valve servers allow temp files so that is the
example I used here."
So all vmt's files can bypass sv_pure 1. And I believe this mod is mostly
made of vmt files, so valve's sv_pure 1 bull is pretty redundant.
*/
const bool bUseCache = false;
#endif
// If pathID is null, we cannot cache the result because that has a weird iterate-through-a-bunch-of-locations behavior.
const bool bUseCacheForRead = bUseCache && !refreshCache && pathID != NULL;
const bool bUseCacheForWrite = bUseCache && pathID != NULL;
COM_TimestampedLog( "KeyValues::LoadFromFile(%s%s%s): Begin", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : "" );
// Keep a cache of keyvalues, try to load it here.
if ( bUseCacheForRead && KeyValuesSystem()->LoadFileKeyValuesFromCache( this, resourceName, pathID, filesystem ) ) {
COM_TimestampedLog( "KeyValues::LoadFromFile(%s%s%s): End / CacheHit", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : "" );
return true;
}
FileHandle_t f = filesystem->Open(resourceName, "rb", pathID);
if ( !f )
{
COM_TimestampedLog("KeyValues::LoadFromFile(%s%s%s): End / FileNotFound", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : "");
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 );
}
// The cache relies on the KeyValuesSystem string table, which will only be valid if we're
// using classic mode.
if ( bUseCacheForWrite && bRetOK )
{
KeyValuesSystem()->AddFileKeyValuesToCache( this, resourceName, pathID );
}
( (IFileSystem *)filesystem )->FreeOptimalReadBuffer( buffer );
COM_TimestampedLog("KeyValues::LoadFromFile(%s%s%s): End / Success", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : "");
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*/, bool bCacheResult /*= 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;
}
KeyValuesSystem()->InvalidateCacheForFile( resourceName, pathID );
if ( bCacheResult ) {
KeyValuesSystem()->AddFileKeyValuesToCache( this, resourceName, pathID );
}
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
//-----------------------------------------------------------------------------
2022-02-23 11:50:30 +00:00
KeyValues *KeyValues::FindKey(intp keySymbol) const
2020-04-22 16:56:21 +00:00
{
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)dat->m_pValue );
2020-04-22 16:56:21 +00:00
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)dat->m_pValue );
2020-04-22 16:56:21 +00:00
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;
}
}
//-----------------------------------------------------------------------------
// Purpose: Copies the tree from the other KeyValues into this one, recursively
// beginning with the root specified by rootSrc.
//-----------------------------------------------------------------------------
void KeyValues::CopyKeyValuesFromRecursive( const KeyValues& rootSrc )
{
// This code used to be recursive, which was more elegant. Unfortunately, it also blew the stack for large
// KeyValues. So now we have the iterative version which is uglier but doesn't blow the stack.
// This uses breadth-first traversal.
struct CopyStruct
{
KeyValues* dst;
const KeyValues* src;
};
char tmp[256];
KeyValues* localDst = NULL;
CUtlQueue<CopyStruct> nodeQ;
nodeQ.Insert({ this, &rootSrc });
while ( nodeQ.Count() > 0 )
{
CopyStruct cs = nodeQ.RemoveAtHead();
// Process all the siblings of the current node. If anyone has a child, add it to the queue.
while (cs.src)
{
Assert( (cs.src != NULL) == (cs.dst != NULL) );
// Copy the node contents
cs.dst->CopyKeyValue( *cs.src, sizeof(tmp), tmp );
// Add children to the queue to process later.
if (cs.src->m_pSub) {
cs.dst->m_pSub = localDst = new KeyValues( NULL );
nodeQ.Insert({ localDst, cs.src->m_pSub });
}
// Process siblings until we hit the end of the line.
if (cs.src->m_pPeer) {
cs.dst->m_pPeer = new KeyValues( NULL );
}
else {
cs.dst->m_pPeer = NULL;
}
// Advance to the next peer.
cs.src = cs.src->m_pPeer;
cs.dst = cs.dst->m_pPeer;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Copies a single KeyValue from src to this, using the provided temporary
// buffer if the keytype requires it. Does NOT recurse.
//-----------------------------------------------------------------------------
void KeyValues::CopyKeyValue( const KeyValues& src, size_t tmpBufferSizeB, char* tmpBuffer )
{
m_iKeyName = src.GetNameSymbol();
if ( src.m_pSub )
return;
m_iDataType = src.m_iDataType;
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( tmpBuffer, tmpBufferSizeB, "%d", m_iValue );
int len = Q_strlen(tmpBuffer) + 1;
m_sValue = new char[len];
Q_strncpy( m_sValue, tmpBuffer, len );
}
break;
case TYPE_FLOAT:
{
m_flValue = src.m_flValue;
Q_snprintf( tmpBuffer, tmpBufferSizeB, "%f", m_flValue );
int len = Q_strlen(tmpBuffer) + 1;
m_sValue = new char[len];
Q_strncpy( m_sValue, tmpBuffer, 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;
}
}
KeyValues& KeyValues::operator=( const KeyValues& src )
{
RemoveEverything();
Init(); // reset all values
CopyKeyValuesFromRecursive( 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:
//-----------------------------------------------------------------------------
KeyValues *KeyValues::MakeCopy( bool copySiblings ) const
{
KeyValues* rootDest = MakeCopy();
if ( !copySiblings )
return rootDest;
const KeyValues* curSrc = GetNextKey();
KeyValues* curDest = rootDest;
while (curSrc) {
curDest->SetNextKey( curSrc->MakeCopy() );
curDest = curDest->GetNextKey();
curSrc = curSrc->GetNextKey();
}
return rootDest;
}
//-----------------------------------------------------------------------------
// 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 );
}
}
}
static int s_nSteamDeckCached = -1;
bool IsSteamDeck()
{
if (s_nSteamDeckCached == -1) {
if ( CommandLine()->CheckParm( "-nogamepadui" ) != 0 )
{
s_nSteamDeckCached = 0;
}
else
{
if ( CommandLine()->CheckParm( "-gamepadui" ) != 0 )
{
s_nSteamDeckCached = 1;
}
else
{
char *deck = getenv("SteamDeck");
if ( deck == 0 || *deck == 0 )
s_nSteamDeckCached = 0;
else
s_nSteamDeckCached = atoi(deck) != 0;
}
}
}
return s_nSteamDeckCached;
}
2020-04-22 16:56:21 +00:00
//-----------------------------------------------------------------------------
// 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, "$DECK" ) )
return IsSteamDeck() ^ bNot;
2020-04-22 16:56:21 +00:00
if ( Q_stristr( str, "$X360" ) )
return IsX360() ^ bNot;
2020-04-22 16:56:21 +00:00
if ( Q_stristr( str, "$WIN32" ) )
return IsPC() ^ bNot; // hack hack - for now WIN32 really means IsPC
if ( Q_stristr( str, "$WINDOWS" ) )
return IsWindows() ^ bNot;
2020-04-22 16:56:21 +00:00
if ( Q_stristr( str, "$OSX" ) )
return bNot;
2020-04-22 16:56:21 +00:00
if ( Q_stristr( str, "$LINUX" ) )
return ( IsLinux() || IsBSD() || IsOSX() ) ^ bNot;
2020-04-22 16:56:21 +00:00
if ( Q_stristr( str, "$POSIX" ) )
return IsPosix() ^ bNot;
2020-04-22 16:56:21 +00:00
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;
COM_TimestampedLog("KeyValues::LoadFromBuffer(%s%s%s): Begin", pPathID ? pPathID : "", pPathID && resourceName ? "/" : "", resourceName ? resourceName : "");
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 );
}
bool retVal = LoadFromBuffer( resourceName, buf, pFileSystem, pPathID );
COM_TimestampedLog("KeyValues::LoadFromBuffer(%s%s%s): End", pPathID ? pPathID : "", pPathID && resourceName ? "/" : "", resourceName ? resourceName : "");
return retVal;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void KeyValues::RecursiveLoadFromBuffer( char const *resourceName, CUtlBuffer &buf )
{
CKeyErrorContext errorReport(this);
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 = 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:
{
2022-02-23 11:50:30 +00:00
buffer.PutPtr( dat->m_pValue );
2020-04-22 16:56:21 +00:00
}
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 );
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 );
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:
{
2022-02-23 11:50:30 +00:00
dat->m_pValue = buffer.GetPtr();
2020-04-22 16:56:21 +00:00
}
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 )
{
if (pMem)
{
KeyValuesSystem()->FreeKeyValuesMemory(pMem);
}
2020-04-22 16:56:21 +00:00
}
void KeyValues::operator delete( void *pMem, int nBlockUse, const char *pFileName, int nLine )
{
if (pMem)
{
KeyValuesSystem()->FreeKeyValuesMemory(pMem);
}
2020-04-22 16:56:21 +00:00
}
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 */, bool bSorted /*= false*/ )
{
if ( !pDump->KvBeginKey( this, nIndentLevel ) )
return false;
if ( bSorted )
{
CUtlSortVector< KeyValues*, CUtlSortVectorKeyValuesByName > vecSortedKeys;
// Dump values
for ( KeyValues *val = this ? GetFirstValue() : NULL; val; val = val->GetNextValue() )
{
vecSortedKeys.InsertNoSort( val );
}
vecSortedKeys.RedoSort();
FOR_EACH_VEC( vecSortedKeys, i )
{
if ( !pDump->KvWriteValue( vecSortedKeys[i], nIndentLevel + 1 ) )
return false;
}
vecSortedKeys.Purge();
// Dump subkeys
for ( KeyValues *sub = this ? GetFirstTrueSubKey() : NULL; sub; sub = sub->GetNextTrueSubKey() )
{
vecSortedKeys.InsertNoSort( sub );
}
vecSortedKeys.RedoSort();
FOR_EACH_VEC( vecSortedKeys, i )
{
if ( !vecSortedKeys[i]->Dump( pDump, nIndentLevel + 1, bSorted ) )
return false;
}
}
else
{
// 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" ) &&
KvWriteIndent( nIndentLevel ) &&
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;
}