From c8181a1719fefc8b9d7ed52888f6a207fcdf026f Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Mon, 31 Oct 2022 23:15:26 +0000 Subject: add KeyValues class (#303) --- NorthstarDLL/NorthstarDLL.vcxproj | 11 +- NorthstarDLL/NorthstarDLL.vcxproj.filters | 18 +- NorthstarDLL/keyvalues.cpp | 1448 ++++++++++++++++++++++++++--- NorthstarDLL/keyvalues.h | 134 +++ NorthstarDLL/modkeyvalues.cpp | 107 +++ NorthstarDLL/modpdef.cpp | 119 +++ NorthstarDLL/modscriptsrson.cpp | 66 ++ NorthstarDLL/pdef.cpp | 117 --- NorthstarDLL/pdef.h | 4 - NorthstarDLL/r2engine.h | 3 +- NorthstarDLL/scriptsrson.cpp | 64 -- NorthstarDLL/scriptsrson.h | 4 - 12 files changed, 1759 insertions(+), 336 deletions(-) create mode 100644 NorthstarDLL/keyvalues.h create mode 100644 NorthstarDLL/modkeyvalues.cpp create mode 100644 NorthstarDLL/modpdef.cpp create mode 100644 NorthstarDLL/modscriptsrson.cpp delete mode 100644 NorthstarDLL/pdef.cpp delete mode 100644 NorthstarDLL/pdef.h delete mode 100644 NorthstarDLL/scriptsrson.cpp delete mode 100644 NorthstarDLL/scriptsrson.h 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 @@ stdcpp20 $(ProjectDir)include;%(AdditionalIncludeDirectories) MultiThreadedDLL + Disabled Windows @@ -120,6 +121,7 @@ + @@ -547,10 +549,8 @@ - - @@ -573,6 +573,7 @@ + @@ -588,7 +589,7 @@ - + @@ -605,7 +606,7 @@ Create Create - + @@ -620,7 +621,7 @@ - + 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 @@ Header Files\Mods - - Header Files\Mods\Compiled Assets - Header Files\Console @@ -1464,9 +1461,6 @@ Header Files\Game Functions - - Header Files\Mods\Compiled Assets - Header Files\Game Functions @@ -1494,6 +1488,9 @@ Header Files\Math + + Header Files + @@ -1508,13 +1505,13 @@ Source Files\Mods - + Source Files\Mods\Compiled Assets Source Files\Server\Authentication - + Source Files\Mods\Compiled Assets @@ -1526,7 +1523,7 @@ Source Files\Dedicated Server - + Source Files\Mods\Compiled Assets @@ -1700,6 +1697,9 @@ Source Files\Scripted + + Source Files + 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 - -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 + +// 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(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(pKey->m_flValue); + case TYPE_UINT64: + return *reinterpret_cast(pKey->m_sValue); + case TYPE_PTR: + return static_cast(reinterpret_cast(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(atof(pKey->m_sValue)); + case TYPE_WSTRING: + return static_cast(_wtof(pKey->m_wsValue)); // no wtof + case TYPE_FLOAT: + return pKey->m_flValue; + case TYPE_INT: + return static_cast(pKey->m_iValue); + case TYPE_UINT64: + return static_cast((*(reinterpret_cast(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(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(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(reinterpret_cast(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(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(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(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(pKey->m_flValue); + } + else if (pKey->m_iDataType == TYPE_INT) + { + color[0] = static_cast(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(a); + color[1] = static_cast(b); + color[2] = static_cast(c); + color[3] = static_cast(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(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(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(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(); + V_UnicodeToUTF8 = module.GetExport("V_UnicodeToUTF8").As(); + KeyValuesSystem = module.GetExport("KeyValuesSystem").As(); +} + +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 + +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/modpdef.cpp b/NorthstarDLL/modpdef.cpp new file mode 100644 index 00000000..d33ba8a6 --- /dev/null +++ b/NorthstarDLL/modpdef.cpp @@ -0,0 +1,119 @@ +#include "pch.h" +#include "modmanager.h" +#include "filesystem.h" + +#include +#include +#include + +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..."); + + fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX); + + fs::remove(MOD_PDEF_PATH); + std::string pdef = R2::ReadVPKOriginalFile(VPK_PDEF_PATH); + + for (Mod& mod : m_LoadedMods) + { + if (!mod.m_bEnabled || !mod.Pdiff.size()) + continue; + + // this code probably isn't going to be pretty lol + // refer to shared/pjson.js for an actual okish parser of the pdiff format + // but pretty much, $ENUM_ADD blocks define members added to preexisting enums + // $PROP_START ends the custom stuff, and from there it's just normal props we append to the pdef + + std::map> enumAdds; + + // read pdiff + bool inEnum = false; + bool inProp = false; + std::string currentEnum; + std::string currentLine; + std::istringstream pdiffStream(mod.Pdiff); + + while (std::getline(pdiffStream, currentLine)) + { + if (inProp) + { + // just append to pdef here + pdef += currentLine; + pdef += '\n'; + continue; + } + + // trim leading whitespace + size_t start = currentLine.find_first_not_of(" \n\r\t\f\v"); + size_t end = currentLine.find("//"); + if (end == std::string::npos) + end = currentLine.size() - 1; // last char + + if (!currentLine.size() || !currentLine.compare(start, 2, "//")) + continue; + + if (inEnum) + { + if (!currentLine.compare(start, 9, "$ENUM_END")) + inEnum = false; + else + enumAdds[currentEnum].push_back(currentLine); // only need to push_back current line, if there's syntax errors then game + // pdef parser will handle them + } + else if (!currentLine.compare(start, 9, "$ENUM_ADD")) + { + inEnum = true; + currentEnum = currentLine.substr(start + 10 /*$ENUM_ADD + 1*/, currentLine.size() - end - (start + 10)); + enumAdds.insert(std::make_pair(currentEnum, std::vector())); + } + else if (!currentLine.compare(start, 11, "$PROP_START")) + { + inProp = true; + pdef += "\n// $PROP_START "; + pdef += mod.Name; + pdef += "\n"; + } + } + + // add new members to preexisting enums + // note: this code could 100% be messed up if people put //$ENUM_START comments and the like + // could make it protect against this, but honestly not worth atm + for (auto enumAdd : enumAdds) + { + std::string addStr; + for (std::string enumMember : enumAdd.second) + { + addStr += enumMember; + addStr += '\n'; + } + + // start of enum we're adding to + std::string startStr = "$ENUM_START "; + startStr += enumAdd.first; + + // insert enum values into enum + size_t insertIdx = pdef.find("$ENUM_END", pdef.find(startStr)); + pdef.reserve(addStr.size()); + pdef.insert(insertIdx, addStr); + } + } + + fs::create_directories(MOD_PDEF_PATH.parent_path()); + + std::ofstream writeStream(MOD_PDEF_PATH, std::ios::binary); + writeStream << pdef; + writeStream.close(); + + ModOverrideFile overrideFile; + overrideFile.m_pOwningMod = nullptr; + overrideFile.m_Path = VPK_PDEF_PATH; + + if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end()) + m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile)); + else + m_ModFiles[VPK_PDEF_PATH] = overrideFile; +} diff --git a/NorthstarDLL/modscriptsrson.cpp b/NorthstarDLL/modscriptsrson.cpp new file mode 100644 index 00000000..3615dd80 --- /dev/null +++ b/NorthstarDLL/modscriptsrson.cpp @@ -0,0 +1,66 @@ +#include "pch.h" +#include "modmanager.h" +#include "filesystem.h" +#include "squirrel.h" + +#include + +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"); + fs::path MOD_SCRIPTS_RSON_PATH = fs::path(GetCompiledAssetsPath() / MOD_SCRIPTS_RSON_SUFFIX); + fs::remove(MOD_SCRIPTS_RSON_PATH); + + std::string scriptsRson = R2::ReadVPKOriginalFile(VPK_SCRIPTS_RSON_PATH); + scriptsRson += "\n\n// START MODDED SCRIPT CONTENT\n\n"; // newline before we start custom stuff + + for (Mod& mod : m_LoadedMods) + { + if (!mod.m_bEnabled) + continue; + + // this isn't needed at all, just nice to have imo + scriptsRson += "// MOD: "; + scriptsRson += mod.Name; + scriptsRson += ":\n\n"; + + for (ModScript& script : mod.Scripts) + { + /* should create something with this format for each script + When: "CONTEXT" + Scripts: + [ + _coolscript.gnut + ]*/ + + scriptsRson += "When: \""; + scriptsRson += script.RunOn; + scriptsRson += "\"\n"; + + scriptsRson += "Scripts:\n[\n\t"; + scriptsRson += script.Path; + scriptsRson += "\n]\n\n"; + } + } + + fs::create_directories(MOD_SCRIPTS_RSON_PATH.parent_path()); + + std::ofstream writeStream(MOD_SCRIPTS_RSON_PATH, std::ios::binary); + writeStream << scriptsRson; + writeStream.close(); + + ModOverrideFile overrideFile; + overrideFile.m_pOwningMod = nullptr; + overrideFile.m_Path = VPK_SCRIPTS_RSON_PATH; + + if (m_ModFiles.find(VPK_SCRIPTS_RSON_PATH) == m_ModFiles.end()) + m_ModFiles.insert(std::make_pair(VPK_SCRIPTS_RSON_PATH, overrideFile)); + else + m_ModFiles[VPK_SCRIPTS_RSON_PATH] = overrideFile; + + // todo: for preventing dupe scripts in scripts.rson, we could actually parse when conditions with the squirrel vm, just need a way to + // get a result out of squirrelmanager.ExecuteCode this would probably be the best way to do this, imo +} diff --git a/NorthstarDLL/pdef.cpp b/NorthstarDLL/pdef.cpp deleted file mode 100644 index 05fba710..00000000 --- a/NorthstarDLL/pdef.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "pch.h" -#include "modmanager.h" -#include "filesystem.h" -#include "pdef.h" - -#include -#include -#include - -void ModManager::BuildPdef() -{ - spdlog::info("Building persistent_player_data_version_231.pdef..."); - - fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX); - - fs::remove(MOD_PDEF_PATH); - std::string pdef = R2::ReadVPKOriginalFile(VPK_PDEF_PATH); - - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled || !mod.Pdiff.size()) - continue; - - // this code probably isn't going to be pretty lol - // refer to shared/pjson.js for an actual okish parser of the pdiff format - // but pretty much, $ENUM_ADD blocks define members added to preexisting enums - // $PROP_START ends the custom stuff, and from there it's just normal props we append to the pdef - - std::map> enumAdds; - - // read pdiff - bool inEnum = false; - bool inProp = false; - std::string currentEnum; - std::string currentLine; - std::istringstream pdiffStream(mod.Pdiff); - - while (std::getline(pdiffStream, currentLine)) - { - if (inProp) - { - // just append to pdef here - pdef += currentLine; - pdef += '\n'; - continue; - } - - // trim leading whitespace - size_t start = currentLine.find_first_not_of(" \n\r\t\f\v"); - size_t end = currentLine.find("//"); - if (end == std::string::npos) - end = currentLine.size() - 1; // last char - - if (!currentLine.size() || !currentLine.compare(start, 2, "//")) - continue; - - if (inEnum) - { - if (!currentLine.compare(start, 9, "$ENUM_END")) - inEnum = false; - else - enumAdds[currentEnum].push_back(currentLine); // only need to push_back current line, if there's syntax errors then game - // pdef parser will handle them - } - else if (!currentLine.compare(start, 9, "$ENUM_ADD")) - { - inEnum = true; - currentEnum = currentLine.substr(start + 10 /*$ENUM_ADD + 1*/, currentLine.size() - end - (start + 10)); - enumAdds.insert(std::make_pair(currentEnum, std::vector())); - } - else if (!currentLine.compare(start, 11, "$PROP_START")) - { - inProp = true; - pdef += "\n// $PROP_START "; - pdef += mod.Name; - pdef += "\n"; - } - } - - // add new members to preexisting enums - // note: this code could 100% be messed up if people put //$ENUM_START comments and the like - // could make it protect against this, but honestly not worth atm - for (auto enumAdd : enumAdds) - { - std::string addStr; - for (std::string enumMember : enumAdd.second) - { - addStr += enumMember; - addStr += '\n'; - } - - // start of enum we're adding to - std::string startStr = "$ENUM_START "; - startStr += enumAdd.first; - - // insert enum values into enum - size_t insertIdx = pdef.find("$ENUM_END", pdef.find(startStr)); - pdef.reserve(addStr.size()); - pdef.insert(insertIdx, addStr); - } - } - - fs::create_directories(MOD_PDEF_PATH.parent_path()); - - std::ofstream writeStream(MOD_PDEF_PATH, std::ios::binary); - writeStream << pdef; - writeStream.close(); - - ModOverrideFile overrideFile; - overrideFile.m_pOwningMod = nullptr; - overrideFile.m_Path = VPK_PDEF_PATH; - - if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile)); - else - m_ModFiles[VPK_PDEF_PATH] = overrideFile; -} 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.cpp b/NorthstarDLL/scriptsrson.cpp deleted file mode 100644 index 1e0ded27..00000000 --- a/NorthstarDLL/scriptsrson.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "pch.h" -#include "modmanager.h" -#include "scriptsrson.h" -#include "filesystem.h" -#include "squirrel.h" - -#include - -void ModManager::BuildScriptsRson() -{ - spdlog::info("Building custom scripts.rson"); - fs::path MOD_SCRIPTS_RSON_PATH = fs::path(GetCompiledAssetsPath() / MOD_SCRIPTS_RSON_SUFFIX); - fs::remove(MOD_SCRIPTS_RSON_PATH); - - std::string scriptsRson = R2::ReadVPKOriginalFile(VPK_SCRIPTS_RSON_PATH); - scriptsRson += "\n\n// START MODDED SCRIPT CONTENT\n\n"; // newline before we start custom stuff - - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled) - continue; - - // this isn't needed at all, just nice to have imo - scriptsRson += "// MOD: "; - scriptsRson += mod.Name; - scriptsRson += ":\n\n"; - - for (ModScript& script : mod.Scripts) - { - /* should create something with this format for each script - When: "CONTEXT" - Scripts: - [ - _coolscript.gnut - ]*/ - - scriptsRson += "When: \""; - scriptsRson += script.RunOn; - scriptsRson += "\"\n"; - - scriptsRson += "Scripts:\n[\n\t"; - scriptsRson += script.Path; - scriptsRson += "\n]\n\n"; - } - } - - fs::create_directories(MOD_SCRIPTS_RSON_PATH.parent_path()); - - std::ofstream writeStream(MOD_SCRIPTS_RSON_PATH, std::ios::binary); - writeStream << scriptsRson; - writeStream.close(); - - ModOverrideFile overrideFile; - overrideFile.m_pOwningMod = nullptr; - overrideFile.m_Path = VPK_SCRIPTS_RSON_PATH; - - if (m_ModFiles.find(VPK_SCRIPTS_RSON_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(VPK_SCRIPTS_RSON_PATH, overrideFile)); - else - m_ModFiles[VPK_SCRIPTS_RSON_PATH] = overrideFile; - - // todo: for preventing dupe scripts in scripts.rson, we could actually parse when conditions with the squirrel vm, just need a way to - // get a result out of squirrelmanager.ExecuteCode this would probably be the best way to do this, imo -} 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"; -- cgit v1.2.3