From bcc51460bcff4fa716f6f0b8703485f393e9100a Mon Sep 17 00:00:00 2001 From: rexx <67599507+r-ex@users.noreply.github.com> Date: Tue, 2 May 2023 19:26:49 +0100 Subject: [PATCH] new new modsystem pr (#96) * modsystem v2 initial commit * call CModSystem::Init * clean up custom cvar value handling * add mod script compiling support * add error check to script rson loading yes this error is a duplicate but this one exits the game * fix typo * fix compile error --- r5dev/core/init.cpp | 5 + r5dev/launcher/IApplication.cpp | 3 + r5dev/localize/localize.cpp | 33 ++++ r5dev/localize/localize.h | 45 ++++++ r5dev/pluginsystem/modsystem.cpp | 224 ++++++++++++++++++++++++++ r5dev/pluginsystem/modsystem.h | 66 ++++++++ r5dev/public/tier1/cvar.h | 34 ++++ r5dev/squirrel/sqscript.cpp | 141 ++++++++++++++++ r5dev/squirrel/sqscript.h | 68 +++++++- r5dev/squirrel/sqvm.h | 5 + r5dev/tier1/cvar.cpp | 43 +++++ r5dev/vpc/rson.cpp | 46 ++++++ r5dev/vpc/rson.h | 112 +++++++++++++ r5dev/vproj/clientsdk.vcxproj | 6 + r5dev/vproj/clientsdk.vcxproj.filters | 21 +++ r5dev/vproj/dedicated.vcxproj | 6 + r5dev/vproj/dedicated.vcxproj.filters | 21 +++ r5dev/vproj/gamesdk.vcxproj | 6 + r5dev/vproj/gamesdk.vcxproj.filters | 21 +++ 19 files changed, 904 insertions(+), 2 deletions(-) create mode 100644 r5dev/localize/localize.cpp create mode 100644 r5dev/localize/localize.h create mode 100644 r5dev/pluginsystem/modsystem.cpp create mode 100644 r5dev/pluginsystem/modsystem.h create mode 100644 r5dev/vpc/rson.cpp create mode 100644 r5dev/vpc/rson.h diff --git a/r5dev/core/init.cpp b/r5dev/core/init.cpp index 592b0f30..f5b21afa 100644 --- a/r5dev/core/init.cpp +++ b/r5dev/core/init.cpp @@ -19,6 +19,7 @@ #include "tier1/cvar.h" #include "vpc/IAppSystem.h" #include "vpc/keyvalues.h" +#include "vpc/rson.h" #include "vpc/interfaces.h" #include "vstdlib/callback.h" #include "vstdlib/completion.h" @@ -72,6 +73,7 @@ #ifndef DEDICATED #include "engine/client/clientstate.h" #endif // !DEDICATED +#include "localize/localize.h" #include "engine/enginetrace.h" #include "engine/traceinit.h" #include "engine/common.h" @@ -388,6 +390,7 @@ void DetourRegister() // Register detour classes to be searched and hooked. // VPC REGISTER(VAppSystem); REGISTER(VKeyValues); + REGISTER(VRSON); REGISTER(VFactory); // VstdLib @@ -503,6 +506,8 @@ void DetourRegister() // Register detour classes to be searched and hooked. REGISTER(VEngineTrace); REGISTER(VModelInfo); + REGISTER(VLocalize); + #ifndef DEDICATED REGISTER(HVideoMode_Common); REGISTER(VGL_RMain); diff --git a/r5dev/launcher/IApplication.cpp b/r5dev/launcher/IApplication.cpp index 5af89b97..5e0ee254 100644 --- a/r5dev/launcher/IApplication.cpp +++ b/r5dev/launcher/IApplication.cpp @@ -12,6 +12,7 @@ #include "appframework/engine_launcher_api.h" #include "launcher/IApplication.h" #include "pluginsystem/pluginsystem.h" +#include "pluginsystem/modsystem.h" #include "ebisusdk/EbisuSDK.h" #include "engine/cmodel_bsp.h" #include "engine/sys_engine.h" @@ -106,6 +107,8 @@ bool CModAppSystemGroup::StaticCreate(CModAppSystemGroup* pModAppSystemGroup) //InitPluginSystem(pModAppSystemGroup); //CALL_PLUGIN_CALLBACKS(g_pPluginSystem->GetCreateCallbacks(), pModAppSystemGroup); + g_pModSystem->Init(); + g_pDebugOverlay = g_pFactory->GetFactoryPtr(VDEBUG_OVERLAY_INTERFACE_VERSION, false).RCast(); #ifndef CLIENT_DLL g_pServerGameDLL = g_pFactory->GetFactoryPtr(INTERFACEVERSION_SERVERGAMEDLL, false).RCast(); diff --git a/r5dev/localize/localize.cpp b/r5dev/localize/localize.cpp new file mode 100644 index 00000000..3b97fa64 --- /dev/null +++ b/r5dev/localize/localize.cpp @@ -0,0 +1,33 @@ +#include "core/stdafx.h" +#include "localize/localize.h" +#include "pluginsystem/modsystem.h" + +bool Localize_LoadLocalizationFileLists(CLocalize* thisptr) +{ + v_CLocalize__LoadLocalizationFileLists(thisptr); + + for (auto& mod : g_pModSystem->GetModList()) + { + if (mod.m_iState == CModSystem::eModState::ENABLED) + { + for (auto& it : mod.m_vszLocalizationFiles) + { + v_CLocalize__AddFile(thisptr, it.c_str(), NULL); + } + } + } + + DevMsg(eDLL_T::ENGINE, "Loaded localization files.\n"); + + return true; +} + +void VLocalize::Attach() const +{ + DetourAttach((LPVOID*)&v_CLocalize__LoadLocalizationFileLists, &Localize_LoadLocalizationFileLists); +} + +void VLocalize::Detach() const +{ + DetourDetach((LPVOID*)&v_CLocalize__LoadLocalizationFileLists, &Localize_LoadLocalizationFileLists); +} \ No newline at end of file diff --git a/r5dev/localize/localize.h b/r5dev/localize/localize.h new file mode 100644 index 00000000..4d18e519 --- /dev/null +++ b/r5dev/localize/localize.h @@ -0,0 +1,45 @@ +#pragma once + +#include "tier0/threadtools.h" +#include "tier1/utldict.h" + + +class CLocalize +{ + // todo +}; + +inline CMemory p_CLocalize__AddFile; +inline auto v_CLocalize__AddFile = p_CLocalize__AddFile.RCast(); + +inline CMemory p_CLocalize__LoadLocalizationFileLists; +inline auto v_CLocalize__LoadLocalizationFileLists = p_CLocalize__LoadLocalizationFileLists.RCast(); + + +inline CLocalize* g_pVGuiLocalize; +inline CLocalize* g_pLocalize; + +void Localize_Attach(); +void Localize_Detach(); +/////////////////////////////////////////////////////////////////////////////// +class VLocalize : public IDetour +{ + virtual void GetAdr(void) const { } + virtual void GetFun(void) const + { + p_CLocalize__AddFile = g_GameDll.FindPatternSIMD("E8 ? ? ? ? 49 FF C4").FollowNearCallSelf(); + v_CLocalize__AddFile = p_CLocalize__AddFile.RCast(); + + p_CLocalize__LoadLocalizationFileLists = g_GameDll.FindPatternSIMD("4C 8B DC 53 48 81 EC ? ? ? ? 33 C0"); + v_CLocalize__LoadLocalizationFileLists = p_CLocalize__LoadLocalizationFileLists.RCast(); + } + virtual void GetVar(void) const + { + g_pVGuiLocalize = g_GameDll.FindPatternSIMD("48 8B 0D ? ? ? ? 48 8B 01 FF 50 40 40 38 2D ? ? ? ?").ResolveRelativeAddressSelf(0x3, 0x7).RCast(); + g_pLocalize = g_pVGuiLocalize; // these are set to the same thing in CSourceAppSystemGroup::Create + } + virtual void GetCon(void) const { } + virtual void Attach(void) const; + virtual void Detach(void) const; +}; +/////////////////////////////////////////////////////////////////////////////// diff --git a/r5dev/pluginsystem/modsystem.cpp b/r5dev/pluginsystem/modsystem.cpp new file mode 100644 index 00000000..7afc65f2 --- /dev/null +++ b/r5dev/pluginsystem/modsystem.cpp @@ -0,0 +1,224 @@ +//=============================================================================// +// +// Purpose: Manage loading mods +// +//----------------------------------------------------------------------------- +// +//=============================================================================// + +#include "core/stdafx.h" +#include "modsystem.h" +#include "localize/localize.h" +#include "tier1/cvar.h" +#include "vpc/rson.h" + +//----------------------------------------------------------------------------- +// Purpose: initialize the mod system +// Input : +//----------------------------------------------------------------------------- +void CModSystem::Init() +{ + LoadModStatusList(); + + CreateDirectories("platform\\mods"); + + for (auto& it : fs::directory_iterator("platform\\mods")) + { + if (!it.is_directory()) + continue; + + fs::path basePath = it.path(); + DevMsg(eDLL_T::ENGINE, "Found mod at '%s'.\n", basePath.string().c_str()); + fs::path settingsPath = basePath / "mod.vdf"; + + if (fs::exists(settingsPath)) + { + CModSystem::ModInstance_t modInst = CModSystem::ModInstance_t(basePath); + if (modInst.m_iState != eModState::UNLOADED) + m_vModList.push_back(modInst); + } + } + + WriteModStatusList(); +} + +void CModSystem::LoadModStatusList() +{ + if (FileSystem()->FileExists("platform/mods.vdf")) + { + KeyValues* pModList = FileSystem()->LoadKeyValues(IFileSystem::TYPE_COMMON, "platform/mods.vdf", "GAME"); + + for (KeyValues* pSubKey = pModList->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) + { + size_t idHash = std::hash{}(std::string(pSubKey->GetName())); + m_vEnabledList.emplace(idHash, pSubKey->GetBool()); + } + } +} + +void CModSystem::WriteModStatusList() +{ + KeyValues kv = KeyValues("ModList"); + KeyValues* pModListKV = kv.FindKey("ModList", true); + + for (auto& it : m_vModList) + { + bool enabled = false; + if (it.m_iState == eModState::ENABLED) + enabled = true; + + pModListKV->SetBool(it.m_szModID.c_str(), enabled); + } + + CUtlBuffer uBuf = CUtlBuffer(0i64, 0, CUtlBuffer::TEXT_BUFFER); + + kv.RecursiveSaveToFile(uBuf, 0); + + FileSystem()->WriteFile("platform/mods.vdf", NULL, uBuf); // NULL instead of "GAME" because otherwise for some reason the file clears itself when the process exits +} + + +CModSystem::ModInstance_t::ModInstance_t(const fs::path& basePath) : m_szName(std::string()), m_szModID(std::string()), m_BasePath(basePath), m_szDescription(std::string()), m_szVersion(std::string()), m_SettingsKV(nullptr), m_iState(eModState::LOADING) +{ + std::string settingsPath = (m_BasePath / "mod.vdf").string(); + + KeyValues* pSettingsKV = FileSystem()->LoadKeyValues(IFileSystem::TYPE_COMMON, settingsPath.c_str(), "GAME"); + m_SettingsKV = pSettingsKV; + + if (!m_SettingsKV) + { + SetState(eModState::UNLOADED); + Error(eDLL_T::ENGINE, NO_ERROR, "Failed to parse mod.vdf for mod at path '%s'\n", m_BasePath.string().c_str()); + return; + } + + ///////////////////////////// + // "name" "An R5Reloaded Mod" + // [rexx]: could be optional and have id as fallback + KeyValues* pName = pSettingsKV->FindKey("name"); + + if (!pName) + { + SetState(eModState::UNLOADED); + Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'name' field. Skipping mod...\n", settingsPath.c_str()); + return; + } + + m_szName = pName->GetString(); + + ///////////////////////////// + // "version" "1.0.0" + KeyValues* pVersion = pSettingsKV->FindKey("version"); + + if (!pVersion) + { + SetState(eModState::UNLOADED); + Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'version' field. Skipping mod...\n", settingsPath.c_str()); + return; + } + + m_szVersion = pVersion->GetString(); + + ///////////////////////////// + // "id" "r5reloaded.TestMod" + KeyValues* pId = pSettingsKV->FindKey("id"); + + if (!pId) + { + SetState(eModState::UNLOADED); + Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'id' field. Skipping mod...\n", settingsPath.c_str()); + return; + } + + m_szModID = pId->GetString(); + + ///////////////////////////// + // optional mod description field + m_szDescription = pSettingsKV->GetString("description"); + + size_t idHash = std::hash{}(m_szModID); + + auto& enabledList = g_pModSystem->GetEnabledList(); + if (enabledList.count(idHash) == 0) + { + DevMsg(eDLL_T::ENGINE, "Mod does not exist in 'mods.vdf'. Enabling...\n"); + SetState(eModState::ENABLED); + } + else + { + bool bEnable = enabledList[idHash]; + SetState(bEnable ? eModState::ENABLED : eModState::DISABLED); + + DevMsg(eDLL_T::ENGINE, "Mod exists in 'mods.vdf' and is %s.\n", bEnable ? "enabled" : "disabled"); + } + + if (m_iState != eModState::ENABLED) + return; + + // parse any additional info from mod.vdf + + // add mod folder to search paths so files can be easily loaded from here + // [rexx]: maybe this isn't ideal as the only way of finding the mod's files, as there may be name clashes in files where the engine + // won't really care about the input file name. it may be better to, where possible, request files by file path relative to root (i.e. including platform/mods/{mod}/) + FileSystem()->AddSearchPath(m_BasePath.string().c_str(), "GAME", SearchPathAdd_t::PATH_ADD_TO_TAIL); + + KeyValues* pLocalizationFiles = pSettingsKV->FindKey("LocalizationFiles"); + + if (pLocalizationFiles) + { + for (KeyValues* pSubKey = pLocalizationFiles->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) + { + this->m_vszLocalizationFiles.push_back(pSubKey->GetName()); + } + } + + KeyValues* pConVars = pSettingsKV->FindKey("ConVars"); + + if (pConVars) + { + for (KeyValues* pSubKey = pConVars->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) + { + const char* pszName = pSubKey->GetName(); + const char* pszHelpString = pSubKey->GetString("helpString"); + const char* pszFlagsString = pSubKey->GetString("flags", "NONE"); + + KeyValues* pValues = pSubKey->FindKey("Values"); + + const char* pszDefaultValue = "0"; + bool bMin = false; + bool bMax = false; + float fMin = 0.f; + float fMax = 0.f; + + if (pValues) + { + pszDefaultValue = pValues->GetString("default", "0"); + + // minimum cvar value + if (pValues->FindKey("min")) + { + bMin = true; // has min value + fMin = pValues->GetFloat("min", 0.f); + } + + // maximum cvar value + if (pValues->FindKey("max")) + { + bMax = true; // has max value + fMax = pValues->GetFloat("max", 1.f); + } + } + + int flags = FCVAR_NONE; + if (ConVar::ParseFlagString(pszFlagsString, flags, pszName)) + ConVar::StaticCreate(pszName, pszDefaultValue, flags, pszHelpString, bMin, fMin, bMax, fMax, nullptr, nullptr); + } + } + + std::string scriptsRsonPath = (m_BasePath / "scripts/vscripts/scripts.rson").string(); + + if (FileSystem()->FileExists(scriptsRsonPath.c_str(), "GAME")) + m_bHasScriptCompileList = true; +}; + +CModSystem* g_pModSystem = new CModSystem(); \ No newline at end of file diff --git a/r5dev/pluginsystem/modsystem.h b/r5dev/pluginsystem/modsystem.h new file mode 100644 index 00000000..dfb2128b --- /dev/null +++ b/r5dev/pluginsystem/modsystem.h @@ -0,0 +1,66 @@ +#pragma once + +#include "vpc/keyvalues.h" +#include "vpc/rson.h" +#include + +class CModAppSystemGroup; + + +class CModSystem +{ +public: + enum eModState : int8_t + { + UNLOADED = -1, // loading was unsuccessful (error occurred) + LOADING = 0, // if mod is being loaded + DISABLED = 1, // if disabled by user + ENABLED = 2, // if enabled by user and loaded properly + }; + + struct ModInstance_t + { + ModInstance_t(const fs::path& basePath); + + inline void SetState(eModState state) { m_iState = state; }; + + inline bool IsEnabled() { return m_iState == eModState::ENABLED; }; + + inline const fs::path& GetBasePath() { return m_BasePath; }; + + inline fs::path GetScriptCompileListPath() { return (m_BasePath / "scripts/vscripts/scripts.rson"); }; + + RSON::Node_t* LoadScriptCompileList() + { + return RSON::LoadFromFile(GetScriptCompileListPath().string().c_str()); + }; + + bool m_bHasScriptCompileList; // if this mod has a scripts.rson file that exists + + string m_szName; // mod display name + string m_szModID; // internal mod identifier + fs::path m_BasePath; // path to folder containg all mod files + string m_szDescription; // mod description + string m_szVersion; // version string + + KeyValues* m_SettingsKV; + + eModState m_iState = eModState::UNLOADED; + + std::vector m_vszLocalizationFiles; + }; + + void Init(); + + // load mod enabled/disabled status from file on disk + void LoadModStatusList(); + void WriteModStatusList(); + + inline vector& GetModList() { return m_vModList; }; + inline std::map& GetEnabledList() { return m_vEnabledList; }; + +private: + vector m_vModList; + std::map m_vEnabledList; +}; +extern CModSystem* g_pModSystem; diff --git a/r5dev/public/tier1/cvar.h b/r5dev/public/tier1/cvar.h index 735745ea..c457a5ef 100644 --- a/r5dev/public/tier1/cvar.h +++ b/r5dev/public/tier1/cvar.h @@ -413,6 +413,8 @@ public: void InstallChangeCallback(FnChangeCallback_t callback, bool bInvoke); void RemoveChangeCallback(FnChangeCallback_t callback); + static bool ParseFlagString(const char* pszFlags, int& nFlags, const char* pszConVarName = nullptr); + struct CVValue_t { char* m_pszString; @@ -432,6 +434,38 @@ public: }; //Size: 0x00A0 static_assert(sizeof(ConVar) == 0xA0); + +/////////////////////////////////////////////////////////////////////////////// +// see iconvar.h +static std::map s_ConVarFlags = { + {"NONE", FCVAR_NONE}, + {"DEVELOPMENTONLY", FCVAR_DEVELOPMENTONLY}, + {"GAMEDLL", FCVAR_GAMEDLL}, + {"CLIENTDLL", FCVAR_CLIENTDLL}, + {"HIDDEN", FCVAR_HIDDEN}, + {"PROTECTED", FCVAR_PROTECTED}, + {"SPONLY", FCVAR_SPONLY}, + {"ARCHIVE", FCVAR_ARCHIVE}, + {"NOTIFY", FCVAR_NOTIFY}, + {"USERINFO", FCVAR_USERINFO}, + {"PRINTABLEONLY", FCVAR_PRINTABLEONLY}, + {"GAMEDLL_FOR_REMOTE_CLIENTS", FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS}, + {"UNLOGGED", FCVAR_UNLOGGED}, + {"NEVER_AS_STRING", FCVAR_NEVER_AS_STRING}, + {"REPLICATED", FCVAR_REPLICATED}, + {"CHEAT", FCVAR_CHEAT}, + {"SS", FCVAR_SS}, + {"DEMO", FCVAR_DEMO}, + {"DONTRECORD", FCVAR_DONTRECORD}, + {"SS_ADDED", FCVAR_SS_ADDED}, + {"RELEASE", FCVAR_RELEASE}, + {"RELOAD_MATERIALS", FCVAR_RELOAD_MATERIALS}, + {"RELOAD_TEXTURES", FCVAR_RELOAD_TEXTURES}, + {"NOT_CONNECTED", FCVAR_NOT_CONNECTED}, + {"MATERIAL_SYSTEM_THREAD", FCVAR_MATERIAL_SYSTEM_THREAD}, + {"ARCHIVE_PLAYERPROFILE", FCVAR_ARCHIVE_PLAYERPROFILE}, +}; + /////////////////////////////////////////////////////////////////////////////// void ConVar_PrintDescription(ConCommandBase* pVar); diff --git a/r5dev/squirrel/sqscript.cpp b/r5dev/squirrel/sqscript.cpp index f24d1299..9dd60b5d 100644 --- a/r5dev/squirrel/sqscript.cpp +++ b/r5dev/squirrel/sqscript.cpp @@ -9,6 +9,7 @@ #include "squirrel/sqapi.h" #include "squirrel/sqinit.h" #include "squirrel/sqscript.h" +#include "pluginsystem/modsystem.h" //--------------------------------------------------------------------------------- // Purpose: registers global constant for target context @@ -227,9 +228,26 @@ SQInteger Script_LoadRson(const SQChar* rsonfile) //--------------------------------------------------------------------------------- SQBool Script_LoadScript(HSQUIRRELVM v, const SQChar* path, const SQChar* name, SQInteger flags) { + // search for mod path identifier so the mod can decide where the file is + const char* modPath = strstr(path, MOD_SCRIPT_PATH_IDENTIFIER); + + if (modPath) + path = &modPath[7]; // skip "::MOD::" + + /////////////////////////////////////////////////////////////////////////////// + return v_Script_LoadScript(v, path, name, flags); } +//--------------------------------------------------------------------------------- +// Purpose: parses rson data to get an array of scripts to compile +// Input : +//--------------------------------------------------------------------------------- +bool Script_ParseCompileListRSON(SQCONTEXT context, const char* compileListPath, RSON::Node_t* rson, char** scriptArray, int* pScriptCount, char** precompiledScriptArray, int precompiledScriptCount) +{ + return v_Script_ParseCompileListRSON(context, compileListPath, rson, scriptArray, pScriptCount, precompiledScriptArray, precompiledScriptCount); +} + //--------------------------------------------------------------------------------- // Purpose: Compiles and executes input code on target VM by context // Input : *code - @@ -277,6 +295,128 @@ void Script_Execute(const SQChar* code, const SQCONTEXT context) } } + +void Script_SetCompilingVM(CSquirrelVM* vm, RSON::Node_t* rson) +{ + switch (vm->GetContext()) + { +#ifndef CLIENT_DLL + case SQCONTEXT::SERVER: + { + v_Script_SetCompilingVM_SV(vm->GetContext(), rson); + break; + } +#endif +#ifndef DEDICATED + case SQCONTEXT::CLIENT: + case SQCONTEXT::UI: + { + v_Script_SetCompilingVM_UICL(vm->GetContext(), rson); + break; + } +#endif + } +} + +void CSquirrelVM_CompileModScripts(CSquirrelVM* vm) +{ + for (auto& mod : g_pModSystem->GetModList()) + { + if (!mod.IsEnabled()) + continue; + + if (!mod.m_bHasScriptCompileList) + continue; + + RSON::Node_t* rson = mod.LoadScriptCompileList(); // allocs parsed rson buffer + + if (!rson) + Error(vm->GetVM()->GetNativePrintContext(), EXIT_FAILURE, "%s: Failed to load RSON file %s\n", __FUNCTION__, mod.GetScriptCompileListPath().string().c_str()); + + const char* scriptPathArray[1024]; + int scriptCount = 0; + + Script_SetCompilingVM(vm, rson); + + if (Script_ParseCompileListRSON( + vm->GetContext(), + mod.GetScriptCompileListPath().string().c_str(), + rson, + (char**)scriptPathArray, &scriptCount, + nullptr, 0)) + { + std::vector newScriptPaths; + for (int i = 0; i < scriptCount; ++i) + { + // add "::MOD::" to the start of the script path so it can be identified from Script_LoadScript later + // this is so we can avoid script naming conflicts by removing the engine's forced directory of "scripts/vscripts/" + // and adding the mod path to the start + std::string scriptPath = MOD_SCRIPT_PATH_IDENTIFIER + (mod.GetBasePath() / "scripts/vscripts/" / scriptPathArray[i]).string(); + char* pszScriptPath = _strdup(scriptPath.c_str()); + + // normalise slash direction + V_FixSlashes(pszScriptPath); + + newScriptPaths.emplace_back(pszScriptPath); + scriptPathArray[i] = pszScriptPath; + } + + switch (vm->GetVM()->GetContext()) + { +#ifndef CLIENT_DLL + case SQCONTEXT::SERVER: + { + v_CSquirrelVM_CompileScriptsFromArray_SV(vm, vm->GetContext(), (char**)scriptPathArray, scriptCount); + break; + } +#endif +#ifndef DEDICATED + case SQCONTEXT::CLIENT: + case SQCONTEXT::UI: + { + v_CSquirrelVM_CompileScriptsFromArray_UICL(vm, vm->GetContext(), (char**)scriptPathArray, scriptCount); + break; + } +#endif + } + + // clean up our allocated script paths + for (char* path : newScriptPaths) + { + delete path; + } + } + + // TODO[rexx]: clean up allocated RSON memory. example @ 1408B18E2 + } +} + + +#ifndef DEDICATED +__int64 CSquirrelVM_CompileUICLScripts(CSquirrelVM* vm) +{ + HSQUIRRELVM v = vm->GetVM(); + DevMsg(v->GetNativePrintContext(), (char*)"Loading and compiling script lists\n"); + + CSquirrelVM_CompileModScripts(vm); + + return v_CSquirrelVM_CompileUICLScripts(vm); +} +#endif + +#ifndef CLIENT_DLL +__int64 CSquirrelVM_CompileSVScripts(__int64 a1) +{ + HSQUIRRELVM v = g_pServerScript->GetVM(); + + DevMsg(v->GetNativePrintContext(), (char*)"Loading and compiling script lists\n"); + + CSquirrelVM_CompileModScripts(g_pServerScript); + + return v_CSquirrelVM_CompileSVScripts(a1); +} +#endif + //--------------------------------------------------------------------------------- void VSquirrelVM::Attach() const { @@ -290,6 +430,7 @@ void VSquirrelVM::Attach() const void VSquirrelVM::Detach() const { DetourDetach((LPVOID*)&v_Script_RegisterConstant, &Script_RegisterConstant); + DetourDetach((LPVOID*)&v_CSquirrelVM_Init, &CSquirrelVM_Init); DetourDetach((LPVOID*)&v_Script_DestroySignalEntryListHead, &Script_DestroySignalEntryListHead); DetourDetach((LPVOID*)&v_Script_LoadRson, &Script_LoadRson); diff --git a/r5dev/squirrel/sqscript.h b/r5dev/squirrel/sqscript.h index 1f4ce7e1..08ccec6c 100644 --- a/r5dev/squirrel/sqscript.h +++ b/r5dev/squirrel/sqscript.h @@ -1,6 +1,9 @@ #pragma once #include "squirrel/sqtype.h" #include "squirrel/sqvm.h" +#include "vpc/rson.h" + +#define MOD_SCRIPT_PATH_IDENTIFIER "::MOD::" struct ScriptFunctionBinding_t { @@ -51,6 +54,11 @@ public: return m_sqVM; } + SQCONTEXT GetContext() const + { + return m_iContext; + } + private: SQChar pad0[0x8]; HSQUIRRELVM m_sqVM; @@ -62,8 +70,7 @@ private: SQChar pad4[4]; #endif SQInteger m_nTick; - SQChar pad5[4]; - SQCONTEXT m_iContext; + SQCONTEXT m_iContext; // 0x38 #if !defined (GAMEDLL_S2) && !defined (GAMEDLL_S3) SQChar pad6[4]; #endif @@ -89,6 +96,32 @@ inline auto v_Script_LoadRson = p_Script_LoadRson.RCast(); +#ifndef DEDICATED +inline CMemory p_CSquirrelVM_CompileUICLScripts; +inline auto v_CSquirrelVM_CompileUICLScripts = p_CSquirrelVM_CompileUICLScripts.RCast<__int64(__fastcall*)(CSquirrelVM* vm)>(); + +inline CMemory p_CSquirrelVM_CompileScriptsFromArray_UICL; +inline auto v_CSquirrelVM_CompileScriptsFromArray_UICL = p_CSquirrelVM_CompileScriptsFromArray_UICL.RCast(); + +inline CMemory p_Script_SetCompilingVM_UICL; +inline auto v_Script_SetCompilingVM_UICL = p_Script_SetCompilingVM_UICL.RCast(); +#endif + +#ifndef CLIENT_DLL +inline CMemory p_CSquirrelVM_CompileSVScripts; +inline auto v_CSquirrelVM_CompileSVScripts = p_CSquirrelVM_CompileSVScripts.RCast<__int64(__fastcall*)(__int64 a1)>(); + +inline CMemory p_CSquirrelVM_CompileScriptsFromArray_SV; +inline auto v_CSquirrelVM_CompileScriptsFromArray_SV = p_CSquirrelVM_CompileScriptsFromArray_SV.RCast(); + +inline CMemory p_Script_SetCompilingVM_SV; +inline auto v_Script_SetCompilingVM_SV = p_Script_SetCompilingVM_SV.RCast(); +#endif + +inline CMemory p_Script_ParseCompileListRSON; +inline auto v_Script_ParseCompileListRSON = p_Script_ParseCompileListRSON.RCast(); + + #ifndef CLIENT_DLL inline CSquirrelVM* g_pServerScript; #endif // !CLIENT_DLL @@ -110,6 +143,8 @@ CSquirrelVM* Script_GetScriptHandle(const SQCONTEXT context); SQInteger Script_LoadRson(const SQChar* rsonfile); SQBool Script_LoadScript(HSQUIRRELVM v, const SQChar* path, const SQChar* name, SQInteger flags); +bool Script_ParseCompileListRSON(SQCONTEXT context, const char* compileListPath, RSON::Node_t* rson, char** scriptArray, int* pScriptCount, char** precompiledScriptArray, int precompiledScriptCount); + void Script_Execute(const SQChar* code, const SQCONTEXT context); /////////////////////////////////////////////////////////////////////////////// @@ -144,6 +179,35 @@ class VSquirrelVM : public IDetour v_Script_DestroySignalEntryListHead = p_Script_DestroySignalEntryListHead.RCast();/*48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 56 48 83 EC 50 44 8B 42*/ v_Script_LoadRson = p_Script_LoadRson.RCast(); /*4C 8B DC 49 89 5B 08 57 48 81 EC A0 00 00 00 33*/ v_Script_LoadScript = p_Script_LoadScript.RCast(); /*48 8B C4 48 89 48 08 55 41 56 48 8D 68*/ + +#ifndef DEDICATED + // cl/ui scripts.rson compiling + p_CSquirrelVM_CompileUICLScripts = g_GameDll.FindPatternSIMD("E8 ? ? ? ? 88 05 ? ? ? ? 33 C0").FollowNearCallSelf(); + v_CSquirrelVM_CompileUICLScripts = p_CSquirrelVM_CompileUICLScripts.RCast<__int64(__fastcall*)(CSquirrelVM * vm)>(); + + p_CSquirrelVM_CompileScriptsFromArray_UICL = g_GameDll.FindPatternSIMD("E8 ? ? ? ? 44 0F B6 F0 48 85 DB").FollowNearCallSelf(); + v_CSquirrelVM_CompileScriptsFromArray_UICL = p_CSquirrelVM_CompileScriptsFromArray_UICL.RCast(); + + p_Script_SetCompilingVM_UICL = g_GameDll.FindString("Expected bool value for \"IsTestMap\" in \"%s\"\n").FindPatternSelf("48 89", CMemory::Direction::UP, 100); + v_Script_SetCompilingVM_UICL = p_Script_SetCompilingVM_UICL.RCast(); +#endif + +#ifndef CLIENT_DLL + // sv scripts.rson compiling + p_CSquirrelVM_CompileSVScripts = g_GameDll.FindPatternSIMD("E8 ? ? ? ? 33 DB 88 05 ? ? ? ? ").FollowNearCallSelf(); + v_CSquirrelVM_CompileSVScripts = p_CSquirrelVM_CompileSVScripts.RCast<__int64(__fastcall*)(__int64 a1)>(); + + p_CSquirrelVM_CompileScriptsFromArray_SV = g_GameDll.FindPatternSIMD("E8 ? ? ? ? 0F B6 F0 48 85 DB").FollowNearCallSelf(); + v_CSquirrelVM_CompileScriptsFromArray_SV = p_CSquirrelVM_CompileScriptsFromArray_SV.RCast(); + + p_Script_SetCompilingVM_SV = g_GameDll.FindString("Expected bool value for \"IsTestMap\" in \"%s\"\n", 2).FindPatternSelf("48 89", CMemory::Direction::UP, 100); + v_Script_SetCompilingVM_SV = p_Script_SetCompilingVM_SV.RCast(); +#endif + + p_Script_ParseCompileListRSON = g_GameDll.FindPatternSIMD("4C 89 4C 24 ? 55 41 56"); + v_Script_ParseCompileListRSON = p_Script_ParseCompileListRSON.RCast(); + + } virtual void GetVar(void) const { diff --git a/r5dev/squirrel/sqvm.h b/r5dev/squirrel/sqvm.h index d27163f4..e39057ca 100644 --- a/r5dev/squirrel/sqvm.h +++ b/r5dev/squirrel/sqvm.h @@ -27,6 +27,11 @@ struct SQVM #endif // !GAMEDLL_S0 && !GAMEDLL_S1 && !GAMEDLL_S2 } + eDLL_T GetNativePrintContext() const + { + return (eDLL_T)GetContext(); + } + SQVM* _vftable; _BYTE gap000[16]; #if !defined (GAMEDLL_S0) && !defined (GAMEDLL_S1) && !defined (GAMEDLL_S2) diff --git a/r5dev/tier1/cvar.cpp b/r5dev/tier1/cvar.cpp index dadd59cf..3e92fe8b 100644 --- a/r5dev/tier1/cvar.cpp +++ b/r5dev/tier1/cvar.cpp @@ -1019,6 +1019,49 @@ void ConVar::RemoveChangeCallback(FnChangeCallback_t callback) m_pParent->m_fnChangeCallbacks.FindAndRemove(callback); } +//----------------------------------------------------------------------------- +// Purpose: Parse input flag string into bitfield +// Input : pszFlags - +// nFlags - +// pszConVarName - +//----------------------------------------------------------------------------- +bool ConVar::ParseFlagString(const char* pszFlags, int& nFlags, const char* pszConVarName) +{ + int len = strlen(pszFlags); + int flags = 0; + + std::string sFlag = ""; + for (int i = 0; i < len; ++i) + { + char c = pszFlags[i]; + + if (std::isspace(c)) + continue; + + if (c != '|') + sFlag += c; + + if (c == '|' || i == len - 1) + { + if (sFlag == "") + continue; + + if (s_ConVarFlags.count(sFlag) == 0) + { + Warning(eDLL_T::ENGINE, "%s: Attempted to parse invalid flag '%s' for convar '%s'\n", __FUNCTION__, sFlag.c_str(), pszConVarName); + return false; + } + + flags |= s_ConVarFlags.at(sFlag); + + sFlag = ""; + } + } + nFlags = flags; + + return true; +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/r5dev/vpc/rson.cpp b/r5dev/vpc/rson.cpp new file mode 100644 index 00000000..1257de35 --- /dev/null +++ b/r5dev/vpc/rson.cpp @@ -0,0 +1,46 @@ +#include "core/stdafx.h" +#include +#include "tier1/utlbuffer.h" +#include +#include "vpc/rson.h" + +RSON::Node_t* RSON::LoadFromBuffer(const char* pszBufferName, char* pBuffer, RSON::eFieldType rootType) +{ + return RSON_LoadFromBuffer(pszBufferName, pBuffer, rootType, 0, NULL); +} + +RSON::Node_t* RSON::LoadFromFile(const char* pszFilePath) +{ + if (FileSystem()->FileExists(pszFilePath, "GAME")) + { + FileHandle_t file = FileSystem()->Open(pszFilePath, "rt"); + + if (!file) + return NULL; + + uint32_t nFileSize = FileSystem()->Size(file); + + char* fileBuf = MemAllocSingleton()->Alloc(nFileSize + 1); + + int nRead = FileSystem()->Read(fileBuf, nFileSize, file); + FileSystem()->Close(file); + + fileBuf[nRead] = '\0'; + + RSON::Node_t* node = RSON::LoadFromBuffer(pszFilePath, fileBuf, eFieldType::RSON_OBJECT); + + MemAllocSingleton()->Free(fileBuf); + + if (node) + return node; + else + { + // [rexx]: not sure if this should be fatal or not. ideally this should be handled appropriately + // in the calling function + Error(eDLL_T::ENGINE, NO_ERROR, "Error loading file '%s'\n", pszFilePath); + return NULL; + } + } + + return NULL; +} \ No newline at end of file diff --git a/r5dev/vpc/rson.h b/r5dev/vpc/rson.h new file mode 100644 index 00000000..762c585a --- /dev/null +++ b/r5dev/vpc/rson.h @@ -0,0 +1,112 @@ +#pragma once +#include "mathlib/color.h" +#include "tier1/utlbuffer.h" +#include "public/ifilesystem.h" +#include "filesystem/filesystem.h" + +class RSON +{ +public: + enum eFieldType + { + RSON_NULL = 0x1, + RSON_STRING = 0x2, + RSON_VALUE = 0x4, + RSON_OBJECT = 0x8, + RSON_BOOLEAN = 0x10, + RSON_INTEGER = 0x20, + RSON_SIGNED_INTEGER = 0x40, + RSON_UNSIGNED_INTEGER = 0x80, + RSON_DOUBLE = 0x100, + RSON_ARRAY = 0x1000, + }; + + struct Field_t; + + union Value_t + { + Field_t* pSubKey; + char* pszString; + __int64 integerValue; + }; + + // used for the root node of rson tree + struct Node_t + { + eFieldType m_Type; + int m_nValueCount; + Value_t m_Value; + + Field_t* GetFirstSubKey() + { + if (m_Type & eFieldType::RSON_OBJECT) + return m_Value.pSubKey; + return NULL; + }; + + // does not support finding a key in a different level of the tree + Field_t* FindKey(const char* pszKeyName) + { + if ((m_Type & eFieldType::RSON_OBJECT) == 0) + return NULL; + + for (Field_t* pKey = GetFirstSubKey(); pKey != nullptr; pKey = pKey->GetNextKey()) + { + if (!_stricmp(pKey->m_pszName, pszKeyName)) + return pKey; + } + + return NULL; + } + }; + + // used for every other field of the rson tree + struct Field_t + { + char* m_pszName; + Node_t m_Node; + Field_t* m_pNext; + Field_t* m_pPrev; + + Field_t* GetNextKey() { return m_pNext; }; + Field_t* GetLastKey() { return m_pPrev; }; + + Field_t* GetFirstSubKey() { return m_Node.GetFirstSubKey(); }; + + Field_t* FindKey(const char* pszKeyName) { return m_Node.FindKey(pszKeyName); }; + + const char* GetString() { return (m_Node.m_Type == RSON_STRING) ? m_Node.m_Value.pszString : NULL; }; + }; + +public: + static Node_t* LoadFromBuffer(const char* pszBufferName, char* pBuffer, eFieldType rootType); + + static Node_t* LoadFromFile(const char* pszFilePath); +}; +/////////////////////////////////////////////////////////////////////////////// +inline CMemory p_RSON_LoadFromBuffer; +inline auto RSON_LoadFromBuffer = p_RSON_LoadFromBuffer.RCast(); + +/////////////////////////////////////////////////////////////////////////////// +class VRSON : public IDetour +{ + virtual void GetAdr(void) const + { + + } + virtual void GetFun(void) const + { + p_RSON_LoadFromBuffer = g_GameDll.FindPatternSIMD("E8 ? ? ? ? 48 89 45 60 48 8B D8").FollowNearCallSelf(); + + RSON_LoadFromBuffer = p_RSON_LoadFromBuffer.RCast< RSON::Node_t * (__fastcall*)(const char* bufName, char* buf, RSON::eFieldType rootType, __int64 a4, void* a5)>(); + } + virtual void GetVar(void) const + { + + } + virtual void GetCon(void) const { } + virtual void Attach(void) const { } + virtual void Detach(void) const { } +}; +/////////////////////////////////////////////////////////////////////////////// + diff --git a/r5dev/vproj/clientsdk.vcxproj b/r5dev/vproj/clientsdk.vcxproj index 6d470e52..f345a43a 100644 --- a/r5dev/vproj/clientsdk.vcxproj +++ b/r5dev/vproj/clientsdk.vcxproj @@ -79,6 +79,7 @@ + @@ -99,6 +100,7 @@ + NotUsing @@ -168,6 +170,7 @@ + @@ -272,6 +275,7 @@ + @@ -302,6 +306,7 @@ + @@ -521,6 +526,7 @@ + diff --git a/r5dev/vproj/clientsdk.vcxproj.filters b/r5dev/vproj/clientsdk.vcxproj.filters index 20e6c756..c5ded6c9 100644 --- a/r5dev/vproj/clientsdk.vcxproj.filters +++ b/r5dev/vproj/clientsdk.vcxproj.filters @@ -229,6 +229,9 @@ {3a764173-162a-449c-952b-2db659cbad95} + + {ad59e38d-1dd1-4838-8040-6c5d9f4f660c} + {bef90121-4004-47a7-b338-a41ca8078c12} @@ -675,6 +678,15 @@ sdk\tier1 + + sdk\pluginsystem + + + sdk\vpc + + + sdk\localize + sdk\engine @@ -1730,6 +1742,15 @@ sdk\public\tier0 + + sdk\pluginsystem + + + sdk\vpc + + + sdk\localize + sdk\engine diff --git a/r5dev/vproj/dedicated.vcxproj b/r5dev/vproj/dedicated.vcxproj index 2d9083b3..225a2e2f 100644 --- a/r5dev/vproj/dedicated.vcxproj +++ b/r5dev/vproj/dedicated.vcxproj @@ -278,6 +278,7 @@ + @@ -307,6 +308,7 @@ + @@ -507,6 +509,7 @@ + @@ -576,6 +579,7 @@ + @@ -595,6 +599,7 @@ + NotUsing @@ -657,6 +662,7 @@ + diff --git a/r5dev/vproj/dedicated.vcxproj.filters b/r5dev/vproj/dedicated.vcxproj.filters index 735a7dff..0c2ec2da 100644 --- a/r5dev/vproj/dedicated.vcxproj.filters +++ b/r5dev/vproj/dedicated.vcxproj.filters @@ -178,6 +178,9 @@ {f6e7b39b-4032-4a7a-8a44-3aabcbe46ba5} + + {a1808eb5-d397-446f-beef-33adab77733b} + {4bccf09c-4f8b-4d7e-ab72-54fd8c1fb5cf} @@ -1134,6 +1137,15 @@ sdk\public\tier0 + + sdk\localize + + + sdk\pluginsystem + + + sdk\vpc + sdk\engine @@ -1532,6 +1544,15 @@ sdk\tier1 + + sdk\localize + + + sdk\pluginsystem + + + sdk\vpc + sdk\engine diff --git a/r5dev/vproj/gamesdk.vcxproj b/r5dev/vproj/gamesdk.vcxproj index ecd9d0d4..1eed16cc 100644 --- a/r5dev/vproj/gamesdk.vcxproj +++ b/r5dev/vproj/gamesdk.vcxproj @@ -95,6 +95,7 @@ + @@ -116,6 +117,7 @@ + NotUsing @@ -187,6 +189,7 @@ + @@ -314,6 +317,7 @@ + @@ -348,6 +352,7 @@ + @@ -575,6 +580,7 @@ + diff --git a/r5dev/vproj/gamesdk.vcxproj.filters b/r5dev/vproj/gamesdk.vcxproj.filters index 8bb66895..c61bcc4e 100644 --- a/r5dev/vproj/gamesdk.vcxproj.filters +++ b/r5dev/vproj/gamesdk.vcxproj.filters @@ -238,6 +238,9 @@ {42a3745e-8686-4f83-97bd-cec9336b4937} + + {dd902172-603c-495a-ab31-d786cd1ddbdf} + {f3335ce8-0956-4d3c-9d12-ca2bd5646d8c} @@ -741,6 +744,15 @@ sdk\tier1 + + sdk\pluginsystem + + + sdk\vpc + + + sdk\localize + sdk\engine @@ -1901,6 +1913,15 @@ sdk\public\tier0 + + sdk\pluginsystem + + + sdk\vpc + + + sdk\localize + sdk\engine