diff options
author | uniboi <64006268+uniboi@users.noreply.github.com> | 2024-02-04 02:14:46 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-04 02:14:46 +0100 |
commit | edf013952ca2d110f190dc8cd16e5529846656e4 (patch) | |
tree | ca219c17665810d94f2cb23b27f58fa9c82f3a62 /primedev | |
parent | 6ad955ae0aab8b79910cb4a12777419a78a42a90 (diff) | |
download | NorthstarLauncher-1.23.0-rc1.tar.gz NorthstarLauncher-1.23.0-rc1.zip |
Plugin interfaces (plugins v4) (#615)v1.23.1-rc2v1.23.1-rc1v1.23.1v1.23.0-rc2v1.23.0-rc1v1.23.0
Replaces the current plugin api with source interfaces.
- backwards compatible
- no more json in binaries (wtf)
- does not rely on structs from third party libraries (wtf)
- actually initializes variables
- no more basically unused classes
The launcher exposes almost everything required by plugins in interfaces that allow for backwards compatibility.
The only thing that's passed to a plugin directly is the northstar dll HWND and a struct of data that's different for each plugin.
Diffstat (limited to 'primedev')
24 files changed, 674 insertions, 689 deletions
diff --git a/primedev/Northstar.cmake b/primedev/Northstar.cmake index 8a4cec4e..00a8dcaf 100644 --- a/primedev/Northstar.cmake +++ b/primedev/Northstar.cmake @@ -84,11 +84,16 @@ add_library( "mods/modmanager.h" "mods/modsavefiles.cpp" "mods/modsavefiles.h" - "plugins/plugin_abi.h" - "plugins/pluginbackend.cpp" - "plugins/pluginbackend.h" + "plugins/interfaces/interface.h" + "plugins/interfaces/interface.cpp" + "plugins/interfaces/sys/ISys.h" + "plugins/interfaces/sys/ISys.cpp" + "plugins/interfaces/IPluginId.h" + "plugins/interfaces/IPluginCallbacks.h" "plugins/plugins.cpp" "plugins/plugins.h" + "plugins/pluginmanager.h" + "plugins/pluginmanager.cpp" "scripts/client/clientchathooks.cpp" "scripts/client/cursorposition.cpp" "scripts/client/scriptbrowserhooks.cpp" diff --git a/primedev/core/convar/concommand.cpp b/primedev/core/convar/concommand.cpp index 6e09ef91..02c11ada 100644 --- a/primedev/core/convar/concommand.cpp +++ b/primedev/core/convar/concommand.cpp @@ -2,9 +2,6 @@ #include "shared/misccommands.h" #include "engine/r2engine.h" -#include "plugins/pluginbackend.h" -#include "plugins/plugin_abi.h" - #include <iostream> //----------------------------------------------------------------------------- @@ -152,7 +149,4 @@ ON_DLL_LOAD("engine.dll", ConCommand, (CModule module)) { ConCommandConstructor = module.Offset(0x415F60).RCast<ConCommandConstructorType>(); AddMiscConCommands(); - - g_pPluginCommunicationhandler->m_sEngineData.ConCommandConstructor = - reinterpret_cast<PluginConCommandConstructorType>(ConCommandConstructor); } diff --git a/primedev/core/convar/convar.cpp b/primedev/core/convar/convar.cpp index 94e41fee..767961ed 100644 --- a/primedev/core/convar/convar.cpp +++ b/primedev/core/convar/convar.cpp @@ -3,9 +3,6 @@ #include "convar.h" #include "core/sourceinterface.h" -#include "plugins/pluginbackend.h" -#include "plugins/plugin_abi.h" - #include <float.h> typedef void (*ConVarRegisterType)( @@ -40,12 +37,6 @@ ON_DLL_LOAD("engine.dll", ConVar, (CModule module)) g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007"); g_pCVar = *g_pCVarInterface; - - g_pPluginCommunicationhandler->m_sEngineData.conVarMalloc = reinterpret_cast<PluginConVarMallocType>(conVarMalloc); - g_pPluginCommunicationhandler->m_sEngineData.conVarRegister = reinterpret_cast<PluginConVarRegisterType>(conVarRegister); - g_pPluginCommunicationhandler->m_sEngineData.ConVar_Vtable = reinterpret_cast<void*>(g_pConVar_Vtable); - g_pPluginCommunicationhandler->m_sEngineData.IConVar_Vtable = reinterpret_cast<void*>(g_pIConVar_Vtable); - g_pPluginCommunicationhandler->m_sEngineData.g_pCVar = reinterpret_cast<void*>(g_pCVar); } //----------------------------------------------------------------------------- diff --git a/primedev/core/hooks.cpp b/primedev/core/hooks.cpp index 7ea435fe..20f0cbef 100644 --- a/primedev/core/hooks.cpp +++ b/primedev/core/hooks.cpp @@ -1,5 +1,5 @@ #include "dedicated/dedicated.h" -#include "plugins/pluginbackend.h" +#include "plugins/pluginmanager.h" #include <iostream> #include <wchar.h> @@ -417,7 +417,7 @@ HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags)) if (moduleAddress) { CallLoadLibraryACallbacks(lpLibFileName, moduleAddress); - InformPluginsDLLLoad(fs::path(lpLibFileName), moduleAddress); + g_pPluginManager->InformDllLoad(moduleAddress, fs::path(lpLibFileName)); } return moduleAddress; @@ -459,7 +459,7 @@ HMODULE, WINAPI, (LPCWSTR lpLibFileName)) if (moduleAddress) { CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress); - InformPluginsDLLLoad(fs::path(lpLibFileName), moduleAddress); + g_pPluginManager->InformDllLoad(moduleAddress, fs::path(lpLibFileName)); } return moduleAddress; diff --git a/primedev/core/sourceinterface.h b/primedev/core/sourceinterface.h index 7b5e81f3..bbbbd3bc 100644 --- a/primedev/core/sourceinterface.h +++ b/primedev/core/sourceinterface.h @@ -1,6 +1,13 @@ #pragma once #include <string> +// interface return status +enum class InterfaceStatus : int +{ + IFACE_OK = 0, + IFACE_FAILED, +}; + // literally just copied from ttf2sdk definition typedef void* (*CreateInterfaceFn)(const char* pName, int* pReturnCode); diff --git a/primedev/dllmain.cpp b/primedev/dllmain.cpp index a656dac7..c87c8bae 100644 --- a/primedev/dllmain.cpp +++ b/primedev/dllmain.cpp @@ -3,10 +3,10 @@ #include "core/memalloc.h" #include "core/vanilla.h" #include "config/profile.h" -#include "plugins/plugin_abi.h" #include "plugins/plugins.h" -#include "plugins/pluginbackend.h" +#include "plugins/pluginmanager.h" #include "util/version.h" +#include "util/wininfo.h" #include "squirrel/squirrel.h" #include "server/serverpresence.h" @@ -23,6 +23,8 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: + g_NorthstarModule = hModule; + break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: @@ -65,7 +67,6 @@ bool InitialiseNorthstar() g_pServerPresence = new ServerPresenceManager(); g_pPluginManager = new PluginManager(); - g_pPluginCommunicationhandler = new PluginCommunicationHandler(); g_pPluginManager->LoadPlugins(); InitialiseSquirrelManagers(); diff --git a/primedev/engine/hoststate.cpp b/primedev/engine/hoststate.cpp index ec728afb..d5942551 100644 --- a/primedev/engine/hoststate.cpp +++ b/primedev/engine/hoststate.cpp @@ -7,8 +7,7 @@ #include "engine/r2engine.h" #include "shared/exploit_fixes/ns_limits.h" #include "squirrel/squirrel.h" -#include "plugins/plugins.h" -#include "plugins/pluginbackend.h" +#include "plugins/pluginmanager.h" AUTOHOOK_INIT() diff --git a/primedev/logging/crashhandler.cpp b/primedev/logging/crashhandler.cpp index a01de5a1..75ca8544 100644 --- a/primedev/logging/crashhandler.cpp +++ b/primedev/logging/crashhandler.cpp @@ -4,6 +4,7 @@ #include "util/version.h" #include "mods/modmanager.h" #include "plugins/plugins.h" +#include "plugins/pluginmanager.h" #include <minidumpapiset.h> @@ -524,9 +525,9 @@ void CCrashHandler::FormatLoadedPlugins() if (g_pPluginManager) { spdlog::error("Loaded Plugins:"); - for (const Plugin& plugin : g_pPluginManager->m_vLoadedPlugins) + for (const Plugin& plugin : g_pPluginManager->GetLoadedPlugins()) { - spdlog::error("\t{}", plugin.name); + spdlog::error("\t{}", plugin.GetName()); } } } diff --git a/primedev/plugins/interfaces/IPluginCallbacks.h b/primedev/plugins/interfaces/IPluginCallbacks.h new file mode 100644 index 00000000..c02ce8a6 --- /dev/null +++ b/primedev/plugins/interfaces/IPluginCallbacks.h @@ -0,0 +1,36 @@ +#ifndef IPLUGIN_CALLBACKS_H +#define IPLUGIN_CALLBACKS_H + +#include <windows.h> +#include <stdint.h> +#include "squirrel/squirrel.h" + +// can't use bitwise ops on enum classes but I don't want these in the global namespace (user defined operators suck) +namespace PluginContext +{ + enum : uint64_t + { + DEDICATED = 0x1, + CLIENT = 0x2, + }; +} + +struct PluginNorthstarData +{ + HMODULE pluginHandle; +}; + +class IPluginCallbacks +{ +public: + virtual void + Init(HMODULE northstarModule, const PluginNorthstarData* initData, bool reloaded) = 0; // runs after the plugin is loaded and validated + virtual void Finalize() = 0; // runs after all plugins have been loaded + virtual bool Unload() = 0; // runs just before the library is freed + virtual void OnSqvmCreated(CSquirrelVM* sqvm) = 0; + virtual void OnSqvmDestroying(CSquirrelVM* sqvm) = 0; + virtual void OnLibraryLoaded(HMODULE module, const char* name) = 0; + virtual void RunFrame() = 0; +}; + +#endif diff --git a/primedev/plugins/interfaces/IPluginId.h b/primedev/plugins/interfaces/IPluginId.h new file mode 100644 index 00000000..dc4c548b --- /dev/null +++ b/primedev/plugins/interfaces/IPluginId.h @@ -0,0 +1,31 @@ +#ifndef IPLUGIN_ID_H +#define IPLUGIN_ID_H + +#include <stdint.h> +#include "squirrel/squirrelclasstypes.h" + +#define PLUGIN_ID_VERSION "PluginId001" + +// an identifier for the type of string data requested from the plugin +enum class PluginString : int +{ + NAME = 0, + LOG_NAME = 1, + DEPENDENCY_NAME = 2, +}; + +// an identifier for the type of bitflag requested from the plugin +enum class PluginField : int +{ + CONTEXT = 0, +}; + +// an interface that is required from every plugin to query data about it +class IPluginId +{ +public: + virtual const char* GetString(PluginString prop) = 0; + virtual int64_t GetField(PluginField prop) = 0; +}; + +#endif diff --git a/primedev/plugins/interfaces/interface.cpp b/primedev/plugins/interfaces/interface.cpp new file mode 100644 index 00000000..4c006f2c --- /dev/null +++ b/primedev/plugins/interfaces/interface.cpp @@ -0,0 +1,36 @@ +#include <string.h> +#include "interface.h" + +InterfaceReg* s_pInterfaceRegs; + +InterfaceReg::InterfaceReg(InstantiateInterfaceFn fn, const char* pName) : m_pName(pName) +{ + m_CreateFn = fn; + m_pNext = s_pInterfaceRegs; + s_pInterfaceRegs = this; +} + +void* CreateInterface(const char* pName, InterfaceStatus* pReturnCode) +{ + for (InterfaceReg* pCur = s_pInterfaceRegs; pCur; pCur = pCur->m_pNext) + { + if (strcmp(pCur->m_pName, pName) == 0) + { + if (pReturnCode) + { + *pReturnCode = InterfaceStatus::IFACE_OK; + } + + NS::log::PLUGINSYS->info("creating interface {}", pName); + return pCur->m_CreateFn(); + } + } + + if (pReturnCode) + { + *pReturnCode = InterfaceStatus::IFACE_FAILED; + } + + NS::log::PLUGINSYS->error("could not find interface {}", pName); + return NULL; +} diff --git a/primedev/plugins/interfaces/interface.h b/primedev/plugins/interfaces/interface.h new file mode 100644 index 00000000..440db5b2 --- /dev/null +++ b/primedev/plugins/interfaces/interface.h @@ -0,0 +1,39 @@ +#ifndef INTERFACE_H +#define INTERFACE_H + +typedef void* (*InstantiateInterfaceFn)(); + +// Used internally to register classes. +class InterfaceReg +{ +public: + InterfaceReg(InstantiateInterfaceFn fn, const char* pName); + + InstantiateInterfaceFn m_CreateFn; + const char* m_pName; + InterfaceReg* m_pNext; +}; + +// Use this to expose an interface that can have multiple instances. +#define EXPOSE_INTERFACE(className, interfaceName, versionName) \ + static void* __Create##className##_interface() \ + { \ + return static_cast<interfaceName*>(new className); \ + } \ + static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName); + +#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \ + static void* __Create##className##interfaceName##_interface() \ + { \ + return static_cast<interfaceName*>(&globalVarName); \ + } \ + static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName); + +// Use this to expose a singleton interface. This creates the global variable for you automatically. +#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \ + static className __g_##className##_singleton; \ + EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton) + +EXPORT void* CreateInterface(const char* pName, InterfaceStatus* pReturnCode); + +#endif diff --git a/primedev/plugins/interfaces/sys/ISys.cpp b/primedev/plugins/interfaces/sys/ISys.cpp new file mode 100644 index 00000000..6b0b41dd --- /dev/null +++ b/primedev/plugins/interfaces/sys/ISys.cpp @@ -0,0 +1,66 @@ +#include "plugins/interfaces/interface.h" +#include "ISys.h" +#include "plugins/plugins.h" +#include "plugins/pluginmanager.h" + +class CSys : public ISys +{ +public: + void Log(HMODULE handle, LogLevel level, char* msg) + { + spdlog::level::level_enum spdLevel; + + switch (level) + { + case LogLevel::WARN: + spdLevel = spdlog::level::level_enum::warn; + break; + case LogLevel::ERR: + spdLevel = spdlog::level::level_enum::err; + break; + default: + NS::log::PLUGINSYS->warn("Attempted to log with invalid level {}. Defaulting to info", (int)level); + case LogLevel::INFO: + spdLevel = spdlog::level::level_enum::info; + break; + } + + std::optional<Plugin> plugin = g_pPluginManager->GetPlugin(handle); + if (plugin) + { + plugin->Log(spdLevel, msg); + } + else + { + NS::log::PLUGINSYS->warn("Attempted to log message '{}' with invalid plugin handle {}", msg, static_cast<void*>(handle)); + } + } + + void Unload(HMODULE handle) + { + std::optional<Plugin> plugin = g_pPluginManager->GetPlugin(handle); + if (plugin) + { + plugin->Unload(); + } + else + { + NS::log::PLUGINSYS->warn("Attempted to unload plugin with invalid handle {}", static_cast<void*>(handle)); + } + } + + void Reload(HMODULE handle) + { + std::optional<Plugin> plugin = g_pPluginManager->GetPlugin(handle); + if (plugin) + { + plugin->Reload(); + } + else + { + NS::log::PLUGINSYS->warn("Attempted to reload plugin with invalid handle {}", static_cast<void*>(handle)); + } + } +}; + +EXPOSE_SINGLE_INTERFACE(CSys, ISys, SYS_VERSION); diff --git a/primedev/plugins/interfaces/sys/ISys.h b/primedev/plugins/interfaces/sys/ISys.h new file mode 100644 index 00000000..3e55a6d9 --- /dev/null +++ b/primedev/plugins/interfaces/sys/ISys.h @@ -0,0 +1,21 @@ +#ifndef ILOGGING_H +#define ILOGGING_H + +#define SYS_VERSION "NSSys001" + +enum class LogLevel : int +{ + INFO = 0, + WARN, + ERR, +}; + +class ISys +{ +public: + virtual void Log(HMODULE handle, LogLevel level, char* msg) = 0; + virtual void Unload(HMODULE handle) = 0; + virtual void Reload(HMODULE handle) = 0; +}; + +#endif diff --git a/primedev/plugins/plugin_abi.h b/primedev/plugins/plugin_abi.h deleted file mode 100644 index 16b26a1c..00000000 --- a/primedev/plugins/plugin_abi.h +++ /dev/null @@ -1,151 +0,0 @@ -#pragma once -#include "squirrel/squirrelclasstypes.h" - -#define ABI_VERSION 3 - -enum PluginLoadDLL -{ - ENGINE = 0, - CLIENT, - SERVER -}; - -enum ObjectType -{ - CONCOMMANDS = 0, - CONVAR = 1, -}; - -struct SquirrelFunctions -{ - RegisterSquirrelFuncType RegisterSquirrelFunc; - sq_defconstType __sq_defconst; - - sq_compilebufferType __sq_compilebuffer; - sq_callType __sq_call; - sq_raiseerrorType __sq_raiseerror; - sq_compilefileType __sq_compilefile; - - 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_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_getthisentityType __sq_getthisentity; - sq_getobjectType __sq_getobject; - - sq_stackinfosType __sq_stackinfos; - - 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; - - sq_pushnewstructinstanceType __sq_pushnewstructinstance; - sq_sealstructslotType __sq_sealstructslot; -}; - -struct MessageSource -{ - const char* file; - const char* func; - int line; -}; - -// This is a modified version of spdlog::details::log_msg -// This is so that we can make it cross DLL boundaries -struct LogMsg -{ - int level; - uint64_t timestamp; - const char* msg; - MessageSource source; - int pluginHandle; -}; - -extern "C" -{ - typedef void (*loggerfunc_t)(LogMsg* msg); - typedef void (*PLUGIN_RELAY_INVITE_TYPE)(const char* invite); - typedef void* (*CreateObjectFunc)(ObjectType type); - - typedef void (*PluginFnCommandCallback_t)(void* command); - typedef void (*PluginConCommandConstructorType)( - void* newCommand, const char* name, PluginFnCommandCallback_t callback, const char* helpString, int flags, void* parent); - typedef void (*PluginConVarRegisterType)( - void* pConVar, - const char* pszName, - const char* pszDefaultValue, - int nFlags, - const char* pszHelpString, - bool bMin, - float fMin, - bool bMax, - float fMax, - void* pCallback); - typedef void (*PluginConVarMallocType)(void* pConVarMaloc, int a2, int a3); -} - -struct PluginNorthstarData -{ - const char* version; - HMODULE northstarModule; - int pluginHandle; -}; - -struct PluginInitFuncs -{ - loggerfunc_t logger; - PLUGIN_RELAY_INVITE_TYPE relayInviteFunc; - CreateObjectFunc createObject; -}; - -struct PluginEngineData -{ - PluginConCommandConstructorType ConCommandConstructor; - PluginConVarMallocType conVarMalloc; - PluginConVarRegisterType conVarRegister; - void* ConVar_Vtable; - void* IConVar_Vtable; - void* g_pCVar; -}; - -/// <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); - -typedef void (*PLUGIN_INFORM_DLL_LOAD_TYPE)(const char* dll, PluginEngineData* data, void* dllPtr); -typedef void (*PLUGIN_RUNFRAME)(); diff --git a/primedev/plugins/pluginbackend.cpp b/primedev/plugins/pluginbackend.cpp deleted file mode 100644 index 850394ac..00000000 --- a/primedev/plugins/pluginbackend.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#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" - -#include <filesystem> - -#define EXPORT extern "C" __declspec(dllexport) - -AUTOHOOK_INIT() - -PluginCommunicationHandler* g_pPluginCommunicationhandler; - -static PluginDataRequest storedRequest {PluginDataRequestType::END, (PluginRespondDataCallable) nullptr}; - -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 InformPluginsDLLLoad(fs::path dllPath, void* address) -{ - std::string dllName = dllPath.filename().string(); - - void* data = NULL; - if (strncmp(dllName.c_str(), "engine.dll", 10) == 0) - data = &g_pPluginCommunicationhandler->m_sEngineData; - - g_pPluginManager->InformDLLLoad(dllName.c_str(), data, address); -} diff --git a/primedev/plugins/pluginbackend.h b/primedev/plugins/pluginbackend.h deleted file mode 100644 index 45cd42f3..00000000 --- a/primedev/plugins/pluginbackend.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include "plugin_abi.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); - -public: - std::queue<PluginDataRequest> requestQueue = {}; - std::mutex requestMutex; - - PluginEngineData m_sEngineData {}; -}; - -void InformPluginsDLLLoad(fs::path dllPath, void* address); -extern PluginCommunicationHandler* g_pPluginCommunicationhandler; diff --git a/primedev/plugins/pluginmanager.cpp b/primedev/plugins/pluginmanager.cpp new file mode 100644 index 00000000..718e6956 --- /dev/null +++ b/primedev/plugins/pluginmanager.cpp @@ -0,0 +1,184 @@ +#include "pluginmanager.h" + +#include <regex> +#include "plugins.h" +#include "config/profile.h" +#include "core/convar/concommand.h" + +PluginManager* g_pPluginManager; + +const std::vector<Plugin>& PluginManager::GetLoadedPlugins() const +{ + return this->plugins; +} + +const std::optional<Plugin> PluginManager::GetPlugin(HMODULE handle) const +{ + for (const Plugin& plugin : GetLoadedPlugins()) + if (plugin.m_handle == handle) + return plugin; + return std::nullopt; +} + +void PluginManager::LoadPlugin(fs::path path, bool reloaded) +{ + Plugin plugin = Plugin(path.string()); + + if (!plugin.IsValid()) + { + NS::log::PLUGINSYS->warn("Unloading invalid plugin '{}'", path.string()); + plugin.Unload(); + return; + } + + plugins.push_back(plugin); + plugin.Init(reloaded); +} + +inline void FindPlugins(fs::path pluginPath, std::vector<fs::path>& paths) +{ + // ensure dirs exist + if (!fs::exists(pluginPath) || !fs::is_directory(pluginPath)) + { + return; + } + + for (const fs::directory_entry& entry : fs::directory_iterator(pluginPath)) + { + if (fs::is_regular_file(entry) && entry.path().extension() == ".dll") + paths.emplace_back(entry.path()); + } +} + +bool PluginManager::LoadPlugins(bool reloaded) +{ + if (strstr(GetCommandLineA(), "-noplugins") != NULL) + { + NS::log::PLUGINSYS->warn("-noplugins detected; skipping loading plugins"); + return false; + } + + fs::create_directories(GetThunderstoreModFolderPath()); + + std::vector<fs::path> paths; + + pluginPath = GetNorthstarPrefix() + "\\plugins"; + + fs::path libPath = fs::absolute(pluginPath + "\\lib"); + if (fs::exists(libPath) && fs::is_directory(libPath)) + AddDllDirectory(libPath.wstring().c_str()); + + FindPlugins(pluginPath, paths); + + // Special case for Thunderstore mods dir + std::filesystem::directory_iterator thunderstoreModsDir = fs::directory_iterator(GetThunderstoreModFolderPath()); + // Set up regex for `AUTHOR-MOD-VERSION` pattern + std::regex pattern(R"(.*\\([a-zA-Z0-9_]+)-([a-zA-Z0-9_]+)-(\d+\.\d+\.\d+))"); + for (fs::directory_entry dir : thunderstoreModsDir) + { + fs::path pluginsDir = dir.path() / "plugins"; + // Use regex to match `AUTHOR-MOD-VERSION` pattern + if (!std::regex_match(dir.path().string(), pattern)) + { + spdlog::warn("The following directory did not match 'AUTHOR-MOD-VERSION': {}", dir.path().string()); + continue; // skip loading package that doesn't match + } + + fs::path libDir = fs::absolute(pluginsDir / "lib"); + if (fs::exists(libDir) && fs::is_directory(libDir)) + AddDllDirectory(libDir.wstring().c_str()); + + FindPlugins(pluginsDir, paths); + } + + if (paths.empty()) + { + NS::log::PLUGINSYS->warn("Could not find any plugins. Skipped loading plugins"); + return false; + } + + for (fs::path path : paths) + { + LoadPlugin(path, reloaded); + } + + InformAllPluginsInitialized(); + + return true; +} + +void PluginManager::ReloadPlugins() +{ + for (const Plugin& plugin : this->GetLoadedPlugins()) + { + plugin.Unload(); + } + + this->plugins.clear(); + this->LoadPlugins(true); +} + +void PluginManager::RemovePlugin(HMODULE handle) +{ + for (size_t i = 0; i < plugins.size(); i++) + { + Plugin* plugin = &plugins[i]; + if (plugin->m_handle == handle) + { + plugins.erase(plugins.begin() + i); + return; + } + } +} + +void PluginManager::InformAllPluginsInitialized() const +{ + for (const Plugin& plugin : GetLoadedPlugins()) + { + plugin.Finalize(); + } +} + +void PluginManager::InformSqvmCreated(CSquirrelVM* sqvm) const +{ + for (const Plugin& plugin : GetLoadedPlugins()) + { + plugin.OnSqvmCreated(sqvm); + } +} + +void PluginManager::InformSqvmDestroying(CSquirrelVM* sqvm) const +{ + for (const Plugin& plugin : GetLoadedPlugins()) + { + plugin.OnSqvmDestroying(sqvm); + } +} + +void PluginManager::InformDllLoad(HMODULE module, fs::path path) const +{ + std::string fn = path.filename().string(); // without this the string gets freed immediately lmao + const char* filename = fn.c_str(); + for (const Plugin& plugin : GetLoadedPlugins()) + { + plugin.OnLibraryLoaded(module, filename); + } +} + +void PluginManager::RunFrame() const +{ + for (const Plugin& plugin : GetLoadedPlugins()) + { + plugin.RunFrame(); + } +} + +void ConCommand_reload_plugins(const CCommand& args) +{ + g_pPluginManager->ReloadPlugins(); +} + +ON_DLL_LOAD_RELIESON("engine.dll", PluginManager, ConCommand, (CModule module)) +{ + RegisterConCommand("reload_plugins", ConCommand_reload_plugins, "reloads plugins", FCVAR_NONE); +} diff --git a/primedev/plugins/pluginmanager.h b/primedev/plugins/pluginmanager.h new file mode 100644 index 00000000..8c021851 --- /dev/null +++ b/primedev/plugins/pluginmanager.h @@ -0,0 +1,33 @@ +#ifndef PLUGIN_MANAGER_H +#define PLUGIN_MANAGER_H + +#include <windows.h> + +class Plugin; + +class PluginManager +{ +public: + const std::vector<Plugin>& GetLoadedPlugins() const; + const std::optional<Plugin> GetPlugin(HMODULE handle) const; + bool LoadPlugins(bool reloaded = false); + void LoadPlugin(fs::path path, bool reloaded = false); + void ReloadPlugins(); + void RemovePlugin(HMODULE handle); + + // callback triggers + void InformSqvmCreated(CSquirrelVM* sqvm) const; + void InformSqvmDestroying(CSquirrelVM* sqvm) const; + void InformDllLoad(HMODULE module, fs::path path) const; + void RunFrame() const; + +private: + void InformAllPluginsInitialized() const; + + std::vector<Plugin> plugins; + std::string pluginPath; +}; + +extern PluginManager* g_pPluginManager; + +#endif diff --git a/primedev/plugins/plugins.cpp b/primedev/plugins/plugins.cpp index 311285e1..eddaa8ac 100644 --- a/primedev/plugins/plugins.cpp +++ b/primedev/plugins/plugins.cpp @@ -1,340 +1,233 @@ #include "plugins.h" -#include "config/profile.h" - +#include "pluginmanager.h" #include "squirrel/squirrel.h" -#include "plugins.h" -#include "masterserver/masterserver.h" -#include "core/convar/convar.h" -#include "server/serverpresence.h" -#include <optional> -#include <regex> - -#include "util/version.h" -#include "pluginbackend.h" #include "util/wininfo.h" +#include "core/sourceinterface.h" #include "logging/logging.h" #include "dedicated/dedicated.h" -PluginManager* g_pPluginManager; - -void freeLibrary(HMODULE hLib) +bool isValidSquirrelIdentifier(std::string s) { - if (!FreeLibrary(hLib)) - { - spdlog::error("There was an error while trying to free library"); + if (!s.size()) + return false; // identifiers can't be empty + if (s[0] <= 57) + return false; // identifier can't start with a number + for (char& c : s) + { + // only allow underscores, 0-9, A-Z and a-z + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') + continue; + return false; } + return true; } -EXPORT void PLUGIN_LOG(LogMsg* msg) +Plugin::Plugin(std::string path) : m_location(path) { - 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); -} + HMODULE pluginModule = GetModuleHandleA(path.c_str()); -EXPORT void* CreateObject(ObjectType type) -{ - switch (type) + if (pluginModule) { - case ObjectType::CONVAR: - return (void*)new ConVar; - case ObjectType::CONCOMMANDS: - return (void*)new ConCommand; - default: - return NULL; + // plugins may refuse to get unloaded for any reason so we need to prevent them getting loaded twice when reloading plugins + NS::log::PLUGINSYS->warn("Plugin has already been loaded"); + return; } -} -std::optional<Plugin> PluginManager::LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data) -{ - - Plugin plugin {}; + m_handle = LoadLibraryExA(path.c_str(), 0, LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); - std::string pathstring = path.string(); - std::wstring wpath = path.wstring(); + NS::log::PLUGINSYS->info("loaded plugin handle {}", static_cast<void*>(m_handle)); - 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) + if (!m_handle) { - NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError())); - return std::nullopt; + NS::log::PLUGINSYS->error("Failed to load plugin '{}' (Error: {})", path, GetLastError()); + return; } - HRSRC manifestResource = FindResourceW(datafile, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA); - if (manifestResource == NULL) - { - 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); + m_initData = {.pluginHandle = m_handle}; - rapidjson_document manifestJSON; - manifestJSON.Parse(manifest.c_str()); + CreateInterfaceFn CreatePluginInterface = (CreateInterfaceFn)GetProcAddress(m_handle, "CreateInterface"); - if (manifestJSON.HasParseError()) + if (!CreatePluginInterface) { - 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; + NS::log::PLUGINSYS->error("Plugin at '{}' does not expose CreateInterface()", path); + return; } - if (!manifestJSON.HasMember("run_on_server")) + + m_pluginId = (IPluginId*)CreatePluginInterface(PLUGIN_ID_VERSION, 0); + + if (!m_pluginId) { - NS::log::PLUGINSYS->error("'{}' is missing 'run_on_server' in its manifest", pathstring); - return std::nullopt; + NS::log::PLUGINSYS->error("Could not load IPluginId interface of plugin at '{}'", path); + return; } - if (!manifestJSON.HasMember("run_on_client")) + + const char* name = m_pluginId->GetString(PluginString::NAME); + const char* logName = m_pluginId->GetString(PluginString::LOG_NAME); + const char* dependencyName = m_pluginId->GetString(PluginString::DEPENDENCY_NAME); + int64_t context = m_pluginId->GetField(PluginField::CONTEXT); + + m_runOnServer = context & PluginContext::DEDICATED; + m_runOnClient = context & PluginContext::CLIENT; + + m_name = std::string(name); + m_logName = std::string(logName); + m_dependencyName = std::string(dependencyName); + + if (!name) { - NS::log::PLUGINSYS->error("'{}' is missing 'run_on_client' in its manifest", pathstring); - return std::nullopt; + NS::log::PLUGINSYS->error("Could not load name of plugin at '{}'", path); + return; } - auto test = manifestJSON["api_version"].GetString(); - if (strcmp(manifestJSON["api_version"].GetString(), std::to_string(ABI_VERSION).c_str())) + + if (!logName) { - NS::log::PLUGINSYS->error( - "'{}' has an incompatible API version number in its manifest. Current ABI version is '{}'", pathstring, ABI_VERSION); - return std::nullopt; + NS::log::PLUGINSYS->error("Could not load logName of plugin {}", name); + return; } - // Passed all checks, going to actually load it now - HMODULE pluginLib = - LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); // Load the DLL with lib folders - if (pluginLib == NULL) + if (!dependencyName) { - NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError())); - return std::nullopt; + NS::log::PLUGINSYS->error("Could not load dependencyName of plugin {}", name); + return; } - plugin.init = (PLUGIN_INIT_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT"); - if (plugin.init == NULL) + + if (!isValidSquirrelIdentifier(m_dependencyName)) { - NS::log::PLUGINSYS->info("Library '{}' has no function 'PLUGIN_INIT'", pathstring); - return std::nullopt; + NS::log::PLUGINSYS->error("Dependency name \"{}\" of plugin {} is not valid", dependencyName, name); + return; } - NS::log::PLUGINSYS->info("Succesfully loaded {}", pathstring); - - 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(); - plugin.run_on_client = manifestJSON["run_on_client"].GetBool(); - plugin.run_on_server = manifestJSON["run_on_server"].GetBool(); + m_callbacks = (IPluginCallbacks*)CreatePluginInterface("PluginCallbacks001", 0); - if (!plugin.run_on_server && IsDedicatedServer()) - return std::nullopt; - - if (manifestJSON.HasMember("dependencyName")) + if (!m_callbacks) { - plugin.dependencyName = manifestJSON["dependencyName"].GetString(); + NS::log::PLUGINSYS->error("Could not create callback interface of plugin {}", name); + return; } - else + + m_logger = std::make_shared<ColoredLogger>(m_logName, NS::Colors::PLUGIN); + RegisterLogger(m_logger); + + if (IsDedicatedServer() && !m_runOnServer) { - plugin.dependencyName = plugin.name; + NS::log::PLUGINSYS->info("Unloading {} because it's not supposed to run on dedicated servers", m_name); + return; } - if (std::find_if( - plugin.dependencyName.begin(), - plugin.dependencyName.end(), - [&](char c) -> bool { return !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'); }) != - plugin.dependencyName.end()) + if (!IsDedicatedServer() && !m_runOnClient) { - NS::log::PLUGINSYS->warn("Dependency string \"{}\" in {} is not valid a squirrel constant!", plugin.dependencyName, plugin.name); + NS::log::PLUGINSYS->info("Unloading {} because it's only supposed to run on dedicated servers", m_name); + return; } - 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"); - - plugin.inform_dll_load = (PLUGIN_INFORM_DLL_LOAD_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_DLL_LOAD"); - - plugin.run_frame = (PLUGIN_RUNFRAME)GetProcAddress(pluginLib, "PLUGIN_RUNFRAME"); - - plugin.handle = (int)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; + m_valid = true; } -inline void FindPlugins(fs::path pluginPath, std::vector<fs::path>& paths) +bool Plugin::Unload() const { - // ensure dirs exist - if (!fs::exists(pluginPath) || !fs::is_directory(pluginPath)) + if (!m_handle) + return true; + + if (IsValid()) { - return; + bool unloaded = m_callbacks->Unload(); + + if (!unloaded) + return false; } - for (const fs::directory_entry& entry : fs::directory_iterator(pluginPath)) + if (!FreeLibrary(m_handle)) { - if (fs::is_regular_file(entry) && entry.path().extension() == ".dll") - paths.emplace_back(entry.path()); + NS::log::PLUGINSYS->error("Failed to unload plugin at '{}'", m_location); + return false; } + + g_pPluginManager->RemovePlugin(m_handle); + return true; } -bool PluginManager::LoadPlugins() +void Plugin::Reload() const { - if (strstr(GetCommandLineA(), "-noplugins") != NULL) - { - NS::log::PLUGINSYS->warn("-noplugins detected; skipping loading plugins"); - return false; - } + bool unloaded = Unload(); - fs::create_directories(GetThunderstoreModFolderPath()); + if (!unloaded) + return; - std::vector<fs::path> paths; + g_pPluginManager->LoadPlugin(fs::path(m_location), true); +} - pluginPath = GetNorthstarPrefix() + "\\plugins"; +void Plugin::Log(spdlog::level::level_enum level, char* msg) const +{ + m_logger->log(level, msg); +} - PluginNorthstarData data {}; - std::string ns_version {version}; +bool Plugin::IsValid() const +{ + return m_valid && m_pCreateInterface && m_pluginId && m_callbacks && m_handle; +} - PluginInitFuncs funcs {}; - funcs.logger = PLUGIN_LOG; - funcs.relayInviteFunc = nullptr; - funcs.createObject = CreateObject; +const std::string& Plugin::GetName() const +{ + return m_name; +} - data.version = ns_version.c_str(); - data.northstarModule = g_NorthstarModule; +const std::string& Plugin::GetLogName() const +{ + return m_logName; +} - fs::path libPath = fs::absolute(pluginPath + "\\lib"); - if (fs::exists(libPath) && fs::is_directory(libPath)) - AddDllDirectory(libPath.wstring().c_str()); +const std::string& Plugin::GetDependencyName() const +{ + return m_dependencyName; +} - FindPlugins(pluginPath, paths); +const std::string& Plugin::GetLocation() const +{ + return m_location; +} - // Special case for Thunderstore mods dir - std::filesystem::directory_iterator thunderstoreModsDir = fs::directory_iterator(GetThunderstoreModFolderPath()); - // Set up regex for `AUTHOR-MOD-VERSION` pattern - std::regex pattern(R"(.*\\([a-zA-Z0-9_]+)-([a-zA-Z0-9_]+)-(\d+\.\d+\.\d+))"); - for (fs::directory_entry dir : thunderstoreModsDir) - { - fs::path pluginsDir = dir.path() / "plugins"; - // Use regex to match `AUTHOR-MOD-VERSION` pattern - if (!std::regex_match(dir.path().string(), pattern)) - { - spdlog::warn("The following directory did not match 'AUTHOR-MOD-VERSION': {}", dir.path().string()); - continue; // skip loading package that doesn't match - } - - fs::path libDir = fs::absolute(pluginsDir / "lib"); - if (fs::exists(libDir) && fs::is_directory(libDir)) - AddDllDirectory(libDir.wstring().c_str()); - - FindPlugins(pluginsDir, paths); - } +bool Plugin::ShouldRunOnServer() const +{ + return m_runOnServer; +} - if (paths.empty()) - { - NS::log::PLUGINSYS->warn("Could not find any plugins. Skipped loading plugins"); - return false; - } +bool Plugin::ShouldRunOnClient() const +{ + return m_runOnClient; +} - for (fs::path path : paths) - { - if (LoadPlugin(path, &funcs, &data)) - data.pluginHandle += 1; - } - return true; +void* Plugin::CreateInterface(const char* name, int* status) const +{ + return m_pCreateInterface(name, status); } -void PluginManager::InformSQVMLoad(ScriptContext context, SquirrelFunctions* s) +void Plugin::Init(bool reloaded) const { - for (auto plugin : m_vLoadedPlugins) - { - 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); - } - } + m_callbacks->Init(g_NorthstarModule, &m_initData, reloaded); } -void PluginManager::InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm) +void Plugin::Finalize() const { - for (auto plugin : m_vLoadedPlugins) - { - if (plugin.inform_sqvm_created != NULL) - { - plugin.inform_sqvm_created(context, sqvm); - } - } + m_callbacks->Finalize(); } -void PluginManager::InformSQVMDestroyed(ScriptContext context) +void Plugin::OnSqvmCreated(CSquirrelVM* sqvm) const { - for (auto plugin : m_vLoadedPlugins) - { - if (plugin.inform_sqvm_destroyed != NULL) - { - plugin.inform_sqvm_destroyed(context); - } - } + m_callbacks->OnSqvmCreated(sqvm); } -void PluginManager::InformDLLLoad(const char* dll, void* data, void* dllPtr) +void Plugin::OnSqvmDestroying(CSquirrelVM* sqvm) const { - for (auto plugin : m_vLoadedPlugins) - { - if (plugin.inform_dll_load != NULL) - { - plugin.inform_dll_load(dll, (PluginEngineData*)data, dllPtr); - } - } + NS::log::PLUGINSYS->info("destroying sqvm {}", sqvm->vmContext); + m_callbacks->OnSqvmDestroying(sqvm); } -void PluginManager::RunFrame() +void Plugin::OnLibraryLoaded(HMODULE module, const char* name) const { - for (auto plugin : m_vLoadedPlugins) - { - if (plugin.run_frame != NULL) - { - plugin.run_frame(); - } - } + m_callbacks->OnLibraryLoaded(module, name); +} + +void Plugin::RunFrame() const +{ + m_callbacks->RunFrame(); } diff --git a/primedev/plugins/plugins.h b/primedev/plugins/plugins.h index 4e841f27..d004038c 100644 --- a/primedev/plugins/plugins.h +++ b/primedev/plugins/plugins.h @@ -1,59 +1,50 @@ #pragma once -#include "plugin_abi.h" - -const int IDR_RCDATA1 = 101; +#include "core/sourceinterface.h" +#include "plugins/interfaces/interface.h" +#include "plugins/interfaces/IPluginId.h" +#include "plugins/interfaces/IPluginCallbacks.h" class Plugin { -public: - std::string name; - std::string displayName; - std::string dependencyName; - std::string description; - - std::string api_version; - std::string version; - - // For now this is just implemented as the index into the plugins array - // Maybe a bit shit but it works - int handle; +private: + CreateInterfaceFn m_pCreateInterface; + IPluginId* m_pluginId = 0; + IPluginCallbacks* m_callbacks = 0; - std::shared_ptr<ColoredLogger> logger; + std::shared_ptr<ColoredLogger> m_logger; - bool run_on_client = false; - bool run_on_server = false; + bool m_valid = false; + std::string m_name; + std::string m_logName; + std::string m_dependencyName; + std::string m_location; // path of the dll + bool m_runOnServer; + bool m_runOnClient; 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_INFORM_DLL_LOAD_TYPE inform_dll_load; - - PLUGIN_RUNFRAME run_frame; + HMODULE m_handle; + PluginNorthstarData m_initData; + + Plugin(std::string path); + bool Unload() const; + void Reload() const; + + // sys + void Log(spdlog::level::level_enum level, char* msg) const; + + // callbacks + bool IsValid() const; + const std::string& GetName() const; + const std::string& GetLogName() const; + const std::string& GetDependencyName() const; + const std::string& GetLocation() const; + bool ShouldRunOnServer() const; + bool ShouldRunOnClient() const; + void* CreateInterface(const char* pName, int* pStatus) const; + void Init(bool reloaded) const; + void Finalize() const; + void OnSqvmCreated(CSquirrelVM* sqvm) const; + void OnSqvmDestroying(CSquirrelVM* sqvm) const; + void OnLibraryLoaded(HMODULE module, const char* name) const; + void RunFrame() const; }; - -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 InformDLLLoad(const char* dll, void* data, void* dllPtr); - - void RunFrame(); - -private: - std::string pluginPath; -}; - -extern PluginManager* g_pPluginManager; diff --git a/primedev/squirrel/squirrel.cpp b/primedev/squirrel/squirrel.cpp index affedebb..70dedcb8 100644 --- a/primedev/squirrel/squirrel.cpp +++ b/primedev/squirrel/squirrel.cpp @@ -6,8 +6,8 @@ #include "dedicated/dedicated.h" #include "engine/r2engine.h" #include "core/tier0.h" -#include "plugins/plugin_abi.h" #include "plugins/plugins.h" +#include "plugins/pluginmanager.h" #include "ns_version.h" #include "core/vanilla.h" @@ -157,79 +157,6 @@ 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_compilefile = __sq_compilefile; - - 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_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_getthisentity = __sq_getthisentity; - s->__sq_getobject = __sq_getobject; - - s->__sq_stackinfos = __sq_stackinfos; - - s->__sq_createuserdata = __sq_createuserdata; - s->__sq_setuserdatatypeid = __sq_setuserdatatypeid; - s->__sq_getfunction = __sq_getfunction; - - s->__sq_schedule_call_external = AsyncCall_External; - - s->__sq_getentityfrominstance = __sq_getentityfrominstance; - s->__sq_GetEntityConstant_CBaseEntity = __sq_GetEntityConstant_CBaseEntity; - - s->__sq_pushnewstructinstance = __sq_pushnewstructinstance; - s->__sq_sealstructslot = __sq_sealstructslot; -} - -// Allows for generating squirrelmessages from plugins. -void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function, void* userdata) -{ - SquirrelMessage message {}; - message.functionName = func_name; - message.isExternal = true; - message.externalFunc = function; - message.userdata = userdata; - switch (context) - { - case ScriptContext::CLIENT: - g_pSquirrel<ScriptContext::CLIENT>->messageBuffer->push(message); - break; - case ScriptContext::SERVER: - g_pSquirrel<ScriptContext::SERVER>->messageBuffer->push(message); - break; - case ScriptContext::UI: - g_pSquirrel<ScriptContext::UI>->messageBuffer->push(message); - break; - } -} - // needed to define implementations for squirrelmanager outside of squirrel.h without compiler errors template class SquirrelManager<ScriptContext::SERVER>; template class SquirrelManager<ScriptContext::CLIENT>; @@ -264,11 +191,11 @@ template <ScriptContext context> void SquirrelManager<context>::VMCreated(CSquir defconst(m_pSQVM, pair.first.c_str(), bWasFound); } - auto loadedPlugins = &g_pPluginManager->m_vLoadedPlugins; + std::vector<Plugin> loadedPlugins = g_pPluginManager->GetLoadedPlugins(); for (const auto& pluginName : g_pModManager->m_PluginDependencyConstants) { - auto f = [&](Plugin plugin) -> bool { return plugin.dependencyName == pluginName; }; - defconst(m_pSQVM, pluginName.c_str(), std::find_if(loadedPlugins->begin(), loadedPlugins->end(), f) != loadedPlugins->end()); + auto f = [&](Plugin plugin) -> bool { return plugin.GetDependencyName() == pluginName; }; + defconst(m_pSQVM, pluginName.c_str(), std::find_if(loadedPlugins.begin(), loadedPlugins.end(), f) != loadedPlugins.end()); } defconst(m_pSQVM, "MAX_FOLDER_SIZE", GetMaxSaveFolderSize() / 1024); @@ -284,7 +211,7 @@ template <ScriptContext context> void SquirrelManager<context>::VMCreated(CSquir defconst(m_pSQVM, "VANILLA", g_pVanillaCompatibility->GetVanillaCompatibility()); g_pSquirrel<context>->messageBuffer = new SquirrelMessageBuffer(); - g_pPluginManager->InformSQVMCreated(context, newSqvm); + g_pPluginManager->InformSqvmCreated(newSqvm); } template <ScriptContext context> void SquirrelManager<context>::VMDestroyed() @@ -312,7 +239,7 @@ template <ScriptContext context> void SquirrelManager<context>::VMDestroyed() } } - g_pPluginManager->InformSQVMDestroyed(context); + g_pPluginManager->InformSqvmDestroying(m_pSQVM); // Discard the previous vm and delete the message buffer. m_pSQVM = nullptr; @@ -661,17 +588,10 @@ template <ScriptContext context> void SquirrelManager<context>::ProcessMessageBu size_t argsAmount = message.args.size(); - if (message.isExternal && message.externalFunc != NULL) + for (auto& v : message.args) { - argsAmount = message.externalFunc(m_pSQVM->sqvm, message.userdata); - } - else - { - for (auto& v : message.args) - { - // Execute lambda to push arg to stack - v(); - } + // Execute lambda to push arg to stack + v(); } _call(m_pSQVM->sqvm, (SQInteger)argsAmount); @@ -839,10 +759,6 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module)) g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction = module.Offset(0x6CB0).RCast<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)) @@ -921,10 +837,6 @@ 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/primedev/squirrel/squirrel.h b/primedev/squirrel/squirrel.h index 17f4186d..0c1f24d3 100644 --- a/primedev/squirrel/squirrel.h +++ b/primedev/squirrel/squirrel.h @@ -3,7 +3,6 @@ #include "squirrelclasstypes.h" #include "squirrelautobind.h" #include "core/math/vector.h" -#include "plugins/plugin_abi.h" #include "mods/modmanager.h" /* @@ -51,8 +50,6 @@ 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, void* userdata); - ScriptContext ScriptContextFromString(std::string string); namespace NS::log @@ -417,7 +414,6 @@ public: 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; @@ -483,7 +479,7 @@ requires is_iterable<T> inline VoidFunction SQMessageBufferPushArg(T& arg) { FunctionVector localv = {}; localv.push_back([]{g_pSquirrel<context>->newarray(g_pSquirrel<context>->m_pSQVM->sqvm, 0);}); - + for (const auto& item : arg) { localv.push_back(SQMessageBufferPushArg<context>(item)); localv.push_back([]{g_pSquirrel<context>->arrayappend(g_pSquirrel<context>->m_pSQVM->sqvm, -2);}); @@ -497,7 +493,7 @@ requires is_map<T> inline VoidFunction SQMessageBufferPushArg(T& map) { FunctionVector localv = {}; localv.push_back([]{g_pSquirrel<context>->newtable(g_pSquirrel<context>->m_pSQVM->sqvm);}); - + for (const auto& item : map) { localv.push_back(SQMessageBufferPushArg<context>(item.first)); localv.push_back(SQMessageBufferPushArg<context>(item.second)); diff --git a/primedev/squirrel/squirrelclasstypes.h b/primedev/squirrel/squirrelclasstypes.h index cd777551..91c3c468 100644 --- a/primedev/squirrel/squirrelclasstypes.h +++ b/primedev/squirrel/squirrelclasstypes.h @@ -116,18 +116,11 @@ concept is_iterable = requires(std::ranges::range_value_t<T> x) // clang-format on -typedef int (*SquirrelMessage_External_Pop)(HSquirrelVM* sqvm, void* userdata); -typedef void (*sq_schedule_call_externalType)( - ScriptContext context, const char* funcname, SquirrelMessage_External_Pop function, void* userdata); - class SquirrelMessage { public: std::string functionName; FunctionVector args; - bool isExternal = false; - void* userdata = NULL; - SquirrelMessage_External_Pop externalFunc = NULL; }; class SquirrelMessageBuffer @@ -243,6 +236,3 @@ typedef SQRESULT (*sq_pushnewstructinstanceType)(HSquirrelVM* sqvm, int fieldCou typedef SQRESULT (*sq_sealstructslotType)(HSquirrelVM* sqvm, int slotIndex); #pragma endregion - -// These "external" versions of the types are for plugins -typedef int64_t (*RegisterSquirrelFuncType_External)(ScriptContext context, SQFuncRegistration* funcReg, char unknown); |