aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobTheBob <32057864+BobTheBob9@users.noreply.github.com>2022-10-31 23:15:26 +0000
committerGitHub <noreply@github.com>2022-10-31 23:15:26 +0000
commitc8181a1719fefc8b9d7ed52888f6a207fcdf026f (patch)
treeca4a26d2028bcbe9d7c5a33462834ed1217df9a8
parentd3adc574608540bf089f88fb40b798b60edaadad (diff)
downloadNorthstarLauncher-c8181a1719fefc8b9d7ed52888f6a207fcdf026f.tar.gz
NorthstarLauncher-c8181a1719fefc8b9d7ed52888f6a207fcdf026f.zip
add KeyValues class (#303)
-rw-r--r--NorthstarDLL/NorthstarDLL.vcxproj11
-rw-r--r--NorthstarDLL/NorthstarDLL.vcxproj.filters18
-rw-r--r--NorthstarDLL/keyvalues.cpp1448
-rw-r--r--NorthstarDLL/keyvalues.h134
-rw-r--r--NorthstarDLL/modkeyvalues.cpp107
-rw-r--r--NorthstarDLL/modpdef.cpp (renamed from NorthstarDLL/pdef.cpp)4
-rw-r--r--NorthstarDLL/modscriptsrson.cpp (renamed from NorthstarDLL/scriptsrson.cpp)4
-rw-r--r--NorthstarDLL/pdef.h4
-rw-r--r--NorthstarDLL/r2engine.h3
-rw-r--r--NorthstarDLL/scriptsrson.h4
10 files changed, 1580 insertions, 157 deletions
diff --git a/NorthstarDLL/NorthstarDLL.vcxproj b/NorthstarDLL/NorthstarDLL.vcxproj
index 9ce5da3c..d39fd68b 100644
--- a/NorthstarDLL/NorthstarDLL.vcxproj
+++ b/NorthstarDLL/NorthstarDLL.vcxproj
@@ -94,6 +94,7 @@
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <Optimization>Disabled</Optimization>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -120,6 +121,7 @@
<ClInclude Include="bitbuf.h" />
<ClInclude Include="bits.h" />
<ClInclude Include="crashhandler.h" />
+ <ClInclude Include="keyvalues.h" />
<ClInclude Include="squirreldatatypes.h" />
<ClInclude Include="limits.h" />
<ClInclude Include="maxplayers.h" />
@@ -547,10 +549,8 @@
<ClInclude Include="misccommands.h" />
<ClInclude Include="modmanager.h" />
<ClInclude Include="pch.h" />
- <ClInclude Include="pdef.h" />
<ClInclude Include="playlist.h" />
<ClInclude Include="rpakfilesystem.h" />
- <ClInclude Include="scriptsrson.h" />
<ClInclude Include="serverauthentication.h" />
<ClInclude Include="serverpresence.h" />
<ClInclude Include="sourceconsole.h" />
@@ -573,6 +573,7 @@
<ClCompile Include="clientvideooverrides.cpp" />
<ClCompile Include="concommand.cpp" />
<ClCompile Include="exploitfixes_lzss.cpp" />
+ <ClCompile Include="keyvalues.cpp" />
<ClCompile Include="limits.cpp" />
<ClCompile Include="memory.cpp" />
<ClCompile Include="nsprefix.cpp" />
@@ -588,7 +589,7 @@
<ClCompile Include="hooks.cpp" />
<ClCompile Include="host.cpp" />
<ClCompile Include="hoststate.cpp" />
- <ClCompile Include="keyvalues.cpp" />
+ <ClCompile Include="modkeyvalues.cpp" />
<ClCompile Include="latencyflex.cpp" />
<ClCompile Include="localchatwriter.cpp" />
<ClCompile Include="printmaps.cpp" />
@@ -605,7 +606,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
- <ClCompile Include="pdef.cpp" />
+ <ClCompile Include="modpdef.cpp" />
<ClCompile Include="playlist.cpp" />
<ClCompile Include="plugins.cpp" />
<ClCompile Include="printcommands.cpp" />
@@ -620,7 +621,7 @@
<ClCompile Include="scriptmainmenupromos.cpp" />
<ClCompile Include="scriptmodmenu.cpp" />
<ClCompile Include="scriptserverbrowser.cpp" />
- <ClCompile Include="scriptsrson.cpp" />
+ <ClCompile Include="modscriptsrson.cpp" />
<ClCompile Include="scriptutility.cpp" />
<ClCompile Include="serverauthentication.cpp" />
<ClCompile Include="miscserverscript.cpp" />
diff --git a/NorthstarDLL/NorthstarDLL.vcxproj.filters b/NorthstarDLL/NorthstarDLL.vcxproj.filters
index 9b5837d2..f5d5bd5d 100644
--- a/NorthstarDLL/NorthstarDLL.vcxproj.filters
+++ b/NorthstarDLL/NorthstarDLL.vcxproj.filters
@@ -1446,9 +1446,6 @@
<ClInclude Include="modmanager.h">
<Filter>Header Files\Mods</Filter>
</ClInclude>
- <ClInclude Include="pdef.h">
- <Filter>Header Files\Mods\Compiled Assets</Filter>
- </ClInclude>
<ClInclude Include="printcommand.h">
<Filter>Header Files\Console</Filter>
</ClInclude>
@@ -1464,9 +1461,6 @@
<ClInclude Include="r2server.h">
<Filter>Header Files\Game Functions</Filter>
</ClInclude>
- <ClInclude Include="scriptsrson.h">
- <Filter>Header Files\Mods\Compiled Assets</Filter>
- </ClInclude>
<ClInclude Include="tier0.h">
<Filter>Header Files\Game Functions</Filter>
</ClInclude>
@@ -1494,6 +1488,9 @@
<ClInclude Include="vector.h">
<Filter>Header Files\Math</Filter>
</ClInclude>
+ <ClInclude Include="keyvalues.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -1508,13 +1505,13 @@
<ClCompile Include="modmanager.cpp">
<Filter>Source Files\Mods</Filter>
</ClCompile>
- <ClCompile Include="scriptsrson.cpp">
+ <ClCompile Include="modscriptsrson.cpp">
<Filter>Source Files\Mods\Compiled Assets</Filter>
</ClCompile>
<ClCompile Include="serverauthentication.cpp">
<Filter>Source Files\Server\Authentication</Filter>
</ClCompile>
- <ClCompile Include="keyvalues.cpp">
+ <ClCompile Include="modkeyvalues.cpp">
<Filter>Source Files\Mods\Compiled Assets</Filter>
</ClCompile>
<ClCompile Include="chatcommand.cpp">
@@ -1526,7 +1523,7 @@
<ClCompile Include="dedicatedmaterialsystem.cpp">
<Filter>Source Files\Dedicated Server</Filter>
</ClCompile>
- <ClCompile Include="pdef.cpp">
+ <ClCompile Include="modpdef.cpp">
<Filter>Source Files\Mods\Compiled Assets</Filter>
</ClCompile>
<ClCompile Include="clientauthhooks.cpp">
@@ -1700,6 +1697,9 @@
<ClCompile Include="scriptdatatables.cpp">
<Filter>Source Files\Scripted</Filter>
</ClCompile>
+ <ClCompile Include="keyvalues.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<MASM Include="audio_asm.asm">
diff --git a/NorthstarDLL/keyvalues.cpp b/NorthstarDLL/keyvalues.cpp
index 98a9ce66..afb7ae30 100644
--- a/NorthstarDLL/keyvalues.cpp
+++ b/NorthstarDLL/keyvalues.cpp
@@ -1,132 +1,1316 @@
-#include "pch.h"
-#include "modmanager.h"
-#include "filesystem.h"
-
-#include <fstream>
-
-AUTOHOOK_INIT()
-
-void ModManager::TryBuildKeyValues(const char* filename)
-{
- spdlog::info("Building KeyValues for file {}", filename);
-
- std::string normalisedPath = g_pModManager->NormaliseModFilePath(fs::path(filename));
- fs::path compiledPath = GetCompiledAssetsPath() / filename;
- fs::path compiledDir = compiledPath.parent_path();
- fs::create_directories(compiledDir);
-
- fs::path kvPath(filename);
- std::string ogFilePath = "mod_original_";
- ogFilePath += kvPath.filename().string();
-
- std::string newKvs = "// AUTOGENERATED: MOD PATCH KV\n";
-
- int patchNum = 0;
-
- // copy over patch kv files, and add #bases to new file, last mods' patches should be applied first
- // note: #include should be identical but it's actually just broken, thanks respawn
- for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
- {
- if (!m_LoadedMods[i].m_bEnabled)
- continue;
-
- size_t fileHash = STR_HASH(normalisedPath);
- auto modKv = m_LoadedMods[i].KeyValues.find(fileHash);
- if (modKv != m_LoadedMods[i].KeyValues.end())
- {
- // should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt"
-
- std::string patchFilePath = "mod_patch_";
- patchFilePath += std::to_string(patchNum++);
- patchFilePath += "_";
- patchFilePath += kvPath.filename().string();
-
- newKvs += "#base \"";
- newKvs += patchFilePath;
- newKvs += "\"\n";
-
- fs::remove(compiledDir / patchFilePath);
-
- fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
- }
- }
-
- // add original #base last, #bases don't override preexisting keys, including the ones we've just done
- newKvs += "#base \"";
- newKvs += ogFilePath;
- newKvs += "\"\n";
-
- // load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons)
- std::string originalFile = R2::ReadVPKOriginalFile(filename);
-
- if (!originalFile.length())
- {
- spdlog::warn("Tried to patch kv {} but no base kv was found!", ogFilePath);
- return;
- }
-
- char rootName[64];
- memset(rootName, 0, sizeof(rootName));
-
- // iterate until we hit an ascii char that isn't in a # command or comment to get root obj name
- int i = 0;
- while (!(originalFile[i] >= 65 && originalFile[i] <= 122))
- {
- // if we hit a comment or # thing, iterate until end of line
- if (originalFile[i] == '/' || originalFile[i] == '#')
- while (originalFile[i] != '\n')
- i++;
-
- i++;
- }
-
- int j = 0;
- for (int j = 0; originalFile[i] >= 65 && originalFile[i] <= 122; j++)
- rootName[j] = originalFile[i++];
-
- // empty kv, all the other stuff gets #base'd
- newKvs += rootName;
- newKvs += "\n{\n}\n";
-
- std::ofstream originalFileWriteStream(compiledDir / ogFilePath, std::ios::binary);
- originalFileWriteStream << originalFile;
- originalFileWriteStream.close();
-
- std::ofstream writeStream(compiledPath, std::ios::binary);
- writeStream << newKvs;
- writeStream.close();
-
- ModOverrideFile overrideFile;
- overrideFile.m_pOwningMod = nullptr;
- overrideFile.m_Path = normalisedPath;
-
- if (m_ModFiles.find(normalisedPath) == m_ModFiles.end())
- m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile));
- else
- m_ModFiles[normalisedPath] = overrideFile;
-}
-
-// clang-format off
-AUTOHOOK(KeyValues__LoadFromBuffer, engine.dll + 0x426C30,
-char, __fastcall, (void* self, const char* resourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7))
-// clang-format on
-{
- static void* pSavedFilesystemPtr = nullptr;
-
- // this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of
- // LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it
- // from a function call that always happens before playlists is loaded
-
- // note: would be better if we could serialize this to disk for playlists, as this method breaks saving playlists in demos
- if (pFileSystem != nullptr)
- pSavedFilesystemPtr = pFileSystem;
- if (!pFileSystem && !strcmp(resourceName, "playlists"))
- pFileSystem = pSavedFilesystemPtr;
-
- return KeyValues__LoadFromBuffer(self, resourceName, pBuffer, pFileSystem, a5, a6, a7);
-}
-
-ON_DLL_LOAD("engine.dll", KeyValues, (CModule module))
-{
- AUTOHOOK_DISPATCH()
-}
+#include "pch.h"
+#include "keyvalues.h"
+#include <winnt.h>
+
+// implementation of the ConVar class
+// heavily based on https://github.com/Mauler125/r5sdk/blob/master/r5dev/vpc/keyvalues.cpp
+
+typedef int HKeySymbol;
+#define INVALID_KEY_SYMBOL (-1)
+
+#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_t)(x3); \
+ x2 = (uint16_t)((x3) >> 8); \
+ } while (0)
+
+struct CKeyValuesSystem
+{
+ public:
+ struct __VTable
+ {
+ char pad0[8 * 3]; // 2 methods
+ HKeySymbol (*GetSymbolForString)(CKeyValuesSystem* self, const char* name, bool bCreate);
+ const char* (*GetStringForSymbol)(CKeyValuesSystem* self, HKeySymbol symbol);
+ char pad1[8 * 5];
+ HKeySymbol (*GetSymbolForStringCaseSensitive)(
+ CKeyValuesSystem* self, HKeySymbol& hCaseInsensitiveSymbol, const char* name, bool bCreate);
+ };
+
+ const __VTable* m_pVtable;
+};
+
+int (*V_UTF8ToUnicode)(const char* pUTF8, wchar_t* pwchDest, int cubDestSizeInBytes);
+int (*V_UnicodeToUTF8)(const wchar_t* pUnicode, char* pUTF8, int cubDestSizeInBytes);
+CKeyValuesSystem* (*KeyValuesSystem)();
+
+KeyValues::KeyValues() {} // default constructor for copying and such
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+// Input : *pszSetName -
+//-----------------------------------------------------------------------------
+KeyValues::KeyValues(const char* pszSetName)
+{
+ Init();
+ SetName(pszSetName);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+// Input : *pszSetName -
+// *pszFirstKey -
+// *pszFirstValue -
+//-----------------------------------------------------------------------------
+KeyValues::KeyValues(const char* pszSsetName, const char* pszFirstKey, const char* pszFirstValue)
+{
+ Init();
+ SetName(pszSsetName);
+ SetString(pszFirstKey, pszFirstValue);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+// Input : *pszSetName -
+// *pszFirstKey -
+// *pwszFirstValue -
+//-----------------------------------------------------------------------------
+KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, const wchar_t* pwszFirstValue)
+{
+ Init();
+ SetName(pszSetName);
+ SetWString(pszFirstKey, pwszFirstValue);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+// Input : *pszSetName -
+// *pszFirstKey -
+// iFirstValue -
+//-----------------------------------------------------------------------------
+KeyValues::KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue)
+{
+ 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)
+{
+ 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)
+{
+ Init();
+ SetName(pszSetName);
+ SetInt(pszFirstKey, iFirstValue);
+ SetInt(pszSecondKey, iSecondValue);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+KeyValues::~KeyValues(void)
+{
+ RemoveEverything();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize member variables
+//-----------------------------------------------------------------------------
+void KeyValues::Init(void)
+{
+ 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)
+{
+ delete m_pSub;
+ 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)
+{
+ delete this;
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+ delete dat;
+ }
+
+ for (dat = m_pPeer; dat && dat != this; dat = datNext)
+ {
+ datNext = dat->m_pPeer;
+ dat->m_pPeer = nullptr;
+ delete dat;
+ }
+
+ delete[] m_sValue;
+ m_sValue = nullptr;
+ delete[] m_wsValue;
+ m_wsValue = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// 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)
+// Input : *pszKeyName -
+// bCreate -
+// Output : *KeyValues
+//-----------------------------------------------------------------------------
+KeyValues* KeyValues::FindKey(const char* pszKeyName, bool bCreate)
+{
+ assert(this, "Member function called on NULL KeyValues");
+
+ if (!pszKeyName || !*pszKeyName)
+ return this;
+
+ const char* pSubStr = strchr(pszKeyName, '/');
+
+ HKeySymbol iSearchStr = KeyValuesSystem()->m_pVtable->GetSymbolForString(KeyValuesSystem(), pszKeyName, bCreate);
+ if (iSearchStr == INVALID_KEY_SYMBOL)
+ {
+ // not found, couldn't possibly be in key value list
+ return nullptr;
+ }
+
+ KeyValues* pLastKVs = nullptr;
+ KeyValues* pCurrentKVs;
+ // find the searchStr in the current peer list
+ for (pCurrentKVs = m_pSub; pCurrentKVs != NULL; pCurrentKVs = pCurrentKVs->m_pPeer)
+ {
+ pLastKVs = pCurrentKVs; // record the last item looked at (for if we need to append to the end of the list)
+
+ // symbol compare
+ if (pLastKVs->m_iKeyName == (uint32_t)iSearchStr)
+ break;
+ }
+
+ if (!pCurrentKVs && m_pChain)
+ pCurrentKVs = m_pChain->FindKey(pszKeyName, false);
+
+ // make sure a key was found
+ if (!pCurrentKVs)
+ {
+ if (bCreate)
+ {
+ // we need to create a new key
+ pCurrentKVs = new KeyValues(pszKeyName);
+ // Assert(dat != NULL);
+
+ // insert new key at end of list
+ if (pLastKVs)
+ pLastKVs->m_pPeer = pCurrentKVs;
+ else
+ m_pSub = pCurrentKVs;
+
+ pCurrentKVs->m_pPeer = NULL;
+
+ // a key graduates to be a submsg as soon as it's m_pSub is set
+ // this should be the only place m_pSub is set
+ m_iDataType = TYPE_NONE;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+ // if we've still got a subStr we need to keep looking deeper in the tree
+ if (pSubStr)
+ {
+ // recursively chain down through the paths in the string
+ return pCurrentKVs->FindKey(pSubStr + 1, bCreate);
+ }
+
+ return pCurrentKVs;
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+ delete pSubKey;
+ }
+ 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;
+ delete pSubKey;
+ }
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the name of the current key section
+// Output : const char*
+//-----------------------------------------------------------------------------
+const char* KeyValues::GetName(void) const
+{
+ return KeyValuesSystem()->m_pVtable->GetStringForSymbol(
+ KeyValuesSystem(), MAKE_3_BYTES_FROM_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2));
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the integer value of a keyName. Default value is returned
+// if the keyName can't be found.
+// Input : *pszKeyName -
+// nDefaultValue -
+// Output : int
+//-----------------------------------------------------------------------------
+int KeyValues::GetInt(const char* pszKeyName, int iDefaultValue)
+{
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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)
+ {
+ 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;
+ };
+ }
+ return pDefaultValue;
+}
+
+//-----------------------------------------------------------------------------
+// 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_t*>(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", reinterpret_cast<uint64_t>(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_t*>(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, ARRAYSIZE(wbuf), L"%f", pKey->m_flValue);
+ SetWString(pszKeyName, wbuf);
+ break;
+ case TYPE_PTR:
+ swprintf(wbuf, ARRAYSIZE(wbuf), L"%lld", static_cast<int64_t>(reinterpret_cast<size_t>(pKey->m_pValue)));
+ SetWString(pszKeyName, wbuf);
+ break;
+ case TYPE_INT:
+ swprintf(wbuf, ARRAYSIZE(wbuf), L"%d", pKey->m_iValue);
+ SetWString(pszKeyName, wbuf);
+ break;
+ case TYPE_UINT64:
+ {
+ swprintf(wbuf, ARRAYSIZE(wbuf), L"%lld", *(reinterpret_cast<uint64_t*>(pKey->m_sValue)));
+ SetWString(pszKeyName, wbuf);
+ }
+ break;
+ case TYPE_COLOR:
+ swprintf(wbuf, 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;
+ wchar_t* pWBuf = new wchar_t[bufSize];
+ int result = V_UTF8ToUnicode(pKey->m_sValue, pWBuf, static_cast<int>(bufSize * sizeof(wchar_t)));
+ if (result >= 0) // may be a zero length string
+ {
+ SetWString(pszKeyName, pWBuf);
+ delete[] pWBuf;
+ }
+ else
+ {
+ delete[] pWBuf;
+ return pwszDefaultValue;
+ }
+
+ 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);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the integer value of a keyName.
+// Input : *pszKeyName -
+// iValue -
+//-----------------------------------------------------------------------------
+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)
+{
+ KeyValues* pKey = FindKey(pszKeyName, true);
+
+ if (pKey)
+ {
+ // delete the old value
+ delete[] pKey->m_sValue;
+ // make sure we're not storing the WSTRING - as we're converting over to STRING
+ delete[] pKey->m_wsValue;
+ pKey->m_wsValue = nullptr;
+
+ pKey->m_sValue = new char[sizeof(uint64_t)];
+ *(reinterpret_cast<uint64_t*>(pKey->m_sValue)) = nValue;
+ pKey->m_iDataType = TYPE_UINT64;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the float value of a keyName.
+// Input : *pszKeyName -
+// flValue -
+//-----------------------------------------------------------------------------
+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()->m_pVtable->GetSymbolForStringCaseSensitive(KeyValuesSystem(), hCaseInsensitiveKeyName, pszSetName, false);
+
+ 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
+ delete[] m_sValue;
+ // make sure we're not storing the WSTRING - as we're converting over to STRING
+ delete[] m_wsValue;
+ 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);
+ m_sValue = new char[len + 1];
+ 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
+ delete[] pKey->m_wsValue;
+ // make sure we're not storing the STRING - as we're converting over to WSTRING
+ delete[] pKey->m_sValue;
+ 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);
+ pKey->m_wsValue = new wchar_t[len + 1];
+ 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];
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+ m_sValue = new char[len];
+ 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;
+ m_sValue = new char[len];
+ 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;
+ m_sValue = new char[len];
+ strncpy(m_sValue, buf, len);
+ }
+ break;
+ case TYPE_PTR:
+ {
+ m_pValue = src.m_pValue;
+ }
+ break;
+ case TYPE_UINT64:
+ {
+ m_sValue = new char[sizeof(uint64_t)];
+ 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)
+ {
+ m_pSub = new KeyValues;
+
+ m_pSub->Init();
+ m_pSub->SetName(nullptr);
+
+ m_pSub->RecursiveCopyKeyValues(*src.m_pSub);
+ }
+
+ // Handle the immediate peer
+ if (src.m_pPeer)
+ {
+ m_pPeer = new KeyValues;
+
+ m_pPeer->Init();
+ m_pPeer->SetName(nullptr);
+
+ m_pPeer->RecursiveCopyKeyValues(*src.m_pPeer);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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
+{
+ KeyValues* pNewKeyValue = new KeyValues;
+
+ pNewKeyValue->Init();
+ pNewKeyValue->SetName(GetName());
+
+ // copy data
+ pNewKeyValue->m_iDataType = m_iDataType;
+ switch (m_iDataType)
+ {
+ case TYPE_STRING:
+ {
+ if (m_sValue)
+ {
+ size_t len = strlen(m_sValue);
+ assert(!pNewKeyValue->m_sValue);
+ pNewKeyValue->m_sValue = new char[len + 1];
+ memcpy(pNewKeyValue->m_sValue, m_sValue, len + 1);
+ }
+ }
+ break;
+ case TYPE_WSTRING:
+ {
+ if (m_wsValue)
+ {
+ size_t len = wcslen(m_wsValue);
+ pNewKeyValue->m_wsValue = new wchar_t[len + 1];
+ memcpy(pNewKeyValue->m_wsValue, m_wsValue, len + 1 * sizeof(wchar_t));
+ }
+ }
+ 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:
+ pNewKeyValue->m_sValue = new char[sizeof(uint64_t)];
+ memcpy(pNewKeyValue->m_sValue, m_sValue, sizeof(uint64_t));
+ break;
+ };
+
+ // recursively copy subkeys
+ CopySubkeys(pNewKeyValue);
+ return pNewKeyValue;
+}
+
+ON_DLL_LOAD("vstdlib.dll", KeyValues, (CModule module))
+{
+ V_UTF8ToUnicode = module.GetExport("V_UTF8ToUnicode").As<int (*)(const char*, wchar_t*, int)>();
+ V_UnicodeToUTF8 = module.GetExport("V_UnicodeToUTF8").As<int (*)(const wchar_t*, char*, int)>();
+ KeyValuesSystem = module.GetExport("KeyValuesSystem").As<CKeyValuesSystem* (*)()>();
+}
+
+AUTOHOOK_INIT()
+
+// clang-format off
+AUTOHOOK(KeyValues__LoadFromBuffer, engine.dll + 0x426C30,
+char, __fastcall, (KeyValues* self, const char* pResourceName, const char* pBuffer, void* pFileSystem, void* a5, void* a6, int a7))
+// clang-format on
+{
+ static void* pSavedFilesystemPtr = nullptr;
+
+ // this is just to allow playlists to get a valid pFileSystem ptr for kv building, other functions that call this particular overload of
+ // LoadFromBuffer seem to get called on network stuff exclusively not exactly sure what the address wanted here is, so just taking it
+ // from a function call that always happens before playlists is loaded
+
+ // note: would be better if we could serialize this to disk for playlists, as this method breaks saving playlists in demos
+ if (pFileSystem != nullptr)
+ pSavedFilesystemPtr = pFileSystem;
+ if (!pFileSystem && !strcmp(pResourceName, "playlists"))
+ pFileSystem = pSavedFilesystemPtr;
+
+ return KeyValues__LoadFromBuffer(self, pResourceName, pBuffer, pFileSystem, a5, a6, a7);
+}
+
+ON_DLL_LOAD("engine.dll", EngineKeyValues, (CModule module))
+{
+ AUTOHOOK_DISPATCH()
+}
diff --git a/NorthstarDLL/keyvalues.h b/NorthstarDLL/keyvalues.h
new file mode 100644
index 00000000..3ea682d6
--- /dev/null
+++ b/NorthstarDLL/keyvalues.h
@@ -0,0 +1,134 @@
+#pragma once
+#include "color.h"
+
+enum KeyValuesTypes_t : char
+{
+ TYPE_NONE = 0x0,
+ TYPE_STRING = 0x1,
+ TYPE_INT = 0x2,
+ TYPE_FLOAT = 0x3,
+ TYPE_PTR = 0x4,
+ TYPE_WSTRING = 0x5,
+ TYPE_COLOR = 0x6,
+ TYPE_UINT64 = 0x7,
+ TYPE_COMPILED_INT_BYTE = 0x8,
+ TYPE_COMPILED_INT_0 = 0x9,
+ TYPE_COMPILED_INT_1 = 0xA,
+ TYPE_NUMTYPES = 0xB,
+};
+
+enum MergeKeyValuesOp_t
+{
+ MERGE_KV_ALL,
+ MERGE_KV_UPDATE, // update values are copied into storage, adding new keys to storage or updating existing ones
+ MERGE_KV_DELETE, // update values specify keys that get deleted from storage
+ MERGE_KV_BORROW, // update values only update existing keys in storage, keys in update that do not exist in storage are discarded
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Simple recursive data access class
+// Used in vgui for message parameters and resource files
+// Destructor deletes all child KeyValues nodes
+// Data is stored in key (string names) - (string/int/float)value pairs called nodes.
+//
+// About KeyValues Text File Format:
+
+// It has 3 control characters '{', '}' and '"'. Names and values may be quoted or
+// not. The quote '"' character must not be used within name or values, only for
+// quoting whole tokens. You may use escape sequences wile parsing and add within a
+// quoted token a \" to add quotes within your name or token. When using Escape
+// Sequence the parser must now that by setting KeyValues::UsesEscapeSequences( true ),
+// which it's off by default. Non-quoted tokens ends with a whitespace, '{', '}' and '"'.
+// So you may use '{' and '}' within quoted tokens, but not for non-quoted tokens.
+// An open bracket '{' after a key name indicates a list of subkeys which is finished
+// with a closing bracket '}'. Subkeys use the same definitions recursively.
+// Whitespaces are space, return, newline and tabulator. Allowed Escape sequences
+// are \n, \t, \\, \n and \". The number character '#' is used for macro purposes
+// (eg #include), don't use it as first character in key names.
+//-----------------------------------------------------------------------------
+class KeyValues
+{
+ private:
+ KeyValues(); // for internal use only
+
+ public:
+ // Constructors/destructors
+ KeyValues(const char* pszSetName);
+ KeyValues(const char* pszSetName, const char* pszFirstKey, const char* pszFirstValue);
+ KeyValues(const char* pszSetName, const char* pszFirstKey, const wchar_t* pwszFirstValue);
+ KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue);
+ KeyValues(
+ const char* pszSetName, const char* pszFirstKey, const char* pszFirstValue, const char* pszSecondKey, const char* pszSecondValue);
+ KeyValues(const char* pszSetName, const char* pszFirstKey, int iFirstValue, const char* pszSecondKey, int iSecondValue);
+ ~KeyValues(void);
+
+ void Init(void);
+ void Clear(void);
+ void DeleteThis(void);
+ void RemoveEverything();
+
+ KeyValues* FindKey(const char* pKeyName, bool bCreate = false);
+ KeyValues* FindLastSubKey(void) const;
+
+ void AddSubKey(KeyValues* pSubkey);
+ void RemoveSubKey(KeyValues* pSubKey);
+ void InsertSubKey(int nIndex, KeyValues* pSubKey);
+ bool ContainsSubKey(KeyValues* pSubKey);
+ void SwapSubKey(KeyValues* pExistingSubkey, KeyValues* pNewSubKey);
+ void ElideSubKey(KeyValues* pSubKey);
+
+ // Data access
+ bool IsEmpty(const char* pszKeyName);
+ KeyValues* GetFirstTrueSubKey(void) const;
+ KeyValues* GetNextTrueSubKey(void) const;
+ KeyValues* GetFirstValue(void) const;
+ KeyValues* GetNextValue(void) const;
+ KeyValues* GetFirstSubKey() const;
+ KeyValues* GetNextKey() const;
+ const char* GetName(void) const;
+ int GetInt(const char* pszKeyName, int iDefaultValue);
+ uint64_t GetUint64(const char* pszKeyName, uint64_t nDefaultValue);
+ void* GetPtr(const char* pszKeyName, void* pDefaultValue);
+ float GetFloat(const char* pszKeyName, float flDefaultValue);
+ const char* GetString(const char* pszKeyName = nullptr, const char* pszDefaultValue = "");
+ const wchar_t* GetWString(const char* pszKeyName = nullptr, const wchar_t* pwszDefaultValue = L"");
+ Color GetColor(const char* pszKeyName, const Color& defaultColor);
+ KeyValuesTypes_t GetDataType(const char* pszKeyName);
+ KeyValuesTypes_t GetDataType(void) const;
+
+ // Key writing
+ void SetInt(const char* pszKeyName, int iValue);
+ void SetUint64(const char* pszKeyName, uint64_t nValue);
+ void SetPtr(const char* pszKeyName, void* pValue);
+ void SetNextKey(KeyValues* pDat);
+ void SetName(const char* pszName);
+ void SetString(const char* pszKeyName, const char* pszValue);
+ void SetWString(const char* pszKeyName, const wchar_t* pwszValue);
+ void SetStringValue(char const* pszValue);
+ void SetColor(const char* pszKeyName, Color color);
+ void SetFloat(const char* pszKeyName, float flValue);
+
+ void RecursiveCopyKeyValues(KeyValues& src);
+ void CopySubkeys(KeyValues* pParent) const;
+ KeyValues* MakeCopy(void) const;
+
+ public:
+ uint32_t m_iKeyName : 24; // 0x0000
+ uint32_t m_iKeyNameCaseSensitive1 : 8; // 0x0003
+ char* m_sValue; // 0x0008
+ wchar_t* m_wsValue; // 0x0010
+ union // 0x0018
+ {
+ int m_iValue;
+ float m_flValue;
+ void* m_pValue;
+ unsigned char m_Color[4];
+ };
+ char m_szShortName[8]; // 0x0020
+ char m_iDataType; // 0x0028
+ char m_bHasEscapeSequences; // 0x0029
+ uint16_t m_iKeyNameCaseSensitive2; // 0x002A
+ KeyValues* m_pPeer; // 0x0030
+ KeyValues* m_pSub; // 0x0038
+ KeyValues* m_pChain; // 0x0040
+};
diff --git a/NorthstarDLL/modkeyvalues.cpp b/NorthstarDLL/modkeyvalues.cpp
new file mode 100644
index 00000000..75188329
--- /dev/null
+++ b/NorthstarDLL/modkeyvalues.cpp
@@ -0,0 +1,107 @@
+#include "pch.h"
+#include "modmanager.h"
+#include "filesystem.h"
+
+#include <fstream>
+
+AUTOHOOK_INIT()
+
+void ModManager::TryBuildKeyValues(const char* filename)
+{
+ spdlog::info("Building KeyValues for file {}", filename);
+
+ std::string normalisedPath = g_pModManager->NormaliseModFilePath(fs::path(filename));
+ fs::path compiledPath = GetCompiledAssetsPath() / filename;
+ fs::path compiledDir = compiledPath.parent_path();
+ fs::create_directories(compiledDir);
+
+ fs::path kvPath(filename);
+ std::string ogFilePath = "mod_original_";
+ ogFilePath += kvPath.filename().string();
+
+ std::string newKvs = "// AUTOGENERATED: MOD PATCH KV\n";
+
+ int patchNum = 0;
+
+ // copy over patch kv files, and add #bases to new file, last mods' patches should be applied first
+ // note: #include should be identical but it's actually just broken, thanks respawn
+ for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--)
+ {
+ if (!m_LoadedMods[i].m_bEnabled)
+ continue;
+
+ size_t fileHash = STR_HASH(normalisedPath);
+ auto modKv = m_LoadedMods[i].KeyValues.find(fileHash);
+ if (modKv != m_LoadedMods[i].KeyValues.end())
+ {
+ // should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt"
+
+ std::string patchFilePath = "mod_patch_";
+ patchFilePath += std::to_string(patchNum++);
+ patchFilePath += "_";
+ patchFilePath += kvPath.filename().string();
+
+ newKvs += "#base \"";
+ newKvs += patchFilePath;
+ newKvs += "\"\n";
+
+ fs::remove(compiledDir / patchFilePath);
+
+ fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
+ }
+ }
+
+ // add original #base last, #bases don't override preexisting keys, including the ones we've just done
+ newKvs += "#base \"";
+ newKvs += ogFilePath;
+ newKvs += "\"\n";
+
+ // load original file, so we can parse out the name of the root obj (e.g. WeaponData for weapons)
+ std::string originalFile = R2::ReadVPKOriginalFile(filename);
+
+ if (!originalFile.length())
+ {
+ spdlog::warn("Tried to patch kv {} but no base kv was found!", ogFilePath);
+ return;
+ }
+
+ char rootName[64];
+ memset(rootName, 0, sizeof(rootName));
+
+ // iterate until we hit an ascii char that isn't in a # command or comment to get root obj name
+ int i = 0;
+ while (!(originalFile[i] >= 65 && originalFile[i] <= 122))
+ {
+ // if we hit a comment or # thing, iterate until end of line
+ if (originalFile[i] == '/' || originalFile[i] == '#')
+ while (originalFile[i] != '\n')
+ i++;
+
+ i++;
+ }
+
+ int j = 0;
+ for (int j = 0; originalFile[i] >= 65 && originalFile[i] <= 122; j++)
+ rootName[j] = originalFile[i++];
+
+ // empty kv, all the other stuff gets #base'd
+ newKvs += rootName;
+ newKvs += "\n{\n}\n";
+
+ std::ofstream originalFileWriteStream(compiledDir / ogFilePath, std::ios::binary);
+ originalFileWriteStream << originalFile;
+ originalFileWriteStream.close();
+
+ std::ofstream writeStream(compiledPath, std::ios::binary);
+ writeStream << newKvs;
+ writeStream.close();
+
+ ModOverrideFile overrideFile;
+ overrideFile.m_pOwningMod = nullptr;
+ overrideFile.m_Path = normalisedPath;
+
+ if (m_ModFiles.find(normalisedPath) == m_ModFiles.end())
+ m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile));
+ else
+ m_ModFiles[normalisedPath] = overrideFile;
+}
diff --git a/NorthstarDLL/pdef.cpp b/NorthstarDLL/modpdef.cpp
index 05fba710..d33ba8a6 100644
--- a/NorthstarDLL/pdef.cpp
+++ b/NorthstarDLL/modpdef.cpp
@@ -1,12 +1,14 @@
#include "pch.h"
#include "modmanager.h"
#include "filesystem.h"
-#include "pdef.h"
#include <map>
#include <sstream>
#include <fstream>
+const fs::path MOD_PDEF_SUFFIX = "cfg/server/persistent_player_data_version_231.pdef";
+const char* VPK_PDEF_PATH = "cfg/server/persistent_player_data_version_231.pdef";
+
void ModManager::BuildPdef()
{
spdlog::info("Building persistent_player_data_version_231.pdef...");
diff --git a/NorthstarDLL/scriptsrson.cpp b/NorthstarDLL/modscriptsrson.cpp
index 1e0ded27..3615dd80 100644
--- a/NorthstarDLL/scriptsrson.cpp
+++ b/NorthstarDLL/modscriptsrson.cpp
@@ -1,11 +1,13 @@
#include "pch.h"
#include "modmanager.h"
-#include "scriptsrson.h"
#include "filesystem.h"
#include "squirrel.h"
#include <fstream>
+const std::string MOD_SCRIPTS_RSON_SUFFIX = "scripts/vscripts/scripts.rson";
+const char* VPK_SCRIPTS_RSON_PATH = "scripts\\vscripts\\scripts.rson";
+
void ModManager::BuildScriptsRson()
{
spdlog::info("Building custom scripts.rson");
diff --git a/NorthstarDLL/pdef.h b/NorthstarDLL/pdef.h
deleted file mode 100644
index 379e76da..00000000
--- a/NorthstarDLL/pdef.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-
-const fs::path MOD_PDEF_SUFFIX = "cfg/server/persistent_player_data_version_231.pdef";
-const char* VPK_PDEF_PATH = "cfg/server/persistent_player_data_version_231.pdef";
diff --git a/NorthstarDLL/r2engine.h b/NorthstarDLL/r2engine.h
index 016e7db1..e428e1cc 100644
--- a/NorthstarDLL/r2engine.h
+++ b/NorthstarDLL/r2engine.h
@@ -1,4 +1,5 @@
#pragma once
+#include "keyvalues.h"
// use the R2 namespace for game funcs
namespace R2
@@ -167,7 +168,7 @@ namespace R2
// +0x56
char pad1[0x202];
- void** m_ConVars; // this is a KeyValues* object but not got that struct mapped out atm
+ KeyValues* m_ConVars; // this is a KeyValues* object but not got that struct mapped out atm
char pad2[0x240];
diff --git a/NorthstarDLL/scriptsrson.h b/NorthstarDLL/scriptsrson.h
deleted file mode 100644
index 5e1d5684..00000000
--- a/NorthstarDLL/scriptsrson.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-
-const std::string MOD_SCRIPTS_RSON_SUFFIX = "scripts/vscripts/scripts.rson";
-const char* VPK_SCRIPTS_RSON_PATH = "scripts\\vscripts\\scripts.rson";