aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDedicatedTest/masterserver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'NorthstarDedicatedTest/masterserver.cpp')
-rw-r--r--NorthstarDedicatedTest/masterserver.cpp304
1 files changed, 233 insertions, 71 deletions
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<LPVOID*>(&CHostState__State_NewGame));
+ ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E5D0, CHostState__State_ChangeLevelMPHook, reinterpret_cast<LPVOID*>(&CHostState__State_ChangeLevelMP));
+ ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E520, CHostState__State_ChangeLevelSPHook, reinterpret_cast<LPVOID*>(&CHostState__State_ChangeLevelSP));
+ ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E640, CHostState__State_GameShutdownHook, reinterpret_cast<LPVOID*>(&CHostState__State_GameShutdown));
} \ No newline at end of file