og-northstar-launcher/NorthstarDLL/plugins/plugins.cpp
Jack 98af0dcbb4
Move plugin communication handler init out of plugin manager init (#581)
Plugin state requires this to be initialised even if `-noplugins` is passed, but if `-noplugins` is passed, it doesn't get initialised.

Moving this to `dllmain` guarantees that it is initialised properly and prevents #580
2023-10-15 21:57:41 +02:00

321 lines
9.2 KiB
C++

#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 = LoadLibraryW(wpptr); // Load the DLL as a data file
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;
}
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.push_presence = (PLUGIN_PUSH_PRESENCE_TYPE)GetProcAddress(pluginLib, "PLUGIN_RECEIVE_PRESENCE");
plugin.inform_dll_load = (PLUGIN_INFORM_DLL_LOAD_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_DLL_LOAD");
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::recursive_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;
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
}
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::PushPresence(PluginGameStatePresence* data)
{
for (auto plugin : m_vLoadedPlugins)
{
if (plugin.push_presence != NULL)
{
plugin.push_presence(data);
}
}
}
void PluginManager::InformDLLLoad(PluginLoadDLL dll, void* data)
{
for (auto plugin : m_vLoadedPlugins)
{
if (plugin.inform_dll_load != NULL)
{
plugin.inform_dll_load(dll, data);
}
}
}