From 450d0b1ed437cf37b4309af952af8904f3f07768 Mon Sep 17 00:00:00 2001 From: Emma Miler Date: Tue, 11 Apr 2023 20:49:16 +0200 Subject: Plugin system v2 (#343) * Some work * Rewrite gamestate presence * Add plugin system logger * Format changes * Format chjange * Fix gamestate stuff * some callback stuff * move around invite stuff * move invite to funcs * fix presence server data * Actually call InformSQVMCreated * bruh * Fix TODO's * Formatting * Fix filters * Add InformDLLLoads * Fix plugin handle always being 0 * Formatting * Fix merge issues * Formatting * Mods can add files compiled at SQVM init * Some Small Fixes * Add changes from review * Fix load failure * Add new squirrel functions * actually call InformSQVMDestroyed * add CreateObject function * answers to complaints * remove snake cases from GameStatePresence --------- Co-authored-by: cat_or_not <41955154+catornot@users.noreply.github.com> --- NorthstarDLL/NorthstarDLL.vcxproj | 5 +- NorthstarDLL/NorthstarDLL.vcxproj.filters | 27 +- NorthstarDLL/config/profile.cpp | 3 - NorthstarDLL/core/convar/concommand.cpp | 4 + NorthstarDLL/core/convar/convar.cpp | 7 + NorthstarDLL/core/hooks.cpp | 2 - NorthstarDLL/core/math/color.cpp | 2 + NorthstarDLL/core/math/color.h | 2 + NorthstarDLL/dllmain.cpp | 135 +---- NorthstarDLL/engine/hoststate.cpp | 4 + NorthstarDLL/logging/logging.cpp | 10 + NorthstarDLL/logging/logging.h | 3 + NorthstarDLL/masterserver/masterserver.cpp | 9 +- NorthstarDLL/masterserver/masterserver.h | 5 +- NorthstarDLL/mods/modmanager.cpp | 5 + NorthstarDLL/mods/modmanager.h | 2 + NorthstarDLL/pch.h | 6 + NorthstarDLL/plugins/plugin_abi.h | 192 +++++-- NorthstarDLL/plugins/pluginbackend.cpp | 94 ++++ NorthstarDLL/plugins/pluginbackend.h | 45 ++ NorthstarDLL/plugins/plugins.cpp | 581 ++++++++------------- NorthstarDLL/plugins/plugins.h | 62 ++- .../scripts/client/scriptserverbrowser.cpp | 2 +- NorthstarDLL/server/serverpresence.cpp | 5 +- NorthstarDLL/server/serverpresence.h | 9 +- NorthstarDLL/shared/gamepresence.cpp | 81 +++ NorthstarDLL/shared/gamepresence.h | 48 ++ NorthstarDLL/squirrel/squirrel.cpp | 83 ++- NorthstarDLL/squirrel/squirrel.h | 12 +- NorthstarDLL/squirrel/squirrelclasstypes.h | 1 + NorthstarLauncher/main.cpp | 17 - 31 files changed, 892 insertions(+), 571 deletions(-) create mode 100644 NorthstarDLL/plugins/pluginbackend.cpp create mode 100644 NorthstarDLL/plugins/pluginbackend.h create mode 100644 NorthstarDLL/shared/gamepresence.cpp create mode 100644 NorthstarDLL/shared/gamepresence.h 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 @@ - + @@ -440,6 +440,7 @@ + @@ -507,6 +508,7 @@ Create Create + @@ -533,6 +535,7 @@ + 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 @@ Header Files\mods - - Header Files\plugins - Header Files\plugins @@ -1065,9 +1062,6 @@ Header Files - - Header Files - Header Files @@ -1161,6 +1155,18 @@ Header Files\scripts + + Header Files\plugins + + + Header Files\plugins + + + Header Files\shared + + + Header Files\util + Header Files\shared @@ -1420,6 +1426,15 @@ Source Files\scripts + + Source Files\plugins + + + Source Files\shared + + + Source Files\util + Source Files\core 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 //----------------------------------------------------------------------------- @@ -148,4 +150,6 @@ ON_DLL_LOAD("engine.dll", ConCommand, (CModule module)) { ConCommandConstructor = module.Offset(0x415F60).As(); 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 typedef void (*ConVarRegisterType)( @@ -37,6 +39,11 @@ ON_DLL_LOAD("engine.dll", ConVar, (CModule module)) R2::g_pCVarInterface = new SourceInterface("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 #include -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 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->m_pSQVM != nullptr && g_pSquirrel->m_pSQVM->sqvm != nullptr) g_pSquirrel->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 echo; std::shared_ptr NORTHSTAR; + std::shared_ptr 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 logger) +{ + loggers.push_back(logger); +} + void RegisterCustomSink(std::shared_ptr sink) { for (auto& logger : loggers) @@ -189,6 +195,8 @@ void InitialiseLogging() NS::log::rpak = std::make_shared("RPAK_FSYS", NS::Colors::RPAK); NS::log::echo = std::make_shared("ECHO", NS::Colors::ECHO); + NS::log::PLUGINSYS = std::make_shared("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 NORTHSTAR; + extern std::shared_ptr PLUGINSYS; + void FlushLoggers(); }; // namespace NS::log void RegisterCustomSink(std::shared_ptr sink); +void RegisterLogger(std::shared_ptr 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 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 ConCommands; // custom localisation files created by the mod std::vector 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 +#include "squirrel/squirrelclasstypes.h" -#define ABI_VERSION 1 -/// -/// 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 -/// -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; +}; + +/// 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. +/// + +// 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 lock(requestMutex); + if (!requestQueue.empty()) + { + storedRequest = requestQueue.front(); + switch (storedRequest.type) + { + default: + spdlog::error("{} was called with invalid request type '{}'", __FUNCTION__, static_cast(storedRequest.type)); + } + requestQueue.pop(); + } +} + +void PluginCommunicationHandler::PushRequest(PluginDataRequestType type, PluginRespondDataCallable func) +{ + std::lock_guard 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 +#include + +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 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 -#include -#include +#include "util/version.h" +#include "pluginbackend.h" +#include "util/wininfo.h" +#include "logging/logging.h" +#include "dedicated/dedicated.h" -/// -/// 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 -/// -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; - -/// -/// 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 -/// -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->getstring(sqvm, 1); - gameState.mapDisplayName = g_pSquirrel->getstring(sqvm, 2); - gameState.playlist = g_pSquirrel->getstring(sqvm, 3); - gameState.playlistDisplayName = g_pSquirrel->getstring(sqvm, 4); - gameState.connected = g_pSquirrel->getbool(sqvm, 5); - gameState.loading = g_pSquirrel->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->getinteger(sqvm, 1); - serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 2); - gameState.ourScore = g_pSquirrel->getinteger(sqvm, 3); - gameState.secondHighestScore = g_pSquirrel->getinteger(sqvm, 4); - gameState.highestScore = g_pSquirrel->getinteger(sqvm, 5); - serverInfo.roundBased = g_pSquirrel->getbool(sqvm, 6); - serverInfo.scoreLimit = g_pSquirrel->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->getstring(sqvm, 1); - serverInfo.name = g_pSquirrel->getstring(sqvm, 2); - serverInfo.password = g_pSquirrel->getstring(sqvm, 3); - gameState.players = g_pSquirrel->getinteger(sqvm, 4); - serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 5); - gameState.map = g_pSquirrel->getstring(sqvm, 6); - gameState.mapDisplayName = g_pSquirrel->getstring(sqvm, 7); - gameState.playlist = g_pSquirrel->getstring(sqvm, 8); - gameState.playlistDisplayName = g_pSquirrel->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 PluginManager::LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data) { - AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = g_pSquirrel->getstring(sqvm, 1); - serverInfo.name = g_pSquirrel->getstring(sqvm, 2); - serverInfo.password = g_pSquirrel->getstring(sqvm, 3); - serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 4); - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; -} -// float timeInFuture -SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm) -{ - AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.endTime = ceil(g_pSquirrel->getfloat(sqvm, 1)); - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; -} + Plugin plugin {}; -// bool loading -SQRESULT SQ_SetConnected(HSquirrelVM* sqvm) -{ - AcquireSRWLockExclusive(&gameStateLock); - gameState.loading = g_pSquirrel->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(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 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 && g_pSquirrel) + for (auto plugin : m_vLoadedPlugins) { - g_pSquirrel->AddFuncRegistration( - "void", - "NSUpdateGameStateUI", - "string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading", - "", - SQ_UpdateGameStateUI); - g_pSquirrel->AddFuncRegistration( - "void", - "NSUpdateGameStateClient", - "int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit", - "", - SQ_UpdateGameStateClient); - g_pSquirrel->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->AddFuncRegistration( - "void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds); - g_pSquirrel->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo); - g_pSquirrel->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected); - g_pSquirrel->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 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 m_vLoadedPlugins; + + public: + bool LoadPlugins(); + std::optional 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(); } 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->m_pSQVM != nullptr && g_pSquirrel->m_pSQVM->sqvm != nullptr) + g_pSquirrel->Call("NorthstarCodeCallback_GenerateUIPresence"); + + if (g_pSquirrel->m_pSQVM != nullptr && g_pSquirrel->m_pSQVM->sqvm != nullptr) + { + auto test = g_pSquirrel->Call("NorthstarCodeCallback_GenerateGameState"); + } + g_pPluginCommunicationhandler->GeneratePresenceObjects(); +} + +ADD_SQFUNC("void", NSPushGameStateData, "GameStateStruct gamestate", "", ScriptContext::CLIENT) +{ + SQStructInstance* structInst = g_pSquirrel->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->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 @@ -152,6 +154,52 @@ const char* SQTypeNameFromID(int type) return ""; } +template void SquirrelManager::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 void SquirrelManager::VMCreated(CSquir defconst(m_pSQVM, pair.first.c_str(), bWasFound); } g_pSquirrel->messageBuffer = new SquirrelMessageBuffer(); + g_pPluginManager->InformSQVMCreated(context, newSqvm); } template void SquirrelManager::VMDestroyed() @@ -235,6 +284,8 @@ template void SquirrelManager::VMDestroyed() } } + g_pPluginManager->InformSQVMDestroyed(context); + // Discard the previous vm and delete the message buffer. m_pSQVM = nullptr; @@ -358,6 +409,23 @@ template CSquirrelVM* __fastcall CreateNewVMHook(void* a return sqvm; } +template bool (*__fastcall CSquirrelVM_init)(CSquirrelVM* vm, ScriptContext realContext, float time); +template bool __fastcall CSquirrelVM_initHook(CSquirrelVM* vm, ScriptContext realContext, float time) +{ + bool ret = CSquirrelVM_init(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->compilefile(vm, path.c_str(), name.c_str(), 0)) + g_pSquirrel->compilefile(vm, path.c_str(), name.c_str(), 1); + } + } + return ret; +} + template void (*__fastcall DestroyVM)(void* a1, CSquirrelVM* sqvm); template void __fastcall DestroyVMHook(void* a1, CSquirrelVM* sqvm) { @@ -638,8 +706,10 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) g_pSquirrel->__sq_compilebuffer = module.Offset(0x3110).As(); g_pSquirrel->__sq_pushroottable = module.Offset(0x5860).As(); + g_pSquirrel->__sq_compilefile = module.Offset(0xF950).As(); g_pSquirrel->__sq_compilebuffer = g_pSquirrel->__sq_compilebuffer; g_pSquirrel->__sq_pushroottable = g_pSquirrel->__sq_pushroottable; + g_pSquirrel->__sq_compilefile = g_pSquirrel->__sq_compilefile; g_pSquirrel->__sq_call = module.Offset(0x8650).As(); g_pSquirrel->__sq_call = g_pSquirrel->__sq_call; @@ -733,6 +803,8 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) MAKEHOOK(module.Offset(0x10190), &CallScriptInitCallbackHook, &CallScriptInitCallback); + MAKEHOOK(module.Offset(0xE3B0), &CSquirrelVM_initHook, &CSquirrelVM_init); + RegisterConCommand("script_client", ConCommand_script, "Executes script code on the client vm", FCVAR_CLIENTDLL); RegisterConCommand("script_ui", ConCommand_script, "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->__sq_getfunction = module.Offset(0x6CB0).As(); g_pSquirrel->__sq_getfunction = g_pSquirrel->__sq_getfunction; + + SquirrelFunctions s = {}; + g_pSquirrel->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->__sq_compilebuffer = module.Offset(0x3110).As(); g_pSquirrel->__sq_pushroottable = module.Offset(0x5840).As(); g_pSquirrel->__sq_call = module.Offset(0x8620).As(); + g_pSquirrel->__sq_compilefile = module.Offset(0x1CD80).As(); g_pSquirrel->__sq_newarray = module.Offset(0x39F0).As(); g_pSquirrel->__sq_arrayappend = module.Offset(0x3C70).As(); @@ -804,7 +881,7 @@ ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module)) MAKEHOOK(module.Offset(0x26E20), &DestroyVMHook, &DestroyVM); MAKEHOOK(module.Offset(0x799E0), &ScriptCompileErrorHook, &SQCompileError); MAKEHOOK(module.Offset(0x1D5C0), &CallScriptInitCallbackHook, &CallScriptInitCallback); - + MAKEHOOK(module.Offset(0x17BE0), &CSquirrelVM_initHook, &CSquirrelVM_init); // 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(); + + SquirrelFunctions s = {}; + g_pSquirrel->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 std::shared_ptr 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 class SquirrelManager : public virtual Squirrel SQRESULT setupfunc(const SQChar* funcname); void AddFuncOverride(std::string name, SQFunction func); void ProcessMessageBuffer(); + void GenerateSquirrelFunctionsStruct(SquirrelFunctions* s); }; template SquirrelManager* 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); diff --git a/NorthstarLauncher/main.cpp b/NorthstarLauncher/main.cpp index 17128e5b..dd3b68f5 100644 --- a/NorthstarLauncher/main.cpp +++ b/NorthstarLauncher/main.cpp @@ -27,8 +27,6 @@ HMODULE hTier0Module; wchar_t exePath[4096]; wchar_t buffer[8192]; -bool noLoadPlugins = false; - DWORD GetProcessByName(std::wstring processName) { HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); @@ -292,19 +290,6 @@ bool LoadNorthstar() } ((bool (*)())Hook_Init)(); - FARPROC LoadPlugins = nullptr; - if (!noLoadPlugins) - { - LoadPlugins = GetProcAddress(hHookModule, "LoadPlugins"); - if (!hHookModule || LoadPlugins == nullptr) - { - std::cout << "Failed to get function pointer to LoadPlugins of Northstar.dll" << std::endl; - LibraryLoadError(GetLastError(), L"Northstar.dll", buffer); - return false; - } - ((bool (*)())LoadPlugins)(); - } - return true; } @@ -356,8 +341,6 @@ int main(int argc, char* argv[]) dedicated = true; else if (!strcmp(argv[i], "-nostubs")) nostubs = true; - else if (!strcmp(argv[i], "-noplugins")) - noLoadPlugins = true; if (!noOriginStartup && !dedicated) { -- cgit v1.2.3