mirror of
https://github.com/0TheSpy/Seaside.git
synced 2025-01-09 19:08:48 +08:00
1831 lines
36 KiB
C++
1831 lines
36 KiB
C++
#include "sdk/keyvalues.h"
|
|
#include "sdk/IKeyValuesSystem.h"
|
|
|
|
#include "Interfaces.hpp"
|
|
|
|
void V_strncpy(char* pDest, char const* pSrc, int maxLen)
|
|
{
|
|
strncpy(pDest, pSrc, maxLen);
|
|
if (maxLen > 0)
|
|
{
|
|
pDest[maxLen - 1] = 0;
|
|
}
|
|
}
|
|
|
|
char* V_strncat(char* pDest, const char* pSrc, size_t destBufferSize, int max_chars_to_copy)
|
|
{
|
|
size_t charstocopy = (size_t)0;
|
|
|
|
size_t len = strlen(pDest);
|
|
size_t srclen = strlen(pSrc);
|
|
if (max_chars_to_copy <= COPY_ALL_CHARACTERS)
|
|
charstocopy = srclen;
|
|
else
|
|
charstocopy = (size_t)min(max_chars_to_copy, (int)srclen);
|
|
|
|
if (len + charstocopy >= destBufferSize)
|
|
charstocopy = destBufferSize - len - 1;
|
|
|
|
if ((int)charstocopy <= 0)
|
|
return pDest;
|
|
|
|
ANALYZE_SUPPRESS(6059);
|
|
char* pOut = strncat(pDest, pSrc, charstocopy);
|
|
return pOut;
|
|
}
|
|
|
|
|
|
int _V_strcmp(const char* file, int line, const char* s1, const char* s2)
|
|
{
|
|
return strcmp(s1, s2);
|
|
}
|
|
|
|
int V_strncmp(const char* s1, const char* s2, int count)
|
|
{
|
|
while (count > 0)
|
|
{
|
|
if (*s1 != *s2)
|
|
return (unsigned char)*s1 < (unsigned char)*s2 ? -1 : 1;
|
|
if (*s1 == '\0')
|
|
return 0;
|
|
s1++;
|
|
s2++;
|
|
count--;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int V_stricmp(const char* str1, const char* str2)
|
|
{
|
|
if (str1 == str2)
|
|
{
|
|
return 0;
|
|
}
|
|
const unsigned char* s1 = (const unsigned char*)str1;
|
|
const unsigned char* s2 = (const unsigned char*)str2;
|
|
for (; *s1; ++s1, ++s2)
|
|
{
|
|
if (*s1 != *s2)
|
|
{
|
|
unsigned char c1 = *s1 | 0x20;
|
|
unsigned char c2 = *s2 | 0x20;
|
|
if (c1 != c2 || (unsigned char)(c1 - 'a') > ('z' - 'a'))
|
|
{
|
|
if ((c1 | c2) >= 0x80) return stricmp((const char*)s1, (const char*)s2);
|
|
if ((unsigned char)(c1 - 'a') > ('z' - 'a')) c1 = *s1;
|
|
if ((unsigned char)(c2 - 'a') > ('z' - 'a')) c2 = *s2;
|
|
return c1 > c2 ? 1 : -1;
|
|
}
|
|
}
|
|
}
|
|
return *s2 ? -1 : 0;
|
|
}
|
|
|
|
class CLeakTrack
|
|
{
|
|
public:
|
|
CLeakTrack()
|
|
{
|
|
}
|
|
~CLeakTrack()
|
|
{
|
|
if (keys.Count() != 0)
|
|
{
|
|
Assert(0);
|
|
}
|
|
}
|
|
|
|
struct kve
|
|
{
|
|
KeyValues* kv;
|
|
char name[256];
|
|
};
|
|
|
|
void AddKv(KeyValues* kv, char const* name)
|
|
{
|
|
kve k;
|
|
V_strncpy(k.name, name ? name : "NULL", sizeof(k.name));
|
|
k.kv = kv;
|
|
|
|
keys.AddToTail(k);
|
|
}
|
|
|
|
void RemoveKv(KeyValues* kv)
|
|
{
|
|
int c = keys.Count();
|
|
for (int i = 0; i < c; i++)
|
|
{
|
|
if (keys[i].kv == kv)
|
|
{
|
|
keys.Remove(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CUtlVector< kve > keys;
|
|
};
|
|
|
|
|
|
static CLeakTrack track;
|
|
|
|
void KeyValues::Init()
|
|
{
|
|
m_iKeyName = 0;
|
|
m_iKeyNameCaseSensitive1 = 0;
|
|
m_iKeyNameCaseSensitive2 = 0;
|
|
m_iDataType = TYPE_NONE;
|
|
|
|
m_pSub = NULL;
|
|
m_pPeer = NULL;
|
|
m_pChain = NULL;
|
|
|
|
m_sValue = NULL;
|
|
m_wsValue = NULL;
|
|
m_pValue = NULL;
|
|
|
|
m_bHasEscapeSequences = 0;
|
|
}
|
|
|
|
void KeyValues::SetName(const char* setName)
|
|
{
|
|
HKeySymbol hCaseSensitiveKeyName = INVALID_KEY_SYMBOL, hCaseInsensitiveKeyName = INVALID_KEY_SYMBOL;
|
|
hCaseSensitiveKeyName = iff.keyValuesSystem->GetSymbolForStringCaseSensitive(hCaseInsensitiveKeyName, setName);
|
|
|
|
m_iKeyName = hCaseInsensitiveKeyName;
|
|
SPLIT_3_BYTES_INTO_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2, hCaseSensitiveKeyName);
|
|
}
|
|
|
|
KeyValues::KeyValues(const char* setName)
|
|
{
|
|
TRACK_KV_ADD(this, setName);
|
|
Init();
|
|
SetName(setName);
|
|
}
|
|
|
|
void* KeyValues::operator new(size_t iAllocSize)
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
return iff.keyValuesSystem->AllocKeyValuesMemory(iAllocSize);
|
|
}
|
|
|
|
void KeyValues::operator delete(void* pMem)
|
|
{
|
|
iff.keyValuesSystem->FreeKeyValuesMemory((KeyValues*)pMem);
|
|
}
|
|
|
|
#define TRACK_KV_ADD( ptr, name ) track.AddKv( ptr, name )
|
|
#define TRACK_KV_REMOVE( ptr ) track.RemoveKv( ptr )
|
|
|
|
|
|
KeyValues::~KeyValues()
|
|
{
|
|
TRACK_KV_REMOVE(this);
|
|
RemoveEverything();
|
|
}
|
|
|
|
void KeyValues::RemoveEverything()
|
|
{
|
|
KeyValues* dat;
|
|
KeyValues* datNext = NULL;
|
|
for (dat = m_pSub; dat != NULL; dat = datNext)
|
|
{
|
|
datNext = dat->m_pPeer;
|
|
dat->m_pPeer = NULL;
|
|
delete dat;
|
|
}
|
|
|
|
for (dat = m_pPeer; dat && dat != this; dat = datNext)
|
|
{
|
|
datNext = dat->m_pPeer;
|
|
dat->m_pPeer = NULL;
|
|
delete dat;
|
|
}
|
|
|
|
delete[] m_sValue;
|
|
m_sValue = NULL;
|
|
delete[] m_wsValue;
|
|
m_wsValue = NULL;
|
|
}
|
|
|
|
char CUtlCharConversion::FindConversion(const char* pString, int* pLength)
|
|
{
|
|
for (int i = 0; i < m_nCount; ++i)
|
|
{
|
|
if (!Q_strcmp(pString, m_pReplacements[(unsigned char)m_pList[i]].m_pReplacementString))
|
|
{
|
|
*pLength = m_pReplacements[(unsigned char)m_pList[i]].m_nLength;
|
|
return m_pList[i];
|
|
}
|
|
}
|
|
|
|
*pLength = 0;
|
|
return '\0';
|
|
}
|
|
|
|
CUtlCharConversion::CUtlCharConversion(char nEscapeChar, const char* pDelimiter, int nCount, ConversionArray_t* pArray)
|
|
{
|
|
m_nEscapeChar = nEscapeChar;
|
|
m_pDelimiter = pDelimiter;
|
|
m_nCount = nCount;
|
|
m_nDelimiterLength = Q_strlen(pDelimiter);
|
|
m_nMaxConversionLength = 0;
|
|
|
|
memset(m_pReplacements, 0, sizeof(m_pReplacements));
|
|
|
|
for (int i = 0; i < nCount; ++i)
|
|
{
|
|
m_pList[i] = pArray[i].m_nActualChar;
|
|
ConversionInfo_t& info = m_pReplacements[(unsigned char)m_pList[i]];
|
|
Assert(info.m_pReplacementString == 0);
|
|
info.m_pReplacementString = pArray[i].m_pReplacementString;
|
|
info.m_nLength = Q_strlen(info.m_pReplacementString);
|
|
if (info.m_nLength > m_nMaxConversionLength)
|
|
{
|
|
m_nMaxConversionLength = info.m_nLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CUtlBuffer::OnGetOverflow(int nSize)
|
|
{
|
|
return (this->*m_GetOverflowFunc)(nSize);
|
|
}
|
|
|
|
|
|
class CUtlCStringConversion : public CUtlCharConversion
|
|
{
|
|
public:
|
|
CUtlCStringConversion(char nEscapeChar, const char* pDelimiter, int nCount, ConversionArray_t* pArray);
|
|
|
|
virtual char FindConversion(const char* pString, int* pLength);
|
|
|
|
private:
|
|
char m_pConversion[256];
|
|
};
|
|
|
|
CUtlCStringConversion::CUtlCStringConversion(char nEscapeChar, const char* pDelimiter, int nCount, ConversionArray_t* pArray) :
|
|
CUtlCharConversion(nEscapeChar, pDelimiter, nCount, pArray)
|
|
{
|
|
memset(m_pConversion, 0x0, sizeof(m_pConversion));
|
|
for (int i = 0; i < nCount; ++i)
|
|
{
|
|
m_pConversion[(unsigned char)pArray[i].m_pReplacementString[0]] = pArray[i].m_nActualChar;
|
|
}
|
|
}
|
|
|
|
char CUtlCStringConversion::FindConversion(const char* pString, int* pLength)
|
|
{
|
|
char c = m_pConversion[(unsigned char)pString[0]];
|
|
*pLength = (c != '\0') ? 1 : 0;
|
|
return c;
|
|
}
|
|
|
|
bool CUtlBuffer::OnPutOverflow(int nSize)
|
|
{
|
|
return (this->*m_PutOverflowFunc)(nSize);
|
|
}
|
|
|
|
void CUtlBuffer::SetOverflowFuncs(UtlBufferOverflowFunc_t getFunc, UtlBufferOverflowFunc_t putFunc)
|
|
{
|
|
m_GetOverflowFunc = getFunc;
|
|
m_PutOverflowFunc = putFunc;
|
|
}
|
|
|
|
void CUtlBuffer::Get(void* pMem, int size)
|
|
{
|
|
if (size > 0 && CheckGet(size))
|
|
{
|
|
int Index = m_Get - m_nOffset;
|
|
Assert(m_Memory.IsIdxValid(Index) && m_Memory.IsIdxValid(Index + size - 1));
|
|
|
|
memcpy(pMem, &m_Memory[Index], size);
|
|
m_Get += size;
|
|
}
|
|
}
|
|
|
|
void CUtlBuffer::GetString(char* pString, int nMaxChars)
|
|
{
|
|
if (!IsValid())
|
|
{
|
|
*pString = 0;
|
|
return;
|
|
}
|
|
|
|
if (nMaxChars == 0)
|
|
{
|
|
nMaxChars = INT_MAX;
|
|
}
|
|
|
|
int nLen = PeekStringLength();
|
|
|
|
if (IsText())
|
|
{
|
|
EatWhiteSpace();
|
|
}
|
|
|
|
if (nLen == 0)
|
|
{
|
|
*pString = 0;
|
|
m_Error |= GET_OVERFLOW;
|
|
return;
|
|
}
|
|
|
|
if (nLen <= nMaxChars)
|
|
{
|
|
Get(pString, nLen - 1);
|
|
pString[nLen - 1] = 0;
|
|
}
|
|
else
|
|
{
|
|
Get(pString, nMaxChars - 1);
|
|
pString[nMaxChars - 1] = 0;
|
|
SeekGet(SEEK_CURRENT, nLen - 1 - nMaxChars);
|
|
}
|
|
|
|
if (!IsText())
|
|
{
|
|
VerifyEquals(GetChar(), 0);
|
|
}
|
|
}
|
|
|
|
|
|
bool CUtlBuffer::CheckPut(int nSize)
|
|
{
|
|
if ((m_Error & PUT_OVERFLOW) || IsReadOnly())
|
|
return false;
|
|
|
|
if ((m_Put < m_nOffset) || (m_Memory.NumAllocated() < m_Put - m_nOffset + nSize))
|
|
{
|
|
if (!OnPutOverflow(nSize))
|
|
{
|
|
m_Error |= PUT_OVERFLOW;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
char CUtlCharConversion::GetEscapeChar() const
|
|
{
|
|
return m_nEscapeChar;
|
|
}
|
|
|
|
int CUtlCharConversion::MaxConversionLength() const
|
|
{
|
|
return m_nMaxConversionLength;
|
|
}
|
|
|
|
|
|
char CUtlBuffer::GetDelimitedCharInternal(CUtlCharConversion* pConv)
|
|
{
|
|
char c = GetChar();
|
|
if (c == pConv->GetEscapeChar())
|
|
{
|
|
int nLength = pConv->MaxConversionLength();
|
|
if (!CheckArbitraryPeekGet(0, nLength))
|
|
return '\0';
|
|
|
|
c = pConv->FindConversion((const char*)PeekGet(), &nLength);
|
|
SeekGet(SEEK_CURRENT, nLength);
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
bool CUtlBuffer::PutOverflow(int nSize)
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
if (m_Memory.IsExternallyAllocated())
|
|
{
|
|
if (!IsGrowable())
|
|
return false;
|
|
|
|
m_Memory.ConvertToGrowableMemory(0);
|
|
}
|
|
|
|
while (Size() < m_Put - m_nOffset + nSize)
|
|
{
|
|
m_Memory.Grow();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CUtlBuffer::GetOverflow(int nSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool CUtlBuffer::PeekStringMatch(int nOffset, const char* pString, int nLen)
|
|
{
|
|
if (!CheckPeekGet(nOffset, nLen))
|
|
return false;
|
|
return !Q_strncmp((const char*)PeekGet(nOffset), pString, nLen);
|
|
}
|
|
|
|
int CUtlBuffer::PeekWhiteSpace(int nOffset)
|
|
{
|
|
if (!IsText() || !IsValid())
|
|
return 0;
|
|
|
|
while (CheckPeekGet(nOffset, sizeof(char)))
|
|
{
|
|
if (!isspace(*(unsigned char*)PeekGet(nOffset)))
|
|
break;
|
|
nOffset += sizeof(char);
|
|
}
|
|
|
|
return nOffset;
|
|
}
|
|
|
|
bool CUtlBuffer::CheckArbitraryPeekGet(int nOffset, int& nIncrement)
|
|
{
|
|
if (TellGet() + nOffset >= TellMaxPut())
|
|
{
|
|
nIncrement = 0;
|
|
return false;
|
|
}
|
|
|
|
if (TellGet() + nOffset + nIncrement > TellMaxPut())
|
|
{
|
|
nIncrement = TellMaxPut() - TellGet() - nOffset;
|
|
}
|
|
|
|
CheckPeekGet(nOffset, nIncrement);
|
|
int nMaxGet = TellMaxPut() - TellGet();
|
|
if (nMaxGet < nIncrement)
|
|
{
|
|
nIncrement = nMaxGet;
|
|
}
|
|
return (nIncrement != 0);
|
|
}
|
|
|
|
|
|
|
|
int CUtlBuffer::PeekStringLength()
|
|
{
|
|
if (!IsValid())
|
|
return 0;
|
|
|
|
int nOffset = 0;
|
|
if (IsText())
|
|
{
|
|
nOffset = PeekWhiteSpace(nOffset);
|
|
}
|
|
|
|
int nStartingOffset = nOffset;
|
|
|
|
do
|
|
{
|
|
int nPeekAmount = 128;
|
|
|
|
if (!CheckArbitraryPeekGet(nOffset, nPeekAmount))
|
|
{
|
|
if (nOffset == nStartingOffset)
|
|
return 0;
|
|
return nOffset - nStartingOffset + 1;
|
|
}
|
|
|
|
const char* pTest = (const char*)PeekGet(nOffset);
|
|
|
|
if (!IsText())
|
|
{
|
|
for (int i = 0; i < nPeekAmount; ++i)
|
|
{
|
|
if (pTest[i] == 0)
|
|
return (i + nOffset - nStartingOffset + 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < nPeekAmount; ++i)
|
|
{
|
|
if (isspace((unsigned char)pTest[i]) || (pTest[i] == 0))
|
|
return (i + nOffset - nStartingOffset + 1);
|
|
}
|
|
}
|
|
|
|
nOffset += nPeekAmount;
|
|
|
|
} while (true);
|
|
}
|
|
|
|
|
|
class CUtlNoEscConversion : public CUtlCharConversion
|
|
{
|
|
public:
|
|
CUtlNoEscConversion(char nEscapeChar, const char* pDelimiter, int nCount, ConversionArray_t* pArray) :
|
|
CUtlCharConversion(nEscapeChar, pDelimiter, nCount, pArray) {}
|
|
|
|
virtual char FindConversion(const char* pString, int* pLength) { *pLength = 0; return 0; }
|
|
};
|
|
|
|
BEGIN_CUSTOM_CHAR_CONVERSION(CUtlCStringConversion, s_StringCharConversion, (char*)"\"", '\\')
|
|
{ '\n', (char*)"n" },
|
|
{ '\t', (char*)"t" },
|
|
{ '\v', (char*)"v" },
|
|
{ '\b', (char*)"b" },
|
|
{ '\r', (char*)"r" },
|
|
{ '\f', (char*)"f" },
|
|
{ '\a', (char*)"a" },
|
|
{ '\\', (char*)"\\" },
|
|
{ '\?', (char*)"\?" },
|
|
{ '\'', (char*)"\'" },
|
|
{ '\"', (char*)"\"" },
|
|
END_CUSTOM_CHAR_CONVERSION(CUtlCStringConversion, s_StringCharConversion, (char*)"\"", '\\')
|
|
|
|
CUtlCharConversion* GetCStringCharConversion()
|
|
{
|
|
return &s_StringCharConversion;
|
|
}
|
|
|
|
BEGIN_CUSTOM_CHAR_CONVERSION(CUtlNoEscConversion, s_NoEscConversion, (char*)"\"", 0x7F)
|
|
{
|
|
0x7F, (char*)""
|
|
},
|
|
END_CUSTOM_CHAR_CONVERSION(CUtlNoEscConversion, s_NoEscConversion, (char*)"\"", 0x7F)
|
|
|
|
CUtlCharConversion* GetNoEscCharConversion()
|
|
{
|
|
return &s_NoEscConversion;
|
|
}
|
|
|
|
|
|
const char* CUtlCharConversion::GetDelimiter() const
|
|
{
|
|
return m_pDelimiter;
|
|
}
|
|
|
|
int CUtlCharConversion::GetDelimiterLength() const
|
|
{
|
|
return m_nDelimiterLength;
|
|
}
|
|
|
|
void CUtlBuffer::AddNullTermination(int m)
|
|
{
|
|
if (m_Put > m_nMaxPut)
|
|
{
|
|
if (!IsReadOnly() && ((m_Error & PUT_OVERFLOW) == 0))
|
|
{
|
|
if (CheckPut(1))
|
|
{
|
|
int Index = m_Put - m_nOffset;
|
|
Assert(m_Memory.IsIdxValid(Index));
|
|
if (Index >= 0)
|
|
{
|
|
m_Memory[Index] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Error &= ~PUT_OVERFLOW;
|
|
}
|
|
}
|
|
m_nMaxPut = m_Put;
|
|
}
|
|
}
|
|
|
|
CUtlBuffer::CUtlBuffer(int growSize, int initSize, int nFlags) :
|
|
m_Error(0)
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
m_Memory.Init(growSize, initSize);
|
|
m_Get = 0;
|
|
m_Put = 0;
|
|
m_nTab = 0;
|
|
m_nOffset = 0;
|
|
m_Flags = nFlags;
|
|
if ((initSize != 0) && !IsReadOnly())
|
|
{
|
|
m_nMaxPut = -1;
|
|
AddNullTermination(0);
|
|
}
|
|
else
|
|
{
|
|
m_nMaxPut = 0;
|
|
}
|
|
SetOverflowFuncs(&CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow);
|
|
}
|
|
|
|
CUtlBuffer::CUtlBuffer(const void* pBuffer, int nSize, int nFlags) :
|
|
m_Memory((unsigned char*)pBuffer, nSize), m_Error(0)
|
|
{
|
|
Assert(nSize != 0);
|
|
|
|
m_Get = 0;
|
|
m_Put = 0;
|
|
m_nTab = 0;
|
|
m_nOffset = 0;
|
|
m_Flags = nFlags;
|
|
if (IsReadOnly())
|
|
{
|
|
m_nMaxPut = nSize;
|
|
}
|
|
else
|
|
{
|
|
m_nMaxPut = -1;
|
|
AddNullTermination(0);
|
|
}
|
|
SetOverflowFuncs(&CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow);
|
|
}
|
|
|
|
|
|
void CUtlBuffer::GetDelimitedString(CUtlCharConversion* pConv, char* pString, int nMaxChars)
|
|
{
|
|
if (!IsText() || !pConv)
|
|
{
|
|
GetString(pString, nMaxChars);
|
|
return;
|
|
}
|
|
|
|
if (!IsValid())
|
|
{
|
|
*pString = 0;
|
|
return;
|
|
}
|
|
|
|
if (nMaxChars == 0)
|
|
{
|
|
nMaxChars = INT_MAX;
|
|
}
|
|
|
|
EatWhiteSpace();
|
|
if (!PeekStringMatch(0, pConv->GetDelimiter(), pConv->GetDelimiterLength()))
|
|
return;
|
|
|
|
SeekGet(SEEK_CURRENT, pConv->GetDelimiterLength());
|
|
|
|
int nRead = 0;
|
|
while (IsValid())
|
|
{
|
|
if (PeekStringMatch(0, pConv->GetDelimiter(), pConv->GetDelimiterLength()))
|
|
{
|
|
SeekGet(SEEK_CURRENT, pConv->GetDelimiterLength());
|
|
break;
|
|
}
|
|
|
|
char c = GetDelimitedCharInternal(pConv);
|
|
|
|
if (nRead < nMaxChars)
|
|
{
|
|
pString[nRead] = c;
|
|
++nRead;
|
|
}
|
|
}
|
|
|
|
if (nRead >= nMaxChars)
|
|
{
|
|
nRead = nMaxChars - 1;
|
|
}
|
|
pString[nRead] = '\0';
|
|
}
|
|
|
|
void CUtlBuffer::EatWhiteSpace()
|
|
{
|
|
if (IsText() && IsValid())
|
|
{
|
|
while (CheckGet(sizeof(char)))
|
|
{
|
|
if (!isspace(*(const unsigned char*)PeekGet()))
|
|
break;
|
|
m_Get += sizeof(char);
|
|
}
|
|
}
|
|
}
|
|
bool CUtlBuffer::EatCPPComment()
|
|
{
|
|
if (IsText() && IsValid())
|
|
{
|
|
const char* pPeek = (const char*)PeekGet(2 * sizeof(char), 0);
|
|
if (!pPeek || (pPeek[0] != '/') || (pPeek[1] != '/'))
|
|
return false;
|
|
|
|
m_Get += 2;
|
|
|
|
for (char c = GetChar(); IsValid(); c = GetChar())
|
|
{
|
|
if (c == '\n')
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CUtlBuffer::CheckGet(int nSize)
|
|
{
|
|
if (m_Error & GET_OVERFLOW)
|
|
return false;
|
|
|
|
if (TellMaxPut() < m_Get + nSize)
|
|
{
|
|
m_Error |= GET_OVERFLOW;
|
|
return false;
|
|
}
|
|
|
|
if ((m_Get < m_nOffset) || (m_Memory.NumAllocated() < m_Get - m_nOffset + nSize))
|
|
{
|
|
if (!OnGetOverflow(nSize))
|
|
{
|
|
m_Error |= GET_OVERFLOW;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CUtlBuffer::CheckPeekGet(int nOffset, int nSize)
|
|
{
|
|
if (m_Error & GET_OVERFLOW)
|
|
return false;
|
|
|
|
bool bOk = CheckGet(nOffset + nSize);
|
|
m_Error &= ~GET_OVERFLOW;
|
|
return bOk;
|
|
}
|
|
|
|
const void* CUtlBuffer::PeekGet(int nMaxSize, int nOffset)
|
|
{
|
|
if (!CheckPeekGet(nOffset, nMaxSize))
|
|
return NULL;
|
|
|
|
int Index = m_Get + nOffset - m_nOffset;
|
|
Assert(m_Memory.IsIdxValid(Index) && m_Memory.IsIdxValid(Index + nMaxSize - 1));
|
|
|
|
return &m_Memory[Index];
|
|
}
|
|
|
|
void CUtlBuffer::SeekGet(SeekType_t type, int offset)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SEEK_HEAD:
|
|
m_Get = offset;
|
|
break;
|
|
|
|
case SEEK_CURRENT:
|
|
m_Get += offset;
|
|
break;
|
|
|
|
case SEEK_TAIL:
|
|
m_Get = m_nMaxPut - offset;
|
|
break;
|
|
}
|
|
|
|
if (m_Get > m_nMaxPut)
|
|
{
|
|
m_Error |= GET_OVERFLOW;
|
|
}
|
|
else
|
|
{
|
|
m_Error &= ~GET_OVERFLOW;
|
|
if (m_Get < m_nOffset || m_Get >= m_nOffset + Size())
|
|
{
|
|
OnGetOverflow(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void KeyValues::Clear(void)
|
|
{
|
|
delete m_pSub;
|
|
m_pSub = NULL;
|
|
m_iDataType = TYPE_NONE;
|
|
}
|
|
|
|
void KeyValues::deleteThis()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
|
|
KeyValues* KeyValues::CreateKey(const char* keyName)
|
|
{
|
|
KeyValues* dat = new KeyValues(keyName);
|
|
dat->UsesEscapeSequences(m_bHasEscapeSequences != 0);
|
|
AddSubKey(dat);
|
|
return dat;
|
|
}
|
|
|
|
|
|
void KeyValues::AddSubKey(KeyValues* pSubkey)
|
|
{
|
|
Assert(pSubkey->m_pPeer == NULL);
|
|
|
|
if (m_pSub == NULL)
|
|
{
|
|
m_pSub = pSubkey;
|
|
}
|
|
else
|
|
{
|
|
KeyValues* pTempDat = m_pSub;
|
|
while (pTempDat->GetNextKey() != NULL)
|
|
{
|
|
pTempDat = pTempDat->GetNextKey();
|
|
}
|
|
|
|
pTempDat->SetNextKey(pSubkey);
|
|
}
|
|
}
|
|
|
|
|
|
void KeyValues::RemoveSubKey(KeyValues* subKey)
|
|
{
|
|
if (!subKey)
|
|
return;
|
|
|
|
if (m_pSub == subKey)
|
|
{
|
|
m_pSub = subKey->m_pPeer;
|
|
}
|
|
else
|
|
{
|
|
KeyValues* kv = m_pSub;
|
|
while (kv->m_pPeer)
|
|
{
|
|
if (kv->m_pPeer == subKey)
|
|
{
|
|
kv->m_pPeer = subKey->m_pPeer;
|
|
break;
|
|
}
|
|
|
|
kv = kv->m_pPeer;
|
|
}
|
|
}
|
|
|
|
subKey->m_pPeer = NULL;
|
|
}
|
|
|
|
static char* s_LastFileLoadingFrom = ( char*)"unknown";
|
|
|
|
|
|
void KeyValues::UsesEscapeSequences(bool state)
|
|
{
|
|
m_bHasEscapeSequences = state;
|
|
}
|
|
|
|
|
|
int V_snprintf(char* pDest, int maxLen, char const* pFormat, ...)
|
|
{
|
|
Assert(maxLen >= 0);
|
|
AssertValidWritePtr(pDest, maxLen);
|
|
va_list marker;
|
|
|
|
va_start(marker, pFormat);
|
|
#ifdef _WIN32
|
|
int len = _vsnprintf(pDest, maxLen, pFormat, marker);
|
|
#elif defined _LINUX || defined __APPLE__
|
|
int len = vsnprintf(pDest, maxLen, pFormat, marker);
|
|
#else
|
|
#error "define vsnprintf type."
|
|
#endif
|
|
va_end(marker);
|
|
|
|
if (len < 0)
|
|
{
|
|
len = maxLen;
|
|
pDest[maxLen - 1] = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
int V_vsnprintf(char* pDest, int maxLen, char const* pFormat, va_list params)
|
|
{
|
|
Assert(maxLen > 0);
|
|
AssertValidWritePtr(pDest, maxLen);
|
|
int len = _vsnprintf(pDest, maxLen, pFormat, params);
|
|
|
|
if (len < 0)
|
|
{
|
|
len = maxLen;
|
|
pDest[maxLen - 1] = 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
KeyValues* KeyValues::FindKey(int keySymbol) const
|
|
{
|
|
for (KeyValues* dat = m_pSub; dat != NULL; dat = dat->m_pPeer)
|
|
{
|
|
if (dat->m_iKeyName == keySymbol)
|
|
return dat;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int (*KeyValues::s_pfGetSymbolForString)(const char* name, bool bCreate) = &KeyValues::GetSymbolForStringClassic;
|
|
const char* (*KeyValues::s_pfGetStringForSymbol)(int symbol) = &KeyValues::GetStringForSymbolClassic;
|
|
CKeyValuesGrowableStringTable* KeyValues::s_pGrowableStringTable = NULL;
|
|
|
|
int KeyValues::GetSymbolForStringClassic(const char* name, bool bCreate)
|
|
{
|
|
return iff.keyValuesSystem->GetSymbolForString(name, bCreate);
|
|
}
|
|
|
|
const char* KeyValues::GetStringForSymbolClassic(int symbol)
|
|
{
|
|
return iff.keyValuesSystem->GetStringForSymbol(symbol);
|
|
}
|
|
|
|
KeyValues* KeyValues::GetNextTrueSubKey()
|
|
{
|
|
KeyValues* pRet = m_pPeer;
|
|
while (pRet && pRet->m_iDataType != TYPE_NONE)
|
|
pRet = pRet->m_pPeer;
|
|
|
|
return pRet;
|
|
}
|
|
|
|
|
|
KeyValues* KeyValues::FindKey(const char* keyName, bool bCreate)
|
|
{
|
|
if (!keyName || !keyName[0])
|
|
return this;
|
|
|
|
char szBuf[256];
|
|
const char* subStr = strchr(keyName, '/');
|
|
const char* searchStr = keyName;
|
|
|
|
if (subStr)
|
|
{
|
|
int size = subStr - keyName;
|
|
Q_memcpy(szBuf, keyName, size);
|
|
szBuf[size] = 0;
|
|
searchStr = szBuf;
|
|
}
|
|
|
|
HKeySymbol iSearchStr = s_pfGetSymbolForString(searchStr, bCreate);
|
|
|
|
if (iSearchStr == INVALID_KEY_SYMBOL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
KeyValues* lastItem = NULL;
|
|
KeyValues* dat;
|
|
for (dat = m_pSub; dat != NULL; dat = dat->m_pPeer)
|
|
{
|
|
lastItem = dat;
|
|
|
|
if (dat->m_iKeyName == iSearchStr)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dat && m_pChain)
|
|
{
|
|
dat = m_pChain->FindKey(keyName, false);
|
|
}
|
|
|
|
if (!dat)
|
|
{
|
|
if (bCreate)
|
|
{
|
|
dat = new KeyValues(searchStr);
|
|
dat->UsesEscapeSequences(m_bHasEscapeSequences != 0);
|
|
if (lastItem)
|
|
{
|
|
lastItem->m_pPeer = dat;
|
|
}
|
|
else
|
|
{
|
|
m_pSub = dat;
|
|
}
|
|
dat->m_pPeer = NULL;
|
|
|
|
m_iDataType = TYPE_NONE;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (subStr)
|
|
{
|
|
return dat->FindKey(subStr + 1, bCreate);
|
|
}
|
|
|
|
return dat;
|
|
}
|
|
|
|
void KeyValues::SetString(const char* keyName, const char* value)
|
|
{
|
|
KeyValues* dat = FindKey(keyName, true);
|
|
|
|
if (dat)
|
|
{
|
|
if (dat->m_iDataType == TYPE_STRING && dat->m_sValue == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
delete[] dat->m_sValue;
|
|
delete[] dat->m_wsValue;
|
|
dat->m_wsValue = NULL;
|
|
|
|
if (!value)
|
|
{
|
|
value = "";
|
|
}
|
|
|
|
int len = Q_strlen(value);
|
|
dat->m_sValue = new char[len + 1];
|
|
Q_memcpy(dat->m_sValue, value, len + 1);
|
|
|
|
dat->m_iDataType = TYPE_STRING;
|
|
}
|
|
}
|
|
|
|
|
|
const char* KeyValues::GetString(const char* keyName, const char* defaultValue)
|
|
{
|
|
KeyValues* dat = FindKey(keyName, false);
|
|
if (dat)
|
|
{
|
|
char buf[64];
|
|
switch (dat->m_iDataType)
|
|
{
|
|
case TYPE_FLOAT:
|
|
Q_snprintf(buf, sizeof(buf), "%f", dat->m_flValue);
|
|
SetString(keyName, buf);
|
|
break;
|
|
case TYPE_INT:
|
|
case TYPE_PTR:
|
|
Q_snprintf(buf, sizeof(buf), "%d", dat->m_iValue);
|
|
SetString(keyName, buf);
|
|
break;
|
|
case TYPE_UINT64:
|
|
Q_snprintf(buf, sizeof(buf), "%lld", *((uint64*)(dat->m_sValue)));
|
|
SetString(keyName, buf);
|
|
break;
|
|
|
|
case TYPE_WSTRING:
|
|
{
|
|
char wideBuf[512];
|
|
int result = Q_UnicodeToUTF8(dat->m_wsValue, wideBuf, 512);
|
|
if (result)
|
|
{
|
|
SetString(keyName, wideBuf);
|
|
}
|
|
else
|
|
{
|
|
return defaultValue;
|
|
}
|
|
break;
|
|
}
|
|
case TYPE_STRING:
|
|
break;
|
|
default:
|
|
return defaultValue;
|
|
};
|
|
|
|
return dat->m_sValue;
|
|
}
|
|
return defaultValue;
|
|
}
|
|
|
|
|
|
const char* KeyValues::GetName(void) const
|
|
{
|
|
return iff.keyValuesSystem->GetStringForSymbol(m_iKeyName);
|
|
}
|
|
|
|
int KeyValues::GetNameSymbol() const
|
|
{
|
|
return m_iKeyName;
|
|
}
|
|
|
|
bool KeyValues::LoadFromFile(IBaseFileSystem* filesystem, const char* resourceName, const char* pathID)
|
|
{
|
|
Assert(filesystem);
|
|
Assert(IsX360() || (IsPC() && _heapchk() == _HEAPOK));
|
|
|
|
FileHandle_t f = iff.g_pFullFileSystem->Open(resourceName, "rb", pathID);
|
|
if (!f)
|
|
return false;
|
|
|
|
s_LastFileLoadingFrom = (char*)resourceName;
|
|
|
|
int fileSize = filesystem->Size(f);
|
|
unsigned bufSize = ((IFileSystem*)filesystem)->GetOptimalReadSize(f, fileSize + 1);
|
|
|
|
char* buffer = (char*)((IFileSystem*)filesystem)->AllocOptimalReadBuffer(f, bufSize);
|
|
Assert(buffer);
|
|
|
|
bool bRetOK = (((IFileSystem*)filesystem)->ReadEx(buffer, bufSize, fileSize, f) != 0);
|
|
|
|
filesystem->Close(f);
|
|
|
|
if (bRetOK)
|
|
{
|
|
buffer[fileSize] = 0;
|
|
bRetOK = LoadFromBuffer(resourceName, buffer, filesystem);
|
|
}
|
|
|
|
((IFileSystem*)filesystem)->FreeOptimalReadBuffer(buffer);
|
|
|
|
return bRetOK;
|
|
}
|
|
|
|
void KeyValues::SetNextKey(KeyValues* pDat)
|
|
{
|
|
m_pPeer = pDat;
|
|
}
|
|
|
|
void KeyValues::CopySubkeys(KeyValues* pParent) const
|
|
{
|
|
KeyValues* pPrev = NULL;
|
|
for (KeyValues* sub = m_pSub; sub != NULL; sub = sub->m_pPeer)
|
|
{
|
|
KeyValues* dat = sub->MakeCopy();
|
|
|
|
if (pPrev)
|
|
pPrev->m_pPeer = dat;
|
|
else
|
|
pParent->m_pSub = dat;
|
|
dat->m_pPeer = NULL;
|
|
pPrev = dat;
|
|
}
|
|
}
|
|
|
|
KeyValues* KeyValues::MakeCopy(void) const
|
|
{
|
|
KeyValues* newKeyValue = new KeyValues(GetName());
|
|
|
|
newKeyValue->m_iDataType = m_iDataType;
|
|
switch (m_iDataType)
|
|
{
|
|
case TYPE_STRING:
|
|
{
|
|
if (m_sValue)
|
|
{
|
|
int len = Q_strlen(m_sValue);
|
|
Assert(!newKeyValue->m_sValue);
|
|
newKeyValue->m_sValue = new char[len + 1];
|
|
Q_memcpy(newKeyValue->m_sValue, m_sValue, len + 1);
|
|
}
|
|
}
|
|
break;
|
|
case TYPE_WSTRING:
|
|
{
|
|
if (m_wsValue)
|
|
{
|
|
int len = wcslen(m_wsValue);
|
|
newKeyValue->m_wsValue = new wchar_t[len + 1];
|
|
Q_memcpy(newKeyValue->m_wsValue, m_wsValue, (len + 1) * sizeof(wchar_t));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TYPE_INT:
|
|
newKeyValue->m_iValue = m_iValue;
|
|
break;
|
|
|
|
case TYPE_FLOAT:
|
|
newKeyValue->m_flValue = m_flValue;
|
|
break;
|
|
|
|
case TYPE_PTR:
|
|
newKeyValue->m_pValue = m_pValue;
|
|
break;
|
|
|
|
case TYPE_COLOR:
|
|
newKeyValue->m_Color[0] = m_Color[0];
|
|
newKeyValue->m_Color[1] = m_Color[1];
|
|
newKeyValue->m_Color[2] = m_Color[2];
|
|
newKeyValue->m_Color[3] = m_Color[3];
|
|
break;
|
|
|
|
case TYPE_UINT64:
|
|
newKeyValue->m_sValue = new char[sizeof(uint64)];
|
|
Q_memcpy(newKeyValue->m_sValue, m_sValue, sizeof(uint64));
|
|
break;
|
|
};
|
|
|
|
CopySubkeys(newKeyValue);
|
|
return newKeyValue;
|
|
}
|
|
|
|
|
|
|
|
const int MAX_ERROR_STACK = 64;
|
|
class CKeyValuesErrorStack
|
|
{
|
|
public:
|
|
CKeyValuesErrorStack() : m_pFilename("NULL"), m_errorIndex(0), m_maxErrorIndex(0) {}
|
|
|
|
void SetFilename(const char* pFilename)
|
|
{
|
|
m_pFilename = pFilename;
|
|
m_maxErrorIndex = 0;
|
|
}
|
|
|
|
int Push(int symName)
|
|
{
|
|
if (m_errorIndex < MAX_ERROR_STACK)
|
|
{
|
|
m_errorStack[m_errorIndex] = symName;
|
|
}
|
|
m_errorIndex++;
|
|
m_maxErrorIndex = max(m_maxErrorIndex, (m_errorIndex - 1));
|
|
return m_errorIndex - 1;
|
|
}
|
|
|
|
void Pop()
|
|
{
|
|
m_errorIndex--;
|
|
Assert(m_errorIndex >= 0);
|
|
}
|
|
|
|
void Reset(int stackLevel, int symName)
|
|
{
|
|
Assert(stackLevel >= 0 && stackLevel < m_errorIndex);
|
|
m_errorStack[stackLevel] = symName;
|
|
}
|
|
|
|
void ReportError(const char* pError)
|
|
{
|
|
printfdbg("KeyValues Error: %s in file %s\n", pError, m_pFilename);
|
|
for (int i = 0; i < m_maxErrorIndex; i++)
|
|
{
|
|
if (m_errorStack[i] != INVALID_KEY_SYMBOL)
|
|
{
|
|
if (i < m_errorIndex)
|
|
{
|
|
printfdbg("%s, ", iff.keyValuesSystem->GetStringForSymbol(m_errorStack[i]));
|
|
}
|
|
else
|
|
{
|
|
printfdbg("(*%s*), ", iff.keyValuesSystem->GetStringForSymbol(m_errorStack[i]));
|
|
}
|
|
}
|
|
}
|
|
printfdbg("\n");
|
|
}
|
|
|
|
private:
|
|
int m_errorStack[MAX_ERROR_STACK];
|
|
const char* m_pFilename;
|
|
int m_errorIndex;
|
|
int m_maxErrorIndex;
|
|
} g_KeyValuesErrorStack;
|
|
|
|
|
|
class CKeyErrorContext
|
|
{
|
|
public:
|
|
CKeyErrorContext(KeyValues* pKv)
|
|
{
|
|
Init(pKv->GetNameSymbol());
|
|
}
|
|
|
|
~CKeyErrorContext()
|
|
{
|
|
g_KeyValuesErrorStack.Pop();
|
|
}
|
|
CKeyErrorContext(int symName)
|
|
{
|
|
Init(symName);
|
|
}
|
|
void Reset(int symName)
|
|
{
|
|
g_KeyValuesErrorStack.Reset(m_stackLevel, symName);
|
|
}
|
|
private:
|
|
void Init(int symName)
|
|
{
|
|
m_stackLevel = g_KeyValuesErrorStack.Push(symName);
|
|
}
|
|
|
|
int m_stackLevel;
|
|
};
|
|
|
|
|
|
void KeyValues::RecursiveLoadFromBuffer(char const* resourceName, CUtlBuffer& buf)
|
|
{
|
|
CKeyErrorContext errorReport(this);
|
|
bool wasQuoted;
|
|
bool wasConditional;
|
|
CKeyErrorContext errorKey(INVALID_KEY_SYMBOL);
|
|
while (1)
|
|
{
|
|
bool bAccepted = true;
|
|
|
|
const char* name = ReadToken(buf, wasQuoted, wasConditional);
|
|
|
|
if (!name)
|
|
{
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got EOF instead of keyname");
|
|
break;
|
|
}
|
|
|
|
if (!*name)
|
|
{
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got empty keyname");
|
|
break;
|
|
}
|
|
|
|
if (*name == '}' && !wasQuoted)
|
|
break;
|
|
|
|
KeyValues* dat = CreateKey(name);
|
|
|
|
errorKey.Reset(dat->GetNameSymbol());
|
|
|
|
const char* value = ReadToken(buf, wasQuoted, wasConditional);
|
|
|
|
if (wasConditional && value)
|
|
{
|
|
bAccepted = EvaluateConditional(value);
|
|
|
|
value = ReadToken(buf, wasQuoted, wasConditional);
|
|
}
|
|
|
|
if (!value)
|
|
{
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got NULL key");
|
|
break;
|
|
}
|
|
|
|
if (*value == '}' && !wasQuoted)
|
|
{
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got } in key");
|
|
break;
|
|
}
|
|
|
|
if (*value == '{' && !wasQuoted)
|
|
{
|
|
errorKey.Reset(INVALID_KEY_SYMBOL);
|
|
dat->RecursiveLoadFromBuffer(resourceName, buf);
|
|
}
|
|
else
|
|
{
|
|
if (wasConditional)
|
|
{
|
|
g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got conditional between key and value");
|
|
break;
|
|
}
|
|
|
|
if (dat->m_sValue)
|
|
{
|
|
delete[] dat->m_sValue;
|
|
dat->m_sValue = NULL;
|
|
}
|
|
|
|
int len = Q_strlen(value);
|
|
|
|
char* pIEnd;
|
|
char* pFEnd;
|
|
const char* pSEnd = value + len;
|
|
|
|
int ival = strtol(value, &pIEnd, 10);
|
|
float fval = (float)strtod(value, &pFEnd);
|
|
|
|
if (*value == 0)
|
|
{
|
|
dat->m_iDataType = TYPE_STRING;
|
|
}
|
|
else if ((18 == len) && (value[0] == '0') && (value[1] == 'x'))
|
|
{
|
|
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)
|
|
{
|
|
dat->m_iValue = ival;
|
|
dat->m_iDataType = TYPE_INT;
|
|
}
|
|
else
|
|
{
|
|
dat->m_iDataType = TYPE_STRING;
|
|
}
|
|
|
|
if (dat->m_iDataType == TYPE_STRING)
|
|
{
|
|
dat->m_sValue = new char[len + 1];
|
|
Q_memcpy(dat->m_sValue, value, len + 1);
|
|
}
|
|
|
|
int prevPos = buf.TellGet();
|
|
const char* peek = ReadToken(buf, wasQuoted, wasConditional);
|
|
if (wasConditional)
|
|
{
|
|
bAccepted = EvaluateConditional(peek);
|
|
}
|
|
else
|
|
{
|
|
buf.SeekGet(CUtlBuffer::SEEK_HEAD, prevPos);
|
|
}
|
|
}
|
|
|
|
if (!bAccepted)
|
|
{
|
|
this->RemoveSubKey(dat);
|
|
dat->deleteThis();
|
|
dat = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void KeyValues::AppendIncludedKeys(CUtlVector< KeyValues* >& includedKeys)
|
|
{
|
|
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::RecursiveMergeKeyValues(KeyValues* baseKV)
|
|
{
|
|
for (KeyValues* baseChild = baseKV->m_pSub; baseChild != NULL; baseChild = baseChild->m_pPeer)
|
|
{
|
|
bool bFoundMatch = false;
|
|
|
|
for (KeyValues* newChild = m_pSub; newChild != NULL; newChild = newChild->m_pPeer)
|
|
{
|
|
if (!Q_strcmp(baseChild->GetName(), newChild->GetName()))
|
|
{
|
|
newChild->RecursiveMergeKeyValues(baseChild);
|
|
bFoundMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFoundMatch)
|
|
{
|
|
KeyValues* dat = baseChild->MakeCopy();
|
|
Assert(dat);
|
|
AddSubKey(dat);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool KeyValues::EvaluateConditional(const char* str)
|
|
{
|
|
bool bResult = false;
|
|
bool bXboxUI = IsX360();
|
|
|
|
if (bXboxUI)
|
|
{
|
|
bResult = !Q_stricmp("[$X360]", str);
|
|
}
|
|
else
|
|
{
|
|
bResult = !Q_stricmp("[$WIN32]", str);
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void KeyValues::MergeBaseKeys(CUtlVector< KeyValues* >& baseKeys)
|
|
{
|
|
int includeCount = baseKeys.Count();
|
|
int i;
|
|
for (i = 0; i < includeCount; i++)
|
|
{
|
|
KeyValues* kv = baseKeys[i];
|
|
Assert(kv);
|
|
|
|
RecursiveMergeKeyValues(kv);
|
|
}
|
|
}
|
|
|
|
|
|
void KeyValues::ParseIncludedKeys(char const* resourceName, const char* filetoinclude,
|
|
IBaseFileSystem* pFileSystem, const char* pPathID, CUtlVector< KeyValues* >& includedKeys)
|
|
{
|
|
Assert(resourceName);
|
|
Assert(filetoinclude);
|
|
Assert(pFileSystem);
|
|
if (!pFileSystem)
|
|
{
|
|
return;
|
|
}
|
|
char fullpath[512];
|
|
Q_strncpy(fullpath, resourceName, sizeof(fullpath));
|
|
bool done = false;
|
|
int len = Q_strlen(fullpath);
|
|
while (!done)
|
|
{
|
|
if (len <= 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (fullpath[len - 1] == '\\' ||
|
|
fullpath[len - 1] == '/')
|
|
{
|
|
break;
|
|
}
|
|
|
|
fullpath[len - 1] = 0;
|
|
--len;
|
|
}
|
|
|
|
Q_strncat(fullpath, filetoinclude, sizeof(fullpath), COPY_ALL_CHARACTERS);
|
|
|
|
KeyValues* newKV = new KeyValues(fullpath);
|
|
|
|
newKV->UsesEscapeSequences(m_bHasEscapeSequences != 0);
|
|
|
|
if (newKV->LoadFromFile(pFileSystem, fullpath, pPathID))
|
|
{
|
|
includedKeys.AddToTail(newKV);
|
|
}
|
|
else
|
|
{
|
|
printfdbg("KeyValues::ParseIncludedKeys: Couldn't load included keyvalue file %s\n", fullpath);
|
|
newKV->deleteThis();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int V_UnicodeToUTF8(const wchar_t* pUnicode, char* pUTF8, int cubDestSizeInBytes)
|
|
{
|
|
AssertValidReadPtr(pUnicode);
|
|
|
|
if (cubDestSizeInBytes > 0)
|
|
{
|
|
pUTF8[0] = 0;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
int cchResult = WideCharToMultiByte(CP_UTF8, 0, pUnicode, -1, pUTF8, cubDestSizeInBytes, NULL, NULL);
|
|
#elif POSIX
|
|
int cchResult = 0;
|
|
if (pUnicode && pUTF8)
|
|
cchResult = wcstombs(pUTF8, pUnicode, cubDestSizeInBytes) + 1;
|
|
#endif
|
|
|
|
if (cubDestSizeInBytes > 0)
|
|
{
|
|
pUTF8[cubDestSizeInBytes - 1] = 0;
|
|
}
|
|
|
|
return cchResult;
|
|
}
|
|
|
|
|
|
|
|
#define KEYVALUES_TOKEN_SIZE 1024
|
|
static char s_pTokenBuf[KEYVALUES_TOKEN_SIZE];
|
|
|
|
#pragma warning (disable:4706)
|
|
const char* KeyValues::ReadToken(CUtlBuffer& buf, bool& wasQuoted, bool& wasConditional)
|
|
{
|
|
wasQuoted = false;
|
|
wasConditional = false;
|
|
|
|
if (!buf.IsValid())
|
|
return NULL;
|
|
|
|
while (true)
|
|
{
|
|
buf.EatWhiteSpace();
|
|
if (!buf.IsValid())
|
|
return NULL;
|
|
|
|
if (!buf.EatCPPComment())
|
|
break;
|
|
}
|
|
|
|
const char* c = (const char*)buf.PeekGet(sizeof(char), 0);
|
|
if (!c)
|
|
return NULL;
|
|
|
|
if (*c == '\"')
|
|
{
|
|
wasQuoted = true;
|
|
buf.GetDelimitedString(m_bHasEscapeSequences ? GetCStringCharConversion() : GetNoEscCharConversion(),
|
|
s_pTokenBuf, KEYVALUES_TOKEN_SIZE);
|
|
return s_pTokenBuf;
|
|
}
|
|
|
|
if (*c == '{' || *c == '}')
|
|
{
|
|
s_pTokenBuf[0] = *c;
|
|
s_pTokenBuf[1] = 0;
|
|
buf.SeekGet(CUtlBuffer::SEEK_CURRENT, 1);
|
|
return s_pTokenBuf;
|
|
}
|
|
|
|
bool bReportedError = false;
|
|
bool bConditionalStart = false;
|
|
int nCount = 0;
|
|
while (c = (const char*)buf.PeekGet(sizeof(char), 0))
|
|
{
|
|
if (*c == 0)
|
|
break;
|
|
|
|
if (*c == '"' || *c == '{' || *c == '}')
|
|
break;
|
|
|
|
if (*c == '[')
|
|
bConditionalStart = true;
|
|
|
|
if (*c == ']' && bConditionalStart)
|
|
{
|
|
wasConditional = true;
|
|
}
|
|
|
|
if (isspace(*c))
|
|
break;
|
|
|
|
if (nCount < (KEYVALUES_TOKEN_SIZE - 1))
|
|
{
|
|
s_pTokenBuf[nCount++] = *c;
|
|
}
|
|
else if (!bReportedError)
|
|
{
|
|
bReportedError = true;
|
|
g_KeyValuesErrorStack.ReportError(" ReadToken overflow");
|
|
}
|
|
|
|
buf.SeekGet(CUtlBuffer::SEEK_CURRENT, 1);
|
|
}
|
|
s_pTokenBuf[nCount] = 0;
|
|
return s_pTokenBuf;
|
|
}
|
|
#pragma warning (default:4706)
|
|
|
|
|
|
bool KeyValues::LoadFromBuffer(char const* resourceName, CUtlBuffer& buf, IBaseFileSystem* pFileSystem, const char* pPathID)
|
|
{
|
|
KeyValues* pPreviousKey = NULL;
|
|
KeyValues* pCurrentKey = this;
|
|
CUtlVector< KeyValues* > includedKeys;
|
|
CUtlVector< KeyValues* > baseKeys;
|
|
bool wasQuoted;
|
|
bool wasConditional;
|
|
g_KeyValuesErrorStack.SetFilename(resourceName);
|
|
do
|
|
{
|
|
bool bAccepted = true;
|
|
|
|
const char* s = ReadToken(buf, wasQuoted, wasConditional);
|
|
if (!buf.IsValid() || !s || *s == 0)
|
|
break;
|
|
|
|
if (!Q_stricmp(s, "#include"))
|
|
{
|
|
s = ReadToken(buf, wasQuoted, wasConditional);
|
|
if (!s || *s == 0)
|
|
{
|
|
g_KeyValuesErrorStack.ReportError("#include is NULL ");
|
|
}
|
|
else
|
|
{
|
|
ParseIncludedKeys(resourceName, s, pFileSystem, pPathID, includedKeys);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else if (!Q_stricmp(s, "#base"))
|
|
{
|
|
s = ReadToken(buf, wasQuoted, wasConditional);
|
|
if (!s || *s == 0)
|
|
{
|
|
g_KeyValuesErrorStack.ReportError("#base is NULL ");
|
|
}
|
|
else
|
|
{
|
|
ParseIncludedKeys(resourceName, s, pFileSystem, pPathID, baseKeys);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!pCurrentKey)
|
|
{
|
|
pCurrentKey = new KeyValues(s);
|
|
Assert(pCurrentKey);
|
|
|
|
pCurrentKey->UsesEscapeSequences(m_bHasEscapeSequences != 0);
|
|
|
|
if (pPreviousKey)
|
|
{
|
|
pPreviousKey->SetNextKey(pCurrentKey);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pCurrentKey->SetName(s);
|
|
}
|
|
|
|
s = ReadToken(buf, wasQuoted, wasConditional);
|
|
|
|
if (wasConditional)
|
|
{
|
|
bAccepted = EvaluateConditional(s);
|
|
|
|
s = ReadToken(buf, wasQuoted, wasConditional);
|
|
}
|
|
|
|
if (s && *s == '{' && !wasQuoted)
|
|
{
|
|
pCurrentKey->RecursiveLoadFromBuffer(resourceName, buf);
|
|
}
|
|
else
|
|
{
|
|
g_KeyValuesErrorStack.ReportError("LoadFromBuffer: missing {");
|
|
}
|
|
|
|
if (!bAccepted)
|
|
{
|
|
if (pPreviousKey)
|
|
{
|
|
pPreviousKey->SetNextKey(NULL);
|
|
}
|
|
pCurrentKey->Clear();
|
|
}
|
|
else
|
|
{
|
|
pPreviousKey = pCurrentKey;
|
|
pCurrentKey = NULL;
|
|
}
|
|
} while (buf.IsValid());
|
|
|
|
AppendIncludedKeys(includedKeys);
|
|
{
|
|
int i;
|
|
for (i = includedKeys.Count() - 1; i > 0; i--)
|
|
{
|
|
KeyValues* kv = includedKeys[i];
|
|
kv->deleteThis();
|
|
}
|
|
}
|
|
|
|
MergeBaseKeys(baseKeys);
|
|
{
|
|
int i;
|
|
for (i = baseKeys.Count() - 1; i >= 0; i--)
|
|
{
|
|
KeyValues* kv = baseKeys[i];
|
|
kv->deleteThis();
|
|
}
|
|
}
|
|
|
|
g_KeyValuesErrorStack.SetFilename("");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool KeyValues::LoadFromBuffer(char const* resourceName, const char* pBuffer, IBaseFileSystem* pFileSystem, const char* pPathID)
|
|
{
|
|
if (!pBuffer)
|
|
return true;
|
|
|
|
int nLen = Q_strlen(pBuffer);
|
|
CUtlBuffer buf(pBuffer, nLen, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER);
|
|
return LoadFromBuffer(resourceName, buf, pFileSystem, pPathID);
|
|
}
|