aboutsummaryrefslogtreecommitdiff
path: root/primedev/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'primedev/plugins')
-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/plugins.cpp340
-rw-r--r--primedev/plugins/plugins.h59
5 files changed, 640 insertions, 0 deletions
diff --git a/primedev/plugins/plugin_abi.h b/primedev/plugins/plugin_abi.h
new file mode 100644
index 00000000..16b26a1c
--- /dev/null
+++ b/primedev/plugins/plugin_abi.h
@@ -0,0 +1,151 @@
+#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
new file mode 100644
index 00000000..850394ac
--- /dev/null
+++ b/primedev/plugins/pluginbackend.cpp
@@ -0,0 +1,50 @@
+#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
new file mode 100644
index 00000000..45cd42f3
--- /dev/null
+++ b/primedev/plugins/pluginbackend.h
@@ -0,0 +1,40 @@
+#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/plugins.cpp b/primedev/plugins/plugins.cpp
new file mode 100644
index 00000000..72b64566
--- /dev/null
+++ b/primedev/plugins/plugins.cpp
@@ -0,0 +1,340 @@
+#include "plugins.h"
+#include "config/profile.h"
+
+#include "squirrel/squirrel.h"
+#include "plugins.h"
+#include "masterserver/masterserver.h"
+#include "core/convar/convar.h"
+#include "server/serverpresence.h"
+#include <optional>
+#include <regex>
+
+#include "util/version.h"
+#include "pluginbackend.h"
+#include "util/wininfo.h"
+#include "logging/logging.h"
+#include "dedicated/dedicated.h"
+
+PluginManager* g_pPluginManager;
+
+void freeLibrary(HMODULE hLib)
+{
+ if (!FreeLibrary(hLib))
+ {
+ spdlog::error("There was an error while trying to free library");
+ }
+}
+
+EXPORT void PLUGIN_LOG(LogMsg* msg)
+{
+ 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);
+}
+
+EXPORT void* CreateObject(ObjectType type)
+{
+ switch (type)
+ {
+ case ObjectType::CONVAR:
+ return (void*)new ConVar;
+ case ObjectType::CONCOMMANDS:
+ return (void*)new ConCommand;
+ default:
+ return NULL;
+ }
+}
+
+std::optional<Plugin> PluginManager::LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data)
+{
+
+ Plugin plugin {};
+
+ std::string pathstring = path.string();
+ std::wstring wpath = path.wstring();
+
+ LPCWSTR wpptr = wpath.c_str();
+ HMODULE datafile = LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); // Load the DLL as a data file
+ if (datafile == NULL)
+ {
+ NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError()));
+ return std::nullopt;
+ }
+ 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);
+
+ rapidjson_document manifestJSON;
+ manifestJSON.Parse(manifest.c_str());
+
+ if (manifestJSON.HasParseError())
+ {
+ NS::log::PLUGINSYS->error("Manifest for '{}' was invalid", pathstring);
+ return std::nullopt;
+ }
+ if (!manifestJSON.HasMember("name"))
+ {
+ NS::log::PLUGINSYS->error("'{}' is missing a name in its manifest", pathstring);
+ return std::nullopt;
+ }
+ if (!manifestJSON.HasMember("displayname"))
+ {
+ NS::log::PLUGINSYS->error("'{}' is missing a displayname in its manifest", pathstring);
+ return std::nullopt;
+ }
+ if (!manifestJSON.HasMember("description"))
+ {
+ NS::log::PLUGINSYS->error("'{}' is missing a description in its manifest", pathstring);
+ return std::nullopt;
+ }
+ if (!manifestJSON.HasMember("api_version"))
+ {
+ NS::log::PLUGINSYS->error("'{}' is missing a api_version in its manifest", pathstring);
+ return std::nullopt;
+ }
+ if (!manifestJSON.HasMember("version"))
+ {
+ NS::log::PLUGINSYS->error("'{}' is missing a version in its manifest", pathstring);
+ return std::nullopt;
+ }
+ if (!manifestJSON.HasMember("run_on_server"))
+ {
+ NS::log::PLUGINSYS->error("'{}' is missing 'run_on_server' in its manifest", pathstring);
+ return std::nullopt;
+ }
+ if (!manifestJSON.HasMember("run_on_client"))
+ {
+ NS::log::PLUGINSYS->error("'{}' is missing 'run_on_client' in its manifest", pathstring);
+ return std::nullopt;
+ }
+ auto test = manifestJSON["api_version"].GetString();
+ if (strcmp(manifestJSON["api_version"].GetString(), std::to_string(ABI_VERSION).c_str()))
+ {
+ NS::log::PLUGINSYS->error(
+ "'{}' has an incompatible API version number in its manifest. Current ABI version is '{}'", pathstring, ABI_VERSION);
+ return std::nullopt;
+ }
+ // Passed all checks, going to actually load it now
+
+ HMODULE pluginLib =
+ LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); // Load the DLL with lib folders
+ if (pluginLib == NULL)
+ {
+ NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError()));
+ return std::nullopt;
+ }
+ plugin.init = (PLUGIN_INIT_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT");
+ if (plugin.init == NULL)
+ {
+ NS::log::PLUGINSYS->info("Library '{}' has no function 'PLUGIN_INIT'", pathstring);
+ return std::nullopt;
+ }
+ NS::log::PLUGINSYS->info("Succesfully loaded {}", pathstring);
+
+ 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();
+
+ if (!plugin.run_on_server && IsDedicatedServer())
+ return std::nullopt;
+
+ if (manifestJSON.HasMember("dependencyName"))
+ {
+ plugin.dependencyName = manifestJSON["dependencyName"].GetString();
+ }
+ else
+ {
+ plugin.dependencyName = plugin.name;
+ }
+
+ 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())
+ {
+ NS::log::PLUGINSYS->warn("Dependency string \"{}\" in {} is not valid a squirrel constant!", plugin.dependencyName, plugin.name);
+ }
+
+ 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 = 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;
+}
+
+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()
+{
+ 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";
+
+ PluginNorthstarData data {};
+ std::string ns_version {version};
+
+ PluginInitFuncs funcs {};
+ funcs.logger = PLUGIN_LOG;
+ funcs.relayInviteFunc = nullptr;
+ funcs.createObject = CreateObject;
+
+ data.version = ns_version.c_str();
+ data.northstarModule = g_NorthstarModule;
+
+ 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)
+ {
+ if (LoadPlugin(path, &funcs, &data))
+ data.pluginHandle += 1;
+ }
+ return true;
+}
+
+void PluginManager::InformSQVMLoad(ScriptContext context, SquirrelFunctions* s)
+{
+ 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);
+ }
+ }
+}
+
+void PluginManager::InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm)
+{
+ for (auto plugin : m_vLoadedPlugins)
+ {
+ if (plugin.inform_sqvm_created != NULL)
+ {
+ plugin.inform_sqvm_created(context, sqvm);
+ }
+ }
+}
+
+void PluginManager::InformSQVMDestroyed(ScriptContext context)
+{
+ for (auto plugin : m_vLoadedPlugins)
+ {
+ if (plugin.inform_sqvm_destroyed != NULL)
+ {
+ plugin.inform_sqvm_destroyed(context);
+ }
+ }
+}
+
+void PluginManager::InformDLLLoad(const char* dll, void* data, void* dllPtr)
+{
+ for (auto plugin : m_vLoadedPlugins)
+ {
+ if (plugin.inform_dll_load != NULL)
+ {
+ plugin.inform_dll_load(dll, (PluginEngineData*)data, dllPtr);
+ }
+ }
+}
+
+void PluginManager::RunFrame()
+{
+ for (auto plugin : m_vLoadedPlugins)
+ {
+ if (plugin.run_frame != NULL)
+ {
+ plugin.run_frame();
+ }
+ }
+}
diff --git a/primedev/plugins/plugins.h b/primedev/plugins/plugins.h
new file mode 100644
index 00000000..4e841f27
--- /dev/null
+++ b/primedev/plugins/plugins.h
@@ -0,0 +1,59 @@
+#pragma once
+#include "plugin_abi.h"
+
+const int IDR_RCDATA1 = 101;
+
+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;
+
+ std::shared_ptr<ColoredLogger> logger;
+
+ bool run_on_client = false;
+ bool run_on_server = false;
+
+public:
+ PLUGIN_INIT_TYPE init;
+ PLUGIN_INIT_SQVM_TYPE init_sqvm_client;
+ PLUGIN_INIT_SQVM_TYPE init_sqvm_server;
+ PLUGIN_INFORM_SQVM_CREATED_TYPE inform_sqvm_created;
+ PLUGIN_INFORM_SQVM_DESTROYED_TYPE inform_sqvm_destroyed;
+
+ PLUGIN_INFORM_DLL_LOAD_TYPE inform_dll_load;
+
+ PLUGIN_RUNFRAME run_frame;
+};
+
+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;