aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmma Miler <27428383+emma-miler@users.noreply.github.com>2022-03-04 00:16:01 +0100
committerGitHub <noreply@github.com>2022-03-03 23:16:01 +0000
commit13dcc7d9307b160131ed80832efda99001aa6c6d (patch)
treeff7e021c2927f7d3e0ee0bb84642c6ee1c9b24ab
parent62e137b32ad7c601b2db37e69b9f435a7dbc730d (diff)
downloadNorthstarLauncher-13dcc7d9307b160131ed80832efda99001aa6c6d.tar.gz
NorthstarLauncher-13dcc7d9307b160131ed80832efda99001aa6c6d.zip
Add launcher code for plugin support (#93)
* rpc * RPC * Undoing broken shit * temp * Revert "temp" This reverts commit 4875b5aca2b0d6b03183d220120c03df28759a95. * Include a whole bunch of new stuff * fix id * Added manifest and added some verification * Fixed my oopsies * Safety commit * Moved over to new plugin abi * Small docs changes and made plugin loading use ABI_VERSION * Moving discord rpc plugin stuff to a separate repo * Add some comments to `plugins.cpp` * Moved internal structs from `plugin_abi.h` to `plugins.cpp` * Add CLA `-noplugins` * Code formatting changes so clang-format doesn't scream at me * i hate clang-format * why does this fail to build now wtf * Update plugins.cpp * Merged and updated to new convar system * Implemented changes requested in review * Removed a few more of those nasty comments * Removed extra build configs Co-authored-by: BobTheBob <32057864+BobTheBob9@users.noreply.github.com>
-rw-r--r--LauncherInjector/main.cpp24
-rw-r--r--NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj4
-rw-r--r--NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters12
-rw-r--r--NorthstarDedicatedTest/dllmain.cpp112
-rw-r--r--NorthstarDedicatedTest/main.h3
-rw-r--r--NorthstarDedicatedTest/masterserver.h3
-rw-r--r--NorthstarDedicatedTest/plugin_abi.h72
-rw-r--r--NorthstarDedicatedTest/plugins.cpp410
-rw-r--r--NorthstarDedicatedTest/plugins.h19
9 files changed, 656 insertions, 3 deletions
diff --git a/LauncherInjector/main.cpp b/LauncherInjector/main.cpp
index f262730d..02f82342 100644
--- a/LauncherInjector/main.cpp
+++ b/LauncherInjector/main.cpp
@@ -5,6 +5,7 @@
#include <sstream>
#include <fstream>
#include <Shlwapi.h>
+#include <iostream>
namespace fs = std::filesystem;
@@ -21,6 +22,8 @@ HMODULE hTier0Module;
wchar_t exePath[4096];
wchar_t buffer[8192];
+bool noLoadPlugins = false;
+
DWORD GetProcessByName(std::wstring processName)
{
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
@@ -222,8 +225,21 @@ bool LoadNorthstar()
return false;
}
}
-
((bool (*)())Hook_Init)();
+
+ FARPROC LoadPlugins = nullptr;
+ if (!noLoadPlugins)
+ {
+ LoadPlugins = GetProcAddress(hHookModule, "LoadPlugins");
+ if (!hHookModule || LoadPlugins == nullptr)
+ {
+ printf("Failed to get function pointer to LoadPlugins of Northstar.dll\n");
+ LibraryLoadError(GetLastError(), L"Northstar.dll", buffer);
+ return false;
+ }
+ ((bool (*)())LoadPlugins)();
+ }
+
return true;
}
@@ -242,6 +258,7 @@ HMODULE LoadDediStub(const char* name)
int main(int argc, char* argv[])
{
+
if (!GetExePathWide(exePath, sizeof(exePath)))
{
MessageBoxA(
@@ -261,9 +278,13 @@ int main(int argc, char* argv[])
dedicated = true;
else if (!strcmp(argv[i], "-nostubs"))
nostubs = true;
+ else if (!strcmp(argv[i], "-noplugins"))
+ noLoadPlugins = true;
if (!noOriginStartup && !dedicated)
+ {
EnsureOriginStarted();
+ }
if (dedicated && !nostubs)
{
@@ -364,6 +385,7 @@ int main(int argc, char* argv[])
0);
// auto result = ((__int64(__fastcall*)())LauncherMain)();
// auto result = ((signed __int64(__fastcall*)(__int64))LauncherMain)(0i64);
+
return ((int(/*__fastcall*/*)(HINSTANCE, HINSTANCE, LPSTR, int))LauncherMain)(
NULL, NULL, NULL, 0); // the parameters aren't really used anyways
}
diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
index 6da5f2b7..f12a9eee 100644
--- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
+++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
@@ -115,6 +115,8 @@
<ClInclude Include="chatcommand.h" />
<ClInclude Include="clientchathooks.h" />
<ClInclude Include="localchatwriter.h" />
+ <ClInclude Include="plugins.h" />
+ <ClInclude Include="plugin_abi.h" />
<ClInclude Include="serverchathooks.h" />
<ClInclude Include="clientauthhooks.h" />
<ClInclude Include="color.h" />
@@ -555,6 +557,7 @@
<ClInclude Include="sourceconsole.h" />
<ClInclude Include="sourceinterface.h" />
<ClInclude Include="squirrel.h" />
+ <ClInclude Include="state.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="audio.cpp" />
@@ -595,6 +598,7 @@
</ClCompile>
<ClCompile Include="pdef.cpp" />
<ClCompile Include="playlist.cpp" />
+ <ClCompile Include="plugins.cpp" />
<ClCompile Include="rpakfilesystem.cpp" />
<ClCompile Include="scriptbrowserhooks.cpp" />
<ClCompile Include="scriptmainmenupromos.cpp" />
diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
index f0c8c380..37277e2e 100644
--- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
+++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
@@ -1473,6 +1473,15 @@
<ClInclude Include="localchatwriter.h">
<Filter>Header Files\Client</Filter>
</ClInclude>
+ <ClInclude Include="plugins.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="state.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="plugin_abi.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
@@ -1622,6 +1631,9 @@
<ClCompile Include="localchatwriter.cpp">
<Filter>Source Files\Client</Filter>
</ClCompile>
+ <ClCompile Include="plugins.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<MASM Include="audio_asm.asm">
diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp
index dcff9f4a..d6bc5091 100644
--- a/NorthstarDedicatedTest/dllmain.cpp
+++ b/NorthstarDedicatedTest/dllmain.cpp
@@ -39,6 +39,15 @@
#include "localchatwriter.h"
#include <string.h>
#include "pch.h"
+#include "plugin_abi.h"
+#include "plugins.h"
+
+#include "rapidjson/document.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
+#include "rapidjson/error/en.h"
+
+typedef void (*initPluginFuncPtr)(void* getPluginObject);
bool initialised = false;
@@ -68,6 +77,108 @@ void WaitForDebugger(HMODULE baseAddress)
}
}
+void freeLibrary(HMODULE hLib)
+{
+ if (!FreeLibrary(hLib))
+ {
+ spdlog::error("There was an error while trying to free library");
+ }
+}
+
+bool LoadPlugins()
+{
+
+ std::vector<fs::path> paths;
+
+ std::string pluginPath = GetNorthstarPrefix() + "/plugins";
+
+ // ensure dirs exist
+ fs::recursive_directory_iterator iterator(pluginPath);
+ if (std::filesystem::begin(iterator) == std::filesystem::end(iterator))
+ {
+ spdlog::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());
+ }
+ // system("pause");
+ initGameState();
+ // spdlog::info("Loading the following DLLs in plugins folder:");
+ for (fs::path path : paths)
+ {
+ std::string pathstring = (pluginPath / path).string();
+ std::wstring wpath = (pluginPath / 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)
+ {
+ spdlog::info("Failed to load library {}: ", std::system_category().message(GetLastError()));
+ continue;
+ }
+ HRSRC manifestResource = FindResourceW(datafile, MAKEINTRESOURCE(101), MAKEINTRESOURCE(RT_RCDATA));
+
+ if (manifestResource == NULL)
+ {
+ spdlog::info("Could not find manifest for library {}", pathstring);
+ freeLibrary(datafile);
+ continue;
+ }
+ spdlog::info("Loading resource from library");
+ HGLOBAL myResourceData = LoadResource(datafile, manifestResource);
+ if (myResourceData == NULL)
+ {
+ spdlog::error("Failed to load resource from library");
+ freeLibrary(datafile);
+ continue;
+ }
+ 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())
+ {
+ spdlog::error("Manifest for {} was invalid", pathstring);
+ continue;
+ }
+ if (!manifestJSON.HasMember("api_version"))
+ {
+ spdlog::error("{} does not have a version number in its manifest", pathstring);
+ continue;
+ // spdlog::info(manifestJSON["version"].GetString());
+ }
+ if (strcmp(manifestJSON["api_version"].GetString(), std::to_string(ABI_VERSION).c_str()))
+ {
+ spdlog::error("{} has an incompatible API version number in its manifest", pathstring);
+ continue;
+ }
+ // Passed all checks, going to actually load it now
+
+ HMODULE pluginLib = LoadLibraryW(wpptr); // Load the DLL as a data file
+ if (pluginLib == NULL)
+ {
+ spdlog::info("Failed to load library {}: ", std::system_category().message(GetLastError()));
+ continue;
+ }
+ initPluginFuncPtr initPlugin = (initPluginFuncPtr)GetProcAddress(pluginLib, "initializePlugin");
+ if (initPlugin == NULL)
+ {
+ spdlog::info("Library {} has no function initializePlugin", pathstring);
+ continue;
+ }
+ spdlog::info("Succesfully loaded {}", pathstring);
+ initPlugin(&getPluginObject);
+ }
+ return true;
+}
+
bool InitialiseNorthstar()
{
if (initialised)
@@ -123,6 +234,7 @@ bool InitialiseNorthstar()
AddDllLoadCallback("client.dll", InitialiseScriptMainMenuPromos);
AddDllLoadCallback("client.dll", InitialiseMiscClientFixes);
AddDllLoadCallback("client.dll", InitialiseClientPrintHooks);
+ AddDllLoadCallback("client.dll", InitialisePluginCommands);
AddDllLoadCallback("client.dll", InitialiseClientChatHooks);
AddDllLoadCallback("client.dll", InitialiseLocalChatWriter);
}
diff --git a/NorthstarDedicatedTest/main.h b/NorthstarDedicatedTest/main.h
index 90e88912..64f9cfd2 100644
--- a/NorthstarDedicatedTest/main.h
+++ b/NorthstarDedicatedTest/main.h
@@ -1,3 +1,4 @@
#pragma once
-extern "C" __declspec(dllexport) bool InitialiseNorthstar(); \ No newline at end of file
+extern "C" __declspec(dllexport) bool InitialiseNorthstar();
+extern "C" __declspec(dllexport) bool LoadPlugins(); \ No newline at end of file
diff --git a/NorthstarDedicatedTest/masterserver.h b/NorthstarDedicatedTest/masterserver.h
index 2eae2081..c8fd0cb5 100644
--- a/NorthstarDedicatedTest/masterserver.h
+++ b/NorthstarDedicatedTest/masterserver.h
@@ -123,4 +123,5 @@ void UpdateServerInfoFromUnicodeToUTF8();
void InitialiseSharedMasterServer(HMODULE baseAddress);
extern MasterServerManager* g_MasterServerManager;
-extern ConVar* Cvar_ns_masterserver_hostname; \ No newline at end of file
+extern ConVar* Cvar_ns_masterserver_hostname;
+extern ConVar* Cvar_ns_server_password; \ No newline at end of file
diff --git a/NorthstarDedicatedTest/plugin_abi.h b/NorthstarDedicatedTest/plugin_abi.h
new file mode 100644
index 00000000..edd44ea1
--- /dev/null
+++ b/NorthstarDedicatedTest/plugin_abi.h
@@ -0,0 +1,72 @@
+#pragma once
+#include <string>
+
+#define ABI_VERSION 1
+//clang-format off
+// I hate clang-format
+/// <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
+{
+ UNSUPPORTED = 0,
+ GAMESTATE = 1,
+ SERVERINFO = 2,
+ PLAYERINFO = 3,
+ DUMMY = 0xFFFF
+};
+
+enum GameStateInfoType
+{
+ ourScore = 0,
+ secondHighestScore = 1,
+ highestScore = 2,
+ connected = 3,
+ loading = 4,
+ map = 5,
+ mapDisplayName = 6,
+ playlist = 7,
+ playlistDisplayName = 8,
+ players = 9
+};
+struct GameState
+{
+ 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);
+};
+
+enum ServerInfoType
+{
+ id = 0,
+ name = 1,
+ description = 2,
+ password = 3,
+ maxPlayers = 4,
+ roundBased = 5,
+ scoreLimit = 6,
+ endTime = 7
+};
+struct ServerInfo
+{
+ 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);
+};
+
+enum PlayerInfoType
+{
+ uid = 0
+};
+struct PlayerInfo
+{
+ 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);
+};
+
+//clang-format on \ No newline at end of file
diff --git a/NorthstarDedicatedTest/plugins.cpp b/NorthstarDedicatedTest/plugins.cpp
new file mode 100644
index 00000000..383555cf
--- /dev/null
+++ b/NorthstarDedicatedTest/plugins.cpp
@@ -0,0 +1,410 @@
+#include "pch.h"
+#include "squirrel.h"
+#include "plugins.h"
+#include <chrono>
+#include "masterserver.h"
+#include "convar.h"
+#include <windows.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)
+{
+ switch (var)
+ {
+ case PluginObject::GAMESTATE:
+ return &gameStateExport;
+ case PluginObject::SERVERINFO:
+ return &serverInfoExport;
+ case PluginObject::PLAYERINFO:
+ return &playerInfoExport;
+ default:
+ return (void*)-1;
+ }
+}
+
+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(void* sqvm)
+{
+ AcquireSRWLockExclusive(&gameStateLock);
+ gameState.map = ClientSq_getstring(sqvm, 1);
+ gameState.mapDisplayName = ClientSq_getstring(sqvm, 2);
+ gameState.playlist = ClientSq_getstring(sqvm, 3);
+ gameState.playlistDisplayName = ClientSq_getstring(sqvm, 4);
+ gameState.connected = ClientSq_getbool(sqvm, 5);
+ gameState.loading = ClientSq_getbool(sqvm, 6);
+ ReleaseSRWLockExclusive(&gameStateLock);
+ return SQRESULT_NOTNULL;
+}
+
+// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit
+SQRESULT SQ_UpdateGameStateClient(void* sqvm)
+{
+ AcquireSRWLockExclusive(&gameStateLock);
+ AcquireSRWLockExclusive(&serverInfoLock);
+ gameState.players = ClientSq_getinteger(sqvm, 1);
+ gameState.ourScore = ClientSq_getinteger(sqvm, 2);
+ gameState.secondHighestScore = ClientSq_getinteger(sqvm, 3);
+ gameState.highestScore = ClientSq_getinteger(sqvm, 4);
+ serverInfo.roundBased = ClientSq_getbool(sqvm, 5);
+ serverInfo.scoreLimit = ClientSq_getbool(sqvm, 6);
+ ReleaseSRWLockExclusive(&gameStateLock);
+ ReleaseSRWLockExclusive(&serverInfoLock);
+ return SQRESULT_NOTNULL;
+}
+
+// string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string
+// playlistDisplayName
+SQRESULT SQ_UpdateServerInfo(void* sqvm)
+{
+ AcquireSRWLockExclusive(&gameStateLock);
+ AcquireSRWLockExclusive(&serverInfoLock);
+ serverInfo.id = ClientSq_getstring(sqvm, 1);
+ serverInfo.name = ClientSq_getstring(sqvm, 2);
+ serverInfo.password = ClientSq_getstring(sqvm, 3);
+ gameState.players = ClientSq_getinteger(sqvm, 4);
+ serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 5);
+ gameState.map = ClientSq_getstring(sqvm, 6);
+ gameState.mapDisplayName = ClientSq_getstring(sqvm, 7);
+ gameState.playlist = ClientSq_getstring(sqvm, 8);
+ gameState.playlistDisplayName = ClientSq_getstring(sqvm, 9);
+ ReleaseSRWLockExclusive(&gameStateLock);
+ ReleaseSRWLockExclusive(&serverInfoLock);
+ return SQRESULT_NOTNULL;
+}
+
+// int maxPlayers
+SQRESULT SQ_UpdateServerInfoBetweenRounds(void* sqvm)
+{
+ AcquireSRWLockExclusive(&serverInfoLock);
+ serverInfo.id = ClientSq_getstring(sqvm, 1);
+ serverInfo.name = ClientSq_getstring(sqvm, 2);
+ serverInfo.password = ClientSq_getstring(sqvm, 3);
+ serverInfo.maxPlayers = ClientSq_getinteger(sqvm, 4);
+ ReleaseSRWLockExclusive(&serverInfoLock);
+ return SQRESULT_NOTNULL;
+}
+
+// float timeInFuture
+SQRESULT SQ_UpdateTimeInfo(void* sqvm)
+{
+ AcquireSRWLockExclusive(&serverInfoLock);
+ int endTimeFromNow = ceil(ClientSq_getfloat(sqvm, 1));
+ const auto p1 = std::chrono::system_clock::now().time_since_epoch();
+ serverInfo.endTime = std::chrono::duration_cast<std::chrono::seconds>(p1).count() + endTimeFromNow;
+ ReleaseSRWLockExclusive(&serverInfoLock);
+ return SQRESULT_NOTNULL;
+}
+
+// bool loading
+SQRESULT SQ_SetConnected(void* sqvm)
+{
+ AcquireSRWLockExclusive(&gameStateLock);
+ gameState.loading = ClientSq_getbool(sqvm, 1);
+ ReleaseSRWLockExclusive(&gameStateLock);
+ return SQRESULT_NOTNULL;
+}
+
+SQRESULT SQ_UpdateListenServer(void* sqvm)
+{
+ AcquireSRWLockExclusive(&serverInfoLock);
+ serverInfo.id = g_MasterServerManager->m_ownServerId;
+ serverInfo.password = Cvar_ns_server_password->GetString();
+ ReleaseSRWLockExclusive(&serverInfoLock);
+ return SQRESULT_NOTNULL;
+}
+
+int getServerInfoChar(char* out_buf, size_t out_buf_len, ServerInfoType var)
+{
+ AcquireSRWLockShared(&serverInfoLock);
+ int n = 0;
+ switch (var)
+ {
+ 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;
+ }
+
+ ReleaseSRWLockShared(&serverInfoLock);
+
+ return n;
+}
+int getServerInfoInt(int* out_ptr, ServerInfoType var)
+{
+ AcquireSRWLockShared(&serverInfoLock);
+ int n = 0;
+ switch (var)
+ {
+ 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;
+ }
+
+ ReleaseSRWLockShared(&serverInfoLock);
+
+ return n;
+}
+int getServerInfoBool(bool* out_ptr, ServerInfoType var)
+{
+ AcquireSRWLockShared(&serverInfoLock);
+ int n = 0;
+ switch (var)
+ {
+ case ServerInfoType::roundBased:
+ *out_ptr = serverInfo.roundBased;
+ break;
+ default:
+ n = -1;
+ }
+
+ ReleaseSRWLockShared(&serverInfoLock);
+
+ return n;
+}
+
+int getGameStateChar(char* out_buf, size_t out_buf_len, GameStateInfoType var)
+{
+ AcquireSRWLockShared(&gameStateLock);
+ int n = 0;
+ switch (var)
+ {
+ 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;
+ }
+
+ ReleaseSRWLockShared(&gameStateLock);
+
+ return n;
+}
+int getGameStateInt(int* out_ptr, GameStateInfoType var)
+{
+ 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;
+ }
+
+ ReleaseSRWLockShared(&gameStateLock);
+
+ return n;
+}
+int getGameStateBool(bool* out_ptr, GameStateInfoType var)
+{
+ AcquireSRWLockShared(&gameStateLock);
+ int n = 0;
+ switch (var)
+ {
+ case GameStateInfoType::connected:
+ *out_ptr = gameState.connected;
+ break;
+ case GameStateInfoType::loading:
+ *out_ptr = gameState.loading;
+ break;
+ default:
+ n = -1;
+ }
+
+ ReleaseSRWLockShared(&gameStateLock);
+
+ return n;
+}
+
+int getPlayerInfoChar(char* out_buf, size_t out_buf_len, PlayerInfoType var)
+{
+ AcquireSRWLockShared(&playerInfoLock);
+ int n = 0;
+ switch (var)
+ {
+ default:
+ n = -1;
+ }
+
+ ReleaseSRWLockShared(&playerInfoLock);
+
+ return n;
+}
+int getPlayerInfoInt(int* out_ptr, PlayerInfoType var)
+{
+ AcquireSRWLockShared(&playerInfoLock);
+ int n = 0;
+ switch (var)
+ {
+ case PlayerInfoType::uid:
+ *out_ptr = playerInfo.uid;
+ break;
+ default:
+ n = -1;
+ }
+
+ ReleaseSRWLockShared(&playerInfoLock);
+
+ return n;
+}
+int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var)
+{
+ AcquireSRWLockShared(&playerInfoLock);
+ int n = 0;
+ switch (var)
+ {
+ default:
+ n = -1;
+ }
+
+ ReleaseSRWLockShared(&playerInfoLock);
+
+ return n;
+}
+
+void InitialisePluginCommands(HMODULE baseAddress)
+{
+ // 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
+
+ g_UISquirrelManager->AddFuncRegistration(
+ "void", "NSUpdateGameStateUI", "string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading", "",
+ SQ_UpdateGameStateUI);
+ g_ClientSquirrelManager->AddFuncRegistration(
+ "void", "NSUpdateGameStateClient",
+ "int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit", "",
+ SQ_UpdateGameStateClient);
+ g_UISquirrelManager->AddFuncRegistration(
+ "void", "NSUpdateServerInfo",
+ "string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string "
+ "playlistDisplayName",
+ "", SQ_UpdateServerInfo);
+ g_ClientSquirrelManager->AddFuncRegistration(
+ "void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds);
+ g_ClientSquirrelManager->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo);
+ g_UISquirrelManager->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected);
+ g_UISquirrelManager->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer);
+}
diff --git a/NorthstarDedicatedTest/plugins.h b/NorthstarDedicatedTest/plugins.h
new file mode 100644
index 00000000..27312da9
--- /dev/null
+++ b/NorthstarDedicatedTest/plugins.h
@@ -0,0 +1,19 @@
+#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);
+
+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);
+
+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);
+
+void initGameState();
+void* getPluginObject(PluginObject var);
+
+void InitialisePluginCommands(HMODULE baseAddress); \ No newline at end of file