#include "plugins.h" #include "pluginmanager.h" #include "squirrel/squirrel.h" #include "util/wininfo.h" #include "core/sourceinterface.h" #include "logging/logging.h" #include "dedicated/dedicated.h" bool isValidSquirrelIdentifier(std::string s) { 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; } Plugin::Plugin(std::string path) : m_location(path) { HMODULE pluginModule = GetModuleHandleA(path.c_str()); if (pluginModule) { // 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; } m_handle = LoadLibraryExA(path.c_str(), 0, LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); NS::log::PLUGINSYS->info("loaded plugin handle {}", static_cast<void*>(m_handle)); if (!m_handle) { NS::log::PLUGINSYS->error("Failed to load plugin '{}' (Error: {})", path, GetLastError()); return; } m_initData = {.pluginHandle = m_handle}; m_pCreateInterface = (CreateInterfaceFn)GetProcAddress(m_handle, "CreateInterface"); if (!m_pCreateInterface) { NS::log::PLUGINSYS->error("Plugin at '{}' does not expose CreateInterface()", path); return; } m_pluginId = (IPluginId*)m_pCreateInterface(PLUGIN_ID_VERSION, 0); if (!m_pluginId) { NS::log::PLUGINSYS->error("Could not load IPluginId interface of plugin at '{}'", path); return; } 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; if (!name) { NS::log::PLUGINSYS->error("Could not load name of plugin at '{}'", path); return; } if (!logName) { NS::log::PLUGINSYS->error("Could not load logName of plugin {}", name); return; } if (!dependencyName) { NS::log::PLUGINSYS->error("Could not load dependencyName of plugin {}", name); return; } m_name = std::string(name); m_logName = std::string(logName); m_dependencyName = std::string(dependencyName); if (!isValidSquirrelIdentifier(m_dependencyName)) { NS::log::PLUGINSYS->error("Dependency name \"{}\" of plugin {} is not valid", dependencyName, name); return; } m_callbacks = (IPluginCallbacks*)m_pCreateInterface("PluginCallbacks001", 0); if (!m_callbacks) { NS::log::PLUGINSYS->error("Could not create callback interface of plugin {}", name); return; } m_logger = std::make_shared<ColoredLogger>(m_logName, NS::Colors::PLUGIN); RegisterLogger(m_logger); if (IsDedicatedServer() && !m_runOnServer) { NS::log::PLUGINSYS->info("Unloading {} because it's not supposed to run on dedicated servers", m_name); return; } if (!IsDedicatedServer() && !m_runOnClient) { NS::log::PLUGINSYS->info("Unloading {} because it's only supposed to run on dedicated servers", m_name); return; } m_valid = true; } bool Plugin::Unload() const { if (!m_handle) return true; if (IsValid()) { bool unloaded = m_callbacks->Unload(); if (!unloaded) return false; } if (!FreeLibrary(m_handle)) { NS::log::PLUGINSYS->error("Failed to unload plugin at '{}'", m_location); return false; } g_pPluginManager->RemovePlugin(m_handle); return true; } void Plugin::Reload() const { bool unloaded = Unload(); if (!unloaded) return; g_pPluginManager->LoadPlugin(fs::path(m_location), true); } void Plugin::Log(spdlog::level::level_enum level, char* msg) const { m_logger->log(level, msg); } bool Plugin::IsValid() const { return m_valid && m_pCreateInterface && m_pluginId && m_callbacks && m_handle; } const std::string& Plugin::GetName() const { return m_name; } const std::string& Plugin::GetLogName() const { return m_logName; } const std::string& Plugin::GetDependencyName() const { return m_dependencyName; } const std::string& Plugin::GetLocation() const { return m_location; } bool Plugin::ShouldRunOnServer() const { return m_runOnServer; } bool Plugin::ShouldRunOnClient() const { return m_runOnClient; } void* Plugin::CreateInterface(const char* name, int* status) const { return m_pCreateInterface(name, status); } void Plugin::Init(bool reloaded) const { m_callbacks->Init(g_NorthstarModule, &m_initData, reloaded); } void Plugin::Finalize() const { m_callbacks->Finalize(); } void Plugin::OnSqvmCreated(CSquirrelVM* sqvm) const { m_callbacks->OnSqvmCreated(sqvm); } void Plugin::OnSqvmDestroying(CSquirrelVM* sqvm) const { NS::log::PLUGINSYS->info("destroying sqvm {}", sqvm->vmContext); m_callbacks->OnSqvmDestroying(sqvm); } void Plugin::OnLibraryLoaded(HMODULE module, const char* name) const { m_callbacks->OnLibraryLoaded(module, name); } void Plugin::RunFrame() const { m_callbacks->RunFrame(); }