mirror of
https://github.com/R2Northstar/NorthstarLauncher
synced 2026-08-23 20:26:04 -04:00
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
321 lines
9.2 KiB
C++
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);
|
|
}
|
|
}
|
|
}
|