From 450d0b1ed437cf37b4309af952af8904f3f07768 Mon Sep 17 00:00:00 2001 From: Emma Miler Date: Tue, 11 Apr 2023 20:49:16 +0200 Subject: 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> --- NorthstarDLL/plugins/plugin_abi.h | 192 ++++++++--- NorthstarDLL/plugins/pluginbackend.cpp | 94 ++++++ NorthstarDLL/plugins/pluginbackend.h | 45 +++ NorthstarDLL/plugins/plugins.cpp | 581 +++++++++++++-------------------- NorthstarDLL/plugins/plugins.h | 62 +++- 5 files changed, 560 insertions(+), 414 deletions(-) create mode 100644 NorthstarDLL/plugins/pluginbackend.cpp create mode 100644 NorthstarDLL/plugins/pluginbackend.h (limited to 'NorthstarDLL/plugins') 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 +#include "squirrel/squirrelclasstypes.h" -#define ABI_VERSION 1 -/// -/// 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 -/// -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; +}; + +/// 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. +/// + +// 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 lock(requestMutex); + if (!requestQueue.empty()) + { + storedRequest = requestQueue.front(); + switch (storedRequest.type) + { + default: + spdlog::error("{} was called with invalid request type '{}'", __FUNCTION__, static_cast(storedRequest.type)); + } + requestQueue.pop(); + } +} + +void PluginCommunicationHandler::PushRequest(PluginDataRequestType type, PluginRespondDataCallable func) +{ + std::lock_guard 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 +#include + +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 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 -#include -#include +#include "util/version.h" +#include "pluginbackend.h" +#include "util/wininfo.h" +#include "logging/logging.h" +#include "dedicated/dedicated.h" -/// -/// 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 -/// -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; - -/// -/// 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 -/// -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->getstring(sqvm, 1); - gameState.mapDisplayName = g_pSquirrel->getstring(sqvm, 2); - gameState.playlist = g_pSquirrel->getstring(sqvm, 3); - gameState.playlistDisplayName = g_pSquirrel->getstring(sqvm, 4); - gameState.connected = g_pSquirrel->getbool(sqvm, 5); - gameState.loading = g_pSquirrel->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->getinteger(sqvm, 1); - serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 2); - gameState.ourScore = g_pSquirrel->getinteger(sqvm, 3); - gameState.secondHighestScore = g_pSquirrel->getinteger(sqvm, 4); - gameState.highestScore = g_pSquirrel->getinteger(sqvm, 5); - serverInfo.roundBased = g_pSquirrel->getbool(sqvm, 6); - serverInfo.scoreLimit = g_pSquirrel->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->getstring(sqvm, 1); - serverInfo.name = g_pSquirrel->getstring(sqvm, 2); - serverInfo.password = g_pSquirrel->getstring(sqvm, 3); - gameState.players = g_pSquirrel->getinteger(sqvm, 4); - serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 5); - gameState.map = g_pSquirrel->getstring(sqvm, 6); - gameState.mapDisplayName = g_pSquirrel->getstring(sqvm, 7); - gameState.playlist = g_pSquirrel->getstring(sqvm, 8); - gameState.playlistDisplayName = g_pSquirrel->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 PluginManager::LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data) { - AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.id = g_pSquirrel->getstring(sqvm, 1); - serverInfo.name = g_pSquirrel->getstring(sqvm, 2); - serverInfo.password = g_pSquirrel->getstring(sqvm, 3); - serverInfo.maxPlayers = g_pSquirrel->getinteger(sqvm, 4); - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; -} -// float timeInFuture -SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm) -{ - AcquireSRWLockExclusive(&serverInfoLock); - serverInfo.endTime = ceil(g_pSquirrel->getfloat(sqvm, 1)); - ReleaseSRWLockExclusive(&serverInfoLock); - return SQRESULT_NOTNULL; -} + Plugin plugin {}; -// bool loading -SQRESULT SQ_SetConnected(HSquirrelVM* sqvm) -{ - AcquireSRWLockExclusive(&gameStateLock); - gameState.loading = g_pSquirrel->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(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 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 && g_pSquirrel) + for (auto plugin : m_vLoadedPlugins) { - g_pSquirrel->AddFuncRegistration( - "void", - "NSUpdateGameStateUI", - "string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading", - "", - SQ_UpdateGameStateUI); - g_pSquirrel->AddFuncRegistration( - "void", - "NSUpdateGameStateClient", - "int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit", - "", - SQ_UpdateGameStateClient); - g_pSquirrel->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->AddFuncRegistration( - "void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds); - g_pSquirrel->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo); - g_pSquirrel->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected); - g_pSquirrel->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 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 m_vLoadedPlugins; + + public: + bool LoadPlugins(); + std::optional 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; -- cgit v1.2.3