Plugin interfaces (plugins v4) (#615)

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.
This commit is contained in:
uniboi 2024-02-04 02:14:46 +01:00 committed by GitHub
parent 6ad955ae0a
commit edf013952c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 729 additions and 744 deletions

View File

@ -84,11 +84,16 @@ add_library(
"mods/modmanager.h"
"mods/modsavefiles.cpp"
"mods/modsavefiles.h"
"plugins/plugin_abi.h"
"plugins/pluginbackend.cpp"
"plugins/pluginbackend.h"
"plugins/interfaces/interface.h"
"plugins/interfaces/interface.cpp"
"plugins/interfaces/sys/ISys.h"
"plugins/interfaces/sys/ISys.cpp"
"plugins/interfaces/IPluginId.h"
"plugins/interfaces/IPluginCallbacks.h"
"plugins/plugins.cpp"
"plugins/plugins.h"
"plugins/pluginmanager.h"
"plugins/pluginmanager.cpp"
"scripts/client/clientchathooks.cpp"
"scripts/client/cursorposition.cpp"
"scripts/client/scriptbrowserhooks.cpp"

View File

@ -2,9 +2,6 @@
#include "shared/misccommands.h"
#include "engine/r2engine.h"
#include "plugins/pluginbackend.h"
#include "plugins/plugin_abi.h"
#include <iostream>
//-----------------------------------------------------------------------------
@ -152,7 +149,4 @@ ON_DLL_LOAD("engine.dll", ConCommand, (CModule module))
{
ConCommandConstructor = module.Offset(0x415F60).RCast<ConCommandConstructorType>();
AddMiscConCommands();
g_pPluginCommunicationhandler->m_sEngineData.ConCommandConstructor =
reinterpret_cast<PluginConCommandConstructorType>(ConCommandConstructor);
}

View File

@ -3,9 +3,6 @@
#include "convar.h"
#include "core/sourceinterface.h"
#include "plugins/pluginbackend.h"
#include "plugins/plugin_abi.h"
#include <float.h>
typedef void (*ConVarRegisterType)(
@ -40,12 +37,6 @@ ON_DLL_LOAD("engine.dll", ConVar, (CModule module))
g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007");
g_pCVar = *g_pCVarInterface;
g_pPluginCommunicationhandler->m_sEngineData.conVarMalloc = reinterpret_cast<PluginConVarMallocType>(conVarMalloc);
g_pPluginCommunicationhandler->m_sEngineData.conVarRegister = reinterpret_cast<PluginConVarRegisterType>(conVarRegister);
g_pPluginCommunicationhandler->m_sEngineData.ConVar_Vtable = reinterpret_cast<void*>(g_pConVar_Vtable);
g_pPluginCommunicationhandler->m_sEngineData.IConVar_Vtable = reinterpret_cast<void*>(g_pIConVar_Vtable);
g_pPluginCommunicationhandler->m_sEngineData.g_pCVar = reinterpret_cast<void*>(g_pCVar);
}
//-----------------------------------------------------------------------------

View File

@ -1,5 +1,5 @@
#include "dedicated/dedicated.h"
#include "plugins/pluginbackend.h"
#include "plugins/pluginmanager.h"
#include <iostream>
#include <wchar.h>
@ -417,7 +417,7 @@ HMODULE, WINAPI, (LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags))
if (moduleAddress)
{
CallLoadLibraryACallbacks(lpLibFileName, moduleAddress);
InformPluginsDLLLoad(fs::path(lpLibFileName), moduleAddress);
g_pPluginManager->InformDllLoad(moduleAddress, fs::path(lpLibFileName));
}
return moduleAddress;
@ -459,7 +459,7 @@ HMODULE, WINAPI, (LPCWSTR lpLibFileName))
if (moduleAddress)
{
CallLoadLibraryWCallbacks(lpLibFileName, moduleAddress);
InformPluginsDLLLoad(fs::path(lpLibFileName), moduleAddress);
g_pPluginManager->InformDllLoad(moduleAddress, fs::path(lpLibFileName));
}
return moduleAddress;

View File

@ -1,6 +1,13 @@
#pragma once
#include <string>
// interface return status
enum class InterfaceStatus : int
{
IFACE_OK = 0,
IFACE_FAILED,
};
// literally just copied from ttf2sdk definition
typedef void* (*CreateInterfaceFn)(const char* pName, int* pReturnCode);

View File

@ -3,10 +3,10 @@
#include "core/memalloc.h"
#include "core/vanilla.h"
#include "config/profile.h"
#include "plugins/plugin_abi.h"
#include "plugins/plugins.h"
#include "plugins/pluginbackend.h"
#include "plugins/pluginmanager.h"
#include "util/version.h"
#include "util/wininfo.h"
#include "squirrel/squirrel.h"
#include "server/serverpresence.h"
@ -23,6 +23,8 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_NorthstarModule = hModule;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
@ -65,7 +67,6 @@ bool InitialiseNorthstar()
g_pServerPresence = new ServerPresenceManager();
g_pPluginManager = new PluginManager();
g_pPluginCommunicationhandler = new PluginCommunicationHandler();
g_pPluginManager->LoadPlugins();
InitialiseSquirrelManagers();

View File

@ -7,8 +7,7 @@
#include "engine/r2engine.h"
#include "shared/exploit_fixes/ns_limits.h"
#include "squirrel/squirrel.h"
#include "plugins/plugins.h"
#include "plugins/pluginbackend.h"
#include "plugins/pluginmanager.h"
AUTOHOOK_INIT()

View File

@ -4,6 +4,7 @@
#include "util/version.h"
#include "mods/modmanager.h"
#include "plugins/plugins.h"
#include "plugins/pluginmanager.h"
#include <minidumpapiset.h>
@ -524,9 +525,9 @@ void CCrashHandler::FormatLoadedPlugins()
if (g_pPluginManager)
{
spdlog::error("Loaded Plugins:");
for (const Plugin& plugin : g_pPluginManager->m_vLoadedPlugins)
for (const Plugin& plugin : g_pPluginManager->GetLoadedPlugins())
{
spdlog::error("\t{}", plugin.name);
spdlog::error("\t{}", plugin.GetName());
}
}
}

View File

@ -0,0 +1,36 @@
#ifndef IPLUGIN_CALLBACKS_H
#define IPLUGIN_CALLBACKS_H
#include <windows.h>
#include <stdint.h>
#include "squirrel/squirrel.h"
// can't use bitwise ops on enum classes but I don't want these in the global namespace (user defined operators suck)
namespace PluginContext
{
enum : uint64_t
{
DEDICATED = 0x1,
CLIENT = 0x2,
};
}
struct PluginNorthstarData
{
HMODULE pluginHandle;
};
class IPluginCallbacks
{
public:
virtual void
Init(HMODULE northstarModule, const PluginNorthstarData* initData, bool reloaded) = 0; // runs after the plugin is loaded and validated
virtual void Finalize() = 0; // runs after all plugins have been loaded
virtual bool Unload() = 0; // runs just before the library is freed
virtual void OnSqvmCreated(CSquirrelVM* sqvm) = 0;
virtual void OnSqvmDestroying(CSquirrelVM* sqvm) = 0;
virtual void OnLibraryLoaded(HMODULE module, const char* name) = 0;
virtual void RunFrame() = 0;
};
#endif

View File

@ -0,0 +1,31 @@
#ifndef IPLUGIN_ID_H
#define IPLUGIN_ID_H
#include <stdint.h>
#include "squirrel/squirrelclasstypes.h"
#define PLUGIN_ID_VERSION "PluginId001"
// an identifier for the type of string data requested from the plugin
enum class PluginString : int
{
NAME = 0,
LOG_NAME = 1,
DEPENDENCY_NAME = 2,
};
// an identifier for the type of bitflag requested from the plugin
enum class PluginField : int
{
CONTEXT = 0,
};
// an interface that is required from every plugin to query data about it
class IPluginId
{
public:
virtual const char* GetString(PluginString prop) = 0;
virtual int64_t GetField(PluginField prop) = 0;
};
#endif

View File

@ -0,0 +1,36 @@
#include <string.h>
#include "interface.h"
InterfaceReg* s_pInterfaceRegs;
InterfaceReg::InterfaceReg(InstantiateInterfaceFn fn, const char* pName) : m_pName(pName)
{
m_CreateFn = fn;
m_pNext = s_pInterfaceRegs;
s_pInterfaceRegs = this;
}
void* CreateInterface(const char* pName, InterfaceStatus* pReturnCode)
{
for (InterfaceReg* pCur = s_pInterfaceRegs; pCur; pCur = pCur->m_pNext)
{
if (strcmp(pCur->m_pName, pName) == 0)
{
if (pReturnCode)
{
*pReturnCode = InterfaceStatus::IFACE_OK;
}
NS::log::PLUGINSYS->info("creating interface {}", pName);
return pCur->m_CreateFn();
}
}
if (pReturnCode)
{
*pReturnCode = InterfaceStatus::IFACE_FAILED;
}
NS::log::PLUGINSYS->error("could not find interface {}", pName);
return NULL;
}

View File

@ -0,0 +1,39 @@
#ifndef INTERFACE_H
#define INTERFACE_H
typedef void* (*InstantiateInterfaceFn)();
// Used internally to register classes.
class InterfaceReg
{
public:
InterfaceReg(InstantiateInterfaceFn fn, const char* pName);
InstantiateInterfaceFn m_CreateFn;
const char* m_pName;
InterfaceReg* m_pNext;
};
// Use this to expose an interface that can have multiple instances.
#define EXPOSE_INTERFACE(className, interfaceName, versionName) \
static void* __Create##className##_interface() \
{ \
return static_cast<interfaceName*>(new className); \
} \
static InterfaceReg __g_Create##className##_reg(__Create##className##_interface, versionName);
#define EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, globalVarName) \
static void* __Create##className##interfaceName##_interface() \
{ \
return static_cast<interfaceName*>(&globalVarName); \
} \
static InterfaceReg __g_Create##className##interfaceName##_reg(__Create##className##interfaceName##_interface, versionName);
// Use this to expose a singleton interface. This creates the global variable for you automatically.
#define EXPOSE_SINGLE_INTERFACE(className, interfaceName, versionName) \
static className __g_##className##_singleton; \
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(className, interfaceName, versionName, __g_##className##_singleton)
EXPORT void* CreateInterface(const char* pName, InterfaceStatus* pReturnCode);
#endif

View File

@ -0,0 +1,66 @@
#include "plugins/interfaces/interface.h"
#include "ISys.h"
#include "plugins/plugins.h"
#include "plugins/pluginmanager.h"
class CSys : public ISys
{
public:
void Log(HMODULE handle, LogLevel level, char* msg)
{
spdlog::level::level_enum spdLevel;
switch (level)
{
case LogLevel::WARN:
spdLevel = spdlog::level::level_enum::warn;
break;
case LogLevel::ERR:
spdLevel = spdlog::level::level_enum::err;
break;
default:
NS::log::PLUGINSYS->warn("Attempted to log with invalid level {}. Defaulting to info", (int)level);
case LogLevel::INFO:
spdLevel = spdlog::level::level_enum::info;
break;
}
std::optional<Plugin> plugin = g_pPluginManager->GetPlugin(handle);
if (plugin)
{
plugin->Log(spdLevel, msg);
}
else
{
NS::log::PLUGINSYS->warn("Attempted to log message '{}' with invalid plugin handle {}", msg, static_cast<void*>(handle));
}
}
void Unload(HMODULE handle)
{
std::optional<Plugin> plugin = g_pPluginManager->GetPlugin(handle);
if (plugin)
{
plugin->Unload();
}
else
{
NS::log::PLUGINSYS->warn("Attempted to unload plugin with invalid handle {}", static_cast<void*>(handle));
}
}
void Reload(HMODULE handle)
{
std::optional<Plugin> plugin = g_pPluginManager->GetPlugin(handle);
if (plugin)
{
plugin->Reload();
}
else
{
NS::log::PLUGINSYS->warn("Attempted to reload plugin with invalid handle {}", static_cast<void*>(handle));
}
}
};
EXPOSE_SINGLE_INTERFACE(CSys, ISys, SYS_VERSION);

View File

@ -0,0 +1,21 @@
#ifndef ILOGGING_H
#define ILOGGING_H
#define SYS_VERSION "NSSys001"
enum class LogLevel : int
{
INFO = 0,
WARN,
ERR,
};
class ISys
{
public:
virtual void Log(HMODULE handle, LogLevel level, char* msg) = 0;
virtual void Unload(HMODULE handle) = 0;
virtual void Reload(HMODULE handle) = 0;
};
#endif

View File

@ -1,151 +0,0 @@
#pragma once
#include "squirrel/squirrelclasstypes.h"
#define ABI_VERSION 3
enum PluginLoadDLL
{
ENGINE = 0,
CLIENT,
SERVER
};
enum ObjectType
{
CONCOMMANDS = 0,
CONVAR = 1,
};
struct SquirrelFunctions
{
RegisterSquirrelFuncType RegisterSquirrelFunc;
sq_defconstType __sq_defconst;
sq_compilebufferType __sq_compilebuffer;
sq_callType __sq_call;
sq_raiseerrorType __sq_raiseerror;
sq_compilefileType __sq_compilefile;
sq_newarrayType __sq_newarray;
sq_arrayappendType __sq_arrayappend;
sq_newtableType __sq_newtable;
sq_newslotType __sq_newslot;
sq_pushroottableType __sq_pushroottable;
sq_pushstringType __sq_pushstring;
sq_pushintegerType __sq_pushinteger;
sq_pushfloatType __sq_pushfloat;
sq_pushboolType __sq_pushbool;
sq_pushassetType __sq_pushasset;
sq_pushvectorType __sq_pushvector;
sq_pushobjectType __sq_pushobject;
sq_getstringType __sq_getstring;
sq_getintegerType __sq_getinteger;
sq_getfloatType __sq_getfloat;
sq_getboolType __sq_getbool;
sq_getType __sq_get;
sq_getassetType __sq_getasset;
sq_getuserdataType __sq_getuserdata;
sq_getvectorType __sq_getvector;
sq_getthisentityType __sq_getthisentity;
sq_getobjectType __sq_getobject;
sq_stackinfosType __sq_stackinfos;
sq_createuserdataType __sq_createuserdata;
sq_setuserdatatypeidType __sq_setuserdatatypeid;
sq_getfunctionType __sq_getfunction;
sq_schedule_call_externalType __sq_schedule_call_external;
sq_getentityfrominstanceType __sq_getentityfrominstance;
sq_GetEntityConstantType __sq_GetEntityConstant_CBaseEntity;
sq_pushnewstructinstanceType __sq_pushnewstructinstance;
sq_sealstructslotType __sq_sealstructslot;
};
struct MessageSource
{
const char* file;
const char* func;
int line;
};
// This is a modified version of spdlog::details::log_msg
// This is so that we can make it cross DLL boundaries
struct LogMsg
{
int level;
uint64_t timestamp;
const char* msg;
MessageSource source;
int pluginHandle;
};
extern "C"
{
typedef void (*loggerfunc_t)(LogMsg* msg);
typedef void (*PLUGIN_RELAY_INVITE_TYPE)(const char* invite);
typedef void* (*CreateObjectFunc)(ObjectType type);
typedef void (*PluginFnCommandCallback_t)(void* command);
typedef void (*PluginConCommandConstructorType)(
void* newCommand, const char* name, PluginFnCommandCallback_t callback, const char* helpString, int flags, void* parent);
typedef void (*PluginConVarRegisterType)(
void* pConVar,
const char* pszName,
const char* pszDefaultValue,
int nFlags,
const char* pszHelpString,
bool bMin,
float fMin,
bool bMax,
float fMax,
void* pCallback);
typedef void (*PluginConVarMallocType)(void* pConVarMaloc, int a2, int a3);
}
struct PluginNorthstarData
{
const char* version;
HMODULE northstarModule;
int pluginHandle;
};
struct PluginInitFuncs
{
loggerfunc_t logger;
PLUGIN_RELAY_INVITE_TYPE relayInviteFunc;
CreateObjectFunc createObject;
};
struct PluginEngineData
{
PluginConCommandConstructorType ConCommandConstructor;
PluginConVarMallocType conVarMalloc;
PluginConVarRegisterType conVarRegister;
void* ConVar_Vtable;
void* IConVar_Vtable;
void* g_pCVar;
};
/// <summary> Async communication within the plugin system
/// Due to the asynchronous nature of plugins, combined with the limitations of multi-compiler support
/// and the custom memory allocator used by r2, is it difficult to safely get data across DLL boundaries
/// from Northstar to plugin unless Northstar can own that memory.
/// This means that plugins should manage their own memory and can only receive data from northstar using one of the functions below.
/// These should be exports of the plugin DLL. If they are not exported, they will not be called.
/// Note that it is not required to have these exports if you do not use them.
/// </summary>
// Northstar -> Plugin
typedef void (*PLUGIN_INIT_TYPE)(PluginInitFuncs* funcs, PluginNorthstarData* data);
typedef void (*PLUGIN_INIT_SQVM_TYPE)(SquirrelFunctions* funcs);
typedef void (*PLUGIN_INFORM_SQVM_CREATED_TYPE)(ScriptContext context, CSquirrelVM* sqvm);
typedef void (*PLUGIN_INFORM_SQVM_DESTROYED_TYPE)(ScriptContext context);
typedef void (*PLUGIN_INFORM_DLL_LOAD_TYPE)(const char* dll, PluginEngineData* data, void* dllPtr);
typedef void (*PLUGIN_RUNFRAME)();

View File

@ -1,50 +0,0 @@
#include "pluginbackend.h"
#include "plugin_abi.h"
#include "server/serverpresence.h"
#include "masterserver/masterserver.h"
#include "squirrel/squirrel.h"
#include "plugins.h"
#include "core/convar/concommand.h"
#include <filesystem>
#define EXPORT extern "C" __declspec(dllexport)
AUTOHOOK_INIT()
PluginCommunicationHandler* g_pPluginCommunicationhandler;
static PluginDataRequest storedRequest {PluginDataRequestType::END, (PluginRespondDataCallable) nullptr};
void PluginCommunicationHandler::RunFrame()
{
std::lock_guard<std::mutex> lock(requestMutex);
if (!requestQueue.empty())
{
storedRequest = requestQueue.front();
switch (storedRequest.type)
{
default:
spdlog::error("{} was called with invalid request type '{}'", __FUNCTION__, static_cast<int>(storedRequest.type));
}
requestQueue.pop();
}
}
void PluginCommunicationHandler::PushRequest(PluginDataRequestType type, PluginRespondDataCallable func)
{
std::lock_guard<std::mutex> lock(requestMutex);
requestQueue.push(PluginDataRequest {type, func});
}
void InformPluginsDLLLoad(fs::path dllPath, void* address)
{
std::string dllName = dllPath.filename().string();
void* data = NULL;
if (strncmp(dllName.c_str(), "engine.dll", 10) == 0)
data = &g_pPluginCommunicationhandler->m_sEngineData;
g_pPluginManager->InformDLLLoad(dllName.c_str(), data, address);
}

View File

@ -1,40 +0,0 @@
#pragma once
#include "plugin_abi.h"
#include <queue>
#include <mutex>
enum PluginDataRequestType
{
END = 0,
};
union PluginRespondDataCallable
{
// Empty for now
void* UNUSED;
};
class PluginDataRequest
{
public:
PluginDataRequestType type;
PluginRespondDataCallable func;
PluginDataRequest(PluginDataRequestType type, PluginRespondDataCallable func) : type(type), func(func) {}
};
class PluginCommunicationHandler
{
public:
void RunFrame();
void PushRequest(PluginDataRequestType type, PluginRespondDataCallable func);
public:
std::queue<PluginDataRequest> requestQueue = {};
std::mutex requestMutex;
PluginEngineData m_sEngineData {};
};
void InformPluginsDLLLoad(fs::path dllPath, void* address);
extern PluginCommunicationHandler* g_pPluginCommunicationhandler;

View File

@ -0,0 +1,184 @@
#include "pluginmanager.h"
#include <regex>
#include "plugins.h"
#include "config/profile.h"
#include "core/convar/concommand.h"
PluginManager* g_pPluginManager;
const std::vector<Plugin>& PluginManager::GetLoadedPlugins() const
{
return this->plugins;
}
const std::optional<Plugin> PluginManager::GetPlugin(HMODULE handle) const
{
for (const Plugin& plugin : GetLoadedPlugins())
if (plugin.m_handle == handle)
return plugin;
return std::nullopt;
}
void PluginManager::LoadPlugin(fs::path path, bool reloaded)
{
Plugin plugin = Plugin(path.string());
if (!plugin.IsValid())
{
NS::log::PLUGINSYS->warn("Unloading invalid plugin '{}'", path.string());
plugin.Unload();
return;
}
plugins.push_back(plugin);
plugin.Init(reloaded);
}
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::directory_iterator(pluginPath))
{
if (fs::is_regular_file(entry) && entry.path().extension() == ".dll")
paths.emplace_back(entry.path());
}
}
bool PluginManager::LoadPlugins(bool reloaded)
{
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";
fs::path libPath = fs::absolute(pluginPath + "\\lib");
if (fs::exists(libPath) && fs::is_directory(libPath))
AddDllDirectory(libPath.wstring().c_str());
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
}
fs::path libDir = fs::absolute(pluginsDir / "lib");
if (fs::exists(libDir) && fs::is_directory(libDir))
AddDllDirectory(libDir.wstring().c_str());
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)
{
LoadPlugin(path, reloaded);
}
InformAllPluginsInitialized();
return true;
}
void PluginManager::ReloadPlugins()
{
for (const Plugin& plugin : this->GetLoadedPlugins())
{
plugin.Unload();
}
this->plugins.clear();
this->LoadPlugins(true);
}
void PluginManager::RemovePlugin(HMODULE handle)
{
for (size_t i = 0; i < plugins.size(); i++)
{
Plugin* plugin = &plugins[i];
if (plugin->m_handle == handle)
{
plugins.erase(plugins.begin() + i);
return;
}
}
}
void PluginManager::InformAllPluginsInitialized() const
{
for (const Plugin& plugin : GetLoadedPlugins())
{
plugin.Finalize();
}
}
void PluginManager::InformSqvmCreated(CSquirrelVM* sqvm) const
{
for (const Plugin& plugin : GetLoadedPlugins())
{
plugin.OnSqvmCreated(sqvm);
}
}
void PluginManager::InformSqvmDestroying(CSquirrelVM* sqvm) const
{
for (const Plugin& plugin : GetLoadedPlugins())
{
plugin.OnSqvmDestroying(sqvm);
}
}
void PluginManager::InformDllLoad(HMODULE module, fs::path path) const
{
std::string fn = path.filename().string(); // without this the string gets freed immediately lmao
const char* filename = fn.c_str();
for (const Plugin& plugin : GetLoadedPlugins())
{
plugin.OnLibraryLoaded(module, filename);
}
}
void PluginManager::RunFrame() const
{
for (const Plugin& plugin : GetLoadedPlugins())
{
plugin.RunFrame();
}
}
void ConCommand_reload_plugins(const CCommand& args)
{
g_pPluginManager->ReloadPlugins();
}
ON_DLL_LOAD_RELIESON("engine.dll", PluginManager, ConCommand, (CModule module))
{
RegisterConCommand("reload_plugins", ConCommand_reload_plugins, "reloads plugins", FCVAR_NONE);
}

View File

@ -0,0 +1,33 @@
#ifndef PLUGIN_MANAGER_H
#define PLUGIN_MANAGER_H
#include <windows.h>
class Plugin;
class PluginManager
{
public:
const std::vector<Plugin>& GetLoadedPlugins() const;
const std::optional<Plugin> GetPlugin(HMODULE handle) const;
bool LoadPlugins(bool reloaded = false);
void LoadPlugin(fs::path path, bool reloaded = false);
void ReloadPlugins();
void RemovePlugin(HMODULE handle);
// callback triggers
void InformSqvmCreated(CSquirrelVM* sqvm) const;
void InformSqvmDestroying(CSquirrelVM* sqvm) const;
void InformDllLoad(HMODULE module, fs::path path) const;
void RunFrame() const;
private:
void InformAllPluginsInitialized() const;
std::vector<Plugin> plugins;
std::string pluginPath;
};
extern PluginManager* g_pPluginManager;
#endif

View File

@ -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))
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)
{
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 =
LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_SEARCH_USER_DIRS | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); // Load the DLL with lib folders
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;
}
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())
{
NS::log::PLUGINSYS->warn("Dependency string \"{}\" in {} is not valid a squirrel constant!", 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.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;
}
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::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");
// 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;
}
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;
fs::path libPath = fs::absolute(pluginPath + "\\lib");
if (fs::exists(libPath) && fs::is_directory(libPath))
AddDllDirectory(libPath.wstring().c_str());
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
}
fs::path libDir = fs::absolute(pluginsDir / "lib");
if (fs::exists(libDir) && fs::is_directory(libDir))
AddDllDirectory(libDir.wstring().c_str());
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)
Plugin::Plugin(std::string path) : m_location(path)
{
for (auto plugin : m_vLoadedPlugins)
HMODULE pluginModule = GetModuleHandleA(path.c_str());
if (pluginModule)
{
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);
}
// 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};
CreateInterfaceFn CreatePluginInterface = (CreateInterfaceFn)GetProcAddress(m_handle, "CreateInterface");
if (!CreatePluginInterface)
{
NS::log::PLUGINSYS->error("Plugin at '{}' does not expose CreateInterface()", path);
return;
}
m_pluginId = (IPluginId*)CreatePluginInterface(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;
m_name = std::string(name);
m_logName = std::string(logName);
m_dependencyName = std::string(dependencyName);
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;
}
if (!isValidSquirrelIdentifier(m_dependencyName))
{
NS::log::PLUGINSYS->error("Dependency name \"{}\" of plugin {} is not valid", dependencyName, name);
return;
}
m_callbacks = (IPluginCallbacks*)CreatePluginInterface("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;
}
void PluginManager::InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm)
bool Plugin::Unload() const
{
for (auto plugin : m_vLoadedPlugins)
if (!m_handle)
return true;
if (IsValid())
{
if (plugin.inform_sqvm_created != NULL)
{
plugin.inform_sqvm_created(context, sqvm);
}
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 PluginManager::InformSQVMDestroyed(ScriptContext context)
void Plugin::Reload() const
{
for (auto plugin : m_vLoadedPlugins)
{
if (plugin.inform_sqvm_destroyed != NULL)
{
plugin.inform_sqvm_destroyed(context);
}
}
bool unloaded = Unload();
if (!unloaded)
return;
g_pPluginManager->LoadPlugin(fs::path(m_location), true);
}
void PluginManager::InformDLLLoad(const char* dll, void* data, void* dllPtr)
void Plugin::Log(spdlog::level::level_enum level, char* msg) const
{
for (auto plugin : m_vLoadedPlugins)
{
if (plugin.inform_dll_load != NULL)
{
plugin.inform_dll_load(dll, (PluginEngineData*)data, dllPtr);
}
}
m_logger->log(level, msg);
}
void PluginManager::RunFrame()
bool Plugin::IsValid() const
{
for (auto plugin : m_vLoadedPlugins)
{
if (plugin.run_frame != NULL)
{
plugin.run_frame();
}
}
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();
}

View File

@ -1,59 +1,50 @@
#pragma once
#include "plugin_abi.h"
const int IDR_RCDATA1 = 101;
#include "core/sourceinterface.h"
#include "plugins/interfaces/interface.h"
#include "plugins/interfaces/IPluginId.h"
#include "plugins/interfaces/IPluginCallbacks.h"
class Plugin
{
public:
std::string name;
std::string displayName;
std::string dependencyName;
std::string description;
std::string api_version;
std::string version;
// For now this is just implemented as the index into the plugins array
// Maybe a bit shit but it works
int handle;
std::shared_ptr<ColoredLogger> logger;
bool run_on_client = false;
bool run_on_server = false;
public:
PLUGIN_INIT_TYPE init;
PLUGIN_INIT_SQVM_TYPE init_sqvm_client;
PLUGIN_INIT_SQVM_TYPE init_sqvm_server;
PLUGIN_INFORM_SQVM_CREATED_TYPE inform_sqvm_created;
PLUGIN_INFORM_SQVM_DESTROYED_TYPE inform_sqvm_destroyed;
PLUGIN_INFORM_DLL_LOAD_TYPE inform_dll_load;
PLUGIN_RUNFRAME run_frame;
};
class PluginManager
{
public:
std::vector<Plugin> m_vLoadedPlugins;
public:
bool LoadPlugins();
std::optional<Plugin> LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data);
void InformSQVMLoad(ScriptContext context, SquirrelFunctions* s);
void InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm);
void InformSQVMDestroyed(ScriptContext context);
void InformDLLLoad(const char* dll, void* data, void* dllPtr);
void RunFrame();
private:
std::string pluginPath;
};
CreateInterfaceFn m_pCreateInterface;
IPluginId* m_pluginId = 0;
IPluginCallbacks* m_callbacks = 0;
extern PluginManager* g_pPluginManager;
std::shared_ptr<ColoredLogger> m_logger;
bool m_valid = false;
std::string m_name;
std::string m_logName;
std::string m_dependencyName;
std::string m_location; // path of the dll
bool m_runOnServer;
bool m_runOnClient;
public:
HMODULE m_handle;
PluginNorthstarData m_initData;
Plugin(std::string path);
bool Unload() const;
void Reload() const;
// sys
void Log(spdlog::level::level_enum level, char* msg) const;
// callbacks
bool IsValid() const;
const std::string& GetName() const;
const std::string& GetLogName() const;
const std::string& GetDependencyName() const;
const std::string& GetLocation() const;
bool ShouldRunOnServer() const;
bool ShouldRunOnClient() const;
void* CreateInterface(const char* pName, int* pStatus) const;
void Init(bool reloaded) const;
void Finalize() const;
void OnSqvmCreated(CSquirrelVM* sqvm) const;
void OnSqvmDestroying(CSquirrelVM* sqvm) const;
void OnLibraryLoaded(HMODULE module, const char* name) const;
void RunFrame() const;
};

View File

@ -6,8 +6,8 @@
#include "dedicated/dedicated.h"
#include "engine/r2engine.h"
#include "core/tier0.h"
#include "plugins/plugin_abi.h"
#include "plugins/plugins.h"
#include "plugins/pluginmanager.h"
#include "ns_version.h"
#include "core/vanilla.h"
@ -157,79 +157,6 @@ const char* SQTypeNameFromID(int type)
return "";
}
template <ScriptContext context> void SquirrelManager<context>::GenerateSquirrelFunctionsStruct(SquirrelFunctions* s)
{
s->RegisterSquirrelFunc = RegisterSquirrelFunc;
s->__sq_defconst = __sq_defconst;
s->__sq_compilebuffer = __sq_compilebuffer;
s->__sq_call = __sq_call;
s->__sq_raiseerror = __sq_raiseerror;
s->__sq_compilefile = __sq_compilefile;
s->__sq_newarray = __sq_newarray;
s->__sq_arrayappend = __sq_arrayappend;
s->__sq_newtable = __sq_newtable;
s->__sq_newslot = __sq_newslot;
s->__sq_pushroottable = __sq_pushroottable;
s->__sq_pushstring = __sq_pushstring;
s->__sq_pushinteger = __sq_pushinteger;
s->__sq_pushfloat = __sq_pushfloat;
s->__sq_pushbool = __sq_pushbool;
s->__sq_pushasset = __sq_pushasset;
s->__sq_pushvector = __sq_pushvector;
s->__sq_pushobject = __sq_pushobject;
s->__sq_getstring = __sq_getstring;
s->__sq_getinteger = __sq_getinteger;
s->__sq_getfloat = __sq_getfloat;
s->__sq_getbool = __sq_getbool;
s->__sq_get = __sq_get;
s->__sq_getasset = __sq_getasset;
s->__sq_getuserdata = __sq_getuserdata;
s->__sq_getvector = __sq_getvector;
s->__sq_getthisentity = __sq_getthisentity;
s->__sq_getobject = __sq_getobject;
s->__sq_stackinfos = __sq_stackinfos;
s->__sq_createuserdata = __sq_createuserdata;
s->__sq_setuserdatatypeid = __sq_setuserdatatypeid;
s->__sq_getfunction = __sq_getfunction;
s->__sq_schedule_call_external = AsyncCall_External;
s->__sq_getentityfrominstance = __sq_getentityfrominstance;
s->__sq_GetEntityConstant_CBaseEntity = __sq_GetEntityConstant_CBaseEntity;
s->__sq_pushnewstructinstance = __sq_pushnewstructinstance;
s->__sq_sealstructslot = __sq_sealstructslot;
}
// Allows for generating squirrelmessages from plugins.
void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function, void* userdata)
{
SquirrelMessage message {};
message.functionName = func_name;
message.isExternal = true;
message.externalFunc = function;
message.userdata = userdata;
switch (context)
{
case ScriptContext::CLIENT:
g_pSquirrel<ScriptContext::CLIENT>->messageBuffer->push(message);
break;
case ScriptContext::SERVER:
g_pSquirrel<ScriptContext::SERVER>->messageBuffer->push(message);
break;
case ScriptContext::UI:
g_pSquirrel<ScriptContext::UI>->messageBuffer->push(message);
break;
}
}
// needed to define implementations for squirrelmanager outside of squirrel.h without compiler errors
template class SquirrelManager<ScriptContext::SERVER>;
template class SquirrelManager<ScriptContext::CLIENT>;
@ -264,11 +191,11 @@ template <ScriptContext context> void SquirrelManager<context>::VMCreated(CSquir
defconst(m_pSQVM, pair.first.c_str(), bWasFound);
}
auto loadedPlugins = &g_pPluginManager->m_vLoadedPlugins;
std::vector<Plugin> loadedPlugins = g_pPluginManager->GetLoadedPlugins();
for (const auto& pluginName : g_pModManager->m_PluginDependencyConstants)
{
auto f = [&](Plugin plugin) -> bool { return plugin.dependencyName == pluginName; };
defconst(m_pSQVM, pluginName.c_str(), std::find_if(loadedPlugins->begin(), loadedPlugins->end(), f) != loadedPlugins->end());
auto f = [&](Plugin plugin) -> bool { return plugin.GetDependencyName() == pluginName; };
defconst(m_pSQVM, pluginName.c_str(), std::find_if(loadedPlugins.begin(), loadedPlugins.end(), f) != loadedPlugins.end());
}
defconst(m_pSQVM, "MAX_FOLDER_SIZE", GetMaxSaveFolderSize() / 1024);
@ -284,7 +211,7 @@ template <ScriptContext context> void SquirrelManager<context>::VMCreated(CSquir
defconst(m_pSQVM, "VANILLA", g_pVanillaCompatibility->GetVanillaCompatibility());
g_pSquirrel<context>->messageBuffer = new SquirrelMessageBuffer();
g_pPluginManager->InformSQVMCreated(context, newSqvm);
g_pPluginManager->InformSqvmCreated(newSqvm);
}
template <ScriptContext context> void SquirrelManager<context>::VMDestroyed()
@ -312,7 +239,7 @@ template <ScriptContext context> void SquirrelManager<context>::VMDestroyed()
}
}
g_pPluginManager->InformSQVMDestroyed(context);
g_pPluginManager->InformSqvmDestroying(m_pSQVM);
// Discard the previous vm and delete the message buffer.
m_pSQVM = nullptr;
@ -661,17 +588,10 @@ template <ScriptContext context> void SquirrelManager<context>::ProcessMessageBu
size_t argsAmount = message.args.size();
if (message.isExternal && message.externalFunc != NULL)
for (auto& v : message.args)
{
argsAmount = message.externalFunc(m_pSQVM->sqvm, message.userdata);
}
else
{
for (auto& v : message.args)
{
// Execute lambda to push arg to stack
v();
}
// Execute lambda to push arg to stack
v();
}
_call(m_pSQVM->sqvm, (SQInteger)argsAmount);
@ -839,10 +759,6 @@ ON_DLL_LOAD_RELIESON("client.dll", ClientSquirrel, ConCommand, (CModule module))
g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction = module.Offset(0x6CB0).RCast<sq_getfunctionType>();
g_pSquirrel<ScriptContext::UI>->__sq_getfunction = g_pSquirrel<ScriptContext::CLIENT>->__sq_getfunction;
SquirrelFunctions s = {};
g_pSquirrel<ScriptContext::CLIENT>->GenerateSquirrelFunctionsStruct(&s);
g_pPluginManager->InformSQVMLoad(ScriptContext::CLIENT, &s);
}
ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module))
@ -921,10 +837,6 @@ ON_DLL_LOAD_RELIESON("server.dll", ServerSquirrel, ConCommand, (CModule module))
FCVAR_GAMEDLL | FCVAR_GAMEDLL_FOR_REMOTE_CLIENTS | FCVAR_CHEAT);
StubUnsafeSQFuncs<ScriptContext::SERVER>();
SquirrelFunctions s = {};
g_pSquirrel<ScriptContext::SERVER>->GenerateSquirrelFunctionsStruct(&s);
g_pPluginManager->InformSQVMLoad(ScriptContext::SERVER, &s);
}
void InitialiseSquirrelManagers()

View File

@ -3,7 +3,6 @@
#include "squirrelclasstypes.h"
#include "squirrelautobind.h"
#include "core/math/vector.h"
#include "plugins/plugin_abi.h"
#include "mods/modmanager.h"
/*
@ -51,8 +50,6 @@ const char* GetContextName_Short(ScriptContext context);
eSQReturnType SQReturnTypeFromString(const char* pReturnType);
const char* SQTypeNameFromID(const int iTypeId);
void AsyncCall_External(ScriptContext context, const char* func_name, SquirrelMessage_External_Pop function, void* userdata);
ScriptContext ScriptContextFromString(std::string string);
namespace NS::log
@ -417,7 +414,6 @@ public:
SQRESULT setupfunc(const SQChar* funcname);
void AddFuncOverride(std::string name, SQFunction func);
void ProcessMessageBuffer();
void GenerateSquirrelFunctionsStruct(SquirrelFunctions* s);
};
template <ScriptContext context> SquirrelManager<context>* g_pSquirrel;
@ -483,7 +479,7 @@ requires is_iterable<T>
inline VoidFunction SQMessageBufferPushArg(T& arg) {
FunctionVector localv = {};
localv.push_back([]{g_pSquirrel<context>->newarray(g_pSquirrel<context>->m_pSQVM->sqvm, 0);});
for (const auto& item : arg) {
localv.push_back(SQMessageBufferPushArg<context>(item));
localv.push_back([]{g_pSquirrel<context>->arrayappend(g_pSquirrel<context>->m_pSQVM->sqvm, -2);});
@ -497,7 +493,7 @@ requires is_map<T>
inline VoidFunction SQMessageBufferPushArg(T& map) {
FunctionVector localv = {};
localv.push_back([]{g_pSquirrel<context>->newtable(g_pSquirrel<context>->m_pSQVM->sqvm);});
for (const auto& item : map) {
localv.push_back(SQMessageBufferPushArg<context>(item.first));
localv.push_back(SQMessageBufferPushArg<context>(item.second));

View File

@ -116,18 +116,11 @@ concept is_iterable = requires(std::ranges::range_value_t<T> x)
// clang-format on
typedef int (*SquirrelMessage_External_Pop)(HSquirrelVM* sqvm, void* userdata);
typedef void (*sq_schedule_call_externalType)(
ScriptContext context, const char* funcname, SquirrelMessage_External_Pop function, void* userdata);
class SquirrelMessage
{
public:
std::string functionName;
FunctionVector args;
bool isExternal = false;
void* userdata = NULL;
SquirrelMessage_External_Pop externalFunc = NULL;
};
class SquirrelMessageBuffer
@ -243,6 +236,3 @@ typedef SQRESULT (*sq_pushnewstructinstanceType)(HSquirrelVM* sqvm, int fieldCou
typedef SQRESULT (*sq_sealstructslotType)(HSquirrelVM* sqvm, int slotIndex);
#pragma endregion
// These "external" versions of the types are for plugins
typedef int64_t (*RegisterSquirrelFuncType_External)(ScriptContext context, SQFuncRegistration* funcReg, char unknown);