You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
19 KiB
620 lines
19 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include <vstdlib/ikeyvaluessystem.h> |
|
#include <keyvalues.h> |
|
#include "tier1/mempool.h" |
|
#include "utlsymbol.h" |
|
#include "utlmap.h" |
|
#include "tier0/threadtools.h" |
|
#include "tier1/memstack.h" |
|
#include "tier1/convar.h" |
|
|
|
#ifdef _PS3 |
|
#include "ps3/ps3_core.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
#ifdef NO_SBH // no need to pool if using tier0 small block heap |
|
#define KEYVALUES_USE_POOL 1 |
|
#endif |
|
|
|
// |
|
// Defines platform-endian-specific macros: |
|
// MEM_4BYTES_AS_0_AND_3BYTES : present a 4 byte uint32 as a memory |
|
// layout where first memory byte is zero |
|
// and the other 3 bytes represent value |
|
// MEM_4BYTES_FROM_0_AND_3BYTES: unpack from memory with first zero byte |
|
// and 3 value bytes the original uint32 value |
|
// |
|
// used for efficiently reading/writing storing 3 byte values into memory |
|
// region immediately following a null-byte-terminated string, essentially |
|
// sharing the null-byte-terminator with the first memory byte |
|
// |
|
#if defined( PLAT_LITTLE_ENDIAN ) |
|
// Number in memory has lowest-byte in front, use shifts to make it zero |
|
#define MEM_4BYTES_AS_0_AND_3BYTES( x4bytes ) ( ( (uint32) (x4bytes) ) << 8 ) |
|
#define MEM_4BYTES_FROM_0_AND_3BYTES( x03bytes ) ( ( (uint32) (x03bytes) ) >> 8 ) |
|
#endif |
|
#if defined( PLAT_BIG_ENDIAN ) |
|
// Number in memory has highest-byte in front, use masking to make it zero |
|
#define MEM_4BYTES_AS_0_AND_3BYTES( x4bytes ) ( ( (uint32) (x4bytes) ) & 0x00FFFFFF ) |
|
#define MEM_4BYTES_FROM_0_AND_3BYTES( x03bytes ) ( ( (uint32) (x03bytes) ) & 0x00FFFFFF ) |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Central storage point for KeyValues memory and symbols |
|
//----------------------------------------------------------------------------- |
|
class CKeyValuesSystem : public IKeyValuesSystem |
|
{ |
|
public: |
|
CKeyValuesSystem(); |
|
~CKeyValuesSystem(); |
|
|
|
// registers the size of the KeyValues in the specified instance |
|
// so it can build a properly sized memory pool for the KeyValues objects |
|
// the sizes will usually never differ but this is for versioning safety |
|
void RegisterSizeofKeyValues(int size); |
|
|
|
// allocates/frees a KeyValues object from the shared mempool |
|
void *AllocKeyValuesMemory(int size); |
|
void FreeKeyValuesMemory(void *pMem); |
|
|
|
// symbol table access (used for key names) |
|
HKeySymbol GetSymbolForString( const char *name, bool bCreate ); |
|
const char *GetStringForSymbol(HKeySymbol symbol); |
|
|
|
// returns the wide version of ansi, also does the lookup on #'d strings |
|
void GetLocalizedFromANSI( const char *ansi, wchar_t *outBuf, int unicodeBufferSizeInBytes); |
|
void GetANSIFromLocalized( const wchar_t *wchar, char *outBuf, int ansiBufferSizeInBytes ); |
|
|
|
// for debugging, adds KeyValues record into global list so we can track memory leaks |
|
virtual void AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name); |
|
virtual void RemoveKeyValuesFromMemoryLeakList(void *pMem); |
|
|
|
// set/get a value for keyvalues resolution symbol |
|
// e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables [$LOWVIOLENCE] |
|
virtual void SetKeyValuesExpressionSymbol( const char *name, bool bValue ); |
|
virtual bool GetKeyValuesExpressionSymbol( const char *name ); |
|
|
|
// symbol table access from code with case-preserving requirements (used for key names) |
|
virtual HKeySymbol GetSymbolForStringCaseSensitive( HKeySymbol &hCaseInsensitiveSymbol, const char *name, bool bCreate = true ); |
|
|
|
private: |
|
#ifdef KEYVALUES_USE_POOL |
|
CUtlMemoryPool *m_pMemPool; |
|
#endif |
|
int m_iMaxKeyValuesSize; |
|
|
|
// string hash table |
|
/* |
|
Here's the way key values system data structures are laid out: |
|
hash table with 2047 hash buckets: |
|
[0] { hash_item_t } |
|
[1] |
|
[2] |
|
... |
|
each hash_item_t's stringIndex is an offset in m_Strings memory |
|
at that offset we store the actual null-terminated string followed |
|
by another 3 bytes for an alternative capitalization. |
|
These 3 trailing bytes are set to 0 if no alternative capitalization |
|
variants are present in the dictionary. |
|
These trailing 3 bytes are interpreted as stringIndex into m_Strings |
|
memory for the next alternative capitalization |
|
|
|
Getting a string value by HKeySymbol : constant time access at the |
|
string memory represented by stringIndex |
|
|
|
Getting a symbol for a string value: |
|
1) compute the hash |
|
2) start walking the hash-bucket using special version of stricmp |
|
until a case insensitive match is found |
|
3a) for case-insensitive lookup return the found stringIndex |
|
3b) for case-sensitive lookup keep walking the list of alternative |
|
capitalizations using strcmp until exact case match is found |
|
*/ |
|
CMemoryStack m_Strings; |
|
struct hash_item_t |
|
{ |
|
int stringIndex; |
|
hash_item_t *next; |
|
}; |
|
CUtlMemoryPool m_HashItemMemPool; |
|
CUtlVector<hash_item_t> m_HashTable; |
|
int CaseInsensitiveHash(const char *string, int iBounds); |
|
|
|
struct MemoryLeakTracker_t |
|
{ |
|
int nameIndex; |
|
void *pMem; |
|
}; |
|
static bool MemoryLeakTrackerLessFunc( const MemoryLeakTracker_t &lhs, const MemoryLeakTracker_t &rhs ) |
|
{ |
|
return lhs.pMem < rhs.pMem; |
|
} |
|
CUtlRBTree<MemoryLeakTracker_t, int> m_KeyValuesTrackingList; |
|
|
|
CUtlMap< HKeySymbol, bool > m_KvConditionalSymbolTable; |
|
|
|
CThreadFastMutex m_mutex; |
|
}; |
|
|
|
// EXPOSE_SINGLE_INTERFACE(CKeyValuesSystem, IKeyValuesSystem, KEYVALUES_INTERFACE_VERSION); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Instance singleton and expose interface to rest of code |
|
//----------------------------------------------------------------------------- |
|
static CKeyValuesSystem g_KeyValuesSystem; |
|
|
|
IKeyValuesSystem *KeyValuesSystem() |
|
{ |
|
return &g_KeyValuesSystem; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Constructor |
|
//----------------------------------------------------------------------------- |
|
CKeyValuesSystem::CKeyValuesSystem() : |
|
m_HashItemMemPool(sizeof(hash_item_t), 64, CUtlMemoryPool::GROW_FAST, "CKeyValuesSystem::m_HashItemMemPool"), |
|
m_KeyValuesTrackingList(0, 0, MemoryLeakTrackerLessFunc), |
|
m_KvConditionalSymbolTable( DefLessFunc( HKeySymbol ) ) |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
// initialize hash table |
|
m_HashTable.AddMultipleToTail(2047); |
|
for (int i = 0; i < m_HashTable.Count(); i++) |
|
{ |
|
m_HashTable[i].stringIndex = 0; |
|
m_HashTable[i].next = NULL; |
|
} |
|
|
|
m_Strings.Init( "CKeyValuesSystem::m_Strings", 4*1024*1024, 64*1024, 0, 4 ); |
|
// Make 0 stringIndex to never be returned, by allocating |
|
// and wasting minimal number of alignment bytes now: |
|
char *pszEmpty = ((char *)m_Strings.Alloc(1)); |
|
*pszEmpty = 0; |
|
|
|
#ifdef KEYVALUES_USE_POOL |
|
m_pMemPool = NULL; |
|
#endif |
|
m_iMaxKeyValuesSize = sizeof(KeyValues); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Destructor |
|
//----------------------------------------------------------------------------- |
|
CKeyValuesSystem::~CKeyValuesSystem() |
|
{ |
|
#ifdef KEYVALUES_USE_POOL |
|
#ifdef _DEBUG |
|
// display any memory leaks |
|
if (m_pMemPool && m_pMemPool->Count() > 0) |
|
{ |
|
DevMsg("Leaked KeyValues blocks: %d\n", m_pMemPool->Count()); |
|
} |
|
|
|
// iterate all the existing keyvalues displaying their names |
|
for (int i = 0; i < m_KeyValuesTrackingList.MaxElement(); i++) |
|
{ |
|
if (m_KeyValuesTrackingList.IsValidIndex(i)) |
|
{ |
|
DevMsg("\tleaked KeyValues(%s)\n", &m_Strings[m_KeyValuesTrackingList[i].nameIndex]); |
|
} |
|
} |
|
#endif |
|
|
|
delete m_pMemPool; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: registers the size of the KeyValues in the specified instance |
|
// so it can build a properly sized memory pool for the KeyValues objects |
|
// the sizes will usually never differ but this is for versioning safety |
|
//----------------------------------------------------------------------------- |
|
void CKeyValuesSystem::RegisterSizeofKeyValues(int size) |
|
{ |
|
if (size > m_iMaxKeyValuesSize) |
|
{ |
|
m_iMaxKeyValuesSize = size; |
|
} |
|
} |
|
|
|
static void KVLeak( char const *fmt, ... ) |
|
{ |
|
va_list argptr; |
|
char data[1024]; |
|
|
|
va_start(argptr, fmt); |
|
V_vsnprintf(data, sizeof( data ), fmt, argptr); |
|
va_end(argptr); |
|
|
|
Msg( data ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: allocates a KeyValues object from the shared mempool |
|
//----------------------------------------------------------------------------- |
|
void *CKeyValuesSystem::AllocKeyValuesMemory(int size) |
|
{ |
|
#ifdef KEYVALUES_USE_POOL |
|
// allocate, if we don't have one yet |
|
if (!m_pMemPool) |
|
{ |
|
m_pMemPool = new CUtlMemoryPool(m_iMaxKeyValuesSize, 1024, CUtlMemoryPool::GROW_FAST, "CKeyValuesSystem::m_pMemPool" ); |
|
m_pMemPool->SetErrorReportFunc( KVLeak ); |
|
} |
|
|
|
return m_pMemPool->Alloc(size); |
|
#else |
|
return malloc( size ); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: frees a KeyValues object from the shared mempool |
|
//----------------------------------------------------------------------------- |
|
void CKeyValuesSystem::FreeKeyValuesMemory(void *pMem) |
|
{ |
|
#ifdef KEYVALUES_USE_POOL |
|
m_pMemPool->Free(pMem); |
|
#else |
|
free( pMem ); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: symbol table access (used for key names) |
|
//----------------------------------------------------------------------------- |
|
HKeySymbol CKeyValuesSystem::GetSymbolForString( const char *name, bool bCreate ) |
|
{ |
|
if ( !name ) |
|
{ |
|
return (-1); |
|
} |
|
|
|
AUTO_LOCK( m_mutex ); |
|
MEM_ALLOC_CREDIT(); |
|
|
|
int hash = CaseInsensitiveHash(name, m_HashTable.Count()); |
|
int i = 0; |
|
hash_item_t *item = &m_HashTable[hash]; |
|
while (1) |
|
{ |
|
if (!stricmp(name, (char *)m_Strings.GetBase() + item->stringIndex )) |
|
{ |
|
return (HKeySymbol)item->stringIndex; |
|
} |
|
|
|
i++; |
|
|
|
if (item->next == NULL) |
|
{ |
|
if ( !bCreate ) |
|
{ |
|
// not found |
|
return -1; |
|
} |
|
|
|
// we're not in the table |
|
if (item->stringIndex != 0) |
|
{ |
|
// first item is used, an new item |
|
item->next = (hash_item_t *)m_HashItemMemPool.Alloc(sizeof(hash_item_t)); |
|
item = item->next; |
|
} |
|
|
|
// build up the new item |
|
item->next = NULL; |
|
int numStringBytes = strlen(name); |
|
char *pString = (char *)m_Strings.Alloc( numStringBytes + 1 + 3 ); |
|
if ( !pString ) |
|
{ |
|
Error( "Out of keyvalue string space" ); |
|
return -1; |
|
} |
|
item->stringIndex = pString - (char *)m_Strings.GetBase(); |
|
V_memcpy( pString, name, numStringBytes ); |
|
* reinterpret_cast< uint32 * >( pString + numStringBytes ) = 0; // string null-terminator + 3 alternative spelling bytes |
|
return (HKeySymbol)item->stringIndex; |
|
} |
|
|
|
item = item->next; |
|
} |
|
|
|
// shouldn't be able to get here |
|
Assert(0); |
|
return (-1); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: symbol table access (used for key names) |
|
//----------------------------------------------------------------------------- |
|
HKeySymbol CKeyValuesSystem::GetSymbolForStringCaseSensitive( HKeySymbol &hCaseInsensitiveSymbol, const char *name, bool bCreate ) |
|
{ |
|
if ( !name ) |
|
{ |
|
return (-1); |
|
} |
|
|
|
AUTO_LOCK( m_mutex ); |
|
MEM_ALLOC_CREDIT(); |
|
|
|
int hash = CaseInsensitiveHash(name, m_HashTable.Count()); |
|
int numNameStringBytes = -1; |
|
int i = 0; |
|
hash_item_t *item = &m_HashTable[hash]; |
|
while (1) |
|
{ |
|
char *pCompareString = (char *)m_Strings.GetBase() + item->stringIndex; |
|
int iResult = _V_stricmp_NegativeForUnequal( name, pCompareString ); |
|
if ( iResult == 0 ) |
|
{ |
|
// strings are exactly equal matching every letter's case |
|
hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; |
|
return (HKeySymbol)item->stringIndex; |
|
} |
|
else if ( iResult > 0 ) |
|
{ |
|
// strings are equal in a case-insensitive compare, but have different case for some letters |
|
// Need to walk the case-resolving chain |
|
numNameStringBytes = V_strlen( pCompareString ); |
|
uint32 *pnCaseResolveIndex = reinterpret_cast< uint32 * >( pCompareString + numNameStringBytes ); |
|
hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; |
|
while ( int nAlternativeStringIndex = MEM_4BYTES_FROM_0_AND_3BYTES( *pnCaseResolveIndex ) ) |
|
{ |
|
pCompareString = (char *)m_Strings.GetBase() + nAlternativeStringIndex; |
|
int iResult = strcmp( name, pCompareString ); |
|
if ( !iResult ) |
|
{ |
|
// found an exact match |
|
return (HKeySymbol)nAlternativeStringIndex; |
|
} |
|
// Keep traversing alternative case-resolving chain |
|
pnCaseResolveIndex = reinterpret_cast< uint32 * >( pCompareString + numNameStringBytes ); |
|
} |
|
// Reached the end of alternative case-resolving chain, pnCaseResolveIndex is pointing at 0 bytes |
|
// indicating no further alternative stringIndex |
|
if ( !bCreate ) |
|
{ |
|
// If we aren't interested in creating the actual string index, |
|
// then return symbol with default capitalization |
|
// NOTE: this is not correct value, but it cannot be used to create a new value anyway, |
|
// only for locating a pre-existing value and lookups are case-insensitive |
|
return (HKeySymbol)item->stringIndex; |
|
} |
|
else |
|
{ |
|
char *pString = (char *)m_Strings.Alloc( numNameStringBytes + 1 + 3 ); |
|
if ( !pString ) |
|
{ |
|
Error( "Out of keyvalue string space" ); |
|
return -1; |
|
} |
|
int nNewAlternativeStringIndex = pString - (char *)m_Strings.GetBase(); |
|
V_memcpy( pString, name, numNameStringBytes ); |
|
* reinterpret_cast< uint32 * >( pString + numNameStringBytes ) = 0; // string null-terminator + 3 alternative spelling bytes |
|
*pnCaseResolveIndex = MEM_4BYTES_AS_0_AND_3BYTES( nNewAlternativeStringIndex ); // link previous spelling entry to the new entry |
|
return (HKeySymbol)nNewAlternativeStringIndex; |
|
} |
|
} |
|
|
|
i++; |
|
|
|
if (item->next == NULL) |
|
{ |
|
if ( !bCreate ) |
|
{ |
|
// not found |
|
return -1; |
|
} |
|
|
|
// we're not in the table |
|
if (item->stringIndex != 0) |
|
{ |
|
// first item is used, an new item |
|
item->next = (hash_item_t *)m_HashItemMemPool.Alloc(sizeof(hash_item_t)); |
|
item = item->next; |
|
} |
|
|
|
// build up the new item |
|
item->next = NULL; |
|
int numStringBytes = strlen(name); |
|
char *pString = (char *)m_Strings.Alloc( numStringBytes + 1 + 3 ); |
|
if ( !pString ) |
|
{ |
|
Error( "Out of keyvalue string space" ); |
|
return -1; |
|
} |
|
item->stringIndex = pString - (char *)m_Strings.GetBase(); |
|
V_memcpy( pString, name, numStringBytes ); |
|
* reinterpret_cast< uint32 * >( pString + numStringBytes ) = 0; // string null-terminator + 3 alternative spelling bytes |
|
hCaseInsensitiveSymbol = (HKeySymbol)item->stringIndex; |
|
return (HKeySymbol)item->stringIndex; |
|
} |
|
|
|
item = item->next; |
|
} |
|
|
|
// shouldn't be able to get here |
|
Assert(0); |
|
return (-1); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: symbol table access |
|
//----------------------------------------------------------------------------- |
|
const char *CKeyValuesSystem::GetStringForSymbol(HKeySymbol symbol) |
|
{ |
|
if ( symbol == -1 ) |
|
{ |
|
return ""; |
|
} |
|
return ((char *)m_Strings.GetBase() + (size_t)symbol); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: adds KeyValues record into global list so we can track memory leaks |
|
//----------------------------------------------------------------------------- |
|
void CKeyValuesSystem::AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name) |
|
{ |
|
#ifdef _DEBUG |
|
// only track the memory leaks in debug builds |
|
MemoryLeakTracker_t item = { name, pMem }; |
|
m_KeyValuesTrackingList.Insert(item); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: used to track memory leaks |
|
//----------------------------------------------------------------------------- |
|
void CKeyValuesSystem::RemoveKeyValuesFromMemoryLeakList(void *pMem) |
|
{ |
|
#ifdef _DEBUG |
|
// only track the memory leaks in debug builds |
|
MemoryLeakTracker_t item = { 0, pMem }; |
|
int index = m_KeyValuesTrackingList.Find(item); |
|
m_KeyValuesTrackingList.RemoveAt(index); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: generates a simple hash value for a string |
|
//----------------------------------------------------------------------------- |
|
int CKeyValuesSystem::CaseInsensitiveHash(const char *string, int iBounds) |
|
{ |
|
unsigned int hash = 0; |
|
|
|
for ( ; *string != 0; string++ ) |
|
{ |
|
if (*string >= 'A' && *string <= 'Z') |
|
{ |
|
hash = (hash << 1) + (*string - 'A' + 'a'); |
|
} |
|
else |
|
{ |
|
hash = (hash << 1) + *string; |
|
} |
|
} |
|
|
|
return hash % iBounds; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: set/get a value for keyvalues resolution symbol |
|
// e.g.: SetKeyValuesExpressionSymbol( "LOWVIOLENCE", true ) - enables [$LOWVIOLENCE] |
|
//----------------------------------------------------------------------------- |
|
void CKeyValuesSystem::SetKeyValuesExpressionSymbol( const char *name, bool bValue ) |
|
{ |
|
if ( !name ) |
|
return; |
|
|
|
if ( name[0] == '$' ) |
|
++ name; |
|
|
|
HKeySymbol hSym = GetSymbolForString( name, true ); // find or create symbol |
|
|
|
{ |
|
AUTO_LOCK( m_mutex ); |
|
m_KvConditionalSymbolTable.InsertOrReplace( hSym, bValue ); |
|
} |
|
} |
|
|
|
bool CKeyValuesSystem::GetKeyValuesExpressionSymbol( const char *name ) |
|
{ |
|
if ( !name ) |
|
return false; |
|
|
|
if ( name[0] == '$' ) |
|
++ name; |
|
|
|
HKeySymbol hSym = GetSymbolForString( name, false ); // find or create symbol |
|
if ( hSym != -1 ) |
|
{ |
|
AUTO_LOCK( m_mutex ); |
|
CUtlMap< HKeySymbol, bool >::IndexType_t idx = m_KvConditionalSymbolTable.Find( hSym ); |
|
if ( idx != m_KvConditionalSymbolTable.InvalidIndex() ) |
|
{ |
|
// Found the symbol value in conditional symbol table |
|
return m_KvConditionalSymbolTable.Element( idx ); |
|
} |
|
} |
|
|
|
// |
|
// Fallback conditionals |
|
// |
|
|
|
if ( !V_stricmp( name, "GAMECONSOLESPLITSCREEN" ) ) |
|
{ |
|
#if defined( _GAMECONSOLE ) |
|
return ( XBX_GetNumGameUsers() > 1 ); |
|
#else |
|
return false; |
|
#endif |
|
} |
|
|
|
if ( !V_stricmp( name, "GAMECONSOLEGUEST" ) ) |
|
{ |
|
#if defined( _GAMECONSOLE ) |
|
return ( XBX_GetPrimaryUserIsGuest() != 0 ); |
|
#else |
|
return false; |
|
#endif |
|
} |
|
|
|
if ( !V_stricmp( name, "ENGLISH" ) || |
|
!V_stricmp( name, "JAPANESE" ) || |
|
!V_stricmp( name, "GERMAN" ) || |
|
!V_stricmp( name, "FRENCH" ) || |
|
!V_stricmp( name, "SPANISH" ) || |
|
!V_stricmp( name, "ITALIAN" ) || |
|
!V_stricmp( name, "KOREAN" ) || |
|
!V_stricmp( name, "TCHINESE" ) || |
|
!V_stricmp( name, "PORTUGUESE" ) || |
|
!V_stricmp( name, "SCHINESE" ) || |
|
!V_stricmp( name, "POLISH" ) || |
|
!V_stricmp( name, "RUSSIAN" ) || |
|
!V_stricmp( name, "TURKISH" ) ) |
|
{ |
|
// the language symbols are true if we are in that language |
|
// english is assumed when no language is present |
|
const char *pLanguageString; |
|
#ifdef _GAMECONSOLE |
|
pLanguageString = XBX_GetLanguageString(); |
|
#else |
|
static ConVarRef cl_language( "cl_language" ); |
|
pLanguageString = cl_language.GetString(); |
|
#endif |
|
if ( !pLanguageString || !pLanguageString[0] ) |
|
{ |
|
pLanguageString = "english"; |
|
} |
|
if ( !V_stricmp( name, pLanguageString ) ) |
|
{ |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
// very expensive, back door for DLC updates |
|
if ( !V_strnicmp( name, "CVAR_", 5 ) ) |
|
{ |
|
ConVarRef cvRef( name + 5 ); |
|
if ( cvRef.IsValid() ) |
|
return cvRef.GetBool(); |
|
} |
|
|
|
// purposely warn on these to prevent syntax errors |
|
// need to get these fixed asap, otherwise unintended false behavior |
|
Warning( "KV Conditional: Unknown symbol %s\n", name ); |
|
return false; |
|
}
|
|
|