2022-04-09 16:16:40 +02:00
|
|
|
//=============================================================================//
|
|
|
|
//
|
|
|
|
// Purpose:
|
|
|
|
//
|
|
|
|
// $NoKeywords: $
|
|
|
|
//=============================================================================//
|
|
|
|
|
2021-12-25 22:36:38 +01:00
|
|
|
#include "core/stdafx.h"
|
2022-08-08 00:32:25 +02:00
|
|
|
#include "tier0/memstd.h"
|
2022-05-28 16:31:38 +02:00
|
|
|
#include "tier1/strtools.h"
|
2024-04-05 17:42:05 +02:00
|
|
|
#include "tier1/keyvalues.h"
|
|
|
|
#include "tier1/kvleaktrace.h"
|
|
|
|
#include "tier1/kverrorstack.h"
|
|
|
|
#include "tier1/kverrorcontext.h"
|
|
|
|
#include "tier1/kvtokenreader.h"
|
2022-04-09 16:16:40 +02:00
|
|
|
#include "vstdlib/keyvaluessystem.h"
|
2024-04-05 17:42:05 +02:00
|
|
|
#include "public/ifilesystem.h"
|
2022-05-28 16:31:38 +02:00
|
|
|
#include "mathlib/color.h"
|
2022-03-23 23:28:12 +01:00
|
|
|
#include "rtech/stryder/stryder.h"
|
2021-12-25 22:36:38 +01:00
|
|
|
#include "engine/sys_dll2.h"
|
2022-11-06 14:21:27 +01:00
|
|
|
#include "engine/cmodel_bsp.h"
|
2021-12-25 22:36:38 +01:00
|
|
|
|
2024-04-05 17:42:05 +02:00
|
|
|
static const char* s_LastFileLoadingFrom = "unknown"; // just needed for error messages
|
|
|
|
CExpressionEvaluator g_ExpressionEvaluator;
|
|
|
|
|
|
|
|
#define INTERNALWRITE( pData, nLen ) InternalWrite( pFileSystem, pHandle, pBuf, pData, nLen )
|
|
|
|
|
|
|
|
#define MAKE_3_BYTES_FROM_1_AND_2( x1, x2 ) (( (( uint16_t )x2) << 8 ) | (uint8_t)(x1))
|
|
|
|
#define SPLIT_3_BYTES_INTO_1_AND_2( x1, x2, x3 ) do { x1 = (uint8)(x3); x2 = (uint16)( (x3) >> 8 ); } while( 0 )
|
|
|
|
|
2022-04-09 16:16:40 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
2022-05-28 16:31:38 +02:00
|
|
|
// Purpose: Constructor
|
|
|
|
// Input : *pszSetName -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues::KeyValues(const char* pszSetName)
|
|
|
|
{
|
|
|
|
TRACK_KV_ADD(this, pszSetName);
|
|
|
|
|
|
|
|
Init();
|
|
|
|
SetName(pszSetName);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Constructor
|
|
|
|
// Input : *pszSetName -
|
|
|
|
// *pszFirstKey -
|
|
|
|
// *pszFirstValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues::KeyValues(const char* pszSsetName, const char* pszFirstKey, const char* pszFirstValue)
|
|
|
|
{
|
|
|
|
TRACK_KV_ADD(this, pszSsetName);
|
|
|
|
|
|
|
|
Init();
|
|
|
|
SetName(pszSsetName);
|
|
|
|
SetString(pszFirstKey, pszFirstValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Constructor
|
|
|
|
// Input : *pszSetName -
|
|
|
|
// *pszFirstKey -
|
|
|
|
// *pwszFirstValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, const wchar_t* pwszFirstValue)
|
|
|
|
{
|
|
|
|
TRACK_KV_ADD(this, pszSetName);
|
|
|
|
|
|
|
|
Init();
|
|
|
|
SetName(pszSetName);
|
|
|
|
SetWString(pszFirstKey, pwszFirstValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Constructor
|
|
|
|
// Input : *pszSetName -
|
|
|
|
// *pszFirstKey -
|
|
|
|
// iFirstValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue)
|
|
|
|
{
|
|
|
|
TRACK_KV_ADD(this, pszSetName);
|
|
|
|
|
|
|
|
Init();
|
|
|
|
SetName(pszSetName);
|
|
|
|
SetInt(pszFirstKey, iFirstValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Constructor
|
|
|
|
// Input : *pszSetName -
|
|
|
|
// *pszFirstKey -
|
|
|
|
// *pszFirstValue -
|
|
|
|
// *pszSecondKey -
|
|
|
|
// *pszSecondValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, const char* pszFirstValue, const char* pszSecondKey, const char* pszSecondValue)
|
|
|
|
{
|
|
|
|
TRACK_KV_ADD(this, pszSetName);
|
|
|
|
|
|
|
|
Init();
|
|
|
|
SetName(pszSetName);
|
|
|
|
SetString(pszFirstKey, pszFirstValue);
|
|
|
|
SetString(pszSecondKey, pszSecondValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Constructor
|
|
|
|
// Input : *pszSetName -
|
|
|
|
// *pszFirstKey -
|
|
|
|
// iFirstValue -
|
|
|
|
// *pszSecondKey -
|
|
|
|
// iSecondValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue, const char* pszSecondKey, int iSecondValue)
|
|
|
|
{
|
|
|
|
TRACK_KV_ADD(this, pszSetName);
|
|
|
|
|
|
|
|
Init();
|
|
|
|
SetName(pszSetName);
|
|
|
|
SetInt(pszFirstKey, iFirstValue);
|
|
|
|
SetInt(pszSecondKey, iSecondValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Destructor
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues::~KeyValues(void)
|
|
|
|
{
|
|
|
|
TRACK_KV_REMOVE(this);
|
|
|
|
|
|
|
|
RemoveEverything();
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Initialize member variables
|
2022-04-09 16:16:40 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::Init(void)
|
|
|
|
{
|
2022-05-28 16:31:38 +02:00
|
|
|
m_iKeyName = 0;
|
|
|
|
m_iKeyNameCaseSensitive1 = 0;
|
|
|
|
m_iKeyNameCaseSensitive2 = 0;
|
|
|
|
m_iDataType = TYPE_NONE;
|
|
|
|
|
|
|
|
m_pSub = nullptr;
|
|
|
|
m_pPeer = nullptr;
|
|
|
|
m_pChain = nullptr;
|
|
|
|
|
|
|
|
m_sValue = nullptr;
|
|
|
|
m_wsValue = nullptr;
|
|
|
|
m_pValue = nullptr;
|
|
|
|
|
|
|
|
m_bHasEscapeSequences = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Clear out all subkeys, and the current value
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::Clear(void)
|
|
|
|
{
|
2023-06-26 22:34:24 +02:00
|
|
|
delete m_pSub;
|
2022-05-28 16:31:38 +02:00
|
|
|
m_pSub = nullptr;
|
|
|
|
m_iDataType = TYPE_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// for backwards compat - we used to need this to force the free to run from the same DLL
|
|
|
|
// as the alloc
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::DeleteThis(void)
|
|
|
|
{
|
2023-06-26 22:34:24 +02:00
|
|
|
delete this;
|
2022-05-28 16:31:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: remove everything
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::RemoveEverything(void)
|
|
|
|
{
|
|
|
|
KeyValues* dat;
|
|
|
|
KeyValues* datNext = nullptr;
|
|
|
|
for (dat = m_pSub; dat != nullptr; dat = datNext)
|
|
|
|
{
|
|
|
|
datNext = dat->m_pPeer;
|
|
|
|
dat->m_pPeer = nullptr;
|
2023-06-26 22:34:24 +02:00
|
|
|
delete dat;
|
2022-05-28 16:31:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (dat = m_pPeer; dat && dat != this; dat = datNext)
|
|
|
|
{
|
|
|
|
datNext = dat->m_pPeer;
|
|
|
|
dat->m_pPeer = nullptr;
|
2023-06-26 22:34:24 +02:00
|
|
|
delete dat;
|
2022-05-28 16:31:38 +02:00
|
|
|
}
|
|
|
|
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] m_sValue;
|
2022-05-28 16:31:38 +02:00
|
|
|
m_sValue = nullptr;
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] m_wsValue;
|
2022-05-28 16:31:38 +02:00
|
|
|
m_wsValue = nullptr;
|
2022-04-09 16:16:40 +02:00
|
|
|
}
|
2021-12-25 22:36:38 +01:00
|
|
|
|
2024-04-05 17:42:05 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: looks up a key by symbol name
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::FindKey(int keySymbol) const
|
|
|
|
{
|
|
|
|
AssertMsg(this, "Member function called on NULL KeyValues");
|
|
|
|
for (KeyValues* dat = this ? m_pSub : NULL; dat != NULL; dat = dat->m_pPeer)
|
|
|
|
{
|
|
|
|
if (dat->m_iKeyName == (uint32)keySymbol)
|
|
|
|
return dat;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-04-09 16:16:40 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// 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)
|
2022-05-28 16:31:38 +02:00
|
|
|
// Input : *pszKeyName -
|
2022-04-09 16:16:40 +02:00
|
|
|
// bCreate -
|
|
|
|
// Output : *KeyValues
|
|
|
|
//-----------------------------------------------------------------------------
|
2022-05-28 16:31:38 +02:00
|
|
|
KeyValues* KeyValues::FindKey(const char* pszKeyName, bool bCreate)
|
2022-04-09 16:16:40 +02:00
|
|
|
{
|
2023-09-16 16:13:14 +02:00
|
|
|
// Validate NULL == this early out
|
|
|
|
if (!this)
|
|
|
|
{
|
|
|
|
// Undefined behavior. Could blow up on a new platform. Don't do it.
|
|
|
|
DevWarning(eDLL_T::COMMON, "KeyValues::FindKey called on NULL pointer!");
|
|
|
|
Assert(!bCreate);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// return the current key if a NULL subkey is asked for
|
|
|
|
if (!pszKeyName || !pszKeyName[0])
|
|
|
|
return this;
|
|
|
|
|
|
|
|
// look for '/' characters delimiting sub fields
|
|
|
|
char szBuf[256];
|
|
|
|
const char* subStr = strchr(pszKeyName, '/');
|
|
|
|
const char* searchStr = pszKeyName;
|
|
|
|
|
|
|
|
// pull out the substring if it exists
|
|
|
|
if (subStr)
|
|
|
|
{
|
|
|
|
ptrdiff_t size = subStr - pszKeyName;
|
|
|
|
memcpy(szBuf, pszKeyName, size);
|
|
|
|
szBuf[size] = '\0';
|
|
|
|
searchStr = szBuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
// lookup the symbol for the search string,
|
|
|
|
// we do not need the case-sensitive symbol at this time
|
|
|
|
// because if the key is found, then it will be found by case-insensitive lookup
|
|
|
|
// if the key is not found and needs to be created we will pass the actual searchStr
|
|
|
|
// and have the new KeyValues constructor get/create the case-sensitive symbol
|
|
|
|
HKeySymbol iSearchStr = KeyValuesSystem()->GetSymbolForString(searchStr, bCreate);
|
|
|
|
if (iSearchStr == INVALID_KEY_SYMBOL)
|
|
|
|
{
|
|
|
|
// not found, couldn't possibly be in key value list
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyValues* lastItem = nullptr;
|
|
|
|
KeyValues* dat;
|
|
|
|
// find the searchStr in the current peer list
|
|
|
|
for (dat = m_pSub; dat != NULL; dat = dat->m_pPeer)
|
|
|
|
{
|
|
|
|
lastItem = dat; // record the last item looked at (for if we need to append to the end of the list)
|
|
|
|
|
|
|
|
// symbol compare
|
|
|
|
if (dat->m_iKeyName == (uint32)iSearchStr)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dat && m_pChain)
|
|
|
|
{
|
|
|
|
dat = m_pChain->FindKey(pszKeyName, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure a key was found
|
|
|
|
if (!dat)
|
|
|
|
{
|
|
|
|
if (bCreate)
|
|
|
|
{
|
|
|
|
// we need to create a new key
|
|
|
|
dat = new KeyValues(searchStr);
|
|
|
|
Assert(dat != nullptr);
|
|
|
|
|
|
|
|
// insert new key at end of list
|
|
|
|
if (lastItem)
|
|
|
|
{
|
|
|
|
lastItem->m_pPeer = dat;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_pSub = dat;
|
|
|
|
}
|
|
|
|
dat->m_pPeer = nullptr;
|
|
|
|
|
|
|
|
// 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 nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2022-05-28 16:31:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Locate last child. Returns NULL if we have no children
|
|
|
|
// Output : *KeyValues
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::FindLastSubKey(void) const
|
|
|
|
{
|
|
|
|
// No children?
|
|
|
|
if (m_pSub == nullptr)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
// Scan for the last one
|
|
|
|
KeyValues* pLastChild = m_pSub;
|
|
|
|
while (pLastChild->m_pPeer)
|
|
|
|
pLastChild = pLastChild->m_pPeer;
|
|
|
|
return pLastChild;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Adds a subkey. Make sure the subkey isn't a child of some other keyvalues
|
|
|
|
// Input : *pSubKey -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::AddSubKey(KeyValues* pSubkey)
|
|
|
|
{
|
|
|
|
// Make sure the subkey isn't a child of some other keyvalues
|
|
|
|
Assert(pSubkey != nullptr);
|
|
|
|
Assert(pSubkey->m_pPeer == nullptr);
|
|
|
|
|
|
|
|
// add into subkey list
|
|
|
|
if (m_pSub == nullptr)
|
|
|
|
{
|
|
|
|
m_pSub = pSubkey;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
KeyValues* pTempDat = m_pSub;
|
|
|
|
while (pTempDat->GetNextKey() != nullptr)
|
|
|
|
{
|
|
|
|
pTempDat = pTempDat->GetNextKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
pTempDat->SetNextKey(pSubkey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Remove a subkey from the list
|
|
|
|
// Input : *pSubKey -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::RemoveSubKey(KeyValues* pSubKey)
|
|
|
|
{
|
|
|
|
if (!pSubKey)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// check the list pointer
|
|
|
|
if (m_pSub == pSubKey)
|
|
|
|
{
|
|
|
|
m_pSub = pSubKey->m_pPeer;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// look through the list
|
|
|
|
KeyValues* kv = m_pSub;
|
|
|
|
while (kv->m_pPeer)
|
|
|
|
{
|
|
|
|
if (kv->m_pPeer == pSubKey)
|
|
|
|
{
|
|
|
|
kv->m_pPeer = pSubKey->m_pPeer;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
kv = kv->m_pPeer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pSubKey->m_pPeer = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Insert a subkey at index
|
|
|
|
// Input : nIndex -
|
|
|
|
// *pSubKey -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::InsertSubKey(int nIndex, KeyValues* pSubKey)
|
|
|
|
{
|
|
|
|
// Sub key must be valid and not part of another chain
|
|
|
|
Assert(pSubKey && pSubKey->m_pPeer == nullptr);
|
|
|
|
|
|
|
|
if (nIndex == 0)
|
|
|
|
{
|
|
|
|
pSubKey->m_pPeer = m_pSub;
|
|
|
|
m_pSub = pSubKey;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int nCurrentIndex = 0;
|
|
|
|
for (KeyValues* pIter = GetFirstSubKey(); pIter != nullptr; pIter = pIter->GetNextKey())
|
|
|
|
{
|
|
|
|
++nCurrentIndex;
|
|
|
|
if (nCurrentIndex == nIndex)
|
|
|
|
{
|
|
|
|
pSubKey->m_pPeer = pIter->m_pPeer;
|
|
|
|
pIter->m_pPeer = pSubKey;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Index is out of range if we get here
|
|
|
|
Assert(0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Checks if key contains a subkey
|
|
|
|
// Input : *pSubKey -
|
|
|
|
// Output : true if contains, false otherwise
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KeyValues::ContainsSubKey(KeyValues* pSubKey)
|
|
|
|
{
|
|
|
|
for (KeyValues* pIter = GetFirstSubKey(); pIter != nullptr; pIter = pIter->GetNextKey())
|
|
|
|
{
|
|
|
|
if (pSubKey == pIter)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Swaps existing subkey with another
|
|
|
|
// Input : *pExistingSubkey -
|
|
|
|
// *pNewSubKey -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SwapSubKey(KeyValues* pExistingSubkey, KeyValues* pNewSubKey)
|
|
|
|
{
|
|
|
|
Assert(pExistingSubkey != nullptr && pNewSubKey != nullptr);
|
|
|
|
|
|
|
|
// Make sure the new sub key isn't a child of some other keyvalues
|
|
|
|
Assert(pNewSubKey->m_pPeer == nullptr);
|
|
|
|
|
|
|
|
// Check the list pointer
|
|
|
|
if (m_pSub == pExistingSubkey)
|
|
|
|
{
|
|
|
|
pNewSubKey->m_pPeer = pExistingSubkey->m_pPeer;
|
|
|
|
pExistingSubkey->m_pPeer = nullptr;
|
|
|
|
m_pSub = pNewSubKey;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Look through the list
|
|
|
|
KeyValues* kv = m_pSub;
|
|
|
|
while (kv->m_pPeer)
|
|
|
|
{
|
|
|
|
if (kv->m_pPeer == pExistingSubkey)
|
|
|
|
{
|
|
|
|
pNewSubKey->m_pPeer = pExistingSubkey->m_pPeer;
|
|
|
|
pExistingSubkey->m_pPeer = nullptr;
|
|
|
|
kv->m_pPeer = pNewSubKey;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
kv = kv->m_pPeer;
|
|
|
|
}
|
|
|
|
// Existing sub key should always be found, otherwise it's a bug in the calling code.
|
|
|
|
Assert(kv->m_pPeer != nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Elides subkey
|
|
|
|
// Input : *pSubKey -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::ElideSubKey(KeyValues* pSubKey)
|
|
|
|
{
|
|
|
|
// This pointer's "next" pointer needs to be fixed up when we elide the key
|
|
|
|
KeyValues** ppPointerToFix = &m_pSub;
|
|
|
|
for (KeyValues* pKeyIter = m_pSub; pKeyIter != nullptr; ppPointerToFix = &pKeyIter->m_pPeer, pKeyIter = pKeyIter->GetNextKey())
|
|
|
|
{
|
|
|
|
if (pKeyIter == pSubKey)
|
|
|
|
{
|
|
|
|
if (pSubKey->m_pSub == nullptr)
|
|
|
|
{
|
|
|
|
// No children, simply remove the key
|
|
|
|
*ppPointerToFix = pSubKey->m_pPeer;
|
2023-06-26 22:34:24 +02:00
|
|
|
delete pSubKey;
|
2022-05-28 16:31:38 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*ppPointerToFix = pSubKey->m_pSub;
|
|
|
|
// Attach the remainder of this chain to the last child of pSubKey
|
|
|
|
KeyValues* pChildIter = pSubKey->m_pSub;
|
|
|
|
while (pChildIter->m_pPeer != nullptr)
|
|
|
|
{
|
|
|
|
pChildIter = pChildIter->m_pPeer;
|
|
|
|
}
|
|
|
|
// Now points to the last child of pSubKey
|
|
|
|
pChildIter->m_pPeer = pSubKey->m_pPeer;
|
|
|
|
// Detach the node to be elided
|
|
|
|
pSubKey->m_pSub = nullptr;
|
|
|
|
pSubKey->m_pPeer = nullptr;
|
2023-06-26 22:34:24 +02:00
|
|
|
delete pSubKey;
|
2022-05-28 16:31:38 +02:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Key not found; that's caller error.
|
|
|
|
Assert(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Check if a keyName has no value assigned to it.
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// Output : true on success, false otherwise
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KeyValues::IsEmpty(const char* pszKeyName)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (!pKey)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (pKey->m_iDataType == TYPE_NONE && pKey->m_pSub == nullptr)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: gets the first true sub key
|
|
|
|
// Output : *KeyValues
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::GetFirstTrueSubKey(void) const
|
|
|
|
{
|
|
|
|
Assert(this, "Member function called on NULL KeyValues");
|
|
|
|
KeyValues* pRet = this ? m_pSub : nullptr;
|
|
|
|
while (pRet && pRet->m_iDataType != TYPE_NONE)
|
|
|
|
pRet = pRet->m_pPeer;
|
|
|
|
|
|
|
|
return pRet;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: gets the next true sub key
|
|
|
|
// Output : *KeyValues
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::GetNextTrueSubKey(void) const
|
|
|
|
{
|
|
|
|
Assert(this, "Member function called on NULL KeyValues");
|
|
|
|
KeyValues* pRet = this ? m_pPeer : nullptr;
|
|
|
|
while (pRet && pRet->m_iDataType != TYPE_NONE)
|
|
|
|
pRet = pRet->m_pPeer;
|
|
|
|
|
|
|
|
return pRet;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: gets the first value
|
|
|
|
// Output : *KeyValues
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::GetFirstValue(void) const
|
|
|
|
{
|
|
|
|
Assert(this, "Member function called on NULL KeyValues");
|
|
|
|
KeyValues* pRet = this ? m_pSub : nullptr;
|
|
|
|
while (pRet && pRet->m_iDataType == TYPE_NONE)
|
|
|
|
pRet = pRet->m_pPeer;
|
|
|
|
|
|
|
|
return pRet;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: gets the next value
|
|
|
|
// Output : *KeyValues
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::GetNextValue(void) const
|
|
|
|
{
|
|
|
|
Assert(this, "Member function called on NULL KeyValues");
|
|
|
|
KeyValues* pRet = this ? m_pPeer : nullptr;
|
|
|
|
while (pRet && pRet->m_iDataType == TYPE_NONE)
|
|
|
|
pRet = pRet->m_pPeer;
|
|
|
|
|
|
|
|
return pRet;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Return the first subkey in the list
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::GetFirstSubKey() const
|
|
|
|
{
|
|
|
|
Assert(this, "Member function called on NULL KeyValues");
|
|
|
|
return this ? m_pSub : nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Return the next subkey
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::GetNextKey() const
|
|
|
|
{
|
|
|
|
Assert(this, "Member function called on NULL KeyValues");
|
|
|
|
return this ? m_pPeer : nullptr;
|
2022-04-09 16:16:40 +02:00
|
|
|
}
|
2021-12-25 22:36:38 +01:00
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
2022-04-09 16:16:40 +02:00
|
|
|
// Purpose: Get the name of the current key section
|
|
|
|
// Output : const char*
|
2021-12-25 22:36:38 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
2022-04-09 16:16:40 +02:00
|
|
|
const char* KeyValues::GetName(void) const
|
|
|
|
{
|
2022-05-28 16:31:38 +02:00
|
|
|
return KeyValuesSystem()->GetStringForSymbol(MAKE_3_BYTES_FROM_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2));
|
2022-04-09 16:16:40 +02:00
|
|
|
}
|
|
|
|
|
2024-04-05 17:42:05 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the symbol name of the current key section
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int KeyValues::GetNameSymbol() const
|
|
|
|
{
|
|
|
|
AssertMsg(this, "Member function called on NULL KeyValues");
|
|
|
|
return this ? m_iKeyName : INVALID_KEY_SYMBOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the symbol name of the current key section case sensitive
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int KeyValues::GetNameSymbolCaseSensitive() const
|
|
|
|
{
|
|
|
|
AssertMsg(this, "Member function called on NULL KeyValues");
|
|
|
|
return this ? MAKE_3_BYTES_FROM_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2) : INVALID_KEY_SYMBOL;
|
|
|
|
}
|
|
|
|
|
2022-04-09 16:16:40 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the integer value of a keyName. Default value is returned
|
|
|
|
// if the keyName can't be found.
|
2022-05-28 16:31:38 +02:00
|
|
|
// Input : *pszKeyName -
|
2022-04-09 16:16:40 +02:00
|
|
|
// nDefaultValue -
|
|
|
|
// Output : int
|
|
|
|
//-----------------------------------------------------------------------------
|
2022-05-28 16:31:38 +02:00
|
|
|
int KeyValues::GetInt(const char* pszKeyName, int iDefaultValue)
|
2022-04-09 16:16:40 +02:00
|
|
|
{
|
2022-05-28 16:31:38 +02:00
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
switch (pKey->m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_STRING:
|
|
|
|
return atoi(pKey->m_sValue);
|
|
|
|
case TYPE_WSTRING:
|
|
|
|
return _wtoi(pKey->m_wsValue);
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
return static_cast<int>(pKey->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 pKey->m_iValue;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return iDefaultValue;
|
|
|
|
}
|
2022-04-09 16:16:40 +02:00
|
|
|
|
2022-05-28 16:31:38 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the integer value of a keyName. Default value is returned
|
|
|
|
// if the keyName can't be found.
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// nDefaultValue -
|
|
|
|
// Output : uint64_t
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
uint64_t KeyValues::GetUint64(const char* pszKeyName, uint64_t nDefaultValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
switch (pKey->m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_STRING:
|
|
|
|
{
|
|
|
|
uint64_t uiResult = 0ull;
|
|
|
|
sscanf(pKey->m_sValue, "%lld", &uiResult);
|
|
|
|
return uiResult;
|
|
|
|
}
|
|
|
|
case TYPE_WSTRING:
|
|
|
|
{
|
|
|
|
uint64_t uiResult = 0ull;
|
|
|
|
swscanf(pKey->m_wsValue, L"%lld", &uiResult);
|
|
|
|
return uiResult;
|
|
|
|
}
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
return static_cast<int>(pKey->m_flValue);
|
|
|
|
case TYPE_UINT64:
|
|
|
|
return *reinterpret_cast<uint64_t*>(pKey->m_sValue);
|
|
|
|
case TYPE_PTR:
|
|
|
|
return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(pKey->m_pValue));
|
|
|
|
case TYPE_INT:
|
|
|
|
default:
|
|
|
|
return pKey->m_iValue;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return nDefaultValue;
|
|
|
|
}
|
2022-04-09 16:16:40 +02:00
|
|
|
|
2022-05-28 16:31:38 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the pointer value of a keyName. Default value is returned
|
|
|
|
// if the keyName can't be found.
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// pDefaultValue -
|
|
|
|
// Output : void*
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void* KeyValues::GetPtr(const char* pszKeyName, void* pDefaultValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (pKey)
|
2022-04-09 16:16:40 +02:00
|
|
|
{
|
2022-05-28 16:31:38 +02:00
|
|
|
switch (pKey->m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_PTR:
|
|
|
|
return pKey->m_pValue;
|
|
|
|
|
|
|
|
case TYPE_WSTRING:
|
|
|
|
case TYPE_STRING:
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
case TYPE_INT:
|
|
|
|
case TYPE_UINT64:
|
|
|
|
default:
|
|
|
|
return nullptr;
|
|
|
|
};
|
2022-04-09 16:16:40 +02:00
|
|
|
}
|
2022-05-28 16:31:38 +02:00
|
|
|
return pDefaultValue;
|
|
|
|
}
|
2022-04-09 16:16:40 +02:00
|
|
|
|
2022-05-28 16:31:38 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the float value of a keyName. Default value is returned
|
|
|
|
// if the keyName can't be found.
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// flDefaultValue -
|
|
|
|
// Output : float
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
float KeyValues::GetFloat(const char* pszKeyName, float flDefaultValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
switch (pKey->m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_STRING:
|
|
|
|
return static_cast<float>(atof(pKey->m_sValue));
|
|
|
|
case TYPE_WSTRING:
|
|
|
|
return static_cast<float>(_wtof(pKey->m_wsValue)); // no wtof
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
return pKey->m_flValue;
|
|
|
|
case TYPE_INT:
|
|
|
|
return static_cast<float>(pKey->m_iValue);
|
|
|
|
case TYPE_UINT64:
|
|
|
|
return static_cast<float>((*(reinterpret_cast<uint64*>(pKey->m_sValue))));
|
|
|
|
case TYPE_PTR:
|
|
|
|
default:
|
|
|
|
return 0.0f;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return flDefaultValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the string pointer of a keyName. Default value is returned
|
|
|
|
// if the keyName can't be found.
|
|
|
|
// // Input : *pszKeyName -
|
|
|
|
// pszDefaultValue -
|
|
|
|
// Output : const char*
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const char* KeyValues::GetString(const char* pszKeyName, const char* pszDefaultValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
// convert the data to string form then return it
|
|
|
|
char buf[64];
|
|
|
|
switch (pKey->m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
snprintf(buf, sizeof(buf), "%f", pKey->m_flValue);
|
|
|
|
SetString(pszKeyName, buf);
|
|
|
|
break;
|
|
|
|
case TYPE_PTR:
|
|
|
|
snprintf(buf, sizeof(buf), "%lld", CastPtrToInt64(pKey->m_pValue));
|
|
|
|
SetString(pszKeyName, buf);
|
|
|
|
break;
|
|
|
|
case TYPE_INT:
|
|
|
|
snprintf(buf, sizeof(buf), "%d", pKey->m_iValue);
|
|
|
|
SetString(pszKeyName, buf);
|
|
|
|
break;
|
|
|
|
case TYPE_UINT64:
|
|
|
|
snprintf(buf, sizeof(buf), "%lld", *(reinterpret_cast<uint64*>(pKey->m_sValue)));
|
|
|
|
SetString(pszKeyName, buf);
|
|
|
|
break;
|
|
|
|
case TYPE_COLOR:
|
|
|
|
snprintf(buf, sizeof(buf), "%d %d %d %d", pKey->m_Color[0], pKey->m_Color[1], pKey->m_Color[2], pKey->m_Color[3]);
|
|
|
|
SetString(pszKeyName, buf);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TYPE_WSTRING:
|
|
|
|
{
|
|
|
|
// convert the string to char *, set it for future use, and return it
|
|
|
|
char wideBuf[512];
|
|
|
|
int result = V_UnicodeToUTF8(pKey->m_wsValue, wideBuf, 512);
|
|
|
|
if (result)
|
|
|
|
{
|
|
|
|
// note: this will copy wideBuf
|
|
|
|
SetString(pszKeyName, wideBuf);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return pszDefaultValue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TYPE_STRING:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return pszDefaultValue;
|
|
|
|
};
|
|
|
|
|
|
|
|
return pKey->m_sValue;
|
|
|
|
}
|
|
|
|
return pszDefaultValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the wide string pointer of a keyName. Default value is returned
|
|
|
|
// if the keyName can't be found.
|
|
|
|
// // Input : *pszKeyName -
|
|
|
|
// pwszDefaultValue -
|
|
|
|
// Output : const wchar_t*
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const wchar_t* KeyValues::GetWString(const char* pszKeyName, const wchar_t* pwszDefaultValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
wchar_t wbuf[64];
|
|
|
|
switch (pKey->m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
swprintf(wbuf, Q_ARRAYSIZE(wbuf), L"%f", pKey->m_flValue);
|
|
|
|
SetWString(pszKeyName, wbuf);
|
|
|
|
break;
|
|
|
|
case TYPE_PTR:
|
|
|
|
swprintf(wbuf, Q_ARRAYSIZE(wbuf), L"%lld", static_cast<int64_t>(reinterpret_cast<size_t>(pKey->m_pValue)));
|
|
|
|
SetWString(pszKeyName, wbuf);
|
|
|
|
break;
|
|
|
|
case TYPE_INT:
|
|
|
|
swprintf(wbuf, Q_ARRAYSIZE(wbuf), L"%d", pKey->m_iValue);
|
|
|
|
SetWString(pszKeyName, wbuf);
|
|
|
|
break;
|
|
|
|
case TYPE_UINT64:
|
|
|
|
{
|
|
|
|
swprintf(wbuf, Q_ARRAYSIZE(wbuf), L"%lld", *(reinterpret_cast<uint64_t*>(pKey->m_sValue)));
|
|
|
|
SetWString(pszKeyName, wbuf);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TYPE_COLOR:
|
|
|
|
swprintf(wbuf, Q_ARRAYSIZE(wbuf), L"%d %d %d %d", pKey->m_Color[0], pKey->m_Color[1], pKey->m_Color[2], pKey->m_Color[3]);
|
|
|
|
SetWString(pszKeyName, wbuf);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TYPE_WSTRING:
|
|
|
|
break;
|
|
|
|
case TYPE_STRING:
|
|
|
|
{
|
|
|
|
size_t bufSize = strlen(pKey->m_sValue) + 1;
|
2023-06-26 22:34:24 +02:00
|
|
|
wchar_t* pWBuf = new wchar_t[bufSize];
|
|
|
|
int result = V_UTF8ToUnicode(pKey->m_sValue, pWBuf, int(bufSize * sizeof(wchar_t)));
|
2022-05-28 16:31:38 +02:00
|
|
|
if (result >= 0) // may be a zero length string
|
|
|
|
{
|
|
|
|
SetWString(pszKeyName, pWBuf);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] pWBuf;
|
2022-05-28 16:31:38 +02:00
|
|
|
return pwszDefaultValue;
|
|
|
|
}
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] pWBuf;
|
2022-05-28 16:31:38 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return pwszDefaultValue;
|
|
|
|
};
|
|
|
|
|
|
|
|
return reinterpret_cast<const wchar_t*>(pKey->m_wsValue);
|
|
|
|
}
|
|
|
|
return pwszDefaultValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Gets a color
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// &defaultColor -
|
|
|
|
// Output : Color
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
Color KeyValues::GetColor(const char* pszKeyName, const Color& defaultColor)
|
|
|
|
{
|
|
|
|
Color color = defaultColor;
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
if (pKey->m_iDataType == TYPE_COLOR)
|
|
|
|
{
|
|
|
|
color[0] = pKey->m_Color[0];
|
|
|
|
color[1] = pKey->m_Color[1];
|
|
|
|
color[2] = pKey->m_Color[2];
|
|
|
|
color[3] = pKey->m_Color[3];
|
|
|
|
}
|
|
|
|
else if (pKey->m_iDataType == TYPE_FLOAT)
|
|
|
|
{
|
|
|
|
color[0] = static_cast<unsigned char>(pKey->m_flValue);
|
|
|
|
}
|
|
|
|
else if (pKey->m_iDataType == TYPE_INT)
|
|
|
|
{
|
|
|
|
color[0] = static_cast<unsigned char>(pKey->m_iValue);
|
|
|
|
}
|
|
|
|
else if (pKey->m_iDataType == TYPE_STRING)
|
|
|
|
{
|
|
|
|
// parse the colors out of the string
|
|
|
|
float a = 0, b = 0, c = 0, d = 0;
|
|
|
|
sscanf(pKey->m_sValue, "%f %f %f %f", &a, &b, &c, &d);
|
|
|
|
color[0] = static_cast<unsigned char>(a);
|
|
|
|
color[1] = static_cast<unsigned char>(b);
|
|
|
|
color[2] = static_cast<unsigned char>(c);
|
|
|
|
color[3] = static_cast<unsigned char>(d);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the data type of the value stored in a keyName
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValuesTypes_t KeyValues::GetDataType(const char* pszKeyName)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, false);
|
|
|
|
if (pKey)
|
|
|
|
return static_cast<KeyValuesTypes_t>(pKey->m_iDataType);
|
|
|
|
|
|
|
|
return TYPE_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Get the data type of the value stored in this keyName
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValuesTypes_t KeyValues::GetDataType(void) const
|
|
|
|
{
|
|
|
|
return static_cast<KeyValuesTypes_t>(m_iDataType);
|
2022-04-09 16:16:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Set the integer value of a keyName.
|
2022-05-28 16:31:38 +02:00
|
|
|
// Input : *pszKeyName -
|
2022-04-09 16:16:40 +02:00
|
|
|
// iValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
2022-05-28 16:31:38 +02:00
|
|
|
void KeyValues::SetInt(const char* pszKeyName, int iValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, true);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
pKey->m_iValue = iValue;
|
|
|
|
pKey->m_iDataType = TYPE_INT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Set the integer value of a keyName.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SetUint64(const char* pszKeyName, uint64_t nValue)
|
2022-04-09 16:16:40 +02:00
|
|
|
{
|
2022-05-28 16:31:38 +02:00
|
|
|
KeyValues* pKey = FindKey(pszKeyName, true);
|
|
|
|
|
|
|
|
if (pKey)
|
2022-04-09 16:16:40 +02:00
|
|
|
{
|
2022-05-28 16:31:38 +02:00
|
|
|
// delete the old value
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] pKey->m_sValue;
|
2022-05-28 16:31:38 +02:00
|
|
|
// make sure we're not storing the WSTRING - as we're converting over to STRING
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] pKey->m_wsValue;
|
2022-05-28 16:31:38 +02:00
|
|
|
pKey->m_wsValue = nullptr;
|
|
|
|
|
2023-06-26 22:34:24 +02:00
|
|
|
pKey->m_sValue = new char[sizeof(uint64)];
|
2022-05-28 16:31:38 +02:00
|
|
|
*(reinterpret_cast<uint64_t*>(pKey->m_sValue)) = nValue;
|
|
|
|
pKey->m_iDataType = TYPE_UINT64;
|
2022-04-09 16:16:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Set the float value of a keyName.
|
2022-05-28 16:31:38 +02:00
|
|
|
// Input : *pszKeyName -
|
2022-04-09 16:16:40 +02:00
|
|
|
// flValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
2022-05-28 16:31:38 +02:00
|
|
|
void KeyValues::SetFloat(const char* pszKeyName, float flValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, true);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
pKey->m_flValue = flValue;
|
|
|
|
pKey->m_iDataType = TYPE_FLOAT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Set the name value of a keyName.
|
|
|
|
// Input : *pszSetName -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SetName(const char* pszSetName)
|
|
|
|
{
|
|
|
|
HKeySymbol hCaseSensitiveKeyName = INVALID_KEY_SYMBOL, hCaseInsensitiveKeyName = INVALID_KEY_SYMBOL;
|
|
|
|
hCaseSensitiveKeyName = KeyValuesSystem()->GetSymbolForStringCaseSensitive(hCaseInsensitiveKeyName, pszSetName);
|
|
|
|
|
|
|
|
m_iKeyName = hCaseInsensitiveKeyName;
|
|
|
|
SPLIT_3_BYTES_INTO_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2, hCaseSensitiveKeyName);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Set the pointer value of a keyName.
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// *pValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SetPtr(const char* pszKeyName, void* pValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, true);
|
|
|
|
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
pKey->m_pValue = pValue;
|
|
|
|
pKey->m_iDataType = TYPE_PTR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Set the string value (internal)
|
|
|
|
// Input : *pszValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SetStringValue(char const* pszValue)
|
|
|
|
{
|
|
|
|
// delete the old value
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] m_sValue;
|
2022-05-28 16:31:38 +02:00
|
|
|
// make sure we're not storing the WSTRING - as we're converting over to STRING
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] m_wsValue;
|
2022-05-28 16:31:38 +02:00
|
|
|
m_wsValue = nullptr;
|
|
|
|
|
|
|
|
if (!pszValue)
|
|
|
|
{
|
|
|
|
// ensure a valid value
|
|
|
|
pszValue = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
// allocate memory for the new value and copy it in
|
|
|
|
size_t len = strlen(pszValue);
|
2023-06-26 22:34:24 +02:00
|
|
|
m_sValue = new char[len + 1];
|
2022-05-28 16:31:38 +02:00
|
|
|
memcpy(m_sValue, pszValue, len + 1);
|
|
|
|
|
|
|
|
m_iDataType = TYPE_STRING;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Sets this key's peer to the KeyValues passed in
|
|
|
|
// Input : *pDat -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SetNextKey(KeyValues* pDat)
|
|
|
|
{
|
|
|
|
m_pPeer = pDat;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Set the string value of a keyName.
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// *pszValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SetString(const char* pszKeyName, const char* pszValue)
|
|
|
|
{
|
|
|
|
if (KeyValues* pKey = FindKey(pszKeyName, true))
|
|
|
|
{
|
|
|
|
pKey->SetStringValue(pszValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Set the string value of a keyName.
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// *pwszValue -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SetWString(const char* pszKeyName, const wchar_t* pwszValue)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, true);
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
// delete the old value
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] pKey->m_wsValue;
|
2022-05-28 16:31:38 +02:00
|
|
|
// make sure we're not storing the STRING - as we're converting over to WSTRING
|
2023-06-26 22:34:24 +02:00
|
|
|
delete[] pKey->m_sValue;
|
2022-05-28 16:31:38 +02:00
|
|
|
pKey->m_sValue = nullptr;
|
|
|
|
|
|
|
|
if (!pwszValue)
|
|
|
|
{
|
|
|
|
// ensure a valid value
|
|
|
|
pwszValue = L"";
|
|
|
|
}
|
|
|
|
|
|
|
|
// allocate memory for the new value and copy it in
|
|
|
|
size_t len = wcslen(pwszValue);
|
2023-06-26 22:34:24 +02:00
|
|
|
pKey->m_wsValue = new wchar_t[len + 1];
|
2022-05-28 16:31:38 +02:00
|
|
|
memcpy(pKey->m_wsValue, pwszValue, (len + 1) * sizeof(wchar_t));
|
|
|
|
|
|
|
|
pKey->m_iDataType = TYPE_WSTRING;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Sets a color
|
|
|
|
// Input : *pszKeyName -
|
|
|
|
// color -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::SetColor(const char* pszKeyName, Color color)
|
|
|
|
{
|
|
|
|
KeyValues* pKey = FindKey(pszKeyName, true);
|
|
|
|
|
|
|
|
if (pKey)
|
|
|
|
{
|
|
|
|
pKey->m_iDataType = TYPE_COLOR;
|
|
|
|
pKey->m_Color[0] = color[0];
|
|
|
|
pKey->m_Color[1] = color[1];
|
|
|
|
pKey->m_Color[2] = color[2];
|
|
|
|
pKey->m_Color[3] = color[3];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-23 10:34:11 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: if parser should translate escape sequences ( /n, /t etc), set to true
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::UsesEscapeSequences(bool bState)
|
|
|
|
{
|
|
|
|
m_bHasEscapeSequences = bState;
|
|
|
|
}
|
|
|
|
|
2022-05-28 16:31:38 +02:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : &src -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::RecursiveCopyKeyValues(KeyValues& src)
|
|
|
|
{
|
|
|
|
// garymcthack - need to check this code for possible buffer overruns.
|
|
|
|
|
|
|
|
m_iKeyName = src.m_iKeyName;
|
|
|
|
m_iKeyNameCaseSensitive1 = src.m_iKeyNameCaseSensitive1;
|
|
|
|
m_iKeyNameCaseSensitive2 = src.m_iKeyNameCaseSensitive2;
|
|
|
|
|
|
|
|
if (!src.m_pSub)
|
|
|
|
{
|
|
|
|
m_iDataType = src.m_iDataType;
|
|
|
|
char buf[256];
|
|
|
|
switch (src.m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_NONE:
|
|
|
|
break;
|
|
|
|
case TYPE_STRING:
|
|
|
|
if (src.m_sValue)
|
|
|
|
{
|
|
|
|
size_t len = strlen(src.m_sValue) + 1;
|
2023-06-26 22:34:24 +02:00
|
|
|
m_sValue = new char[len];
|
2022-05-28 16:31:38 +02:00
|
|
|
strncpy(m_sValue, src.m_sValue, len);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TYPE_INT:
|
|
|
|
{
|
|
|
|
m_iValue = src.m_iValue;
|
|
|
|
snprintf(buf, sizeof(buf), "%d", m_iValue);
|
|
|
|
size_t len = strlen(buf) + 1;
|
2023-06-26 22:34:24 +02:00
|
|
|
m_sValue = new char[len];
|
2022-05-28 16:31:38 +02:00
|
|
|
strncpy(m_sValue, buf, len);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
{
|
|
|
|
m_flValue = src.m_flValue;
|
|
|
|
snprintf(buf, sizeof(buf), "%f", m_flValue);
|
|
|
|
size_t len = strlen(buf) + 1;
|
2023-06-26 22:34:24 +02:00
|
|
|
m_sValue = new char[len];
|
2022-05-28 16:31:38 +02:00
|
|
|
strncpy(m_sValue, buf, len);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TYPE_PTR:
|
|
|
|
{
|
|
|
|
m_pValue = src.m_pValue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TYPE_UINT64:
|
|
|
|
{
|
2023-06-26 22:34:24 +02:00
|
|
|
m_sValue = new char[sizeof(uint64)];
|
2022-05-28 16:31:38 +02:00
|
|
|
memcpy(m_sValue, src.m_sValue, sizeof(uint64_t));
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle the immediate child
|
|
|
|
if (src.m_pSub)
|
|
|
|
{
|
2023-06-26 22:34:24 +02:00
|
|
|
m_pSub = new KeyValues(NULL);
|
2022-05-28 16:31:38 +02:00
|
|
|
m_pSub->RecursiveCopyKeyValues(*src.m_pSub);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle the immediate peer
|
|
|
|
if (src.m_pPeer)
|
|
|
|
{
|
2023-06-26 22:34:24 +02:00
|
|
|
m_pPeer = new KeyValues(NULL);
|
2022-05-28 16:31:38 +02:00
|
|
|
m_pPeer->RecursiveCopyKeyValues(*src.m_pPeer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
2022-11-22 09:04:28 +01:00
|
|
|
// Purpose:
|
|
|
|
// Input : &buf -
|
|
|
|
// nIndentLevel -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::RecursiveSaveToFile(CUtlBuffer& buf, int nIndentLevel)
|
|
|
|
{
|
|
|
|
RecursiveSaveToFile(NULL, FILESYSTEM_INVALID_HANDLE, &buf, nIndentLevel);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
2024-04-05 17:42:05 +02:00
|
|
|
// Purpose: Write out keyvalue data
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::InternalWrite(IBaseFileSystem* filesystem, FileHandle_t f, CUtlBuffer* pBuf, const void* pData, ssize_t len)
|
|
|
|
{
|
|
|
|
if (filesystem)
|
|
|
|
{
|
|
|
|
filesystem->Write(pData, len, f);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pBuf)
|
|
|
|
{
|
|
|
|
pBuf->Put(pData, len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Write out a set of indenting
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::WriteIndents(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, int nIndentLevel)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < nIndentLevel; i++)
|
|
|
|
{
|
|
|
|
INTERNALWRITE("\t", 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Write out a string where we convert the double quotes to backslash double quote
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::WriteConvertedString(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, const char* pszString)
|
|
|
|
{
|
|
|
|
// handle double quote chars within the string
|
|
|
|
// the worst possible case is that the whole string is quotes
|
|
|
|
size_t len = V_strlen(pszString);
|
|
|
|
char* convertedString = (char*)alloca((len + 1) * sizeof(char) * 2);
|
|
|
|
size_t j = 0;
|
|
|
|
for (size_t i = 0; i <= len; i++)
|
|
|
|
{
|
|
|
|
if (pszString[i] == '\"')
|
|
|
|
{
|
|
|
|
convertedString[j] = '\\';
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
else if (m_bHasEscapeSequences && pszString[i] == '\\')
|
|
|
|
{
|
|
|
|
convertedString[j] = '\\';
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
convertedString[j] = pszString[i];
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
|
|
|
|
INTERNALWRITE(convertedString, V_strlen(convertedString));
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Save keyvalues to disk, if subkey values are detected, calls
|
2022-11-22 09:04:28 +01:00
|
|
|
// itself to save those
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::RecursiveSaveToFile(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, int nIndentLevel)
|
|
|
|
{
|
2024-04-05 17:42:05 +02:00
|
|
|
// write header
|
|
|
|
WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel);
|
|
|
|
INTERNALWRITE("\"", 1);
|
|
|
|
WriteConvertedString(pFileSystem, pHandle, pBuf, GetName());
|
|
|
|
INTERNALWRITE("\"\n", 2);
|
|
|
|
WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel);
|
|
|
|
INTERNALWRITE("{\n", 2);
|
|
|
|
|
|
|
|
// loop through all our keys writing them to disk
|
|
|
|
for (KeyValues* dat = m_pSub; dat != NULL; dat = dat->m_pPeer)
|
|
|
|
{
|
|
|
|
if (dat->m_pSub)
|
|
|
|
{
|
|
|
|
dat->RecursiveSaveToFile(pFileSystem, pHandle, pBuf, nIndentLevel + 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// only write non-empty keys
|
|
|
|
switch (dat->m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_STRING:
|
|
|
|
{
|
|
|
|
if (dat->m_sValue && *(dat->m_sValue))
|
|
|
|
{
|
|
|
|
WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1);
|
|
|
|
INTERNALWRITE("\"", 1);
|
|
|
|
WriteConvertedString(pFileSystem, pHandle, pBuf, dat->GetName());
|
|
|
|
INTERNALWRITE("\"\t\t\"", 4);
|
|
|
|
|
|
|
|
WriteConvertedString(pFileSystem, pHandle, pBuf, dat->m_sValue);
|
|
|
|
|
|
|
|
INTERNALWRITE("\"\n", 2);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TYPE_WSTRING:
|
|
|
|
{
|
|
|
|
if (dat->m_wsValue)
|
|
|
|
{
|
|
|
|
static char buf[KEYVALUES_TOKEN_SIZE];
|
|
|
|
// make sure we have enough space
|
|
|
|
int result = V_UnicodeToUTF8(dat->m_wsValue, buf, KEYVALUES_TOKEN_SIZE);
|
|
|
|
if (result)
|
|
|
|
{
|
|
|
|
WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1);
|
|
|
|
INTERNALWRITE("\"", 1);
|
|
|
|
INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName()));
|
|
|
|
INTERNALWRITE("\"\t\t\"", 4);
|
|
|
|
|
|
|
|
WriteConvertedString(pFileSystem, pHandle, pBuf, buf);
|
|
|
|
|
|
|
|
INTERNALWRITE("\"\n", 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case TYPE_INT:
|
|
|
|
{
|
|
|
|
WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1);
|
|
|
|
INTERNALWRITE("\"", 1);
|
|
|
|
INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName()));
|
|
|
|
INTERNALWRITE("\"\t\t\"", 4);
|
|
|
|
|
|
|
|
char buf[32];
|
|
|
|
V_snprintf(buf, sizeof(buf), "%d", dat->m_iValue);
|
|
|
|
|
|
|
|
INTERNALWRITE(buf, V_strlen(buf));
|
|
|
|
INTERNALWRITE("\"\n", 2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case TYPE_UINT64:
|
|
|
|
{
|
|
|
|
WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1);
|
|
|
|
INTERNALWRITE("\"", 1);
|
|
|
|
INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName()));
|
|
|
|
INTERNALWRITE("\"\t\t\"", 4);
|
|
|
|
|
|
|
|
char buf[32];
|
|
|
|
// write "0x" + 16 char 0-padded hex encoded 64 bit value
|
|
|
|
V_snprintf(buf, sizeof(buf), "0x%016llX", *((uint64*)dat->m_sValue));
|
|
|
|
|
|
|
|
INTERNALWRITE(buf, V_strlen(buf));
|
|
|
|
INTERNALWRITE("\"\n", 2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
{
|
|
|
|
WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1);
|
|
|
|
INTERNALWRITE("\"", 1);
|
|
|
|
INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName()));
|
|
|
|
INTERNALWRITE("\"\t\t\"", 4);
|
|
|
|
|
|
|
|
char buf[48];
|
|
|
|
V_snprintf(buf, sizeof(buf), "%f", dat->m_flValue);
|
|
|
|
|
|
|
|
INTERNALWRITE(buf, V_strlen(buf));
|
|
|
|
INTERNALWRITE("\"\n", 2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TYPE_COLOR:
|
|
|
|
DevMsg(eDLL_T::COMMON, "%s: TODO, missing code for TYPE_COLOR.\n", __FUNCTION__);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// write tail
|
|
|
|
WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel);
|
|
|
|
INTERNALWRITE("}\n", 2);
|
2022-11-22 09:04:28 +01:00
|
|
|
}
|
|
|
|
|
2022-11-23 10:34:11 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
2024-04-05 17:42:05 +02:00
|
|
|
// Purpose:
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::RecursiveLoadFromBuffer(char const* resourceName, CKeyValuesTokenReader& tokenReader, GetSymbolProc_t pfnEvaluateSymbolProc)
|
|
|
|
{
|
|
|
|
CKeyErrorContext errorReport(GetNameSymbolCaseSensitive());
|
|
|
|
bool wasQuoted;
|
|
|
|
bool wasConditional;
|
|
|
|
if (errorReport.GetStackLevel() > 100)
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: recursion overflow");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// keep this out of the stack until a key is parsed
|
|
|
|
CKeyErrorContext errorKey(INVALID_KEY_SYMBOL);
|
|
|
|
|
|
|
|
// Locate the last child. (Almost always, we will not have any children.)
|
|
|
|
// We maintain the pointer to the last child here, so we don't have to re-locate
|
|
|
|
// it each time we append the next subkey, which causes O(N^2) time
|
|
|
|
KeyValues* pLastChild = FindLastSubKey();
|
|
|
|
|
|
|
|
// Keep parsing until we hit the closing brace which terminates this block, or a parse error
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
bool bAccepted = true;
|
|
|
|
|
|
|
|
// get the key name
|
|
|
|
const char* name = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
|
|
|
|
if (!name) // EOF stop reading
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got EOF instead of keyname");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!*name) // empty token, maybe "" or EOF
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got empty keyname");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*name == '}' && !wasQuoted) // top level closed, stop reading
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Always create the key; note that this could potentially
|
|
|
|
// cause some duplication, but that's what we want sometimes
|
|
|
|
KeyValues* dat = CreateKeyUsingKnownLastChild(name, pLastChild);
|
|
|
|
|
|
|
|
errorKey.Reset(dat->GetNameSymbolCaseSensitive());
|
|
|
|
|
|
|
|
// get the value
|
|
|
|
const char* value = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
|
|
|
|
bool bFoundConditional = wasConditional;
|
|
|
|
if (wasConditional && value)
|
|
|
|
{
|
|
|
|
bAccepted = EvaluateConditional(value, pfnEvaluateSymbolProc);
|
|
|
|
|
|
|
|
// get the real value
|
|
|
|
value = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!value)
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got NULL key");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// support the '=' as an assignment, makes multiple-keys-on-one-line easier to read in a keyvalues file
|
|
|
|
if (*value == '=' && !wasQuoted)
|
|
|
|
{
|
|
|
|
// just skip over it
|
|
|
|
value = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
bFoundConditional = wasConditional;
|
|
|
|
if (wasConditional && value)
|
|
|
|
{
|
|
|
|
bAccepted = EvaluateConditional(value, pfnEvaluateSymbolProc);
|
|
|
|
|
|
|
|
// get the real value
|
|
|
|
value = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bFoundConditional && bAccepted)
|
|
|
|
{
|
|
|
|
// if there is a conditional key see if we already have the key defined and blow it away, last one in the list wins
|
|
|
|
KeyValues* pExistingKey = this->FindKey(dat->GetNameSymbol());
|
|
|
|
if (pExistingKey && pExistingKey != dat)
|
|
|
|
{
|
|
|
|
this->RemoveSubKey(pExistingKey);
|
|
|
|
pExistingKey->DeleteThis();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!value)
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got NULL key");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*value == '}' && !wasQuoted)
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got } in key");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*value == '{' && !wasQuoted)
|
|
|
|
{
|
|
|
|
// this isn't a key, it's a section
|
|
|
|
errorKey.Reset(INVALID_KEY_SYMBOL);
|
|
|
|
// sub value list
|
|
|
|
dat->RecursiveLoadFromBuffer(resourceName, tokenReader, pfnEvaluateSymbolProc);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (wasConditional)
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got conditional between key and value");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dat->m_sValue)
|
|
|
|
{
|
|
|
|
delete[] dat->m_sValue;
|
|
|
|
dat->m_sValue = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t len = V_strlen(value);
|
|
|
|
|
|
|
|
// Here, let's determine if we got a float or an int....
|
|
|
|
char* pIEnd; // pos where int scan ended
|
|
|
|
char* pFEnd; // pos where float scan ended
|
|
|
|
const char* pSEnd = value + len; // pos where token ends
|
|
|
|
|
|
|
|
const long lval = strtol(value, &pIEnd, 10);
|
|
|
|
const float fval = (float)strtod(value, &pFEnd);
|
|
|
|
const bool bOverflow = (lval == LONG_MAX || lval == LONG_MIN) && errno == ERANGE;
|
|
|
|
#ifdef POSIX
|
|
|
|
// strtod supports hex representation in strings under posix but we DON'T
|
|
|
|
// want that support in keyvalues, so undo it here if needed
|
|
|
|
if (len > 1 && tolower(value[1]) == 'x')
|
|
|
|
{
|
|
|
|
fval = 0.0f;
|
|
|
|
pFEnd = (char*)value;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (*value == 0)
|
|
|
|
{
|
|
|
|
dat->m_iDataType = TYPE_STRING;
|
|
|
|
}
|
|
|
|
else if ((18 == len) && (value[0] == '0') && (value[1] == 'x'))
|
|
|
|
{
|
|
|
|
// an 18-byte value prefixed with "0x" (followed by 16 hex digits) is an int64 value
|
|
|
|
int64 retVal = 0;
|
|
|
|
for (int i = 2; i < 2 + 16; i++)
|
|
|
|
{
|
|
|
|
char digit = value[i];
|
|
|
|
if (digit >= 'a')
|
|
|
|
digit -= 'a' - ('9' + 1);
|
|
|
|
else
|
|
|
|
if (digit >= 'A')
|
|
|
|
digit -= 'A' - ('9' + 1);
|
|
|
|
retVal = (retVal * 16) + (digit - '0');
|
|
|
|
}
|
|
|
|
dat->m_sValue = new char[sizeof(uint64)];
|
|
|
|
*((uint64*)dat->m_sValue) = retVal;
|
|
|
|
dat->m_iDataType = TYPE_UINT64;
|
|
|
|
}
|
|
|
|
else if ((pFEnd > pIEnd) && (pFEnd == pSEnd))
|
|
|
|
{
|
|
|
|
dat->m_flValue = fval;
|
|
|
|
dat->m_iDataType = TYPE_FLOAT;
|
|
|
|
}
|
|
|
|
else if (pIEnd == pSEnd && !bOverflow)
|
|
|
|
{
|
|
|
|
dat->m_iValue = static_cast<int>(lval);
|
|
|
|
dat->m_iDataType = TYPE_INT;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dat->m_iDataType = TYPE_STRING;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dat->m_iDataType == TYPE_STRING)
|
|
|
|
{
|
|
|
|
// copy in the string information
|
|
|
|
dat->m_sValue = new char[len + 1];
|
|
|
|
memcpy(dat->m_sValue, value, len + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look ahead one token for a conditional tag
|
|
|
|
const char* peek = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
if (wasConditional)
|
|
|
|
{
|
|
|
|
bAccepted = EvaluateConditional(peek, pfnEvaluateSymbolProc);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tokenReader.SeekBackOneToken();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Assert(dat->m_pPeer == NULL);
|
|
|
|
if (bAccepted)
|
|
|
|
{
|
|
|
|
Assert(pLastChild == NULL || pLastChild->m_pPeer == dat);
|
|
|
|
pLastChild = dat;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//this->RemoveSubKey( dat );
|
|
|
|
if (pLastChild == NULL)
|
|
|
|
{
|
|
|
|
Assert(this->m_pSub == dat);
|
|
|
|
this->m_pSub = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Assert(pLastChild->m_pPeer == dat);
|
|
|
|
pLastChild->m_pPeer = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete dat;
|
|
|
|
dat = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// prevent two threads from entering this at the same time and trying to share the global error reporting and parse buffers
|
|
|
|
static CThreadFastMutex g_KVMutex;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Read from a buffer...
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KeyValues::LoadFromBuffer(char const* resourceName, CUtlBuffer& buf, IBaseFileSystem* pFileSystem, const char* pPathID, GetSymbolProc_t pfnEvaluateSymbolProc)
|
|
|
|
{
|
|
|
|
AUTO_LOCK(g_KVMutex);
|
|
|
|
|
|
|
|
//if (IsGameConsole())
|
|
|
|
//{
|
|
|
|
// // Let's not crash if the buffer is empty
|
|
|
|
// unsigned char* pData = buf.Size() > 0 ? (unsigned char*)buf.PeekGet() : NULL;
|
|
|
|
// if (pData && (unsigned int)pData[0] == KV_BINARY_POOLED_FORMAT)
|
|
|
|
// {
|
|
|
|
// // skip past binary marker
|
|
|
|
// buf.GetUnsignedChar();
|
|
|
|
// // get the pool identifier, allows the fs to bind the expected string pool
|
|
|
|
// unsigned int poolKey = buf.GetUnsignedInt();
|
|
|
|
|
|
|
|
// RemoveEverything();
|
|
|
|
// Init();
|
|
|
|
|
|
|
|
// return ReadAsBinaryPooledFormat(buf, pFileSystem, poolKey, pfnEvaluateSymbolProc);
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
|
|
|
|
KeyValues* pPreviousKey = NULL;
|
|
|
|
KeyValues* pCurrentKey = this;
|
|
|
|
CUtlVector< KeyValues* > includedKeys;
|
|
|
|
CUtlVector< KeyValues* > baseKeys;
|
|
|
|
bool wasQuoted;
|
|
|
|
bool wasConditional;
|
|
|
|
CKeyValuesTokenReader tokenReader(this, buf);
|
|
|
|
|
|
|
|
g_KeyValuesErrorStack.SetFilename(resourceName);
|
|
|
|
do
|
|
|
|
{
|
|
|
|
bool bAccepted = true;
|
|
|
|
|
|
|
|
// the first thing must be a key
|
|
|
|
const char* s = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
if (!buf.IsValid() || !s)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!wasQuoted && *s == '\0')
|
|
|
|
{
|
|
|
|
// non quoted empty strings stop parsing
|
|
|
|
// quoted empty strings are allowed to support unnnamed KV sections
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!V_stricmp(s, "#include")) // special include macro (not a key name)
|
|
|
|
{
|
|
|
|
s = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
// Name of subfile to load is now in s
|
|
|
|
|
|
|
|
if (!s || *s == 0)
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("#include is NULL ");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ParseIncludedKeys(resourceName, s, pFileSystem, pPathID, includedKeys, pfnEvaluateSymbolProc);
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (!V_stricmp(s, "#base"))
|
|
|
|
{
|
|
|
|
s = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
// Name of subfile to load is now in s
|
|
|
|
|
|
|
|
if (!s || *s == 0)
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("#base is NULL ");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ParseIncludedKeys(resourceName, s, pFileSystem, pPathID, baseKeys, pfnEvaluateSymbolProc);
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pCurrentKey)
|
|
|
|
{
|
|
|
|
pCurrentKey = new KeyValues(s);
|
|
|
|
Assert(pCurrentKey);
|
|
|
|
|
|
|
|
pCurrentKey->UsesEscapeSequences(m_bHasEscapeSequences != 0); // same format has parent use
|
|
|
|
|
|
|
|
if (pPreviousKey)
|
|
|
|
{
|
|
|
|
pPreviousKey->SetNextKey(pCurrentKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pCurrentKey->SetName(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the '{'
|
|
|
|
s = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
|
|
|
|
if (wasConditional)
|
|
|
|
{
|
|
|
|
bAccepted = EvaluateConditional(s, pfnEvaluateSymbolProc);
|
|
|
|
|
|
|
|
// Now get the '{'
|
|
|
|
s = tokenReader.ReadToken(wasQuoted, wasConditional);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s && *s == '{' && !wasQuoted)
|
|
|
|
{
|
|
|
|
// header is valid so load the file
|
|
|
|
pCurrentKey->RecursiveLoadFromBuffer(resourceName, tokenReader, pfnEvaluateSymbolProc);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("LoadFromBuffer: missing {");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!bAccepted)
|
|
|
|
{
|
|
|
|
if (pPreviousKey)
|
|
|
|
{
|
|
|
|
pPreviousKey->SetNextKey(NULL);
|
|
|
|
}
|
|
|
|
pCurrentKey->Clear();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pPreviousKey = pCurrentKey;
|
|
|
|
pCurrentKey = NULL;
|
|
|
|
}
|
|
|
|
} while (buf.IsValid());
|
|
|
|
|
|
|
|
AppendIncludedKeys(includedKeys);
|
|
|
|
{
|
|
|
|
// delete included keys!
|
|
|
|
int i;
|
|
|
|
for (i = includedKeys.Count() - 1; i > 0; i--)
|
|
|
|
{
|
|
|
|
KeyValues* kv = includedKeys[i];
|
|
|
|
delete kv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MergeBaseKeys(baseKeys);
|
|
|
|
{
|
|
|
|
// delete base keys!
|
|
|
|
int i;
|
|
|
|
for (i = baseKeys.Count() - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
KeyValues* kv = baseKeys[i];
|
|
|
|
delete kv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool bErrors = g_KeyValuesErrorStack.EncounteredAnyErrors();
|
|
|
|
g_KeyValuesErrorStack.SetFilename("");
|
|
|
|
g_KeyValuesErrorStack.ClearErrorFlag();
|
|
|
|
return !bErrors;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Read from a buffer...
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KeyValues::LoadFromBuffer(char const* resourceName, const char* pBuffer, IBaseFileSystem* pFileSystem, const char* pPathID, GetSymbolProc_t pfnEvaluateSymbolProc)
|
|
|
|
{
|
|
|
|
if (!pBuffer)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (IsGameConsole() && (unsigned int)((unsigned char*)pBuffer)[0] == KV_BINARY_POOLED_FORMAT)
|
|
|
|
{
|
|
|
|
// bad, got a binary compiled KV file through an unexpected text path
|
|
|
|
// not all paths support binary compiled kv, needs to get fixed
|
|
|
|
// need to have caller supply buffer length (strlen not valid), this interface change was never plumbed
|
|
|
|
Warning(eDLL_T::COMMON, "ERROR! Binary compiled KV '%s' in an unexpected handler\n", resourceName);
|
|
|
|
Assert(0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t nLen = V_strlen(pBuffer);
|
|
|
|
CUtlBuffer buf(pBuffer, nLen, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER);
|
|
|
|
// Translate Unicode files into UTF-8 before proceeding
|
|
|
|
if (nLen > 2 && (uint8)pBuffer[0] == 0xFF && (uint8)pBuffer[1] == 0xFE)
|
|
|
|
{
|
|
|
|
int nUTF8Len = V_UnicodeToUTF8((wchar_t*)(pBuffer + 2), NULL, 0);
|
|
|
|
char* pUTF8Buf = new char[nUTF8Len];
|
|
|
|
V_UnicodeToUTF8((wchar_t*)(pBuffer + 2), pUTF8Buf, nUTF8Len);
|
|
|
|
buf.AssumeMemory(pUTF8Buf, nUTF8Len, nUTF8Len, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER);
|
|
|
|
}
|
|
|
|
return LoadFromBuffer(resourceName, buf, pFileSystem, pPathID, pfnEvaluateSymbolProc);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Load keyValues from disk
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KeyValues::LoadFromFile(IBaseFileSystem* filesystem, const char* resourceName, const char* pathID, GetSymbolProc_t pfnEvaluateSymbolProc)
|
|
|
|
{
|
|
|
|
//TM_ZONE_FILTERED( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s %s", __FUNCTION__, tmDynamicString( TELEMETRY_LEVEL0, resourceName ) );
|
|
|
|
|
2024-01-12 03:08:31 +01:00
|
|
|
FileHandle_t f = filesystem->Open(resourceName, "rb", pathID);
|
2024-04-05 17:42:05 +02:00
|
|
|
if (!f)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
s_LastFileLoadingFrom = (char*)resourceName;
|
|
|
|
|
|
|
|
// load file into a null-terminated buffer
|
|
|
|
const ssize_t fileSize = filesystem->Size(f);
|
2024-01-12 03:08:31 +01:00
|
|
|
|
|
|
|
if (!fileSize)
|
|
|
|
return false;
|
|
|
|
|
2024-04-05 17:42:05 +02:00
|
|
|
std::unique_ptr<char[]> pBuf(new char[fileSize + 1]);
|
|
|
|
|
|
|
|
const ssize_t nRead = filesystem->Read(pBuf.get(), fileSize, f);
|
|
|
|
filesystem->Close(f);
|
|
|
|
|
|
|
|
// TODO[ AMOS ]: unicode null terminate?
|
|
|
|
pBuf[nRead] = '\0';
|
|
|
|
|
|
|
|
return LoadFromBuffer(resourceName, pBuf.get(), filesystem, pathID, pfnEvaluateSymbolProc);
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : includedKeys -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::AppendIncludedKeys(CUtlVector< KeyValues* >& includedKeys)
|
|
|
|
{
|
|
|
|
// Append any included keys, too...
|
|
|
|
int includeCount = includedKeys.Count();
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < includeCount; i++)
|
|
|
|
{
|
|
|
|
KeyValues* kv = includedKeys[i];
|
|
|
|
Assert(kv);
|
|
|
|
|
|
|
|
KeyValues* insertSpot = this;
|
|
|
|
while (insertSpot->GetNextKey())
|
|
|
|
{
|
|
|
|
insertSpot = insertSpot->GetNextKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
insertSpot->SetNextKey(kv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyValues::ParseIncludedKeys(char const* resourceName, const char* filetoinclude,
|
|
|
|
IBaseFileSystem* pFileSystem, const char* pPathID, CUtlVector< KeyValues* >& includedKeys, GetSymbolProc_t pfnEvaluateSymbolProc)
|
|
|
|
{
|
|
|
|
Assert(resourceName);
|
|
|
|
Assert(filetoinclude);
|
|
|
|
Assert(pFileSystem);
|
|
|
|
|
|
|
|
// Load it...
|
|
|
|
if (!pFileSystem)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get relative subdirectory
|
|
|
|
char fullpath[512];
|
|
|
|
V_strncpy(fullpath, resourceName, sizeof(fullpath));
|
|
|
|
|
|
|
|
// Strip off characters back to start or first /
|
|
|
|
bool done = false;
|
|
|
|
size_t len = V_strlen(fullpath);
|
|
|
|
while (!done)
|
|
|
|
{
|
|
|
|
if (len == 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fullpath[len - 1] == '\\' ||
|
|
|
|
fullpath[len - 1] == '/')
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// zero it
|
|
|
|
fullpath[len - 1] = 0;
|
|
|
|
--len;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append included file
|
|
|
|
V_strncat(fullpath, filetoinclude, sizeof(fullpath));
|
|
|
|
|
|
|
|
KeyValues* newKV = new KeyValues(fullpath);
|
|
|
|
|
|
|
|
// CUtlSymbol save = s_CurrentFileSymbol; // did that had any use ???
|
|
|
|
|
|
|
|
newKV->UsesEscapeSequences(m_bHasEscapeSequences != 0); // use same format as parent
|
|
|
|
|
|
|
|
if (newKV->LoadFromFile(pFileSystem, fullpath, pPathID, pfnEvaluateSymbolProc))
|
|
|
|
{
|
|
|
|
includedKeys.AddToTail(newKV);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DevMsg(eDLL_T::COMMON, "%s: Couldn't load included keyvalue file %s\n", __FUNCTION__, fullpath);
|
|
|
|
delete newKV;
|
|
|
|
}
|
|
|
|
|
|
|
|
// s_CurrentFileSymbol = save;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : baseKeys -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::MergeBaseKeys(CUtlVector< KeyValues* >& baseKeys)
|
|
|
|
{
|
|
|
|
const int includeCount = baseKeys.Count();
|
|
|
|
|
|
|
|
for (int i = 0; i < includeCount; i++)
|
|
|
|
{
|
|
|
|
KeyValues* kv = baseKeys[i];
|
|
|
|
Assert(kv);
|
|
|
|
|
|
|
|
RecursiveMergeKeyValues(kv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose:
|
|
|
|
// Input : baseKV - keyvalues we're basing ourselves on
|
2022-11-23 10:34:11 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
2024-04-05 17:42:05 +02:00
|
|
|
void KeyValues::RecursiveMergeKeyValues(KeyValues* baseKV)
|
2022-11-23 10:34:11 +01:00
|
|
|
{
|
2024-04-05 17:42:05 +02:00
|
|
|
// Merge ourselves
|
|
|
|
// we always want to keep our value, so nothing to do here
|
|
|
|
|
|
|
|
// Now merge our children
|
|
|
|
for (KeyValues* baseChild = baseKV->m_pSub; baseChild != NULL; baseChild = baseChild->m_pPeer)
|
|
|
|
{
|
|
|
|
// for each child in base, see if we have a matching kv
|
|
|
|
|
|
|
|
bool bFoundMatch = false;
|
|
|
|
|
|
|
|
// If we have a child by the same name, merge those keys
|
|
|
|
for (KeyValues* newChild = m_pSub; newChild != NULL; newChild = newChild->m_pPeer)
|
|
|
|
{
|
|
|
|
if (!V_strcmp(baseChild->GetName(), newChild->GetName()))
|
|
|
|
{
|
|
|
|
newChild->RecursiveMergeKeyValues(baseChild);
|
|
|
|
bFoundMatch = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not merged, append this key
|
|
|
|
if (!bFoundMatch)
|
|
|
|
{
|
|
|
|
KeyValues* dat = baseChild->MakeCopy();
|
|
|
|
Assert(dat);
|
|
|
|
AddSubKey(dat);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Returns whether a keyvalues conditional expression string evaluates to true or false
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KeyValues::EvaluateConditional(const char* pExpressionString, GetSymbolProc_t pfnEvaluateSymbolProc)
|
|
|
|
{
|
|
|
|
// evaluate the infix expression, calling the symbol proc to resolve each symbol's value
|
|
|
|
bool bResult = false;
|
|
|
|
const bool bValid = g_ExpressionEvaluator.Evaluate(bResult, pExpressionString, pfnEvaluateSymbolProc);
|
|
|
|
if (!bValid)
|
|
|
|
{
|
|
|
|
g_KeyValuesErrorStack.ReportError("KV Conditional Evaluation Error");
|
|
|
|
}
|
|
|
|
|
|
|
|
return bResult;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::CreateKeyUsingKnownLastChild(const char* keyName, KeyValues* pLastChild)
|
|
|
|
{
|
|
|
|
// Create a new key
|
|
|
|
KeyValues* dat = new KeyValues(keyName);
|
|
|
|
|
|
|
|
dat->UsesEscapeSequences(m_bHasEscapeSequences != 0); // use same format as parent does
|
|
|
|
|
|
|
|
// add into subkey list
|
|
|
|
AddSubkeyUsingKnownLastChild(dat, pLastChild);
|
|
|
|
|
|
|
|
return dat;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::AddSubkeyUsingKnownLastChild(KeyValues* pSubkey, KeyValues* pLastChild)
|
|
|
|
{
|
|
|
|
// Make sure the subkey isn't a child of some other keyvalues
|
|
|
|
Assert(pSubkey != NULL);
|
|
|
|
Assert(pSubkey->m_pPeer == NULL);
|
|
|
|
|
|
|
|
// Empty child list?
|
|
|
|
if (pLastChild == NULL)
|
|
|
|
{
|
|
|
|
Assert(m_pSub == NULL);
|
|
|
|
m_pSub = pSubkey;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Assert(m_pSub != NULL);
|
|
|
|
Assert(pLastChild->m_pPeer == NULL);
|
|
|
|
|
|
|
|
pLastChild->SetNextKey(pSubkey);
|
|
|
|
}
|
2022-11-23 10:34:11 +01:00
|
|
|
}
|
|
|
|
|
2022-11-22 09:04:28 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
2022-05-28 16:31:38 +02:00
|
|
|
// Purpose: Make a new copy of all subkeys, add them all to the passed-in keyvalues
|
|
|
|
// Input : *pParent -
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KeyValues::CopySubkeys(KeyValues* pParent) const
|
|
|
|
{
|
|
|
|
// recursively copy subkeys
|
|
|
|
// Also maintain ordering....
|
|
|
|
KeyValues* pPrev = nullptr;
|
|
|
|
for (KeyValues* pSub = m_pSub; pSub != nullptr; pSub = pSub->m_pPeer)
|
|
|
|
{
|
|
|
|
// take a copy of the subkey
|
|
|
|
KeyValues* pKey = pSub->MakeCopy();
|
|
|
|
|
|
|
|
// add into subkey list
|
|
|
|
if (pPrev)
|
|
|
|
{
|
|
|
|
pPrev->m_pPeer = pKey;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pParent->m_pSub = pKey;
|
|
|
|
}
|
|
|
|
pKey->m_pPeer = nullptr;
|
|
|
|
pPrev = pKey;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Purpose: Makes a copy of the whole key-value pair set
|
|
|
|
// Output : KeyValues*
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KeyValues* KeyValues::MakeCopy(void) const
|
2022-04-09 16:16:40 +02:00
|
|
|
{
|
2023-06-26 22:34:24 +02:00
|
|
|
KeyValues* pNewKeyValue = new KeyValues(GetName());
|
2022-05-28 16:31:38 +02:00
|
|
|
|
|
|
|
// copy data
|
|
|
|
pNewKeyValue->m_iDataType = m_iDataType;
|
|
|
|
switch (m_iDataType)
|
|
|
|
{
|
|
|
|
case TYPE_STRING:
|
2022-04-09 16:16:40 +02:00
|
|
|
{
|
2022-05-28 16:31:38 +02:00
|
|
|
if (m_sValue)
|
|
|
|
{
|
|
|
|
size_t len = strlen(m_sValue);
|
|
|
|
Assert(!pNewKeyValue->m_sValue);
|
2023-06-26 22:34:24 +02:00
|
|
|
pNewKeyValue->m_sValue = new char[len + 1];
|
2022-05-28 16:31:38 +02:00
|
|
|
memcpy(pNewKeyValue->m_sValue, m_sValue, len + 1);
|
|
|
|
}
|
2022-04-09 16:16:40 +02:00
|
|
|
}
|
2022-05-28 16:31:38 +02:00
|
|
|
break;
|
|
|
|
case TYPE_WSTRING:
|
|
|
|
{
|
|
|
|
if (m_wsValue)
|
|
|
|
{
|
|
|
|
size_t len = wcslen(m_wsValue);
|
2023-06-26 22:34:24 +02:00
|
|
|
pNewKeyValue->m_wsValue = new wchar_t[len + 1];
|
|
|
|
memcpy(pNewKeyValue->m_wsValue, m_wsValue, (len+1)*sizeof(wchar_t));
|
2022-05-28 16:31:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TYPE_INT:
|
|
|
|
pNewKeyValue->m_iValue = m_iValue;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TYPE_FLOAT:
|
|
|
|
pNewKeyValue->m_flValue = m_flValue;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TYPE_PTR:
|
|
|
|
pNewKeyValue->m_pValue = m_pValue;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TYPE_COLOR:
|
|
|
|
pNewKeyValue->m_Color[0] = m_Color[0];
|
|
|
|
pNewKeyValue->m_Color[1] = m_Color[1];
|
|
|
|
pNewKeyValue->m_Color[2] = m_Color[2];
|
|
|
|
pNewKeyValue->m_Color[3] = m_Color[3];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TYPE_UINT64:
|
2023-06-26 22:34:24 +02:00
|
|
|
pNewKeyValue->m_sValue = new char[sizeof(uint64)];
|
2022-05-28 16:31:38 +02:00
|
|
|
memcpy(pNewKeyValue->m_sValue, m_sValue, sizeof(uint64_t));
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
|
|
|
|
// recursively copy subkeys
|
|
|
|
CopySubkeys(pNewKeyValue);
|
|
|
|
return pNewKeyValue;
|
|
|
|
}
|