From 9a3e1ec2daf753106ee95d53719d94921d3b051f Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Mon, 9 Aug 2021 22:21:46 +0100 Subject: add mod localisation, say command and more masterserver stuff --- .../NorthstarDedicatedTest.vcxproj | 2 + .../NorthstarDedicatedTest.vcxproj.filters | 6 + NorthstarDedicatedTest/chatcommand.cpp | 7 +- NorthstarDedicatedTest/dllmain.cpp | 3 +- NorthstarDedicatedTest/filesystem.cpp | 1 + NorthstarDedicatedTest/gameutils.cpp | 19 +- NorthstarDedicatedTest/gameutils.h | 30 ++ NorthstarDedicatedTest/masterserver.cpp | 304 ++++++++++++++++----- NorthstarDedicatedTest/masterserver.h | 15 +- NorthstarDedicatedTest/modlocalisation.cpp | 38 +++ NorthstarDedicatedTest/modlocalisation.h | 3 + NorthstarDedicatedTest/modmanager.cpp | 12 + NorthstarDedicatedTest/modmanager.h | 2 + NorthstarDedicatedTest/pch.h | 1 + NorthstarDedicatedTest/scriptserverbrowser.cpp | 50 +++- NorthstarDedicatedTest/serverauthentication.cpp | 27 ++ NorthstarDedicatedTest/serverauthentication.h | 5 +- NorthstarDedicatedTest/squirrel.cpp | 4 +- 18 files changed, 443 insertions(+), 86 deletions(-) create mode 100644 NorthstarDedicatedTest/modlocalisation.cpp create mode 100644 NorthstarDedicatedTest/modlocalisation.h (limited to 'NorthstarDedicatedTest') diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj index 234894fa..b6ceebf1 100644 --- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj +++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj @@ -310,6 +310,7 @@ + @@ -334,6 +335,7 @@ + diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters index 0da3d860..041c8e84 100644 --- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters +++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters @@ -561,6 +561,9 @@ Header Files\Client + + Header Files\Client + @@ -635,6 +638,9 @@ Source Files\Client + + Source Files\Client + diff --git a/NorthstarDedicatedTest/chatcommand.cpp b/NorthstarDedicatedTest/chatcommand.cpp index 866a1d79..4742b5fd 100644 --- a/NorthstarDedicatedTest/chatcommand.cpp +++ b/NorthstarDedicatedTest/chatcommand.cpp @@ -2,19 +2,20 @@ #include "chatcommand.h" #include "concommand.h" -typedef void(__fastcall *ClientSayTextType)(void* a1, const char* message, char unknownAlways1, bool isTeamChat); +// note: isIngameChat is an int64 because the whole register the arg is stored in needs to be 0'd out to work +typedef void(__fastcall *ClientSayTextType)(void* a1, const char* message, __int64 isIngameChat, bool isTeamChat); ClientSayTextType SayText; void ConCommand_say(const CCommand& args) { if (args.ArgC() >= 2) - SayText(nullptr, args.Arg(1), 0, false); + SayText(nullptr, args.ArgS(), true, false); } void ConCommand_say_team(const CCommand& args) { if (args.ArgC() >= 2) - SayText(nullptr, args.Arg(1), 0, true); + SayText(nullptr, args.ArgS(), true, true); } void InitialiseChatCommands(HMODULE baseAddress) diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp index f5118900..8cea40a0 100644 --- a/NorthstarDedicatedTest/dllmain.cpp +++ b/NorthstarDedicatedTest/dllmain.cpp @@ -15,6 +15,7 @@ #include "masterserver.h" #include "gameutils.h" #include "chatcommand.h" +#include "modlocalisation.h" bool initialised = false; @@ -60,11 +61,11 @@ void InitialiseNorthstar() if (!IsDedicated()) { AddDllLoadCallback("client.dll", InitialiseClientSquirrel); - AddDllLoadCallback("client.dll", InitialiseSourceConsole); AddDllLoadCallback("engine.dll", InitialiseChatCommands); AddDllLoadCallback("client.dll", InitialiseScriptModMenu); AddDllLoadCallback("client.dll", InitialiseScriptServerBrowser); + AddDllLoadCallback("localize.dll", InitialiseModLocalisation); } AddDllLoadCallback("server.dll", InitialiseServerSquirrel); diff --git a/NorthstarDedicatedTest/filesystem.cpp b/NorthstarDedicatedTest/filesystem.cpp index 15b8b4dd..458cfd5c 100644 --- a/NorthstarDedicatedTest/filesystem.cpp +++ b/NorthstarDedicatedTest/filesystem.cpp @@ -28,6 +28,7 @@ FileHandle_t ReadFileFromFilesystemHook(IFileSystem* filesystem, const char* pPa bool readingOriginalFile; std::string currentModPath; SourceInterface* g_Filesystem; +void* g_pVGuiLocalize; void InitialiseFilesystem(HMODULE baseAddress) { diff --git a/NorthstarDedicatedTest/gameutils.cpp b/NorthstarDedicatedTest/gameutils.cpp index 9d61ab0a..1cf919fb 100644 --- a/NorthstarDedicatedTest/gameutils.cpp +++ b/NorthstarDedicatedTest/gameutils.cpp @@ -1,13 +1,28 @@ #include "pch.h" #include "gameutils.h" +#include "convar.h" // cmd.h - Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; Cbuf_AddTextType Cbuf_AddText; +// hoststate stuff +CHostState* g_GameCHostStateSingleton; + +// network stuff +ConVar* Cvar_hostport; + +// playlist stuff +GetCurrentPlaylistType GetCurrentPlaylistName; + void InitialiseEngineGameUtilFunctions(HMODULE baseAddress) { Cbuf_GetCurrentPlayer = (Cbuf_GetCurrentPlayerType)((char*)baseAddress + 0x120630); Cbuf_AddText = (Cbuf_AddTextType)((char*)baseAddress + 0x1203B0); -} \ No newline at end of file + + g_GameCHostStateSingleton = (CHostState*)((char*)baseAddress + 0x7CF180); + + Cvar_hostport = (ConVar*)((char*)baseAddress + 0x1FA6070); + + GetCurrentPlaylistName = (GetCurrentPlaylistType)((char*)baseAddress + 0x18C640); +} diff --git a/NorthstarDedicatedTest/gameutils.h b/NorthstarDedicatedTest/gameutils.h index 4c2254e7..042e9bd5 100644 --- a/NorthstarDedicatedTest/gameutils.h +++ b/NorthstarDedicatedTest/gameutils.h @@ -1,4 +1,5 @@ #pragma once +#include "convar.h" // cmd.h @@ -54,4 +55,33 @@ extern Cbuf_GetCurrentPlayerType Cbuf_GetCurrentPlayer; typedef void(*Cbuf_AddTextType)(ECommandTarget_t eTarget, const char* text, cmd_source_t source); extern Cbuf_AddTextType Cbuf_AddText; + +// hoststate stuff + +struct CHostState +{ +public: + int32_t m_iCurrentState; + int32_t m_iNextState; + + float m_vecLocation[3]; + float m_angLocation[3]; + + char m_levelName[32]; + + // not reversed past this point, struct seems weird +}; + +extern CHostState* g_GameCHostStateSingleton; + +// network stuff + +extern ConVar* Cvar_hostport; + + +// playlist stuff + +typedef const char*(*GetCurrentPlaylistType)(); +extern GetCurrentPlaylistType GetCurrentPlaylistName; + void InitialiseEngineGameUtilFunctions(HMODULE baseAddress); \ No newline at end of file diff --git a/NorthstarDedicatedTest/masterserver.cpp b/NorthstarDedicatedTest/masterserver.cpp index d8b765ec..b8d1cd73 100644 --- a/NorthstarDedicatedTest/masterserver.cpp +++ b/NorthstarDedicatedTest/masterserver.cpp @@ -1,62 +1,54 @@ #include "pch.h" #include "masterserver.h" +#include "concommand.h" +#include "gameutils.h" +#include "hookutils.h" #include "httplib.h" #include "rapidjson/document.h" #include "rapidjson/error/en.h" -#include "concommand.h" - ConVar* Cvar_ns_masterserver_hostname; ConVar* Cvar_ns_masterserver_port; +ConVar* Cvar_ns_report_server_to_masterserver; +ConVar* Cvar_ns_report_sp_server_to_masterserver; -MasterServerManager* g_MasterServerManager; +ConVar* Cvar_ns_server_name; +ConVar* Cvar_ns_server_desc; +ConVar* Cvar_ns_server_password; -// requires password constructor -RemoteServerInfo::RemoteServerInfo(const char* newId, const char* newName, const char* newDescription, const char* newMap, const char* newPlaylist, int newPlayerCount, int newMaxPlayers) -{ - // passworded servers don't have public ips - requiresPassword = true; - memset(&ip, 0, sizeof(ip)); - port = 0; +MasterServerManager* g_MasterServerManager; - strncpy((char*)id, newId, 31); - id[31] = 0; - strncpy((char*)name, newName, 63); - name[63] = 0; +typedef void(*CHostState__State_NewGameType)(CHostState* hostState); +CHostState__State_NewGameType CHostState__State_NewGame; - description = std::string(newDescription); +typedef void(*CHostState__State_ChangeLevelMPType)(CHostState* hostState); +CHostState__State_ChangeLevelMPType CHostState__State_ChangeLevelMP; - strncpy((char*)map, newMap, 31); - map[31] = 0; - strncpy((char*)playlist, newPlaylist, 15); - playlist[15] = 0; +typedef void(*CHostState__State_ChangeLevelSPType)(CHostState* hostState); +CHostState__State_ChangeLevelSPType CHostState__State_ChangeLevelSP; - playerCount = newPlayerCount; - maxPlayers = newMaxPlayers; -} +typedef void(*CHostState__State_GameShutdownType)(CHostState* hostState); +CHostState__State_GameShutdownType CHostState__State_GameShutdown; -// doesnt require password constructor -RemoteServerInfo::RemoteServerInfo(const char* newId, const char* newName, const char* newDescription, const char* newMap, const char* newPlaylist, int newPlayerCount, int newMaxPlayers, in_addr newIp, int newPort) +RemoteServerInfo::RemoteServerInfo(const char* newId, const char* newName, const char* newDescription, const char* newMap, const char* newPlaylist, int newPlayerCount, int newMaxPlayers, bool newRequiresPassword) { - requiresPassword = false; + // passworded servers don't have public ips + requiresPassword = newRequiresPassword; - strncpy((char*)id, newId, 31); - id[31] = 0; - strncpy((char*)name, newName, 63); - name[63] = 0; + strncpy((char*)id, newId, sizeof(id)); + id[sizeof(id) - 1] = 0; + strncpy((char*)name, newName, sizeof(name)); + name[sizeof(name) - 1] = 0; description = std::string(newDescription); - strncpy((char*)map, newMap, 31); - map[31] = 0; - strncpy((char*)playlist, newPlaylist, 15); - playlist[15] = 0; + strncpy((char*)map, newMap, sizeof(map)); + map[sizeof(map) - 1] = 0; + strncpy((char*)playlist, newPlaylist, sizeof(playlist)); + playlist[sizeof(playlist) - 1] = 0; playerCount = newPlayerCount; maxPlayers = newMaxPlayers; - - ip = newIp; - port = newPort; } void MasterServerManager::ClearServerList() @@ -142,25 +134,15 @@ void MasterServerManager::RequestServerList() goto REQUEST_END_CLEANUP; } - bool hasPassword = serverObj["hasPassword"].GetBool(); const char* id = serverObj["id"].GetString(); bool createNewServerInfo = true; for (RemoteServerInfo& server : m_remoteServers) { // if server already exists, update info rather than adding to it - if (!strncmp((const char*)server.id, id, 31)) + if (!strncmp((const char*)server.id, id, 32)) { - if (hasPassword) - server = RemoteServerInfo(id, serverObj["name"].GetString(), serverObj["description"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt()); - else - { - in_addr addr; - addr.S_un.S_addr = serverObj["ip"].GetUint64(); - - server = RemoteServerInfo(id, serverObj["name"].GetString(), serverObj["description"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt(), addr, serverObj["port"].GetInt()); - } - + server = RemoteServerInfo(id, serverObj["name"].GetString(), serverObj["description"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt(), serverObj["hasPassword"].IsTrue()); createNewServerInfo = false; break; } @@ -168,24 +150,7 @@ void MasterServerManager::RequestServerList() // server didn't exist if (createNewServerInfo) - { - // passworded servers shouldn't send ip/port - if (hasPassword) - m_remoteServers.emplace_back(id, serverObj["name"].GetString(), serverObj["description"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt()); - else - { - if (!serverObj.HasMember("ip") || !serverObj["ip"].IsUint64() || !serverObj.HasMember("port") || !serverObj["port"].IsNumber()) - { - spdlog::error("Failed reading masterserver response: malformed server object"); - goto REQUEST_END_CLEANUP; - } - - in_addr addr; - addr.S_un.S_addr = serverObj["ip"].GetUint64(); - - m_remoteServers.emplace_back(id, serverObj["name"].GetString(), serverObj["description"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt(), addr, serverObj["port"].GetInt()); - } - } + m_remoteServers.emplace_back(id, serverObj["name"].GetString(), serverObj["description"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt(), serverObj["hasPassword"].IsTrue()); spdlog::info("Server {} on map {} with playlist {} has {}/{} players", serverObj["name"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt()); } @@ -205,7 +170,7 @@ void MasterServerManager::RequestServerList() requestThread.detach(); } -void MasterServerManager::TryAuthenticateWithServer(char* serverId, char* password) +void MasterServerManager::AuthenticateWithServer(char* serverId, char* password) { // dont wait, just stop if we're trying to do 2 auth requests at once if (m_authenticatingWithGameServer) @@ -222,8 +187,6 @@ void MasterServerManager::TryAuthenticateWithServer(char* serverId, char* passwo spdlog::info("Attempting authentication with server of id \"{}\"", serverId); - spdlog::info(fmt::format("/client/auth_with_server?server={}&password={}", serverId, password)); - if (auto result = http.Post(fmt::format("/client/auth_with_server?server={}&password={}", serverId, password).c_str())) { m_successfullyConnected = true; @@ -256,13 +219,13 @@ void MasterServerManager::TryAuthenticateWithServer(char* serverId, char* passwo goto REQUEST_END_CLEANUP; } - if (!connectionInfoJson.HasMember("success") || !connectionInfoJson.HasMember("ip") || !connectionInfoJson["ip"].IsUint64() || !connectionInfoJson.HasMember("port") || !connectionInfoJson["port"].IsNumber() || !connectionInfoJson.HasMember("authToken") || !connectionInfoJson["authToken"].IsString()) + if (!connectionInfoJson.HasMember("success") || !connectionInfoJson.HasMember("ip") || !connectionInfoJson["ip"].IsString() || !connectionInfoJson.HasMember("port") || !connectionInfoJson["port"].IsNumber() || !connectionInfoJson.HasMember("authToken") || !connectionInfoJson["authToken"].IsString()) { spdlog::error("Failed reading masterserver authentication response: malformed json object"); goto REQUEST_END_CLEANUP; } - m_pendingConnectionInfo.ip.S_un.S_addr = connectionInfoJson["ip"].GetUint64(); + m_pendingConnectionInfo.ip.S_un.S_addr = inet_addr(connectionInfoJson["ip"].GetString()); m_pendingConnectionInfo.port = connectionInfoJson["port"].GetInt(); strncpy(m_pendingConnectionInfo.authToken, connectionInfoJson["authToken"].GetString(), 31); @@ -286,16 +249,215 @@ void MasterServerManager::TryAuthenticateWithServer(char* serverId, char* passwo requestThread.detach(); } +void MasterServerManager::AddSelfToServerList(int port, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password) +{ + if (!Cvar_ns_report_server_to_masterserver->m_nValue) + return; + + if (!Cvar_ns_report_sp_server_to_masterserver->m_nValue && !strncmp(map, "sp_", 3)) + return; + + std::thread requestThread([this, port, name, description, map, playlist, maxPlayers, password] { + httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString, Cvar_ns_masterserver_port->m_nValue); + http.set_connection_timeout(20); + + m_ownServerId[0] = 0; + + std::string request; + if (*password) + request = fmt::format("/server/add_server?port={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", port, name, description, map, playlist, maxPlayers, password); + else + request = fmt::format("/server/add_server?port={}&name={}&description={}&map={}&playlist={}&maxPlayers={}", port, name, description, map, playlist, maxPlayers); + + if (auto result = http.Post(request.c_str())) + { + m_successfullyConnected = true; + + rapidjson::Document serverAddedJson; + serverAddedJson.Parse(result->body.c_str()); + + if (serverAddedJson.HasParseError()) + { + spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(serverAddedJson.GetParseError())); + return; + } + + if (!serverAddedJson.IsObject()) + { + spdlog::error("Failed reading masterserver authentication response: root object is not an object"); + return; + } + + if (serverAddedJson.HasMember("error")) + { + spdlog::error("Failed reading masterserver response: got fastify error response"); + spdlog::error(result->body); + return; + } + + if (!serverAddedJson["success"].IsTrue()) + { + spdlog::error("Adding server to masterserver failed: \"success\" is not true"); + return; + } + + if (!serverAddedJson.HasMember("id") || !serverAddedJson["id"].IsString()) + { + spdlog::error("Failed reading masterserver response: malformed json object"); + return; + } + + strncpy(m_ownServerId, serverAddedJson["id"].GetString(), sizeof(m_ownServerId)); + m_ownServerId[sizeof(m_ownServerId) - 1] = 0; + + + // heartbeat thread + std::thread heartbeatThread([this] { + httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString, Cvar_ns_masterserver_port->m_nValue); + http.set_connection_timeout(10); + + while (*m_ownServerId) + { + Sleep(15000); + http.Post(fmt::format("/server/heartbeat?id={}", m_ownServerId).c_str()); + } + }); + + heartbeatThread.detach(); + } + else + { + spdlog::error("Failed authenticating with server: error {}", result.error()); + m_successfullyConnected = false; + } + }); + + requestThread.detach(); +} + +void MasterServerManager::UpdateServerMapAndPlaylist(char* map, char* playlist) +{ + // dont call this if we don't have a server id + if (!*m_ownServerId) + return; + + std::thread requestThread([this, map, playlist] { + httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString, Cvar_ns_masterserver_port->m_nValue); + http.set_connection_timeout(10); + + // we dont process this at all atm, maybe do later, but atm not necessary + if (auto result = http.Post(fmt::format("/server/update_values?id={}&map={}&playlist={}", m_ownServerId, map, playlist).c_str())) + { + m_successfullyConnected = true; + } + else + { + m_successfullyConnected = false; + } + }); + + requestThread.detach(); +} + +void MasterServerManager::UpdateServerPlayerCount(int playerCount) +{ + // dont call this if we don't have a server id + if (!*m_ownServerId) + return; + + std::thread requestThread([this, playerCount] { + httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString, Cvar_ns_masterserver_port->m_nValue); + http.set_connection_timeout(10); + + // we dont process this at all atm, maybe do later, but atm not necessary + if (auto result = http.Post(fmt::format("/server/update_values?id={}&playerCount={}", m_ownServerId, playerCount).c_str())) + { + m_successfullyConnected = true; + } + else + { + m_successfullyConnected = false; + } + }); + + requestThread.detach(); +} + +void MasterServerManager::RemoveSelfFromServerList() +{ + // dont call this if we don't have a server id + if (!*m_ownServerId || !Cvar_ns_report_server_to_masterserver->m_nValue) + return; + + std::thread requestThread([this] { + httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString, Cvar_ns_masterserver_port->m_nValue); + http.set_connection_timeout(10); + + // we dont process this at all atm, maybe do later, but atm not necessary + if (auto result = http.Delete(fmt::format("/server/remove_server?id={}", m_ownServerId).c_str())) + { + m_successfullyConnected = true; + } + else + { + m_successfullyConnected = false; + } + + m_ownServerId[0] = 0; + }); + + requestThread.detach(); +} + void ConCommand_ns_fetchservers(const CCommand& args) { g_MasterServerManager->RequestServerList(); } +void CHostState__State_NewGameHook(CHostState* hostState) +{ + // not 100% we should do this here, but whatever + Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode); + + g_MasterServerManager->AddSelfToServerList(Cvar_hostport->m_nValue, Cvar_ns_server_name->m_pszString, Cvar_ns_server_desc->m_pszString, hostState->m_levelName, (char*)GetCurrentPlaylistName(), 16, Cvar_ns_server_password->m_pszString); + CHostState__State_NewGame(hostState); +} + +void CHostState__State_ChangeLevelMPHook(CHostState* hostState) +{ + g_MasterServerManager->UpdateServerMapAndPlaylist(hostState->m_levelName, (char*)GetCurrentPlaylistName()); + CHostState__State_ChangeLevelMP(hostState); +} + +void CHostState__State_ChangeLevelSPHook(CHostState* hostState) +{ + g_MasterServerManager->UpdateServerMapAndPlaylist(hostState->m_levelName, (char*)GetCurrentPlaylistName()); + CHostState__State_ChangeLevelSP(hostState); +} + +void CHostState__State_GameShutdownHook(CHostState* hostState) +{ + g_MasterServerManager->RemoveSelfFromServerList(); + CHostState__State_GameShutdown(hostState); +} + void InitialiseSharedMasterServer(HMODULE baseAddress) { Cvar_ns_masterserver_hostname = RegisterConVar("ns_masterserver_hostname", "localhost", FCVAR_NONE, ""); Cvar_ns_masterserver_port = RegisterConVar("ns_masterserver_port", "8080", FCVAR_NONE, ""); + + Cvar_ns_server_name = RegisterConVar("ns_server_name", "Unnamed Northstar Server", FCVAR_GAMEDLL, ""); + Cvar_ns_server_desc = RegisterConVar("ns_server_desc", "Default server description", FCVAR_GAMEDLL, ""); + Cvar_ns_server_password = RegisterConVar("ns_server_password", "", FCVAR_GAMEDLL, ""); + Cvar_ns_report_server_to_masterserver = RegisterConVar("ns_report_server_to_masterserver", "1", FCVAR_GAMEDLL, ""); + Cvar_ns_report_sp_server_to_masterserver = RegisterConVar("ns_report_sp_server_to_masterserver", "0", FCVAR_GAMEDLL, ""); g_MasterServerManager = new MasterServerManager; RegisterConCommand("ns_fetchservers", ConCommand_ns_fetchservers, "", FCVAR_CLIENTDLL); + + HookEnabler hook; + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E7D0, CHostState__State_NewGameHook, reinterpret_cast(&CHostState__State_NewGame)); + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E5D0, CHostState__State_ChangeLevelMPHook, reinterpret_cast(&CHostState__State_ChangeLevelMP)); + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E520, CHostState__State_ChangeLevelSPHook, reinterpret_cast(&CHostState__State_ChangeLevelSP)); + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E640, CHostState__State_GameShutdownHook, reinterpret_cast(&CHostState__State_GameShutdown)); } \ No newline at end of file diff --git a/NorthstarDedicatedTest/masterserver.h b/NorthstarDedicatedTest/masterserver.h index 76e19b48..9b8e8a89 100644 --- a/NorthstarDedicatedTest/masterserver.h +++ b/NorthstarDedicatedTest/masterserver.h @@ -5,7 +5,7 @@ class RemoteServerInfo { public: - char id[32]; + char id[33]; // 32 bytes + nullterminator // server info char name[64]; @@ -18,12 +18,9 @@ public: // connection stuff bool requiresPassword; - in_addr ip; - int port; public: - RemoteServerInfo(const char* newId, const char* newName, const char* newDescription, const char* newMap, const char* newPlaylist, int newPlayerCount, int newMaxPlayers); - RemoteServerInfo(const char* newId, const char* newName, const char* newDescription, const char* newMap, const char* newPlaylist, int newPlayerCount, int newMaxPlayers, in_addr newIp, int newPort); + RemoteServerInfo(const char* newId, const char* newName, const char* newDescription, const char* newMap, const char* newPlaylist, int newPlayerCount, int newMaxPlayers, bool newRequiresPassword); }; struct RemoteServerConnectionInfo @@ -42,6 +39,8 @@ private: bool m_authenticatingWithGameServer = false; public: + char m_ownServerId[33]; + bool m_scriptRequestingServerList = false; bool m_successfullyConnected = true; @@ -56,7 +55,11 @@ public: public: void ClearServerList(); void RequestServerList(); - void TryAuthenticateWithServer(char* serverId, char* password); + void AuthenticateWithServer(char* serverId, char* password); + void AddSelfToServerList(int port, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password); + void UpdateServerMapAndPlaylist(char* map, char* playlist); + void UpdateServerPlayerCount(int playerCount); + void RemoveSelfFromServerList(); }; void InitialiseSharedMasterServer(HMODULE baseAddress); diff --git a/NorthstarDedicatedTest/modlocalisation.cpp b/NorthstarDedicatedTest/modlocalisation.cpp new file mode 100644 index 00000000..ccc06722 --- /dev/null +++ b/NorthstarDedicatedTest/modlocalisation.cpp @@ -0,0 +1,38 @@ +#include "pch.h" +#include "modlocalisation.h" +#include "hookutils.h" +#include "modmanager.h" + +typedef char(*AddLocalisationFileType)(void* g_pVguiLocalize, const char* path, const char* pathId); +AddLocalisationFileType AddLocalisationFile; + +bool loadModLocalisationFiles = true; + +char AddLocalisationFileHook(void* g_pVguiLocalize, const char* path, char* pathId) +{ + char ret = AddLocalisationFile(g_pVguiLocalize, path, pathId); + + if (!loadModLocalisationFiles) + return ret; + + loadModLocalisationFiles = false; + + for (Mod* mod : g_ModManager->m_loadedMods) + { + for (std::string& localisationFile : mod->LocalisationFiles) + { + spdlog::info("Adding mod localisation file {}", localisationFile); + AddLocalisationFile(g_pVguiLocalize, localisationFile.c_str(), pathId); + } + } + + loadModLocalisationFiles = true; + + return ret; +} + +void InitialiseModLocalisation(HMODULE baseAddress) +{ + HookEnabler hook; + ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x6D80, AddLocalisationFileHook, reinterpret_cast(&AddLocalisationFile)); +} \ No newline at end of file diff --git a/NorthstarDedicatedTest/modlocalisation.h b/NorthstarDedicatedTest/modlocalisation.h new file mode 100644 index 00000000..b7d9cd50 --- /dev/null +++ b/NorthstarDedicatedTest/modlocalisation.h @@ -0,0 +1,3 @@ +#pragma once + +void InitialiseModLocalisation(HMODULE baseAddress); \ No newline at end of file diff --git a/NorthstarDedicatedTest/modmanager.cpp b/NorthstarDedicatedTest/modmanager.cpp index 342736e6..dedbbbc8 100644 --- a/NorthstarDedicatedTest/modmanager.cpp +++ b/NorthstarDedicatedTest/modmanager.cpp @@ -163,6 +163,17 @@ Mod::Mod(fs::path modDir, char* jsonBuf) } } + if (modJson.HasMember("Localisation") && modJson["Localisation"].IsArray()) + { + for (auto& localisationStr : modJson["Localisation"].GetArray()) + { + if (!localisationStr.IsString()) + continue; + + LocalisationFiles.push_back(localisationStr.GetString()); + } + } + wasReadSuccessfully = true; } @@ -237,6 +248,7 @@ void ModManager::LoadMods() if (fs::is_regular_file(file) && file.path().extension() == "vpk") mod->Vpks.push_back(file.path().string()); + // read keyvalues paths if (fs::exists(mod->ModDirectory / "keyvalues")) { for (fs::directory_entry file : fs::recursive_directory_iterator(mod->ModDirectory / "keyvalues")) diff --git a/NorthstarDedicatedTest/modmanager.h b/NorthstarDedicatedTest/modmanager.h index ad5e74ff..079e1613 100644 --- a/NorthstarDedicatedTest/modmanager.h +++ b/NorthstarDedicatedTest/modmanager.h @@ -70,6 +70,8 @@ public: std::vector Scripts; // convars created by the mod std::vector ConVars; + // custom localisation files created by the mod + std::vector LocalisationFiles; // other files: diff --git a/NorthstarDedicatedTest/pch.h b/NorthstarDedicatedTest/pch.h index 04e5847d..19192f04 100644 --- a/NorthstarDedicatedTest/pch.h +++ b/NorthstarDedicatedTest/pch.h @@ -5,6 +5,7 @@ #define _CRT_SECURE_NO_WARNINGS #define RAPIDJSON_NOMEMBERITERATORCLASS // need this for rapidjson #define NOMINMAX // this too +#define _WINSOCK_DEPRECATED_NO_WARNINGS // temp because i'm very lazy and want to use inet_addr, remove later // add headers that you want to pre-compile here #include diff --git a/NorthstarDedicatedTest/scriptserverbrowser.cpp b/NorthstarDedicatedTest/scriptserverbrowser.cpp index 419ed274..c5446031 100644 --- a/NorthstarDedicatedTest/scriptserverbrowser.cpp +++ b/NorthstarDedicatedTest/scriptserverbrowser.cpp @@ -49,6 +49,21 @@ SQInteger SQ_GetServerName(void* sqvm) return 1; } +// string function NSGetServerDescription( int serverIndex ) +SQInteger SQ_GetServerDescription(void* sqvm) +{ + SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + + if (serverIndex >= g_MasterServerManager->m_remoteServers.size()) + { + spdlog::warn("Tried to get description of server index {} when only {} servers are available", serverIndex, g_MasterServerManager->m_remoteServers.size()); + return 0; + } + + ClientSq_pushstring(sqvm, g_MasterServerManager->m_remoteServers[serverIndex].description.c_str(), -1); + return 1; +} + // string function NSGetServerMap( int serverIndex ) SQInteger SQ_GetServerMap(void* sqvm) { @@ -79,6 +94,36 @@ SQInteger SQ_GetServerPlaylist(void* sqvm) return 1; } +// int function NSGetServerPlayerCount( int serverIndex ) +SQInteger SQ_GetServerPlayerCount(void* sqvm) +{ + SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + + if (serverIndex >= g_MasterServerManager->m_remoteServers.size()) + { + spdlog::warn("Tried to get playercount of server index {} when only {} servers are available", serverIndex, g_MasterServerManager->m_remoteServers.size()); + return 0; + } + + ClientSq_pushinteger(sqvm, g_MasterServerManager->m_remoteServers[serverIndex].playerCount); + return 1; +} + +// int function NSGetServerMaxPlayerCount( int serverIndex ) +SQInteger SQ_GetServerMaxPlayerCount(void* sqvm) +{ + SQInteger serverIndex = ClientSq_getinteger(sqvm, 1); + + if (serverIndex >= g_MasterServerManager->m_remoteServers.size()) + { + spdlog::warn("Tried to get max playercount of server index {} when only {} servers are available", serverIndex, g_MasterServerManager->m_remoteServers.size()); + return 0; + } + + ClientSq_pushinteger(sqvm, g_MasterServerManager->m_remoteServers[serverIndex].maxPlayers); + return 1; +} + // string function NSGetServerID( int serverIndex ) SQInteger SQ_GetServerID(void* sqvm) { @@ -131,7 +176,7 @@ SQInteger SQ_TryAuthWithServer(void* sqvm) } // do auth - g_MasterServerManager->TryAuthenticateWithServer(g_MasterServerManager->m_remoteServers[serverIndex].id, (char*)password); + g_MasterServerManager->AuthenticateWithServer(g_MasterServerManager->m_remoteServers[serverIndex].id, (char*)password); return 0; } @@ -181,8 +226,11 @@ void InitialiseScriptServerBrowser(HMODULE baseAddress) g_UISquirrelManager->AddFuncRegistration("void", "NSClearRecievedServerList", "", "", SQ_ClearRecievedServerList); g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerName", "int serverIndex", "", SQ_GetServerName); + g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerDescription", "int serverIndex", "", SQ_GetServerDescription); g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerMap", "int serverIndex", "", SQ_GetServerMap); g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerPlaylist", "int serverIndex", "", SQ_GetServerPlaylist); + g_UISquirrelManager->AddFuncRegistration("int", "NSGetServerPlayerCount", "int serverIndex", "", SQ_GetServerPlayerCount); + g_UISquirrelManager->AddFuncRegistration("int", "NSGetServerMaxPlayerCount", "int serverIndex", "", SQ_GetServerMaxPlayerCount); g_UISquirrelManager->AddFuncRegistration("string", "NSGetServerID", "int serverIndex", "", SQ_GetServerID); g_UISquirrelManager->AddFuncRegistration("bool", "NSServerRequiresPassword", "int serverIndex", "", SQ_ServerRequiresPassword); diff --git a/NorthstarDedicatedTest/serverauthentication.cpp b/NorthstarDedicatedTest/serverauthentication.cpp index 820cb69d..964c6b16 100644 --- a/NorthstarDedicatedTest/serverauthentication.cpp +++ b/NorthstarDedicatedTest/serverauthentication.cpp @@ -2,8 +2,11 @@ #include "serverauthentication.h" #include "convar.h" #include "hookutils.h" +#include "masterserver.h" +#include "httplib.h" #include #include +#include // hook types @@ -25,10 +28,25 @@ CGameClient__ExecuteStringCommandType CGameClient__ExecuteStringCommand; // global vars ServerAuthenticationManager* g_ServerAuthenticationManager; +ConVar* Cvar_ns_player_auth_port; ConVar* CVar_ns_auth_allow_insecure; ConVar* CVar_ns_auth_allow_insecure_write; ConVar* CVar_sv_quota_stringcmdspersecond; +void ServerAuthenticationManager::StartPlayerAuthServer() +{ + m_runningPlayerAuthThread = true; + + std::thread serverThread([this] { + while (m_runningPlayerAuthThread) + { + + } + }); + + serverThread.detach(); +} + void ServerAuthenticationManager::AddPlayerAuthData(char* authToken, char* uid, char* pdata, size_t pdataSize) { @@ -122,6 +140,8 @@ void ServerAuthenticationManager::WritePersistentData(void* player) // auth hooks +int playerCount = 0; // temp + // store these in vars so we can use them in CBaseClient::Connect // this is fine because ptrs won't decay by the time we use this, just don't use it outside of cbaseclient::connect char* nextPlayerToken; @@ -146,6 +166,8 @@ char CBaseClient__ConnectHook(void* self, char* name, __int64 netchan_ptr_arg, c else if (!g_ServerAuthenticationManager->AuthenticatePlayer(self, nextPlayerUid, nextPlayerToken)) CBaseClient__Disconnect(self, 1, "Authentication Failed"); + playerCount++; + return ret; } @@ -154,7 +176,10 @@ void CBaseClient__ActivatePlayerHook(void* self) // if we're authed, write our persistent data // RemovePlayerAuthData returns true if it removed successfully, i.e. on first call only, and we only want to write on >= second call (since this func is called on map loads) if (*((char*)self + 0x4A0) >= (char)0x3 && !g_ServerAuthenticationManager->RemovePlayerAuthData(self)) + { g_ServerAuthenticationManager->WritePersistentData(self); + g_MasterServerManager->UpdateServerPlayerCount(playerCount); + } CBaseClient__ActivatePlayer(self); } @@ -172,6 +197,8 @@ void CBaseClient__DisconnectHook(void* self, uint32_t unknownButAlways1, const c // dcing, write persistent data g_ServerAuthenticationManager->WritePersistentData(self); + g_MasterServerManager->UpdateServerPlayerCount(playerCount = std::max(playerCount - 1, 0)); + CBaseClient__Disconnect(self, unknownButAlways1, buf); } diff --git a/NorthstarDedicatedTest/serverauthentication.h b/NorthstarDedicatedTest/serverauthentication.h index 66802f31..e9aae4f0 100644 --- a/NorthstarDedicatedTest/serverauthentication.h +++ b/NorthstarDedicatedTest/serverauthentication.h @@ -15,8 +15,10 @@ class ServerAuthenticationManager { public: std::unordered_map m_authData; + bool m_runningPlayerAuthThread = false; public: + void StartPlayerAuthServer(); void AddPlayerAuthData(char* authToken, char* uid, char* pdata, size_t pdataSize); bool AuthenticatePlayer(void* player, int64_t uid, char* authToken); bool RemovePlayerAuthData(void* player); @@ -25,4 +27,5 @@ public: void InitialiseServerAuthentication(HMODULE baseAddress); -extern ServerAuthenticationManager* g_ServerAuthenticationManager; \ No newline at end of file +extern ServerAuthenticationManager* g_ServerAuthenticationManager; +extern ConVar* Cvar_ns_player_auth_port; \ No newline at end of file diff --git a/NorthstarDedicatedTest/squirrel.cpp b/NorthstarDedicatedTest/squirrel.cpp index 90988eca..50e24075 100644 --- a/NorthstarDedicatedTest/squirrel.cpp +++ b/NorthstarDedicatedTest/squirrel.cpp @@ -163,7 +163,9 @@ void InitialiseServerSquirrel(HMODULE baseAddress) ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x799E0, &ScriptCompileErrorHook, reinterpret_cast(&ServerSQCompileError)); // server compileerror function ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x1D5C0, &CallScriptInitCallbackHook, reinterpret_cast(&ServerCallScriptInitCallback)); // server callscriptinitcallback function - RegisterConCommand("script", ExecuteCodeCommand, "Executes script code on the server vm", FCVAR_GAMEDLL); + // cheat and clientcmd_can_execute allows clients to execute this, but since it's unsafe we only allow it when cheats are enabled + // for script_client and script_ui, we don't use cheats, so clients can execute them on themselves all they want + RegisterConCommand("script", ExecuteCodeCommand, "Executes script code on the server vm", FCVAR_GAMEDLL | FCVAR_CLIENTCMD_CAN_EXECUTE | FCVAR_CHEAT); } // hooks -- cgit v1.2.3