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/plugins/plugins.cpp | |
parent | 6ad955ae0aab8b79910cb4a12777419a78a42a90 (diff) | |
download | NorthstarLauncher-1.23.0-rc2.tar.gz NorthstarLauncher-1.23.0-rc2.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/plugins/plugins.cpp')
-rw-r--r-- | primedev/plugins/plugins.cpp | 407 |
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(); } |