aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDLL/plugins
diff options
context:
space:
mode:
authorEmma Miler <emma.pi@protonmail.com>2023-04-11 20:49:16 +0200
committerGitHub <noreply@github.com>2023-04-11 20:49:16 +0200
commit450d0b1ed437cf37b4309af952af8904f3f07768 (patch)
tree3f0fd39b5663b5c3f4b9e76c70cc4987f22d66c7 /NorthstarDLL/plugins
parent72da1da5b4c04ccc1154853a308cab6459c60b79 (diff)
downloadNorthstarLauncher-450d0b1ed437cf37b4309af952af8904f3f07768.tar.gz
NorthstarLauncher-450d0b1ed437cf37b4309af952af8904f3f07768.zip
Plugin system v2 (#343)
* Some work * Rewrite gamestate presence * Add plugin system logger * Format changes * Format chjange * Fix gamestate stuff * some callback stuff * move around invite stuff * move invite to funcs * fix presence server data * Actually call InformSQVMCreated * bruh * Fix TODO's * Formatting * Fix filters * Add InformDLLLoads * Fix plugin handle always being 0 * Formatting * Fix merge issues * Formatting * Mods can add files compiled at SQVM init * Some Small Fixes * Add changes from review * Fix load failure * Add new squirrel functions * actually call InformSQVMDestroyed * add CreateObject function * answers to complaints * remove snake cases from GameStatePresence --------- Co-authored-by: cat_or_not <41955154+catornot@users.noreply.github.com>
Diffstat (limited to 'NorthstarDLL/plugins')
-rw-r--r--NorthstarDLL/plugins/plugin_abi.h192
-rw-r--r--NorthstarDLL/plugins/pluginbackend.cpp94
-rw-r--r--NorthstarDLL/plugins/pluginbackend.h45
-rw-r--r--NorthstarDLL/plugins/plugins.cpp581
-rw-r--r--NorthstarDLL/plugins/plugins.h62
5 files changed, 560 insertions, 414 deletions
diff --git a/NorthstarDLL/plugins/plugin_abi.h b/NorthstarDLL/plugins/plugin_abi.h
index 4b176a32..c9fa9d25 100644
--- a/NorthstarDLL/plugins/plugin_abi.h
+++ b/NorthstarDLL/plugins/plugin_abi.h
@@ -1,68 +1,164 @@
#pragma once
#include <string>
+#include "squirrel/squirrelclasstypes.h"
-#define ABI_VERSION 1
-/// <summary>
-/// This enum is used for referencing the different types of objects we can pass to a plugin
-/// Anything exposed to a plugin must not a be C++ type, as they could break when compiling with a different compiler.
-/// Any ABI incompatible change must increment the version number.
-/// Nothing must be removed from this enum, only appended. When it absolutely necessary to deprecate an object, it should return UNSUPPORTED
-/// when retrieved
-/// </summary>
-enum PluginObject
+#define ABI_VERSION 2
+
+enum GameState
{
- UNSUPPORTED = 0,
- GAMESTATE = 1,
- SERVERINFO = 2,
- PLAYERINFO = 3,
- DUMMY = 0xFFFF
+ LOADING = 0,
+ MAINMENU = 1,
+ LOBBY = 2,
+ INGAME = 3
};
-enum GameStateInfoType
+enum PluginLoadDLL
{
- ourScore = 0,
- secondHighestScore = 1,
- highestScore = 2,
- connected = 3,
- loading = 4,
- map = 5,
- mapDisplayName = 6,
- playlist = 7,
- playlistDisplayName = 8,
- players = 9
+ ENGINE = 0,
+ CLIENT,
+ SERVER
};
-struct GameState
+
+enum ObjectType
{
- int (*getGameStateChar)(char* out_buf, size_t out_buf_len, GameStateInfoType var);
- int (*getGameStateInt)(int* out_ptr, GameStateInfoType var);
- int (*getGameStateBool)(bool* out_ptr, GameStateInfoType var);
+ CONCOMMANDS = 0,
+ CONVAR = 1,
};
-enum ServerInfoType
+struct SquirrelFunctions
{
- id = 0,
- name = 1,
- description = 2,
- password = 3,
- maxPlayers = 4,
- roundBased = 5,
- scoreLimit = 6,
- endTime = 7
+ RegisterSquirrelFuncType RegisterSquirrelFunc;
+ sq_defconstType __sq_defconst;
+
+ sq_compilebufferType __sq_compilebuffer;
+ sq_callType __sq_call;
+ sq_raiseerrorType __sq_raiseerror;
+
+ 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_getthisentityType __sq_getthisentity;
+ sq_getobjectType __sq_getobject;
+
+ sq_stackinfosType __sq_stackinfos;
+
+ 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_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;
+};
+
+struct MessageSource
+{
+ const char* file;
+ const char* func;
+ int line;
};
-struct ServerInfo
+
+// This is a modified version of spdlog::details::log_msg
+// This is so that we can make it cross DLL boundaries
+struct LogMsg
{
- int (*getServerInfoChar)(char* out_buf, size_t out_buf_len, ServerInfoType var);
- int (*getServerInfoInt)(int* out_ptr, ServerInfoType var);
- int (*getServerInfoBool)(bool* out_ptr, ServerInfoType var);
+ int level;
+ uint64_t timestamp;
+ const char* msg;
+ MessageSource source;
+ int pluginHandle;
};
-enum PlayerInfoType
+typedef void (*loggerfunc_t)(LogMsg* msg);
+typedef void (*PLUGIN_RELAY_INVITE_TYPE)(const char* invite);
+typedef void* (*CreateObjectFunc)(ObjectType type);
+
+struct PluginNorthstarData
{
- uid = 0
+ const char* version;
+ HMODULE northstarModule;
+ int pluginHandle;
};
-struct PlayerInfo
+
+struct PluginInitFuncs
{
- int (*getPlayerInfoChar)(char* out_buf, size_t out_buf_len, PlayerInfoType var);
- int (*getPlayerInfoInt)(int* out_ptr, PlayerInfoType var);
- int (*getPlayerInfoBool)(bool* out_ptr, PlayerInfoType var);
+ loggerfunc_t logger;
+ PLUGIN_RELAY_INVITE_TYPE relayInviteFunc;
+ CreateObjectFunc createObject;
};
+
+struct PluginEngineData
+{
+ void* ConCommandConstructor;
+ void* conVarMalloc;
+ void* conVarRegister;
+ void* ConVar_Vtable;
+ void* IConVar_Vtable;
+};
+
+struct PluginGameStatePresence
+{
+ const char* id;
+ const char* name;
+ const char* description;
+ const char* password;
+
+ bool isServer;
+ bool isLocal;
+ GameState state;
+
+ const char* map;
+ const char* mapDisplayname;
+ const char* playlist;
+ const char* playlistDisplayname;
+
+ int currentPlayers;
+ int maxPlayers;
+
+ int ownScore;
+ int otherHighestScore; // NOTE: The highest score OR the second highest score if we have the highest
+ int maxScore;
+
+ int timestampEnd;
+};
+
+/// <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);
+
+// Async Communication types
+
+// Northstar -> Plugin
+typedef void (*PLUGIN_PUSH_PRESENCE_TYPE)(PluginGameStatePresence* data);
+typedef void (*PLUGIN_INFORM_DLL_LOAD_TYPE)(PluginLoadDLL dll, void* data);
diff --git a/NorthstarDLL/plugins/pluginbackend.cpp b/NorthstarDLL/plugins/pluginbackend.cpp
new file mode 100644
index 00000000..b442d702
--- /dev/null
+++ b/NorthstarDLL/plugins/pluginbackend.cpp
@@ -0,0 +1,94 @@
+#include "pch.h"
+#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"
+
+#define EXPORT extern "C" __declspec(dllexport)
+
+AUTOHOOK_INIT()
+
+PluginCommunicationHandler* g_pPluginCommunicationhandler;
+
+static PluginDataRequest storedRequest {PluginDataRequestType::END, (PluginRespondDataCallable) nullptr};
+
+void init_plugincommunicationhandler()
+{
+ g_pPluginCommunicationhandler = new PluginCommunicationHandler;
+ g_pPluginCommunicationhandler->requestQueue = {};
+}
+
+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 PluginCommunicationHandler::GeneratePresenceObjects()
+{
+ PluginGameStatePresence presence {};
+
+ presence.id = g_pGameStatePresence->id.c_str();
+ presence.name = g_pGameStatePresence->name.c_str();
+ presence.description = g_pGameStatePresence->description.c_str();
+ presence.password = g_pGameStatePresence->password.c_str();
+
+ presence.isServer = g_pGameStatePresence->isServer;
+ presence.isLocal = g_pGameStatePresence->isLocal;
+
+ if (g_pGameStatePresence->isLoading)
+ presence.state = GameState::LOADING;
+ else if (g_pGameStatePresence->uiMap == "")
+ presence.state = GameState::MAINMENU;
+ else if (g_pGameStatePresence->map == "mp_lobby" && g_pGameStatePresence->isLocal && g_pGameStatePresence->isLobby)
+ presence.state = GameState::LOBBY;
+ else
+ presence.state = GameState::INGAME;
+
+ presence.map = g_pGameStatePresence->map.c_str();
+ presence.mapDisplayname = g_pGameStatePresence->mapDisplayname.c_str();
+ presence.playlist = g_pGameStatePresence->playlist.c_str();
+ presence.playlistDisplayname = g_pGameStatePresence->playlistDisplayname.c_str();
+
+ presence.currentPlayers = g_pGameStatePresence->currentPlayers;
+ presence.maxPlayers = g_pGameStatePresence->maxPlayers;
+ presence.ownScore = g_pGameStatePresence->ownScore;
+ presence.otherHighestScore = g_pGameStatePresence->otherHighestScore;
+ presence.maxScore = g_pGameStatePresence->maxScore;
+ presence.timestampEnd = g_pGameStatePresence->timestampEnd;
+ g_pPluginManager->PushPresence(&presence);
+}
+
+ON_DLL_LOAD_RELIESON("engine.dll", PluginBackendEngine, ConCommand, (CModule module))
+{
+ g_pPluginManager->InformDLLLoad(PluginLoadDLL::ENGINE, &g_pPluginCommunicationhandler->m_sEngineData);
+}
+
+ON_DLL_LOAD_RELIESON("client.dll", PluginBackendClient, ConCommand, (CModule module))
+{
+ g_pPluginManager->InformDLLLoad(PluginLoadDLL::CLIENT, nullptr);
+}
+
+ON_DLL_LOAD_RELIESON("server.dll", PluginBackendServer, ConCommand, (CModule module))
+{
+ g_pPluginManager->InformDLLLoad(PluginLoadDLL::SERVER, nullptr);
+}
diff --git a/NorthstarDLL/plugins/pluginbackend.h b/NorthstarDLL/plugins/pluginbackend.h
new file mode 100644
index 00000000..ef42c508
--- /dev/null
+++ b/NorthstarDLL/plugins/pluginbackend.h
@@ -0,0 +1,45 @@
+#pragma once
+#include "pch.h"
+#include "plugin_abi.h"
+#include "shared/gamepresence.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);
+
+ void GeneratePresenceObjects();
+
+ public:
+ std::queue<PluginDataRequest> requestQueue;
+ std::mutex requestMutex;
+
+ PluginEngineData m_sEngineData {};
+};
+
+void init_plugincommunicationhandler();
+
+extern PluginCommunicationHandler* g_pPluginCommunicationhandler;
diff --git a/NorthstarDLL/plugins/plugins.cpp b/NorthstarDLL/plugins/plugins.cpp
index 47d27da1..6e3ceb5a 100644
--- a/NorthstarDLL/plugins/plugins.cpp
+++ b/NorthstarDLL/plugins/plugins.cpp
@@ -1,421 +1,292 @@
+#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 <chrono>
-#include <windows.h>
+#include "util/version.h"
+#include "pluginbackend.h"
+#include "util/wininfo.h"
+#include "logging/logging.h"
+#include "dedicated/dedicated.h"
-/// <summary>
-/// The data is split into two different representations: one for internal, and one for plugins, for thread safety reasons
-/// The struct exposed to plugins contains getter functions for the various data types.
-/// We can safely use C++ types like std::string here since these are only ever handled by Northstar internally
-/// </summary>
-struct InternalGameState
-{
- int ourScore;
- int secondHighestScore;
- int highestScore;
-
- bool connected;
- bool loading;
- std::string map;
- std::string mapDisplayName;
- std::string playlist;
- std::string playlistDisplayName;
- int players;
-};
-struct InternalServerInfo
-{
- std::string id;
- std::string name;
- std::string description;
- std::string password;
- int maxPlayers;
- bool roundBased;
- int scoreLimit;
- int endTime;
-};
-// TODO: need to extend this to include current player data like loadouts
-struct InternalPlayerInfo
-{
- int uid;
-};
-
-InternalGameState gameState;
-InternalServerInfo serverInfo;
-InternalPlayerInfo playerInfo;
-
-GameState gameStateExport;
-ServerInfo serverInfoExport;
-PlayerInfo playerInfoExport;
-
-/// <summary>
-/// We use SRW Locks because plugins will often be running their own thread
-/// To ensure thread safety, and to make it difficult to fuck up, we force them to use *our* functions to get data
-/// </summary>
-static SRWLOCK gameStateLock;
-static SRWLOCK serverInfoLock;
-static SRWLOCK playerInfoLock;
-
-void* getPluginObject(PluginObject var)
+PluginManager* g_pPluginManager;
+
+void freeLibrary(HMODULE hLib)
{
- switch (var)
+ if (!FreeLibrary(hLib))
{
- case PluginObject::GAMESTATE:
- return &gameStateExport;
- case PluginObject::SERVERINFO:
- return &serverInfoExport;
- case PluginObject::PLAYERINFO:
- return &playerInfoExport;
- default:
- return (void*)-1;
+ spdlog::error("There was an error while trying to free library");
}
}
-void initGameState()
-{
- // Initalize the Slim Reader / Writer locks
- InitializeSRWLock(&gameStateLock);
- InitializeSRWLock(&serverInfoLock);
- InitializeSRWLock(&playerInfoLock);
-
- gameStateExport.getGameStateChar = &getGameStateChar;
- gameStateExport.getGameStateInt = &getGameStateInt;
- gameStateExport.getGameStateBool = &getGameStateBool;
-
- serverInfoExport.getServerInfoChar = &getServerInfoChar;
- serverInfoExport.getServerInfoInt = &getServerInfoInt;
- serverInfoExport.getServerInfoBool = &getServerInfoBool;
-
- playerInfoExport.getPlayerInfoChar = &getPlayerInfoChar;
- playerInfoExport.getPlayerInfoInt = &getPlayerInfoInt;
- playerInfoExport.getPlayerInfoBool = &getPlayerInfoBool;
-
- serverInfo.id = "";
- serverInfo.name = "";
- serverInfo.description = "";
- serverInfo.password = "";
- serverInfo.maxPlayers = 0;
- gameState.connected = false;
- gameState.loading = false;
- gameState.map = "";
- gameState.mapDisplayName = "";
- gameState.playlist = "";
- gameState.playlistDisplayName = "";
- gameState.players = 0;
-
- playerInfo.uid = 123;
-}
-
-// string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading
-SQRESULT SQ_UpdateGameStateUI(HSquirrelVM* sqvm)
-{
- AcquireSRWLockExclusive(&gameStateLock);
- gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
- gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
- gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
- gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 4);
- gameState.connected = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 5);
- gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 6);
- ReleaseSRWLockExclusive(&gameStateLock);
- return SQRESULT_NOTNULL;
-}
-
-// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit
-SQRESULT SQ_UpdateGameStateClient(HSquirrelVM* sqvm)
+EXPORT void PLUGIN_LOG(LogMsg* msg)
{
- AcquireSRWLockExclusive(&gameStateLock);
- AcquireSRWLockExclusive(&serverInfoLock);
- gameState.players = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1);
- serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 2);
- gameState.ourScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 3);
- gameState.secondHighestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
- gameState.highestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 5);
- serverInfo.roundBased = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 6);
- serverInfo.scoreLimit = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 7);
- ReleaseSRWLockExclusive(&gameStateLock);
- ReleaseSRWLockExclusive(&serverInfoLock);
- return SQRESULT_NOTNULL;
+ 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);
}
-// string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string
-// playlistDisplayName
-SQRESULT SQ_UpdateServerInfo(HSquirrelVM* sqvm)
+EXPORT void* CreateObject(ObjectType type)
{
- AcquireSRWLockExclusive(&gameStateLock);
- AcquireSRWLockExclusive(&serverInfoLock);
- serverInfo.id = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1);
- serverInfo.name = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2);
- serverInfo.password = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3);
- gameState.players = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 4);
- serverInfo.maxPlayers = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 5);
- gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 6);
- gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 7);
- gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 8);
- gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 9);
- ReleaseSRWLockExclusive(&gameStateLock);
- ReleaseSRWLockExclusive(&serverInfoLock);
- return SQRESULT_NOTNULL;
+ switch (type)
+ {
+ case ObjectType::CONVAR:
+ return (void*)new ConVar;
+ case ObjectType::CONCOMMANDS:
+ return (void*)new ConCommand;
+ default:
+ return NULL;
+ }
}
-// int maxPlayers
-SQRESULT SQ_UpdateServerInfoBetweenRounds(HSquirrelVM* sqvm)
+std::optional<Plugin> PluginManager::LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data)
{
- AcquireSRWLockExclusive(&serverInfoLock);
- serverInfo.id = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 1);
- serverInfo.name = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2);
- serverInfo.password = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 3);
- serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4);
- ReleaseSRWLockExclusive(&serverInfoLock);
- return SQRESULT_NOTNULL;
-}
-// float timeInFuture
-SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm)
-{
- AcquireSRWLockExclusive(&serverInfoLock);
- serverInfo.endTime = ceil(g_pSquirrel<ScriptContext::CLIENT>->getfloat(sqvm, 1));
- ReleaseSRWLockExclusive(&serverInfoLock);
- return SQRESULT_NOTNULL;
-}
+ Plugin plugin {};
-// bool loading
-SQRESULT SQ_SetConnected(HSquirrelVM* sqvm)
-{
- AcquireSRWLockExclusive(&gameStateLock);
- gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 1);
- ReleaseSRWLockExclusive(&gameStateLock);
- return SQRESULT_NOTNULL;
-}
+ std::string pathstring = (pluginPath / path).string();
+ std::wstring wpath = (pluginPath / path).wstring();
-SQRESULT SQ_UpdateListenServer(HSquirrelVM* sqvm)
-{
- AcquireSRWLockExclusive(&serverInfoLock);
- serverInfo.id = g_pMasterServerManager->m_sOwnServerId;
- serverInfo.password = ""; // g_pServerPresence->Cvar_ns_server_password->GetString(); todo this fr
- ReleaseSRWLockExclusive(&serverInfoLock);
- return SQRESULT_NOTNULL;
-}
+ 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), MAKEINTRESOURCEW(RT_RCDATA));
-int getServerInfoChar(char* out_buf, size_t out_buf_len, ServerInfoType var)
-{
- AcquireSRWLockShared(&serverInfoLock);
- int n = 0;
- switch (var)
+ if (manifestResource == NULL)
{
- case ServerInfoType::id:
- strncpy(out_buf, serverInfo.id.c_str(), out_buf_len);
- break;
- case ServerInfoType::name:
- strncpy(out_buf, serverInfo.name.c_str(), out_buf_len);
- break;
- case ServerInfoType::description:
- strncpy(out_buf, serverInfo.id.c_str(), out_buf_len);
- break;
- case ServerInfoType::password:
- strncpy(out_buf, serverInfo.password.c_str(), out_buf_len);
- break;
- default:
- n = -1;
+ 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);
- ReleaseSRWLockShared(&serverInfoLock);
+ rapidjson_document manifestJSON;
+ manifestJSON.Parse(manifest.c_str());
- return n;
-}
-int getServerInfoInt(int* out_ptr, ServerInfoType var)
-{
- AcquireSRWLockShared(&serverInfoLock);
- int n = 0;
- switch (var)
+ if (manifestJSON.HasParseError())
{
- case ServerInfoType::maxPlayers:
- *out_ptr = serverInfo.maxPlayers;
- break;
- case ServerInfoType::scoreLimit:
- *out_ptr = serverInfo.scoreLimit;
- break;
- case ServerInfoType::endTime:
- *out_ptr = serverInfo.endTime;
- break;
- default:
- n = -1;
+ 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
- ReleaseSRWLockShared(&serverInfoLock);
-
- return n;
-}
-int getServerInfoBool(bool* out_ptr, ServerInfoType var)
-{
- AcquireSRWLockShared(&serverInfoLock);
- int n = 0;
- switch (var)
+ HMODULE pluginLib = LoadLibraryW(wpptr); // Load the DLL as a data file
+ if (pluginLib == NULL)
{
- case ServerInfoType::roundBased:
- *out_ptr = serverInfo.roundBased;
- break;
- default:
- n = -1;
+ 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);
- ReleaseSRWLockShared(&serverInfoLock);
+ 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();
- return n;
-}
+ plugin.run_on_client = manifestJSON["run_on_client"].GetBool();
+ plugin.run_on_server = manifestJSON["run_on_server"].GetBool();
-int getGameStateChar(char* out_buf, size_t out_buf_len, GameStateInfoType var)
-{
- AcquireSRWLockShared(&gameStateLock);
- int n = 0;
- switch (var)
+ if (!plugin.run_on_server && IsDedicatedServer())
+ return std::nullopt;
+
+ if (manifestJSON.HasMember("dependencyName"))
{
- case GameStateInfoType::map:
- strncpy(out_buf, gameState.map.c_str(), out_buf_len);
- break;
- case GameStateInfoType::mapDisplayName:
- strncpy(out_buf, gameState.mapDisplayName.c_str(), out_buf_len);
- break;
- case GameStateInfoType::playlist:
- strncpy(out_buf, gameState.playlist.c_str(), out_buf_len);
- break;
- case GameStateInfoType::playlistDisplayName:
- strncpy(out_buf, gameState.playlistDisplayName.c_str(), out_buf_len);
- break;
- default:
- n = -1;
+ plugin.dependencyName = manifestJSON["dependencyName"].GetString();
+ }
+ else
+ {
+ plugin.dependencyName = plugin.name;
}
- ReleaseSRWLockShared(&gameStateLock);
+ 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");
- return n;
+ 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;
}
-int getGameStateInt(int* out_ptr, GameStateInfoType var)
+
+bool PluginManager::LoadPlugins()
{
- AcquireSRWLockShared(&gameStateLock);
- int n = 0;
- switch (var)
- {
- case GameStateInfoType::ourScore:
- *out_ptr = gameState.ourScore;
- break;
- case GameStateInfoType::secondHighestScore:
- *out_ptr = gameState.secondHighestScore;
- break;
- case GameStateInfoType::highestScore:
- *out_ptr = gameState.highestScore;
- break;
- case GameStateInfoType::players:
- *out_ptr = gameState.players;
- break;
- default:
- n = -1;
- }
+ 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;
+
+ init_plugincommunicationhandler();
- ReleaseSRWLockShared(&gameStateLock);
+ data.version = ns_version.c_str();
+ data.northstarModule = g_NorthstarModule;
- return n;
+ if (!fs::exists(pluginPath))
+ {
+ NS::log::PLUGINSYS->warn("Could not find a plugins directory. Skipped loading plugins");
+ return false;
+ }
+ // ensure dirs exist
+ fs::recursive_directory_iterator iterator(pluginPath);
+ if (std::filesystem::begin(iterator) == std::filesystem::end(iterator))
+ {
+ NS::log::PLUGINSYS->warn("Could not find any plugins. Skipped loading plugins");
+ return false;
+ }
+ for (auto const& entry : iterator)
+ {
+ if (fs::is_regular_file(entry) && entry.path().extension() == ".dll")
+ paths.emplace_back(entry.path().filename());
+ }
+ for (fs::path path : paths)
+ {
+ if (LoadPlugin(path, &funcs, &data))
+ data.pluginHandle += 1;
+ }
+ return true;
}
-int getGameStateBool(bool* out_ptr, GameStateInfoType var)
+
+void PluginManager::InformSQVMLoad(ScriptContext context, SquirrelFunctions* s)
{
- AcquireSRWLockShared(&gameStateLock);
- int n = 0;
- switch (var)
+ for (auto plugin : m_vLoadedPlugins)
{
- case GameStateInfoType::connected:
- *out_ptr = gameState.connected;
- break;
- case GameStateInfoType::loading:
- *out_ptr = gameState.loading;
- break;
- default:
- n = -1;
+ 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);
+ }
}
-
- ReleaseSRWLockShared(&gameStateLock);
-
- return n;
}
-int getPlayerInfoChar(char* out_buf, size_t out_buf_len, PlayerInfoType var)
+void PluginManager::InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm)
{
- AcquireSRWLockShared(&playerInfoLock);
- int n = 0;
- switch (var)
+ for (auto plugin : m_vLoadedPlugins)
{
- default:
- n = -1;
+ if (plugin.inform_sqvm_created != NULL)
+ {
+ plugin.inform_sqvm_created(context, sqvm);
+ }
}
-
- ReleaseSRWLockShared(&playerInfoLock);
-
- return n;
}
-int getPlayerInfoInt(int* out_ptr, PlayerInfoType var)
+
+void PluginManager::InformSQVMDestroyed(ScriptContext context)
{
- AcquireSRWLockShared(&playerInfoLock);
- int n = 0;
- switch (var)
+ for (auto plugin : m_vLoadedPlugins)
{
- case PlayerInfoType::uid:
- *out_ptr = playerInfo.uid;
- break;
- default:
- n = -1;
+ if (plugin.inform_sqvm_destroyed != NULL)
+ {
+ plugin.inform_sqvm_destroyed(context);
+ }
}
-
- ReleaseSRWLockShared(&playerInfoLock);
-
- return n;
}
-int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var)
+
+void PluginManager::PushPresence(PluginGameStatePresence* data)
{
- AcquireSRWLockShared(&playerInfoLock);
- int n = 0;
- switch (var)
+ for (auto plugin : m_vLoadedPlugins)
{
- default:
- n = -1;
+ if (plugin.push_presence != NULL)
+ {
+ plugin.push_presence(data);
+ }
}
-
- ReleaseSRWLockShared(&playerInfoLock);
-
- return n;
}
-ON_DLL_LOAD_CLIENT_RELIESON("client.dll", PluginCommands, ClientSquirrel, (CModule module))
+void PluginManager::InformDLLLoad(PluginLoadDLL dll, void* data)
{
- // i swear there's a way to make this not have be run in 2 contexts but i can't figure it out
- // some funcs i need are just not available in UI or CLIENT
-
- if (g_pSquirrel<ScriptContext::UI> && g_pSquirrel<ScriptContext::CLIENT>)
+ for (auto plugin : m_vLoadedPlugins)
{
- g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
- "void",
- "NSUpdateGameStateUI",
- "string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading",
- "",
- SQ_UpdateGameStateUI);
- g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
- "void",
- "NSUpdateGameStateClient",
- "int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit",
- "",
- SQ_UpdateGameStateClient);
- g_pSquirrel<ScriptContext::UI>->AddFuncRegistration(
- "void",
- "NSUpdateServerInfo",
- "string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, "
- "string "
- "playlistDisplayName",
- "",
- SQ_UpdateServerInfo);
- g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration(
- "void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds);
- g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
- g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
- g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
+ if (plugin.inform_dll_load != NULL)
+ {
+ plugin.inform_dll_load(dll, data);
+ }
}
}
diff --git a/NorthstarDLL/plugins/plugins.h b/NorthstarDLL/plugins/plugins.h
index 953801a2..ffa277d0 100644
--- a/NorthstarDLL/plugins/plugins.h
+++ b/NorthstarDLL/plugins/plugins.h
@@ -1,17 +1,57 @@
#pragma once
#include "plugin_abi.h"
-int getServerInfoChar(char* out_buf, size_t out_buf_len, ServerInfoType var);
-int getServerInfoInt(int* out_ptr, ServerInfoType var);
-int getServerInfoBool(bool* out_ptr, ServerInfoType var);
+const int IDR_RCDATA1 = 101;
-int getGameStateChar(char* out_buf, size_t out_buf_len, GameStateInfoType var);
-int getGameStateInt(int* out_ptr, GameStateInfoType var);
-int getGameStateBool(bool* out_ptr, GameStateInfoType var);
+class Plugin
+{
+ public:
+ std::string name;
+ std::string displayName;
+ std::string dependencyName;
+ std::string description;
-int getPlayerInfoChar(char* out_buf, size_t out_buf_len, PlayerInfoType var);
-int getPlayerInfoInt(int* out_ptr, PlayerInfoType var);
-int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var);
+ std::string api_version;
+ std::string version;
-void initGameState();
-void* getPluginObject(PluginObject var);
+ // 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_PUSH_PRESENCE_TYPE push_presence;
+ PLUGIN_INFORM_DLL_LOAD_TYPE inform_dll_load;
+};
+
+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 PushPresence(PluginGameStatePresence* data);
+
+ void InformDLLLoad(PluginLoadDLL dll, void* data);
+
+ private:
+ std::string pluginPath;
+};
+
+extern PluginManager* g_pPluginManager;