aboutsummaryrefslogtreecommitdiff
path: root/primedev/plugins
diff options
context:
space:
mode:
authoruniboi <64006268+uniboi@users.noreply.github.com>2024-02-04 02:14:46 +0100
committerGitHub <noreply@github.com>2024-02-04 02:14:46 +0100
commitedf013952ca2d110f190dc8cd16e5529846656e4 (patch)
treeca219c17665810d94f2cb23b27f58fa9c82f3a62 /primedev/plugins
parent6ad955ae0aab8b79910cb4a12777419a78a42a90 (diff)
downloadNorthstarLauncher-1.23.0-rc1.tar.gz
NorthstarLauncher-1.23.0-rc1.zip
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/plugins')
-rw-r--r--primedev/plugins/interfaces/IPluginCallbacks.h36
-rw-r--r--primedev/plugins/interfaces/IPluginId.h31
-rw-r--r--primedev/plugins/interfaces/interface.cpp36
-rw-r--r--primedev/plugins/interfaces/interface.h39
-rw-r--r--primedev/plugins/interfaces/sys/ISys.cpp66
-rw-r--r--primedev/plugins/interfaces/sys/ISys.h21
-rw-r--r--primedev/plugins/plugin_abi.h151
-rw-r--r--primedev/plugins/pluginbackend.cpp50
-rw-r--r--primedev/plugins/pluginbackend.h40
-rw-r--r--primedev/plugins/pluginmanager.cpp184
-rw-r--r--primedev/plugins/pluginmanager.h33
-rw-r--r--primedev/plugins/plugins.cpp407
-rw-r--r--primedev/plugins/plugins.h91
13 files changed, 637 insertions, 548 deletions
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;