aboutsummaryrefslogtreecommitdiff
path: root/primedev/plugins/plugins.cpp
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/plugins.cpp
parent6ad955ae0aab8b79910cb4a12777419a78a42a90 (diff)
downloadNorthstarLauncher-1.23.0-rc2.tar.gz
NorthstarLauncher-1.23.0-rc2.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/plugins.cpp')
-rw-r--r--primedev/plugins/plugins.cpp407
1 files changed, 150 insertions, 257 deletions
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();
}