diff options
Diffstat (limited to 'NorthstarDLL')
30 files changed, 892 insertions, 554 deletions
diff --git a/NorthstarDLL/NorthstarDLL.vcxproj b/NorthstarDLL/NorthstarDLL.vcxproj index 12867b4b..da6ed9cd 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj +++ b/NorthstarDLL/NorthstarDLL.vcxproj @@ -430,8 +430,8 @@ <ClInclude Include="logging\sourceconsole.h" />
<ClInclude Include="masterserver\masterserver.h" />
<ClInclude Include="mods\modmanager.h" />
- <ClInclude Include="ns_version.h" />
<ClInclude Include="pch.h" />
+ <ClInclude Include="plugins\pluginbackend.h" />
<ClInclude Include="plugins\plugins.h" />
<ClInclude Include="plugins\plugin_abi.h" />
<ClInclude Include="scripts\scripthttprequesthandler.h" />
@@ -440,6 +440,7 @@ <ClInclude Include="server\r2server.h" />
<ClInclude Include="server\serverchathooks.h" />
<ClInclude Include="server\serverpresence.h" />
+ <ClInclude Include="shared\gamepresence.h" />
<ClInclude Include="shared\keyvalues.h" />
<ClInclude Include="shared\maxplayers.h" />
<ClInclude Include="shared\misccommands.h" />
@@ -507,6 +508,7 @@ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
+ <ClCompile Include="plugins\pluginbackend.cpp" />
<ClCompile Include="plugins\plugins.cpp" />
<ClCompile Include="scripts\client\clientchathooks.cpp" />
<ClCompile Include="scripts\client\cursorposition.cpp" />
@@ -533,6 +535,7 @@ <ClCompile Include="shared\exploit_fixes\exploitfixes_lzss.cpp" />
<ClCompile Include="shared\exploit_fixes\exploitfixes_utf8parser.cpp" />
<ClCompile Include="shared\exploit_fixes\ns_limits.cpp" />
+ <ClCompile Include="shared\gamepresence.cpp" />
<ClCompile Include="shared\keyvalues.cpp" />
<ClCompile Include="shared\maxplayers.cpp" />
<ClCompile Include="shared\misccommands.cpp" />
diff --git a/NorthstarDLL/NorthstarDLL.vcxproj.filters b/NorthstarDLL/NorthstarDLL.vcxproj.filters index 2526c50a..4be3288b 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj.filters +++ b/NorthstarDLL/NorthstarDLL.vcxproj.filters @@ -1032,9 +1032,6 @@ <ClInclude Include="mods\modmanager.h">
<Filter>Header Files\mods</Filter>
</ClInclude>
- <ClInclude Include="plugins\plugin_abi.h">
- <Filter>Header Files\plugins</Filter>
- </ClInclude>
<ClInclude Include="plugins\plugins.h">
<Filter>Header Files\plugins</Filter>
</ClInclude>
@@ -1065,9 +1062,6 @@ <ClInclude Include="dllmain.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="ns_version.h">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -1161,6 +1155,18 @@ <ClInclude Include="scripts\scripthttprequesthandler.h">
<Filter>Header Files\scripts</Filter>
</ClInclude>
+ <ClInclude Include="plugins\plugin_abi.h">
+ <Filter>Header Files\plugins</Filter>
+ </ClInclude>
+ <ClInclude Include="plugins\pluginbackend.h">
+ <Filter>Header Files\plugins</Filter>
+ </ClInclude>
+ <ClInclude Include="shared\gamepresence.h">
+ <Filter>Header Files\shared</Filter>
+ </ClInclude>
+ <ClInclude Include="util\wininfo.h">
+ <Filter>Header Files\util</Filter>
+ </ClInclude>
<ClInclude Include="shared\maxplayers.h">
<Filter>Header Files\shared</Filter>
</ClInclude>
@@ -1420,6 +1426,15 @@ <ClCompile Include="scripts\scripthttprequesthandler.cpp">
<Filter>Source Files\scripts</Filter>
</ClCompile>
+ <ClCompile Include="plugins\pluginbackend.cpp">
+ <Filter>Source Files\plugins</Filter>
+ </ClCompile>
+ <ClCompile Include="shared\gamepresence.cpp">
+ <Filter>Source Files\shared</Filter>
+ </ClCompile>
+ <ClCompile Include="util\wininfo.cpp">
+ <Filter>Source Files\util</Filter>
+ </ClCompile>
<ClCompile Include="core\sourceinterface.cpp">
<Filter>Source Files\core</Filter>
</ClCompile>
diff --git a/NorthstarDLL/config/profile.cpp b/NorthstarDLL/config/profile.cpp index 08c91208..d5361efa 100644 --- a/NorthstarDLL/config/profile.cpp +++ b/NorthstarDLL/config/profile.cpp @@ -17,7 +17,6 @@ void InitialiseNorthstarPrefix() { int space = cla.find(" "); std::string dirname = cla.substr(9, space - 9); - spdlog::info("Found profile in command line arguments: " + dirname); NORTHSTAR_FOLDER_PREFIX = dirname; } else @@ -26,13 +25,11 @@ void InitialiseNorthstarPrefix() int quote1 = cla.find(quote); int quote2 = (cla.substr(quote1 + 1)).find(quote); std::string dirname = cla.substr(quote1 + 1, quote2); - spdlog::info("Found profile in command line arguments: " + dirname); NORTHSTAR_FOLDER_PREFIX = dirname; } } else { - spdlog::info("Profile was not found in command line arguments. Using default: R2Northstar"); NORTHSTAR_FOLDER_PREFIX = "R2Northstar"; } diff --git a/NorthstarDLL/core/convar/concommand.cpp b/NorthstarDLL/core/convar/concommand.cpp index 67c867f8..82594f04 100644 --- a/NorthstarDLL/core/convar/concommand.cpp +++ b/NorthstarDLL/core/convar/concommand.cpp @@ -2,6 +2,8 @@ #include "shared/misccommands.h" #include "engine/r2engine.h" +#include "plugins/pluginbackend.h" + #include <iostream> //----------------------------------------------------------------------------- @@ -148,4 +150,6 @@ ON_DLL_LOAD("engine.dll", ConCommand, (CModule module)) { ConCommandConstructor = module.Offset(0x415F60).As<ConCommandConstructorType>(); AddMiscConCommands(); + + g_pPluginCommunicationhandler->m_sEngineData.ConCommandConstructor = ConCommandConstructor; } diff --git a/NorthstarDLL/core/convar/convar.cpp b/NorthstarDLL/core/convar/convar.cpp index 21fca8c0..6646729a 100644 --- a/NorthstarDLL/core/convar/convar.cpp +++ b/NorthstarDLL/core/convar/convar.cpp @@ -3,6 +3,8 @@ #include "convar.h" #include "core/sourceinterface.h" +#include "plugins/pluginbackend.h" + #include <float.h> typedef void (*ConVarRegisterType)( @@ -37,6 +39,11 @@ ON_DLL_LOAD("engine.dll", ConVar, (CModule module)) R2::g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007"); R2::g_pCVar = *R2::g_pCVarInterface; + + g_pPluginCommunicationhandler->m_sEngineData.conVarMalloc = conVarMalloc; + g_pPluginCommunicationhandler->m_sEngineData.conVarRegister = conVarRegister; + g_pPluginCommunicationhandler->m_sEngineData.ConVar_Vtable = g_pConVar_Vtable; + g_pPluginCommunicationhandler->m_sEngineData.IConVar_Vtable = g_pIConVar_Vtable; } //----------------------------------------------------------------------------- diff --git a/NorthstarDLL/core/hooks.cpp b/NorthstarDLL/core/hooks.cpp index 7c70b0a5..9124d5af 100644 --- a/NorthstarDLL/core/hooks.cpp +++ b/NorthstarDLL/core/hooks.cpp @@ -274,8 +274,6 @@ AUTOHOOK_ABSOLUTEADDR(_GetCommandLineA, GetCommandLineA, LPSTR, WINAPI, ()) return cmdlineOrg; } memcpy(cmdlineModified, args.c_str(), len + 1); - - spdlog::info("Command line: {}", cmdlineModified); } return cmdlineModified; diff --git a/NorthstarDLL/core/math/color.cpp b/NorthstarDLL/core/math/color.cpp index 9936cc5a..7b98043a 100644 --- a/NorthstarDLL/core/math/color.cpp +++ b/NorthstarDLL/core/math/color.cpp @@ -13,6 +13,8 @@ namespace NS::Colors Color RPAK (255, 190, 0 ); Color NORTHSTAR (66 , 72 , 128); Color ECHO (150, 150, 159); + Color PLUGINSYS (244, 60 , 14); + Color PLUGIN (244, 106, 14); Color TRACE (0 , 255, 255); Color DEBUG (0 , 255, 255); diff --git a/NorthstarDLL/core/math/color.h b/NorthstarDLL/core/math/color.h index 4dc9b36e..76cf8a47 100644 --- a/NorthstarDLL/core/math/color.h +++ b/NorthstarDLL/core/math/color.h @@ -186,6 +186,8 @@ namespace NS::Colors extern Color RPAK; extern Color NORTHSTAR; extern Color ECHO; + extern Color PLUGINSYS; + extern Color PLUGIN; extern Color TRACE; extern Color DEBUG; diff --git a/NorthstarDLL/dllmain.cpp b/NorthstarDLL/dllmain.cpp index a697da5b..6fc49be8 100644 --- a/NorthstarDLL/dllmain.cpp +++ b/NorthstarDLL/dllmain.cpp @@ -7,6 +7,8 @@ #include "plugins/plugins.h" #include "util/version.h" #include "squirrel/squirrel.h" +#include "shared/gamepresence.h" +#include "server/serverpresence.h" #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" @@ -16,8 +18,6 @@ #include <string.h> #include <filesystem> -typedef void (*initPluginFuncPtr)(void* (*getPluginObject)(PluginObject)); - BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) @@ -32,110 +32,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv return TRUE; } -void freeLibrary(HMODULE hLib) -{ - if (!FreeLibrary(hLib)) - { - spdlog::error("There was an error while trying to free library"); - } -} - -bool LoadPlugins() -{ - - std::vector<fs::path> paths; - - std::string pluginPath = GetNorthstarPrefix() + "/plugins"; - if (!fs::exists(pluginPath)) - { - spdlog::warn("Could not find a plugins directory. Skipped loading plugins"); - return false; - } - // ensure dirs exist - fs::recursive_directory_iterator iterator(pluginPath); - if (std::filesystem::begin(iterator) == std::filesystem::end(iterator)) - { - spdlog::warn("Could not find any plugins. Skipped loading plugins"); - return false; - } - for (auto const& entry : iterator) - { - if (fs::is_regular_file(entry) && entry.path().extension() == ".dll") - paths.emplace_back(entry.path().filename()); - } - initGameState(); - for (fs::path path : paths) - { - std::string pathstring = (pluginPath / path).string(); - std::wstring wpath = (pluginPath / path).wstring(); - - LPCWSTR wpptr = wpath.c_str(); - HMODULE datafile = - LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); // Load the DLL as a data file - if (datafile == NULL) - { - spdlog::info("Failed to load library {}: ", std::system_category().message(GetLastError())); - continue; - } - HRSRC manifestResource = FindResourceW(datafile, MAKEINTRESOURCEW(101), MAKEINTRESOURCEW(RT_RCDATA)); - - if (manifestResource == NULL) - { - spdlog::info("Could not find manifest for library {}", pathstring); - freeLibrary(datafile); - continue; - } - spdlog::info("Loading resource from library"); - HGLOBAL myResourceData = LoadResource(datafile, manifestResource); - if (myResourceData == NULL) - { - spdlog::error("Failed to load resource from library"); - freeLibrary(datafile); - continue; - } - int manifestSize = SizeofResource(datafile, manifestResource); - std::string manifest = std::string((const char*)LockResource(myResourceData), 0, manifestSize); - freeLibrary(datafile); - - rapidjson_document manifestJSON; - manifestJSON.Parse(manifest.c_str()); - - if (manifestJSON.HasParseError()) - { - spdlog::error("Manifest for {} was invalid", pathstring); - continue; - } - if (!manifestJSON.HasMember("api_version")) - { - spdlog::error("{} does not have a version number in its manifest", pathstring); - continue; - // spdlog::info(manifestJSON["version"].GetString()); - } - if (strcmp(manifestJSON["api_version"].GetString(), std::to_string(ABI_VERSION).c_str())) - { - spdlog::error("{} has an incompatible API version number in its manifest", pathstring); - continue; - } - // Passed all checks, going to actually load it now - - HMODULE pluginLib = LoadLibraryW(wpptr); // Load the DLL as a data file - if (pluginLib == NULL) - { - spdlog::info("Failed to load library {}: ", std::system_category().message(GetLastError())); - continue; - } - initPluginFuncPtr initPlugin = (initPluginFuncPtr)GetProcAddress(pluginLib, "initializePlugin"); - if (initPlugin == NULL) - { - spdlog::info("Library {} has no function initializePlugin", pathstring); - continue; - } - spdlog::info("Succesfully loaded {}", pathstring); - initPlugin(&getPluginObject); - } - return true; -} - bool InitialiseNorthstar() { static bool bInitialised = false; @@ -144,13 +40,29 @@ bool InitialiseNorthstar() bInitialised = true; + InitialiseNorthstarPrefix(); + // initialise the console if needed (-northstar needs this) InitialiseConsole(); // initialise logging before most other things so that they can use spdlog and it have the proper formatting InitialiseLogging(); - - InitialiseNorthstarPrefix(); InitialiseVersion(); + CreateLogFiles(); + + InitialiseCrashHandler(); + + // Write launcher version to log + spdlog::info("NorthstarLauncher version: {}", version); + spdlog::info("Command line: {}", GetCommandLineA()); + spdlog::info("Using profile: {}", GetNorthstarPrefix()); + + InstallInitialHooks(); + + g_pServerPresence = new ServerPresenceManager(); + + g_pGameStatePresence = new GameStatePresence(); + g_pPluginManager = new PluginManager(); + g_pPluginManager->LoadPlugins(); InitialiseSquirrelManagers(); @@ -159,13 +71,6 @@ bool InitialiseNorthstar() curl_global_init_mem(CURL_GLOBAL_DEFAULT, _malloc_base, _free_base, _realloc_base, _strdup_base, _calloc_base); - InitialiseCrashHandler(); - InstallInitialHooks(); - CreateLogFiles(); - - // Write launcher version to log - spdlog::info("NorthstarLauncher version: {}", version); - // run callbacks for any libraries that are already loaded by now CallAllPendingDLLLoadCallbacks(); diff --git a/NorthstarDLL/engine/hoststate.cpp b/NorthstarDLL/engine/hoststate.cpp index 44a66113..d598bd2c 100644 --- a/NorthstarDLL/engine/hoststate.cpp +++ b/NorthstarDLL/engine/hoststate.cpp @@ -7,6 +7,8 @@ #include "engine/r2engine.h" #include "shared/exploit_fixes/ns_limits.h" #include "squirrel/squirrel.h" +#include "plugins/plugins.h" +#include "plugins/pluginbackend.h" AUTOHOOK_INIT() @@ -187,6 +189,8 @@ void, __fastcall, (CHostState* self, double flCurrentTime, float flFrameTime)) if (g_pSquirrel<ScriptContext::SERVER>->m_pSQVM != nullptr && g_pSquirrel<ScriptContext::SERVER>->m_pSQVM->sqvm != nullptr) g_pSquirrel<ScriptContext::SERVER>->ProcessMessageBuffer(); + + g_pGameStatePresence->RunFrame(); } ON_DLL_LOAD_RELIESON("engine.dll", HostState, ConVar, (CModule module)) diff --git a/NorthstarDLL/logging/logging.cpp b/NorthstarDLL/logging/logging.cpp index f88928e1..53795ef0 100644 --- a/NorthstarDLL/logging/logging.cpp +++ b/NorthstarDLL/logging/logging.cpp @@ -28,6 +28,7 @@ namespace NS::log std::shared_ptr<ColoredLogger> echo; std::shared_ptr<ColoredLogger> NORTHSTAR; + std::shared_ptr<ColoredLogger> PLUGINSYS; }; // namespace NS::log // This needs to be called after hooks are loaded so we can access the command line args @@ -148,6 +149,11 @@ void InitialiseConsole() } } +void RegisterLogger(std::shared_ptr<ColoredLogger> logger) +{ + loggers.push_back(logger); +} + void RegisterCustomSink(std::shared_ptr<CustomSink> sink) { for (auto& logger : loggers) @@ -189,6 +195,8 @@ void InitialiseLogging() NS::log::rpak = std::make_shared<ColoredLogger>("RPAK_FSYS", NS::Colors::RPAK); NS::log::echo = std::make_shared<ColoredLogger>("ECHO", NS::Colors::ECHO); + NS::log::PLUGINSYS = std::make_shared<ColoredLogger>("PLUGINSYS", NS::Colors::PLUGINSYS); + loggers.push_back(NS::log::SCRIPT_UI); loggers.push_back(NS::log::SCRIPT_CL); loggers.push_back(NS::log::SCRIPT_SV); @@ -198,6 +206,8 @@ void InitialiseLogging() loggers.push_back(NS::log::NATIVE_SV); loggers.push_back(NS::log::NATIVE_EN); + loggers.push_back(NS::log::PLUGINSYS); + loggers.push_back(NS::log::fs); loggers.push_back(NS::log::rpak); loggers.push_back(NS::log::echo); diff --git a/NorthstarDLL/logging/logging.h b/NorthstarDLL/logging/logging.h index 1cfc6644..2efee80b 100644 --- a/NorthstarDLL/logging/logging.h +++ b/NorthstarDLL/logging/logging.h @@ -100,10 +100,13 @@ namespace NS::log extern std::shared_ptr<ColoredLogger> NORTHSTAR; + extern std::shared_ptr<ColoredLogger> PLUGINSYS; + void FlushLoggers(); }; // namespace NS::log void RegisterCustomSink(std::shared_ptr<CustomSink> sink); +void RegisterLogger(std::shared_ptr<ColoredLogger> logger); inline bool g_bSpdLog_UseAnsiColor = true; diff --git a/NorthstarDLL/masterserver/masterserver.cpp b/NorthstarDLL/masterserver/masterserver.cpp index 37301886..8087237c 100644 --- a/NorthstarDLL/masterserver/masterserver.cpp +++ b/NorthstarDLL/masterserver/masterserver.cpp @@ -572,7 +572,7 @@ void MasterServerManager::AuthenticateWithOwnServer(const char* uid, const char* requestThread.detach(); } -void MasterServerManager::AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password) +void MasterServerManager::AuthenticateWithServer(const char* uid, const char* playerToken, RemoteServerInfo server, const char* password) { // dont wait, just stop if we're trying to do 2 auth requests at once if (m_bAuthenticatingWithGameServer) @@ -584,11 +584,11 @@ void MasterServerManager::AuthenticateWithServer(const char* uid, const char* pl std::string uidStr(uid); std::string tokenStr(playerToken); - std::string serverIdStr(serverId); + std::string serverIdStr(server.id); std::string passwordStr(password); std::thread requestThread( - [this, uidStr, tokenStr, serverIdStr, passwordStr]() + [this, uidStr, tokenStr, serverIdStr, passwordStr, server]() { // esnure that any persistence saving is done, so we know masterserver has newest while (m_bSavingPersistentData) @@ -684,6 +684,9 @@ void MasterServerManager::AuthenticateWithServer(const char* uid, const char* pl m_bHasPendingConnectionInfo = true; m_bSuccessfullyAuthenticatedWithGameServer = true; + + m_currentServer = server; + m_sCurrentServerPassword = passwordStr; } else { diff --git a/NorthstarDLL/masterserver/masterserver.h b/NorthstarDLL/masterserver/masterserver.h index bded9952..21082e2f 100644 --- a/NorthstarDLL/masterserver/masterserver.h +++ b/NorthstarDLL/masterserver/masterserver.h @@ -113,6 +113,9 @@ class MasterServerManager bool m_bHasMainMenuPromoData = false; MainMenuPromoData m_sMainMenuPromoData; + std::optional<RemoteServerInfo> m_currentServer; + std::string m_sCurrentServerPassword; + public: MasterServerManager(); @@ -121,7 +124,7 @@ class MasterServerManager void RequestMainMenuPromos(); void AuthenticateOriginWithMasterServer(const char* uid, const char* originToken); void AuthenticateWithOwnServer(const char* uid, const char* playerToken); - void AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password); + void AuthenticateWithServer(const char* uid, const char* playerToken, RemoteServerInfo server, const char* password); void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize); }; diff --git a/NorthstarDLL/mods/modmanager.cpp b/NorthstarDLL/mods/modmanager.cpp index bc0cb2ed..0f904bcd 100644 --- a/NorthstarDLL/mods/modmanager.cpp +++ b/NorthstarDLL/mods/modmanager.cpp @@ -241,6 +241,11 @@ Mod::Mod(fs::path modDir, char* jsonBuf) } } + if (modJson.HasMember("InitScript") && modJson["InitScript"].IsString()) + { + initScript = modJson["InitScript"].GetString(); + } + if (modJson.HasMember("Localisation") && modJson["Localisation"].IsArray()) { for (auto& localisationStr : modJson["Localisation"].GetArray()) diff --git a/NorthstarDLL/mods/modmanager.h b/NorthstarDLL/mods/modmanager.h index 369eb07b..a4c72f77 100644 --- a/NorthstarDLL/mods/modmanager.h +++ b/NorthstarDLL/mods/modmanager.h @@ -103,6 +103,8 @@ class Mod std::vector<ModConCommand*> ConCommands; // custom localisation files created by the mod std::vector<std::string> LocalisationFiles; + // custom script init.nut + std::string initScript; // other files: diff --git a/NorthstarDLL/pch.h b/NorthstarDLL/pch.h index 55ebba8b..9ad8378c 100644 --- a/NorthstarDLL/pch.h +++ b/NorthstarDLL/pch.h @@ -20,6 +20,11 @@ namespace fs = std::filesystem; +#define EXPORT extern "C" __declspec(dllexport) + +typedef void (*callable)(); +typedef void (*callable_v)(void* v); + // clang-format off #define assert_msg(exp, msg) assert((exp, msg)) //clang-format on @@ -28,6 +33,7 @@ namespace fs = std::filesystem; #include "core/structs.h" #include "core/math/color.h" + #include "spdlog/spdlog.h" #include "logging/logging.h" #include "MinHook.h" diff --git a/NorthstarDLL/plugins/plugin_abi.h b/NorthstarDLL/plugins/plugin_abi.h index 4b176a32..c9fa9d25 100644 --- a/NorthstarDLL/plugins/plugin_abi.h +++ b/NorthstarDLL/plugins/plugin_abi.h @@ -1,68 +1,164 @@ #pragma once #include <string> +#include "squirrel/squirrelclasstypes.h" -#define ABI_VERSION 1 -/// <summary> -/// This enum is used for referencing the different types of objects we can pass to a plugin -/// Anything exposed to a plugin must not a be C++ type, as they could break when compiling with a different compiler. -/// Any ABI incompatible change must increment the version number. -/// Nothing must be removed from this enum, only appended. When it absolutely necessary to deprecate an object, it should return UNSUPPORTED -/// when retrieved -/// </summary> -enum PluginObject +#define ABI_VERSION 2 + +enum GameState { - UNSUPPORTED = 0, - GAMESTATE = 1, - SERVERINFO = 2, - PLAYERINFO = 3, - DUMMY = 0xFFFF + LOADING = 0, + MAINMENU = 1, + LOBBY = 2, + INGAME = 3 }; -enum GameStateInfoType +enum PluginLoadDLL { - ourScore = 0, - secondHighestScore = 1, - highestScore = 2, - connected = 3, - loading = 4, - map = 5, - mapDisplayName = 6, - playlist = 7, - playlistDisplayName = 8, - players = 9 + ENGINE = 0, + CLIENT, + SERVER }; -struct GameState + +enum ObjectType { - int (*getGameStateChar)(char* out_buf, size_t out_buf_len, GameStateInfoType var); - int (*getGameStateInt)(int* out_ptr, GameStateInfoType var); - int (*getGameStateBool)(bool* out_ptr, GameStateInfoType var); + CONCOMMANDS = 0, + CONVAR = 1, }; -enum ServerInfoType +struct SquirrelFunctions { - id = 0, - name = 1, - description = 2, - password = 3, - maxPlayers = 4, - roundBased = 5, - scoreLimit = 6, - endTime = 7 + RegisterSquirrelFuncType RegisterSquirrelFunc; + sq_defconstType __sq_defconst; + + sq_compilebufferType __sq_compilebuffer; + sq_callType __sq_call; + sq_raiseerrorType __sq_raiseerror; + + sq_newarrayType __sq_newarray; + sq_arrayappendType __sq_arrayappend; + + sq_newtableType __sq_newtable; + sq_newslotType __sq_newslot; + + sq_pushroottableType __sq_pushroottable; + sq_pushstringType __sq_pushstring; + sq_pushintegerType __sq_pushinteger; + sq_pushfloatType __sq_pushfloat; + sq_pushboolType __sq_pushbool; + sq_pushassetType __sq_pushasset; + sq_pushvectorType __sq_pushvector; + sq_pushobjectType __sq_pushobject; + sq_getthisentityType __sq_getthisentity; + sq_getobjectType __sq_getobject; + + sq_stackinfosType __sq_stackinfos; + + sq_getstringType __sq_getstring; + sq_getintegerType __sq_getinteger; + sq_getfloatType __sq_getfloat; + sq_getboolType __sq_getbool; + sq_getType __sq_get; + sq_getassetType __sq_getasset; + sq_getuserdataType __sq_getuserdata; + sq_getvectorType __sq_getvector; + + sq_createuserdataType __sq_createuserdata; + sq_setuserdatatypeidType __sq_setuserdatatypeid; + sq_getfunctionType __sq_getfunction; + + sq_schedule_call_externalType __sq_schedule_call_external; + sq_getentityfrominstanceType __sq_getentityfrominstance; + sq_GetEntityConstantType __sq_GetEntityConstant_CBaseEntity; +}; + +struct MessageSource +{ + const char* file; + const char* func; + int line; }; -struct ServerInfo + +// This is a modified version of spdlog::details::log_msg +// This is so that we can make it cross DLL boundaries +struct LogMsg { - int (*getServerInfoChar)(char* out_buf, size_t out_buf_len, ServerInfoType var); - int (*getServerInfoInt)(int* out_ptr, ServerInfoType var); - int (*getServerInfoBool)(bool* out_ptr, ServerInfoType var); + int level; + uint64_t timestamp; + const char* msg; + MessageSource source; + int pluginHandle; }; -enum PlayerInfoType +typedef void (*loggerfunc_t)(LogMsg* msg); +typedef void (*PLUGIN_RELAY_INVITE_TYPE)(const char* invite); +typedef void* (*CreateObjectFunc)(ObjectType type); + +struct PluginNorthstarData { - uid = 0 + const char* version; + HMODULE northstarModule; + int pluginHandle; }; -struct PlayerInfo + +struct PluginInitFuncs { - int (*getPlayerInfoChar)(char* out_buf, size_t out_buf_len, PlayerInfoType var); - int (*getPlayerInfoInt)(int* out_ptr, PlayerInfoType var); - int (*getPlayerInfoBool)(bool* out_ptr, PlayerInfoType var); + loggerfunc_t logger; + PLUGIN_RELAY_INVITE_TYPE relayInviteFunc; + CreateObjectFunc createObject; }; + +struct PluginEngineData +{ + void* ConCommandConstructor; + void* conVarMalloc; + void* conVarRegister; + void* ConVar_Vtable; + void* IConVar_Vtable; +}; + +struct PluginGameStatePresence +{ + const char* id; + const char* name; + const char* description; + const char* password; + + bool isServer; + bool isLocal; + GameState state; + + const char* map; + const char* mapDisplayname; + const char* playlist; + const char* playlistDisplayname; + + int currentPlayers; + int maxPlayers; + + int ownScore; + int otherHighestScore; // NOTE: The highest score OR the second highest score if we have the highest + int maxScore; + + int timestampEnd; +}; + +/// <summary> Async communication within the plugin system +/// Due to the asynchronous nature of plugins, combined with the limitations of multi-compiler support +/// and the custom memory allocator used by r2, is it difficult to safely get data across DLL boundaries +/// from Northstar to plugin unless Northstar can own that memory. +/// This means that plugins should manage their own memory and can only receive data from northstar using one of the functions below. +/// These should be exports of the plugin DLL. If they are not exported, they will not be called. +/// Note that it is not required to have these exports if you do not use them. +/// </summary> + +// Northstar -> Plugin +typedef void (*PLUGIN_INIT_TYPE)(PluginInitFuncs* funcs, PluginNorthstarData* data); +typedef void (*PLUGIN_INIT_SQVM_TYPE)(SquirrelFunctions* funcs); +typedef void (*PLUGIN_INFORM_SQVM_CREATED_TYPE)(ScriptContext context, CSquirrelVM* sqvm); +typedef void (*PLUGIN_INFORM_SQVM_DESTROYED_TYPE)(ScriptContext context); + +// Async Communication types + +// Northstar -> Plugin +typedef void (*PLUGIN_PUSH_PRESENCE_TYPE)(PluginGameStatePresence* data); +typedef void (*PLUGIN_INFORM_DLL_LOAD_TYPE)(PluginLoadDLL dll, void* data); diff --git a/NorthstarDLL/plugins/pluginbackend.cpp b/NorthstarDLL/plugins/pluginbackend.cpp new file mode 100644 index 00000000..b442d702 --- /dev/null +++ b/NorthstarDLL/plugins/pluginbackend.cpp @@ -0,0 +1,94 @@ +#include "pch.h" +#include "pluginbackend.h" +#include "plugin_abi.h" +#include "server/serverpresence.h" +#include "masterserver/masterserver.h" +#include "squirrel/squirrel.h" +#include "plugins.h" + +#include "core/convar/concommand.h" + +#define EXPORT extern "C" __declspec(dllexport) + +AUTOHOOK_INIT() + +PluginCommunicationHandler* g_pPluginCommunicationhandler; + +static PluginDataRequest storedRequest {PluginDataRequestType::END, (PluginRespondDataCallable) nullptr}; + +void init_plugincommunicationhandler() +{ + g_pPluginCommunicationhandler = new PluginCommunicationHandler; + g_pPluginCommunicationhandler->requestQueue = {}; +} + +void PluginCommunicationHandler::RunFrame() +{ + std::lock_guard<std::mutex> lock(requestMutex); + if (!requestQueue.empty()) + { + storedRequest = requestQueue.front(); + switch (storedRequest.type) + { + default: + spdlog::error("{} was called with invalid request type '{}'", __FUNCTION__, static_cast<int>(storedRequest.type)); + } + requestQueue.pop(); + } +} + +void PluginCommunicationHandler::PushRequest(PluginDataRequestType type, PluginRespondDataCallable func) +{ + std::lock_guard<std::mutex> lock(requestMutex); + requestQueue.push(PluginDataRequest {type, func}); +} + +void PluginCommunicationHandler::GeneratePresenceObjects() +{ + PluginGameStatePresence presence {}; + + presence.id = g_pGameStatePresence->id.c_str(); + presence.name = g_pGameStatePresence->name.c_str(); + presence.description = g_pGameStatePresence->description.c_str(); + presence.password = g_pGameStatePresence->password.c_str(); + + presence.isServer = g_pGameStatePresence->isServer; + presence.isLocal = g_pGameStatePresence->isLocal; + + if (g_pGameStatePresence->isLoading) + presence.state = GameState::LOADING; + else if (g_pGameStatePresence->uiMap == "") + presence.state = GameState::MAINMENU; + else if (g_pGameStatePresence->map == "mp_lobby" && g_pGameStatePresence->isLocal && g_pGameStatePresence->isLobby) + presence.state = GameState::LOBBY; + else + presence.state = GameState::INGAME; + + presence.map = g_pGameStatePresence->map.c_str(); + presence.mapDisplayname = g_pGameStatePresence->mapDisplayname.c_str(); + presence.playlist = g_pGameStatePresence->playlist.c_str(); + presence.playlistDisplayname = g_pGameStatePresence->playlistDisplayname.c_str(); + + presence.currentPlayers = g_pGameStatePresence->currentPlayers; + presence.maxPlayers = g_pGameStatePresence->maxPlayers; + presence.ownScore = g_pGameStatePresence->ownScore; + presence.otherHighestScore = g_pGameStatePresence->otherHighestScore; + presence.maxScore = g_pGameStatePresence->maxScore; + presence.timestampEnd = g_pGameStatePresence->timestampEnd; + g_pPluginManager->PushPresence(&presence); +} + +ON_DLL_LOAD_RELIESON("engine.dll", PluginBackendEngine, ConCommand, (CModule module)) +{ + g_pPluginManager->InformDLLLoad(PluginLoadDLL::ENGINE, &g_pPluginCommunicationhandler->m_sEngineData); +} + +ON_DLL_LOAD_RELIESON("client.dll", PluginBackendClient, ConCommand, (CModule module)) +{ + g_pPluginManager->InformDLLLoad(PluginLoadDLL::CLIENT, nullptr); +} + +ON_DLL_LOAD_RELIESON("server.dll", PluginBackendServer, ConCommand, (CModule module)) +{ + g_pPluginManager->InformDLLLoad(PluginLoadDLL::SERVER, nullptr); +} diff --git a/NorthstarDLL/plugins/pluginbackend.h b/NorthstarDLL/plugins/pluginbackend.h new file mode 100644 index 00000000..ef42c508 --- /dev/null +++ b/NorthstarDLL/plugins/pluginbackend.h @@ -0,0 +1,45 @@ +#pragma once +#include "pch.h" +#include "plugin_abi.h" +#include "shared/gamepresence.h" + +#include <queue> +#include <mutex> + +enum PluginDataRequestType +{ + END = 0, +}; + +union PluginRespondDataCallable +{ + // Empty for now + void* UNUSED; +}; + +class PluginDataRequest +{ + public: + PluginDataRequestType type; + PluginRespondDataCallable func; + PluginDataRequest(PluginDataRequestType type, PluginRespondDataCallable func) : type(type), func(func) {} +}; + +class PluginCommunicationHandler +{ + public: + void RunFrame(); + void PushRequest(PluginDataRequestType type, PluginRespondDataCallable func); + + void GeneratePresenceObjects(); + + public: + std::queue<PluginDataRequest> requestQueue; + std::mutex requestMutex; + + PluginEngineData m_sEngineData {}; +}; + +void init_plugincommunicationhandler(); + +extern PluginCommunicationHandler* g_pPluginCommunicationhandler; diff --git a/NorthstarDLL/plugins/plugins.cpp b/NorthstarDLL/plugins/plugins.cpp index 47d27da1..6e3ceb5a 100644 --- a/NorthstarDLL/plugins/plugins.cpp +++ b/NorthstarDLL/plugins/plugins.cpp @@ -1,421 +1,292 @@ +#include "plugins.h" +#include "config/profile.h" + #include "squirrel/squirrel.h" #include "plugins.h" #include "masterserver/masterserver.h" #include "core/convar/convar.h" #include "server/serverpresence.h" +#include <optional> -#include <chrono> -#include <windows.h> +#include "util/version.h" +#include "pluginbackend.h" +#include "util/wininfo.h" +#include "logging/logging.h" +#include "dedicated/dedicated.h" -/// <summary> -/// The data is split into two different representations: one for internal, and one for plugins, for thread safety reasons -/// The struct exposed to plugins contains getter functions for the various data types. -/// We can safely use C++ types like std::string here since these are only ever handled by Northstar internally -/// </summary> -struct InternalGameState -{ - int ourScore; - int secondHighestScore; - int highestScore; - - bool connected; - bool loading; - std::string map; - std::string mapDisplayName; - std::string playlist; - std::string playlistDisplayName; - int players; -}; -struct InternalServerInfo -{ - std::string id; - std::string name; - std::string description; - std::string password; - int maxPlayers; - bool roundBased; - int scoreLimit; - int endTime; -}; -// TODO: need to extend this to include current player data like loadouts -struct InternalPlayerInfo -{ - int uid; -}; - -InternalGameState gameState; -InternalServerInfo serverInfo; -InternalPlayerInfo playerInfo; - -GameState gameStateExport; -ServerInfo serverInfoExport; -PlayerInfo playerInfoExport; - -/// <summary> -/// We use SRW Locks because plugins will often be running their own thread -/// To ensure thread safety, and to make it difficult to fuck up, we force them to use *our* functions to get data -/// </summary> -static SRWLOCK gameStateLock; -static SRWLOCK serverInfoLock; -static SRWLOCK playerInfoLock; - -void* getPluginObject(PluginObject var) +PluginManager* g_pPluginManager; + +void freeLibrary(HMODULE hLib) { - switch (var) + if (!FreeLibrary(hLib)) { - case PluginObject::GAMESTATE: - return &gameStateExport; - case PluginObject::SERVERINFO: - return &serverInfoExport; - case PluginObject::PLAYERINFO: - return &playerInfoExport; - default: - return (void*)-1; + spdlog::error("There was an error while trying to free library"); } } -void initGameState() -{ - // Initalize the Slim Reader / Writer locks - InitializeSRWLock(&gameStateLock); - InitializeSRWLock(&serverInfoLock); - InitializeSRWLock(&playerInfoLock); - - gameStateExport.getGameStateChar = &getGameStateChar; - gameStateExport.getGameStateInt = &getGameStateInt; - gameStateExport.getGameStateBool = &getGameStateBool; - - serverInfoExport.getServerInfoChar = &getServerInfoChar; - serverInfoExport.getServerInfoInt = &getServerInfoInt; - serverInfoExport.getServerInfoBool = &getServerInfoBool; - - playerInfoExport.getPlayerInfoChar = &getPlayerInfoChar; - playerInfoExport.getPlayerInfoInt = &getPlayerInfoInt; - playerInfoExport.getPlayerInfoBool = &getPlayerInfoBool; - - serverInfo.id = ""; - serverInfo.name = ""; - serverInfo.description = ""; - serverInfo.password = ""; - serverInfo.maxPlayers = 0; - gameState.connected = false; - gameState.loading = false; - gameState.map = ""; - gameState.mapDisplayName = ""; - gameState.playlist = ""; - gameState.playlistDisplayName = ""; - gameState.players = 0; - - playerInfo.uid = 123; -} - -// string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading -SQRESULT SQ_UpdateGameStateUI(HSquirrelVM* sqvm) -{ - AcquireSRWLockExclusive(&gameStateLock); - gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1); - gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2); - gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3); - gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 4); - gameState.connected = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 5); - gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 6); - ReleaseSRWLockExclusive(&gameStateLock); - return SQRESULT_NOTNULL; -} - -// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit -SQRESULT SQ_UpdateGameStateClient(HSquirrelVM* sqvm) +EXPORT void PLUGIN_LOG(LogMsg* msg) { - AcquireSRWLockExclusive(&gameStateLock); - AcquireSRWLockExclusive(&serverInfoLock); - gameState.players = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1); - serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 2); - gameState.ourScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 3); - gameState.secondHighestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4); - gameState.highestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 5); - serverInfo.roundBased = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 6); - serverInfo.scoreLimit = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 7); - ReleaseSRWLockExclusive(&gameStateLock); - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; + spdlog::source_loc src {}; + src.filename = msg->source.file; + src.funcname = msg->source.func; + src.line = msg->source.line; + auto&& logger = g_pPluginManager->m_vLoadedPlugins[msg->pluginHandle].logger; + logger->log(src, (spdlog::level::level_enum)msg->level, msg->msg); } -// string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string -// playlistDisplayName -SQRESULT SQ_UpdateServerInfo(HSquirrelVM* sqvm) +EXPORT void* CreateObject(ObjectType type) { - AcquireSRWLockExclusive(&gameStateLock); - AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1); - serverInfo.name = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2); - serverInfo.password = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3); - gameState.players = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 4); - serverInfo.maxPlayers = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 5); - gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 6); - gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 7); - gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 8); - gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 9); - ReleaseSRWLockExclusive(&gameStateLock); - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; + switch (type) + { + case ObjectType::CONVAR: + return (void*)new ConVar; + case ObjectType::CONCOMMANDS: + return (void*)new ConCommand; + default: + return NULL; + } } -// int maxPlayers -SQRESULT SQ_UpdateServerInfoBetweenRounds(HSquirrelVM* sqvm) +std::optional<Plugin> PluginManager::LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data) { - AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 1); - serverInfo.name = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2); - serverInfo.password = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 3); - serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4); - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; -} -// float timeInFuture -SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm) -{ - AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.endTime = ceil(g_pSquirrel<ScriptContext::CLIENT>->getfloat(sqvm, 1)); - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; -} + Plugin plugin {}; -// bool loading -SQRESULT SQ_SetConnected(HSquirrelVM* sqvm) -{ - AcquireSRWLockExclusive(&gameStateLock); - gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 1); - ReleaseSRWLockExclusive(&gameStateLock); - return SQRESULT_NOTNULL; -} + std::string pathstring = (pluginPath / path).string(); + std::wstring wpath = (pluginPath / path).wstring(); -SQRESULT SQ_UpdateListenServer(HSquirrelVM* sqvm) -{ - AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = g_pMasterServerManager->m_sOwnServerId; - serverInfo.password = ""; // g_pServerPresence->Cvar_ns_server_password->GetString(); todo this fr - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; -} + LPCWSTR wpptr = wpath.c_str(); + HMODULE datafile = LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); // Load the DLL as a data file + if (datafile == NULL) + { + NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError())); + return std::nullopt; + } + HRSRC manifestResource = FindResourceW(datafile, MAKEINTRESOURCEW(IDR_RCDATA1), MAKEINTRESOURCEW(RT_RCDATA)); -int getServerInfoChar(char* out_buf, size_t out_buf_len, ServerInfoType var) -{ - AcquireSRWLockShared(&serverInfoLock); - int n = 0; - switch (var) + if (manifestResource == NULL) { - case ServerInfoType::id: - strncpy(out_buf, serverInfo.id.c_str(), out_buf_len); - break; - case ServerInfoType::name: - strncpy(out_buf, serverInfo.name.c_str(), out_buf_len); - break; - case ServerInfoType::description: - strncpy(out_buf, serverInfo.id.c_str(), out_buf_len); - break; - case ServerInfoType::password: - strncpy(out_buf, serverInfo.password.c_str(), out_buf_len); - break; - default: - n = -1; + NS::log::PLUGINSYS->info("Could not find manifest for library '{}'", pathstring); + freeLibrary(datafile); + return std::nullopt; } + HGLOBAL myResourceData = LoadResource(datafile, manifestResource); + if (myResourceData == NULL) + { + NS::log::PLUGINSYS->error("Failed to load manifest from library '{}'", pathstring); + freeLibrary(datafile); + return std::nullopt; + } + int manifestSize = SizeofResource(datafile, manifestResource); + std::string manifest = std::string((const char*)LockResource(myResourceData), 0, manifestSize); + freeLibrary(datafile); - ReleaseSRWLockShared(&serverInfoLock); + rapidjson_document manifestJSON; + manifestJSON.Parse(manifest.c_str()); - return n; -} -int getServerInfoInt(int* out_ptr, ServerInfoType var) -{ - AcquireSRWLockShared(&serverInfoLock); - int n = 0; - switch (var) + if (manifestJSON.HasParseError()) { - case ServerInfoType::maxPlayers: - *out_ptr = serverInfo.maxPlayers; - break; - case ServerInfoType::scoreLimit: - *out_ptr = serverInfo.scoreLimit; - break; - case ServerInfoType::endTime: - *out_ptr = serverInfo.endTime; - break; - default: - n = -1; + NS::log::PLUGINSYS->error("Manifest for '{}' was invalid", pathstring); + return std::nullopt; } + if (!manifestJSON.HasMember("name")) + { + NS::log::PLUGINSYS->error("'{}' is missing a name in its manifest", pathstring); + return std::nullopt; + } + if (!manifestJSON.HasMember("displayname")) + { + NS::log::PLUGINSYS->error("'{}' is missing a displayname in its manifest", pathstring); + return std::nullopt; + } + if (!manifestJSON.HasMember("description")) + { + NS::log::PLUGINSYS->error("'{}' is missing a description in its manifest", pathstring); + return std::nullopt; + } + if (!manifestJSON.HasMember("api_version")) + { + NS::log::PLUGINSYS->error("'{}' is missing a api_version in its manifest", pathstring); + return std::nullopt; + } + if (!manifestJSON.HasMember("version")) + { + NS::log::PLUGINSYS->error("'{}' is missing a version in its manifest", pathstring); + return std::nullopt; + } + if (!manifestJSON.HasMember("run_on_server")) + { + NS::log::PLUGINSYS->error("'{}' is missing 'run_on_server' in its manifest", pathstring); + return std::nullopt; + } + if (!manifestJSON.HasMember("run_on_client")) + { + NS::log::PLUGINSYS->error("'{}' is missing 'run_on_client' in its manifest", pathstring); + return std::nullopt; + } + auto test = manifestJSON["api_version"].GetString(); + if (strcmp(manifestJSON["api_version"].GetString(), std::to_string(ABI_VERSION).c_str())) + { + NS::log::PLUGINSYS->error( + "'{}' has an incompatible API version number '{}' in its manifest. Current ABI version is '{}'", pathstring, ABI_VERSION); + return std::nullopt; + } + // Passed all checks, going to actually load it now - ReleaseSRWLockShared(&serverInfoLock); - - return n; -} -int getServerInfoBool(bool* out_ptr, ServerInfoType var) -{ - AcquireSRWLockShared(&serverInfoLock); - int n = 0; - switch (var) + HMODULE pluginLib = LoadLibraryW(wpptr); // Load the DLL as a data file + if (pluginLib == NULL) { - case ServerInfoType::roundBased: - *out_ptr = serverInfo.roundBased; - break; - default: - n = -1; + NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError())); + return std::nullopt; + } + plugin.init = (PLUGIN_INIT_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT"); + if (plugin.init == NULL) + { + NS::log::PLUGINSYS->info("Library '{}' has no function 'PLUGIN_INIT'", pathstring); + return std::nullopt; } + NS::log::PLUGINSYS->info("Succesfully loaded {}", pathstring); - ReleaseSRWLockShared(&serverInfoLock); + plugin.name = manifestJSON["name"].GetString(); + plugin.displayName = manifestJSON["displayname"].GetString(); + plugin.description = manifestJSON["description"].GetString(); + plugin.api_version = manifestJSON["api_version"].GetString(); + plugin.version = manifestJSON["version"].GetString(); - return n; -} + plugin.run_on_client = manifestJSON["run_on_client"].GetBool(); + plugin.run_on_server = manifestJSON["run_on_server"].GetBool(); -int getGameStateChar(char* out_buf, size_t out_buf_len, GameStateInfoType var) -{ - AcquireSRWLockShared(&gameStateLock); - int n = 0; - switch (var) + if (!plugin.run_on_server && IsDedicatedServer()) + return std::nullopt; + + if (manifestJSON.HasMember("dependencyName")) { - case GameStateInfoType::map: - strncpy(out_buf, gameState.map.c_str(), out_buf_len); - break; - case GameStateInfoType::mapDisplayName: - strncpy(out_buf, gameState.mapDisplayName.c_str(), out_buf_len); - break; - case GameStateInfoType::playlist: - strncpy(out_buf, gameState.playlist.c_str(), out_buf_len); - break; - case GameStateInfoType::playlistDisplayName: - strncpy(out_buf, gameState.playlistDisplayName.c_str(), out_buf_len); - break; - default: - n = -1; + plugin.dependencyName = manifestJSON["dependencyName"].GetString(); + } + else + { + plugin.dependencyName = plugin.name; } - ReleaseSRWLockShared(&gameStateLock); + plugin.init_sqvm_client = (PLUGIN_INIT_SQVM_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT_SQVM_CLIENT"); + plugin.init_sqvm_server = (PLUGIN_INIT_SQVM_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT_SQVM_SERVER"); + plugin.inform_sqvm_created = (PLUGIN_INFORM_SQVM_CREATED_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_SQVM_CREATED"); + plugin.inform_sqvm_destroyed = (PLUGIN_INFORM_SQVM_DESTROYED_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_SQVM_DESTROYED"); - return n; + plugin.push_presence = (PLUGIN_PUSH_PRESENCE_TYPE)GetProcAddress(pluginLib, "PLUGIN_RECEIVE_PRESENCE"); + + plugin.inform_dll_load = (PLUGIN_INFORM_DLL_LOAD_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_DLL_LOAD"); + + plugin.handle = m_vLoadedPlugins.size(); + plugin.logger = std::make_shared<ColoredLogger>(plugin.displayName.c_str(), NS::Colors::PLUGIN); + RegisterLogger(plugin.logger); + NS::log::PLUGINSYS->info("Loading plugin {} version {}", plugin.displayName, plugin.version); + m_vLoadedPlugins.push_back(plugin); + + plugin.init(funcs, data); + + return plugin; } -int getGameStateInt(int* out_ptr, GameStateInfoType var) + +bool PluginManager::LoadPlugins() { - AcquireSRWLockShared(&gameStateLock); - int n = 0; - switch (var) - { - case GameStateInfoType::ourScore: - *out_ptr = gameState.ourScore; - break; - case GameStateInfoType::secondHighestScore: - *out_ptr = gameState.secondHighestScore; - break; - case GameStateInfoType::highestScore: - *out_ptr = gameState.highestScore; - break; - case GameStateInfoType::players: - *out_ptr = gameState.players; - break; - default: - n = -1; - } + std::vector<fs::path> paths; + + pluginPath = GetNorthstarPrefix() + "/plugins"; + + PluginNorthstarData data {}; + std::string ns_version {version}; + + PluginInitFuncs funcs {}; + funcs.logger = PLUGIN_LOG; + funcs.relayInviteFunc = nullptr; + funcs.createObject = CreateObject; + + init_plugincommunicationhandler(); - ReleaseSRWLockShared(&gameStateLock); + data.version = ns_version.c_str(); + data.northstarModule = g_NorthstarModule; - return n; + if (!fs::exists(pluginPath)) + { + NS::log::PLUGINSYS->warn("Could not find a plugins directory. Skipped loading plugins"); + return false; + } + // ensure dirs exist + fs::recursive_directory_iterator iterator(pluginPath); + if (std::filesystem::begin(iterator) == std::filesystem::end(iterator)) + { + NS::log::PLUGINSYS->warn("Could not find any plugins. Skipped loading plugins"); + return false; + } + for (auto const& entry : iterator) + { + if (fs::is_regular_file(entry) && entry.path().extension() == ".dll") + paths.emplace_back(entry.path().filename()); + } + for (fs::path path : paths) + { + if (LoadPlugin(path, &funcs, &data)) + data.pluginHandle += 1; + } + return true; } -int getGameStateBool(bool* out_ptr, GameStateInfoType var) + +void PluginManager::InformSQVMLoad(ScriptContext context, SquirrelFunctions* s) { - AcquireSRWLockShared(&gameStateLock); - int n = 0; - switch (var) + for (auto plugin : m_vLoadedPlugins) { - case GameStateInfoType::connected: - *out_ptr = gameState.connected; - break; - case GameStateInfoType::loading: - *out_ptr = gameState.loading; - break; - default: - n = -1; + if (context == ScriptContext::CLIENT && plugin.init_sqvm_client != NULL) + { + plugin.init_sqvm_client(s); + } + else if (context == ScriptContext::SERVER && plugin.init_sqvm_server != NULL) + { + plugin.init_sqvm_server(s); + } } - - ReleaseSRWLockShared(&gameStateLock); - - return n; } -int getPlayerInfoChar(char* out_buf, size_t out_buf_len, PlayerInfoType var) +void PluginManager::InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm) { - AcquireSRWLockShared(&playerInfoLock); - int n = 0; - switch (var) + for (auto plugin : m_vLoadedPlugins) { - default: - n = -1; + if (plugin.inform_sqvm_created != NULL) + { + plugin.inform_sqvm_created(context, sqvm); + } } - - ReleaseSRWLockShared(&playerInfoLock); - - return n; } -int getPlayerInfoInt(int* out_ptr, PlayerInfoType var) + +void PluginManager::InformSQVMDestroyed(ScriptContext context) { - AcquireSRWLockShared(&playerInfoLock); - int n = 0; - switch (var) + for (auto plugin : m_vLoadedPlugins) { - case PlayerInfoType::uid: - *out_ptr = playerInfo.uid; - break; - default: - n = -1; + if (plugin.inform_sqvm_destroyed != NULL) + { + plugin.inform_sqvm_destroyed(context); + } } - - ReleaseSRWLockShared(&playerInfoLock); - - return n; } -int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var) + +void PluginManager::PushPresence(PluginGameStatePresence* data) { - AcquireSRWLockShared(&playerInfoLock); - int n = 0; - switch (var) + for (auto plugin : m_vLoadedPlugins) { - default: - n = -1; + if (plugin.push_presence != NULL) + { + plugin.push_presence(data); + } } - - ReleaseSRWLockShared(&playerInfoLock); - - return n; } -ON_DLL_LOAD_CLIENT_RELIESON("client.dll", PluginCommands, ClientSquirrel, (CModule module)) +void PluginManager::InformDLLLoad(PluginLoadDLL dll, void* data) { - // i swear there's a way to make this not have be run in 2 contexts but i can't figure it out - // some funcs i need are just not available in UI or CLIENT - - if (g_pSquirrel<ScriptContext::UI> && g_pSquirrel<ScriptContext::CLIENT>) + for (auto plugin : m_vLoadedPlugins) { - g_pSquirrel<ScriptContext::UI>->AddFuncRegistration( - "void", - "NSUpdateGameStateUI", - "string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading", - "", - SQ_UpdateGameStateUI); - g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration( - "void", - "NSUpdateGameStateClient", - "int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit", - "", - SQ_UpdateGameStateClient); - g_pSquirrel<ScriptContext::UI>->AddFuncRegistration( - "void", - "NSUpdateServerInfo", - "string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, " - "string " - "playlistDisplayName", - "", - SQ_UpdateServerInfo); - g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration( - "void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds); - g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo); - g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected); - g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer); + if (plugin.inform_dll_load != NULL) + { + plugin.inform_dll_load(dll, data); + } } } diff --git a/NorthstarDLL/plugins/plugins.h b/NorthstarDLL/plugins/plugins.h index 953801a2..ffa277d0 100644 --- a/NorthstarDLL/plugins/plugins.h +++ b/NorthstarDLL/plugins/plugins.h @@ -1,17 +1,57 @@ #pragma once #include "plugin_abi.h" -int getServerInfoChar(char* out_buf, size_t out_buf_len, ServerInfoType var); -int getServerInfoInt(int* out_ptr, ServerInfoType var); -int getServerInfoBool(bool* out_ptr, ServerInfoType var); +const int IDR_RCDATA1 = 101; -int getGameStateChar(char* out_buf, size_t out_buf_len, GameStateInfoType var); -int getGameStateInt(int* out_ptr, GameStateInfoType var); -int getGameStateBool(bool* out_ptr, GameStateInfoType var); +class Plugin +{ + public: + std::string name; + std::string displayName; + std::string dependencyName; + std::string description; -int getPlayerInfoChar(char* out_buf, size_t out_buf_len, PlayerInfoType var); -int getPlayerInfoInt(int* out_ptr, PlayerInfoType var); -int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var); + std::string api_version; + std::string version; -void initGameState(); -void* getPluginObject(PluginObject var); + // For now this is just implemented as the index into the plugins array + // Maybe a bit shit but it works + int handle; + + std::shared_ptr<ColoredLogger> logger; + + bool run_on_client = false; + bool run_on_server = false; + + public: + PLUGIN_INIT_TYPE init; + PLUGIN_INIT_SQVM_TYPE init_sqvm_client; + PLUGIN_INIT_SQVM_TYPE init_sqvm_server; + PLUGIN_INFORM_SQVM_CREATED_TYPE inform_sqvm_created; + PLUGIN_INFORM_SQVM_DESTROYED_TYPE inform_sqvm_destroyed; + + PLUGIN_PUSH_PRESENCE_TYPE push_presence; + PLUGIN_INFORM_DLL_LOAD_TYPE inform_dll_load; +}; + +class PluginManager +{ + public: + std::vector<Plugin> m_vLoadedPlugins; + + public: + bool LoadPlugins(); + std::optional<Plugin> LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data); + + void InformSQVMLoad(ScriptContext context, SquirrelFunctions* s); + void InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm); + void InformSQVMDestroyed(ScriptContext context); + void PushPresence(PluginGameStatePresence* data); + + void InformDLLLoad(PluginLoadDLL dll, void* data); + + private: + std::string pluginPath; +}; + +extern PluginManager* g_pPluginManager; diff --git a/NorthstarDLL/scripts/client/scriptserverbrowser.cpp b/NorthstarDLL/scripts/client/scriptserverbrowser.cpp index 584ec905..39279de5 100644 --- a/NorthstarDLL/scripts/client/scriptserverbrowser.cpp +++ b/NorthstarDLL/scripts/client/scriptserverbrowser.cpp @@ -336,7 +336,7 @@ ADD_SQFUNC("void", NSTryAuthWithServer, "int serverIndex, string password = ''", g_pMasterServerManager->AuthenticateWithServer( R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken, - g_pMasterServerManager->m_vRemoteServers[serverIndex].id, + g_pMasterServerManager->m_vRemoteServers[serverIndex], (char*)password); return SQRESULT_NULL; diff --git a/NorthstarDLL/server/serverpresence.cpp b/NorthstarDLL/server/serverpresence.cpp index 072ef347..e0b29cb6 100644 --- a/NorthstarDLL/server/serverpresence.cpp +++ b/NorthstarDLL/server/serverpresence.cpp @@ -71,7 +71,7 @@ std::string UnescapeUnicode(const std::string& str) return result; } -ServerPresenceManager::ServerPresenceManager() +void ServerPresenceManager::CreateConVars() { // clang-format off // register convars @@ -230,7 +230,6 @@ void ServerPresenceManager::SetPlayerCount(const int iPlayerCount) ON_DLL_LOAD_RELIESON("engine.dll", ServerPresence, ConVar, (CModule module)) { - g_pServerPresence = new ServerPresenceManager; - + g_pServerPresence->CreateConVars(); Cvar_hostname = module.Offset(0x1315BAE8).Deref().As<ConVar*>(); } diff --git a/NorthstarDLL/server/serverpresence.h b/NorthstarDLL/server/serverpresence.h index 125b2e68..3df2b68d 100644 --- a/NorthstarDLL/server/serverpresence.h +++ b/NorthstarDLL/server/serverpresence.h @@ -3,9 +3,12 @@ struct ServerPresence { + public: int m_iPort; int m_iAuthPort; + std::string m_sServerId; + std::string m_sServerName; std::string m_sServerDesc; char m_Password[256]; // probably bigger than will ever be used in practice, lol @@ -27,6 +30,8 @@ struct ServerPresence m_iPort = obj->m_iPort; m_iAuthPort = obj->m_iAuthPort; + m_sServerId = obj->m_sServerId; + m_sServerName = obj->m_sServerName; m_sServerDesc = obj->m_sServerDesc; memcpy(m_Password, obj->m_Password, sizeof(m_Password)); @@ -69,10 +74,10 @@ class ServerPresenceManager ConVar* Cvar_ns_report_sp_server_to_masterserver; public: - ServerPresenceManager(); - void AddPresenceReporter(ServerPresenceReporter* reporter); + void CreateConVars(); + void CreatePresence(); void DestroyPresence(); void RunFrame(double flCurrentTime); diff --git a/NorthstarDLL/shared/gamepresence.cpp b/NorthstarDLL/shared/gamepresence.cpp new file mode 100644 index 00000000..86a87526 --- /dev/null +++ b/NorthstarDLL/shared/gamepresence.cpp @@ -0,0 +1,81 @@ +#include "pch.h" + +#include "gamepresence.h" +#include "plugins/pluginbackend.h" +#include "plugins/plugins.h" +#include "dedicated/dedicated.h" +#include "server/serverpresence.h" +#include "masterserver/masterserver.h" +#include "squirrel/squirrel.h" + +GameStatePresence* g_pGameStatePresence; + +GameStatePresence::GameStatePresence() +{ + g_pServerPresence->AddPresenceReporter(&m_GameStateServerPresenceReporter); +} + +void GameStateServerPresenceReporter::RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) +{ + g_pGameStatePresence->id = pServerPresence->m_sServerId; + g_pGameStatePresence->name = pServerPresence->m_sServerName; + g_pGameStatePresence->description = pServerPresence->m_sServerDesc; + g_pGameStatePresence->password = pServerPresence->m_Password; + + g_pGameStatePresence->map = pServerPresence->m_MapName; + g_pGameStatePresence->playlist = pServerPresence->m_PlaylistName; + g_pGameStatePresence->currentPlayers = pServerPresence->m_iPlayerCount; + g_pGameStatePresence->maxPlayers = pServerPresence->m_iMaxPlayers; + + g_pGameStatePresence->isLocal = !IsDedicatedServer(); +} + +void GameStatePresence::RunFrame() +{ + if (g_pSquirrel<ScriptContext::UI>->m_pSQVM != nullptr && g_pSquirrel<ScriptContext::UI>->m_pSQVM->sqvm != nullptr) + g_pSquirrel<ScriptContext::UI>->Call("NorthstarCodeCallback_GenerateUIPresence"); + + if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM != nullptr && g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm != nullptr) + { + auto test = g_pSquirrel<ScriptContext::CLIENT>->Call("NorthstarCodeCallback_GenerateGameState"); + } + g_pPluginCommunicationhandler->GeneratePresenceObjects(); +} + +ADD_SQFUNC("void", NSPushGameStateData, "GameStateStruct gamestate", "", ScriptContext::CLIENT) +{ + SQStructInstance* structInst = g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM->sqvm->_stackOfCurrentFunction[1]._VAL.asStructInstance; + g_pGameStatePresence->map = structInst->data[0]._VAL.asString->_val; + g_pGameStatePresence->mapDisplayname = structInst->data[1]._VAL.asString->_val; + g_pGameStatePresence->playlist = structInst->data[2]._VAL.asString->_val; + g_pGameStatePresence->playlistDisplayname = structInst->data[3]._VAL.asString->_val; + + g_pGameStatePresence->currentPlayers = structInst->data[4]._VAL.asInteger; + g_pGameStatePresence->maxPlayers = structInst->data[5]._VAL.asInteger; + g_pGameStatePresence->ownScore = structInst->data[6]._VAL.asInteger; + g_pGameStatePresence->otherHighestScore = structInst->data[7]._VAL.asInteger; + g_pGameStatePresence->maxScore = structInst->data[8]._VAL.asInteger; + g_pGameStatePresence->timestampEnd = ceil(structInst->data[9]._VAL.asFloat); + + if (g_pMasterServerManager->m_currentServer) + { + g_pGameStatePresence->id = g_pMasterServerManager->m_currentServer->id; + g_pGameStatePresence->name = g_pMasterServerManager->m_currentServer->name; + g_pGameStatePresence->description = g_pMasterServerManager->m_currentServer->description; + g_pGameStatePresence->password = g_pMasterServerManager->m_sCurrentServerPassword; + } + + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("void", NSPushUIPresence, "UIPresenceStruct presence", "", ScriptContext::UI) +{ + SQStructInstance* structInst = g_pSquirrel<ScriptContext::UI>->m_pSQVM->sqvm->_stackOfCurrentFunction[1]._VAL.asStructInstance; + + g_pGameStatePresence->isLoading = structInst->data[0]._VAL.asInteger; + g_pGameStatePresence->isLobby = structInst->data[1]._VAL.asInteger; + g_pGameStatePresence->loadingLevel = structInst->data[2]._VAL.asString->_val; + g_pGameStatePresence->uiMap = structInst->data[3]._VAL.asString->_val; + + return SQRESULT_NOTNULL; +} diff --git a/NorthstarDLL/shared/gamepresence.h b/NorthstarDLL/shared/gamepresence.h new file mode 100644 index 00000000..167f83ab --- /dev/null +++ b/NorthstarDLL/shared/gamepresence.h @@ -0,0 +1,48 @@ + +#pragma once +#include "pch.h" +#include "server/serverpresence.h" + +class GameStateServerPresenceReporter : public ServerPresenceReporter +{ + void RunFrame(double flCurrentTime, const ServerPresence* pServerPresence); +}; + +class GameStatePresence +{ + public: + std::string id; + std::string name; + std::string description; + std::string password; // NOTE: May be empty + + bool isServer; + bool isLocal = false; + bool isLoading; + bool isLobby; + std::string loadingLevel; + + std::string uiMap; + + std::string map; + std::string mapDisplayname; + std::string playlist; + std::string playlistDisplayname; + + int currentPlayers; + int maxPlayers; + + int ownScore; + int otherHighestScore; // NOTE: The highest score OR the second highest score if we have the highest + int maxScore; + + int timestampEnd; + + GameStatePresence(); + void RunFrame(); + + protected: + GameStateServerPresenceReporter m_GameStateServerPresenceReporter; +}; + +extern GameStatePresence* g_pGameStatePresence; diff --git a/NorthstarDLL/squirrel/squirrel.cpp b/NorthstarDLL/squirrel/squirrel.cpp index 21ce9ae0..62287245 100644 --- a/NorthstarDLL/squirrel/squirrel.cpp +++ b/NorthstarDLL/squirrel/squirrel.cpp @@ -5,6 +5,8 @@ #include "dedicated/dedicated.h" #include "engine/r2engine.h" #include "core/tier0.h" +#include "plugins/plugin_abi.h" +#include "plugins/plugins.h" #include <any> @@ -152,6 +154,52 @@ const char* SQTypeNameFromID(int type) return ""; } +template <ScriptContext context> void SquirrelManager<context>::GenerateSquirrelFunctionsStruct(SquirrelFunctions* s) +{ + s->RegisterSquirrelFunc = RegisterSquirrelFunc; + s->__sq_defconst = __sq_defconst; + + s->__sq_compilebuffer = __sq_compilebuffer; + s->__sq_call = __sq_call; + s->__sq_raiseerror = __sq_raiseerror; + + s->__sq_newarray = __sq_newarray; + s->__sq_arrayappend = __sq_arrayappend; + + s->__sq_newtable = __sq_newtable; + s->__sq_newslot = __sq_newslot; + + s->__sq_pushroottable = __sq_pushroottable; + s->__sq_pushstring = __sq_pushstring; + s->__sq_pushinteger = __sq_pushinteger; + s->__sq_pushfloat = __sq_pushfloat; + s->__sq_pushbool = __sq_pushbool; + s->__sq_pushasset = __sq_pushasset; + s->__sq_pushvector = __sq_pushvector; + s->__sq_pushobject = __sq_pushobject; + s->__sq_getstring = __sq_getstring; + s->__sq_getthisentity = __sq_getthisentity; + s->__sq_getobject = __sq_getobject; + + s->__sq_stackinfos = __sq_stackinfos; + + s->__sq_getinteger = __sq_getinteger; + s->__sq_getfloat = __sq_getfloat; + s->__sq_getbool = __sq_getbool; + s->__sq_get = __sq_get; + s->__sq_getasset = __sq_getasset; + s->__sq_getuserdata = __sq_getuserdata; + s->__sq_getvector = __sq_getvector; + s->__sq_createuserdata = __sq_createuserdata; + s->__sq_setuserdatatypeid = __sq_setuserdatatypeid; + s->__sq_getfunction = __sq_getfunction; + + s->__sq_getentityfrominstance = __sq_getentityfrominstance; + s->__sq_GetEntityConstant_CBaseEntity = __sq_GetEntityConstant_CBaseEntity; + + s->__sq_schedule_call_external = AsyncCall_External; +} + // Allows for generating squirrelmessages from plugins. // Not used in this version, but will be used later void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function) @@ -208,6 +256,7 @@ template <ScriptContext context> void SquirrelManager<context>::VMCreated(CSquir defconst(m_pSQVM, pair.first.c_str(), bWasFound); } g_pSquirrel<context>->messageBuffer = new SquirrelMessageBuffer(); + g_pPluginManager->InformSQVMCreated(context, newSqvm); } template <ScriptContext context> void SquirrelManager<context>::VMDestroyed() @@ -235,6 +284,8 @@ template <ScriptContext context> void SquirrelManager<context>::VMDestroyed() } } + g_pPluginManager->InformSQVMDestroyed(context); + // Discard the previous vm and delete the message buffer. m_pSQVM = nullptr; @@ -358,6 +409,23 @@ template <ScriptContext context> CSquirrelVM* __fastcall CreateNewVMHook(void* a return sqvm; } +template <ScriptContext context> bool (*__fastcall CSquirrelVM_init)(CSquirrelVM* vm, ScriptContext realContext, float time); +template <ScriptContext context> bool __fastcall CSquirrelVM_initHook(CSquirrelVM* vm, ScriptContext realContext, float time) +{ + bool ret = CSquirrelVM_init<context>(vm, realContext, time); + for (Mod mod : g_pModManager->m_LoadedMods) + { + if (mod.initScript.size() != 0) + { + std::string name = mod.initScript.substr(mod.initScript.find_last_of('/') + 1); + std::string path = std::string("scripts/vscripts/") + mod.initScript; + if (g_pSquirrel<context>->compilefile(vm, path.c_str(), name.c_str(), 0)) + g_pSquirrel<context>->compilefile(vm, path.c_str(), name.c_str(), 1); + } + } + return ret; +} + template <ScriptContext context> void (*__fastcall DestroyVM)(void* a1, CSquirrelVM* sqvm); template <ScriptContext context> void __fastcall DestroyVMHook(void* a1, CSquirrelVM* sqvm) { @@ -638,8 +706,10 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) g_pSquirrel<ScriptContext::CLIENT>->__sq_compilebuffer = module.Offset(0x3110).As<sq_compilebufferType>(); g_pSquirrel<ScriptContext::CLIENT>->__sq_pushroottable = module.Offset(0x5860).As<sq_pushroottableType>(); + g_pSquirrel<ScriptContext::CLIENT>->__sq_compilefile = module.Offset(0xF950).As<sq_compilefileType>(); g_pSquirrel<ScriptContext::UI>->__sq_compilebuffer = g_pSquirrel<ScriptContext::CLIENT>->__sq_compilebuffer; g_pSquirrel<ScriptContext::UI>->__sq_pushroottable = g_pSquirrel<ScriptContext::CLIENT>->__sq_pushroottable; + g_pSquirrel<ScriptContext::UI>->__sq_compilefile = g_pSquirrel<ScriptContext::CLIENT>->__sq_compilefile; g_pSquirrel<ScriptContext::CLIENT>->__sq_call = module.Offset(0x8650).As<sq_callType>(); g_pSquirrel<ScriptContext::UI>->__sq_call = g_pSquirrel<ScriptContext::CLIENT>->__sq_call; @@ -733,6 +803,8 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) MAKEHOOK(module.Offset(0x10190), &CallScriptInitCallbackHook<ScriptContext::CLIENT>, &CallScriptInitCallback<ScriptContext::CLIENT>); + MAKEHOOK(module.Offset(0xE3B0), &CSquirrelVM_initHook<ScriptContext::CLIENT>, &CSquirrelVM_init<ScriptContext::CLIENT>); + RegisterConCommand("script_client", ConCommand_script<ScriptContext::CLIENT>, "Executes script code on the client vm", FCVAR_CLIENTDLL); RegisterConCommand("script_ui", ConCommand_script<ScriptContext::UI>, "Executes script code on the ui vm", FCVAR_CLIENTDLL); @@ -741,6 +813,10 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction = module.Offset(0x6CB0).As<sq_getfunctionType>(); g_pSquirrel<ScriptContext::UI>->__sq_getfunction = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction; + + SquirrelFunctions s = {}; + g_pSquirrel<ScriptContext::CLIENT>->GenerateSquirrelFunctionsStruct(&s); + g_pPluginManager->InformSQVMLoad(ScriptContext::CLIENT, &s); } ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) @@ -752,6 +828,7 @@ ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) g_pSquirrel<ScriptContext::SERVER>->__sq_compilebuffer = module.Offset(0x3110).As<sq_compilebufferType>(); g_pSquirrel<ScriptContext::SERVER>->__sq_pushroottable = module.Offset(0x5840).As<sq_pushroottableType>(); g_pSquirrel<ScriptContext::SERVER>->__sq_call = module.Offset(0x8620).As<sq_callType>(); + g_pSquirrel<ScriptContext::SERVER>->__sq_compilefile = module.Offset(0x1CD80).As<sq_compilefileType>(); g_pSquirrel<ScriptContext::SERVER>->__sq_newarray = module.Offset(0x39F0).As<sq_newarrayType>(); g_pSquirrel<ScriptContext::SERVER>->__sq_arrayappend = module.Offset(0x3C70).As<sq_arrayappendType>(); @@ -804,7 +881,7 @@ ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) MAKEHOOK(module.Offset(0x26E20), &DestroyVMHook<ScriptContext::SERVER>, &DestroyVM<ScriptContext::SERVER>); MAKEHOOK(module.Offset(0x799E0), &ScriptCompileErrorHook<ScriptContext::SERVER>, &SQCompileError<ScriptContext::SERVER>); MAKEHOOK(module.Offset(0x1D5C0), &CallScriptInitCallbackHook<ScriptContext::SERVER>, &CallScriptInitCallback<ScriptContext::SERVER>); - + MAKEHOOK(module.Offset(0x17BE0), &CSquirrelVM_initHook<ScriptContext::SERVER>, &CSquirrelVM_init<ScriptContext::SERVER>); // FCVAR_CHEAT and FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS allows clients to execute this, but since it's unsafe we only allow it when cheats // are enabled for script_client and script_ui, we don't use cheats, so clients can execute them on themselves all they want RegisterConCommand( @@ -814,6 +891,10 @@ ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) FCVAR_GAMEDLL | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS | FCVAR_CHEAT); StubUnsafeSQFuncs<ScriptContext::SERVER>(); + + SquirrelFunctions s = {}; + g_pSquirrel<ScriptContext::SERVER>->GenerateSquirrelFunctionsStruct(&s); + g_pPluginManager->InformSQVMLoad(ScriptContext::SERVER, &s); } void InitialiseSquirrelManagers() diff --git a/NorthstarDLL/squirrel/squirrel.h b/NorthstarDLL/squirrel/squirrel.h index 5708c836..4a28409f 100644 --- a/NorthstarDLL/squirrel/squirrel.h +++ b/NorthstarDLL/squirrel/squirrel.h @@ -3,6 +3,7 @@ #include "squirrelclasstypes.h" #include "squirrelautobind.h" #include "core/math/vector.h" +#include "plugins/plugin_abi.h" #include "mods/modmanager.h" // stolen from ttf2sdk: sqvm types @@ -37,6 +38,8 @@ const char* GetContextName_Short(ScriptContext context); eSQReturnType SQReturnTypeFromString(const char* pReturnType); const char* SQTypeNameFromID(const int iTypeId); +void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function); + ScriptContext ScriptContextFromString(std::string string); namespace NS::log @@ -44,8 +47,6 @@ namespace NS::log template <ScriptContext context> std::shared_ptr<spdlog::logger> squirrel_logger(); }; // namespace NS::log -void schedule_call_external(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function); - // This base class means that only the templated functions have to be rebuilt for each template instance // Cuts down on compile time by ~5 seconds class SquirrelManagerBase @@ -69,6 +70,7 @@ class SquirrelManagerBase sq_compilebufferType __sq_compilebuffer; sq_callType __sq_call; sq_raiseerrorType __sq_raiseerror; + sq_compilefileType __sq_compilefile; sq_newarrayType __sq_newarray; sq_arrayappendType __sq_arrayappend; @@ -129,6 +131,11 @@ class SquirrelManagerBase return __sq_raiseerror(sqvm, sError); } + inline bool compilefile(CSquirrelVM* sqvm, const char* path, const char* name, int a4) + { + return __sq_compilefile(sqvm, path, name, a4); + } + inline void newarray(HSquirrelVM* sqvm, const SQInteger stackpos = 0) { __sq_newarray(sqvm, stackpos); @@ -385,6 +392,7 @@ template <ScriptContext context> class SquirrelManager : public virtual Squirrel SQRESULT setupfunc(const SQChar* funcname); void AddFuncOverride(std::string name, SQFunction func); void ProcessMessageBuffer(); + void GenerateSquirrelFunctionsStruct(SquirrelFunctions* s); }; template <ScriptContext context> SquirrelManager<context>* g_pSquirrel; diff --git a/NorthstarDLL/squirrel/squirrelclasstypes.h b/NorthstarDLL/squirrel/squirrelclasstypes.h index 68b57bae..67f69827 100644 --- a/NorthstarDLL/squirrel/squirrelclasstypes.h +++ b/NorthstarDLL/squirrel/squirrelclasstypes.h @@ -192,6 +192,7 @@ typedef SQRESULT (*sq_compilebufferType)( HSquirrelVM* sqvm, CompileBufferState* compileBuffer, const char* file, int a1, SQBool bShouldThrowError); typedef SQRESULT (*sq_callType)(HSquirrelVM* sqvm, SQInteger iArgs, SQBool bShouldReturn, SQBool bThrowError); typedef SQInteger (*sq_raiseerrorType)(HSquirrelVM* sqvm, const SQChar* pError); +typedef bool (*sq_compilefileType)(CSquirrelVM* sqvm, const char* path, const char* name, int a4); // sq stack array funcs typedef void (*sq_newarrayType)(HSquirrelVM* sqvm, SQInteger iStackpos); |