diff options
Diffstat (limited to 'NorthstarDLL/masterserver.cpp')
-rw-r--r-- | NorthstarDLL/masterserver.cpp | 1048 |
1 files changed, 429 insertions, 619 deletions
diff --git a/NorthstarDLL/masterserver.cpp b/NorthstarDLL/masterserver.cpp index 62d037be..fc747e5d 100644 --- a/NorthstarDLL/masterserver.cpp +++ b/NorthstarDLL/masterserver.cpp @@ -1,151 +1,29 @@ #include "pch.h" #include "masterserver.h" #include "concommand.h" -#include "gameutils.h" -#include "hookutils.h" +#include "playlist.h" #include "serverauthentication.h" -#include "gameutils.h" +#include "tier0.h" +#include "r2engine.h" +#include "modmanager.h" +#include "misccommands.h" +#include "version.h" + #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" #include "rapidjson/error/en.h" -#include "modmanager.h" -#include "misccommands.h" + #include <cstring> #include <regex> -#include "version.h" -#include <chrono> -// NOTE for anyone reading this: we used to use httplib for requests here, but it had issues, so we're moving to curl now for masterserver -// requests so httplib is used exclusively for server stuff now -ConVar* Cvar_ns_masterserver_hostname; -ConVar* Cvar_ns_report_server_to_masterserver; -ConVar* Cvar_ns_report_sp_server_to_masterserver; +using namespace std::chrono_literals; -ConVar* Cvar_ns_server_name; -ConVar* Cvar_ns_server_desc; -ConVar* Cvar_ns_server_password; +MasterServerManager* g_pMasterServerManager; +ConVar* Cvar_ns_masterserver_hostname; ConVar* Cvar_ns_curl_log_enable; -// Source ConVar -ConVar* Cvar_hostname; - -MasterServerManager* g_MasterServerManager; - -typedef void (*CHostState__State_NewGameType)(CHostState* hostState); -CHostState__State_NewGameType CHostState__State_NewGame; - -typedef void (*CHostState__State_ChangeLevelMPType)(CHostState* hostState); -CHostState__State_ChangeLevelMPType CHostState__State_ChangeLevelMP; - -typedef void (*CHostState__State_ChangeLevelSPType)(CHostState* hostState); -CHostState__State_ChangeLevelSPType CHostState__State_ChangeLevelSP; - -typedef void (*CHostState__State_GameShutdownType)(CHostState* hostState); -CHostState__State_GameShutdownType CHostState__State_GameShutdown; - -// Convert a hex digit char to integer. -inline int hctod(char c) -{ - if (c >= 'A' && c <= 'F') - { - return c - 'A' + 10; - } - else if (c >= 'a' && c <= 'f') - { - return c - 'a' + 10; - } - else - { - return c - '0'; - } -} - -// This function interprets all 4-hexadecimal-digit unicode codepoint characters like \u4E2D to UTF-8 encoding. -std::string unescape_unicode(const std::string& str) -{ - std::string result; - std::regex r("\\\\u([a-f\\d]{4})", std::regex::icase); - auto matches_begin = std::sregex_iterator(str.begin(), str.end(), r); - auto matches_end = std::sregex_iterator(); - std::smatch last_match; - for (std::sregex_iterator i = matches_begin; i != matches_end; ++i) - { - last_match = *i; - result.append(last_match.prefix()); - unsigned int cp = 0; - for (int i = 2; i <= 5; ++i) - { - cp *= 16; - cp += hctod(last_match.str()[i]); - } - if (cp <= 0x7F) - { - result.push_back(cp); - } - else if (cp <= 0x7FF) - { - result.push_back((cp >> 6) | 0b11000000 & (~(1 << 5))); - result.push_back(cp & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - } - else if (cp <= 0xFFFF) - { - result.push_back((cp >> 12) | 0b11100000 & (~(1 << 4))); - result.push_back((cp >> 6) & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - result.push_back(cp & ((1 << 6) - 1) | 0b10000000 & (~(1 << 6))); - } - } - if (!last_match.ready()) - { - return str; - } - else - { - result.append(last_match.suffix()); - } - return result; -} - -void UpdateServerInfoFromUnicodeToUTF8() -{ - g_MasterServerManager->m_sUnicodeServerName = unescape_unicode(Cvar_ns_server_name->GetString()); - g_MasterServerManager->m_sUnicodeServerDesc = unescape_unicode(Cvar_ns_server_desc->GetString()); -} - -const char* HttplibErrorToString(httplib::Error error) -{ - switch (error) - { - case httplib::Error::Success: - return "httplib::Error::Success"; - case httplib::Error::Unknown: - return "httplib::Error::Unknown"; - case httplib::Error::Connection: - return "httplib::Error::Connection"; - case httplib::Error::BindIPAddress: - return "httplib::Error::BindIPAddress"; - case httplib::Error::Read: - return "httplib::Error::Read"; - case httplib::Error::Write: - return "httplib::Error::Write"; - case httplib::Error::ExceedRedirectCount: - return "httplib::Error::ExceedRedirectCount"; - case httplib::Error::Canceled: - return "httplib::Error::Canceled"; - case httplib::Error::SSLConnection: - return "httplib::Error::SSLConnection"; - case httplib::Error::SSLLoadingCerts: - return "httplib::Error::SSLLoadingCerts"; - case httplib::Error::SSLServerVerification: - return "httplib::Error::SSLServerVerification"; - case httplib::Error::UnsupportedMultipartBoundaryChars: - return "httplib::Error::UnsupportedMultipartBoundaryChars"; - } - - return ""; -} - RemoteServerInfo::RemoteServerInfo( const char* newId, const char* newName, @@ -159,29 +37,28 @@ RemoteServerInfo::RemoteServerInfo( // passworded servers don't have public ips requiresPassword = newRequiresPassword; - strncpy((char*)id, newId, sizeof(id)); - id[sizeof(id) - 1] = 0; - strncpy((char*)name, newName, sizeof(name)); - name[sizeof(name) - 1] = 0; + strncpy_s((char*)id, sizeof(id), newId, sizeof(id) - 1); + strncpy_s((char*)name, sizeof(name), newName, sizeof(name) - 1); description = std::string(newDescription); - strncpy((char*)map, newMap, sizeof(map)); - map[sizeof(map) - 1] = 0; - strncpy((char*)playlist, newPlaylist, sizeof(playlist)); - playlist[sizeof(playlist) - 1] = 0; + strncpy_s((char*)map, sizeof(map), newMap, sizeof(map) - 1); + strncpy_s((char*)playlist, sizeof(playlist), newPlaylist, sizeof(playlist) - 1); playerCount = newPlayerCount; maxPlayers = newMaxPlayers; } -void MasterServerManager::SetCommonHttpClientOptions(CURL* curl) +void SetCommonHttpClientOptions(CURL* curl) { curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_easy_setopt(curl, CURLOPT_VERBOSE, Cvar_ns_curl_log_enable->GetBool()); curl_easy_setopt(curl, CURLOPT_USERAGENT, &NSUserAgent); + // Timeout since the MS has fucky async functions without await, making curl hang due to a successful connection but no response for ~90 + // seconds. + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // curl_easy_setopt(curl, CURLOPT_STDERR, stdout); - if (CommandLine()->FindParm("-msinsecure")) // TODO: this check doesn't seem to work + if (Tier0::CommandLine()->FindParm("-msinsecure")) // TODO: this check doesn't seem to work { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); @@ -204,7 +81,7 @@ size_t CurlWriteToStringBufferCallback(char* contents, size_t size, size_t nmemb return size * nmemb; } -void MasterServerManager::AuthenticateOriginWithMasterServer(char* uid, char* originToken) +void MasterServerManager::AuthenticateOriginWithMasterServer(const char* uid, const char* originToken) { if (m_bOriginAuthWithMasterServerInProgress) return; @@ -255,8 +132,11 @@ void MasterServerManager::AuthenticateOriginWithMasterServer(char* uid, char* or if (originAuthInfo["success"].IsTrue() && originAuthInfo.HasMember("token") && originAuthInfo["token"].IsString()) { - strncpy(m_sOwnClientAuthToken, originAuthInfo["token"].GetString(), sizeof(m_sOwnClientAuthToken)); - m_sOwnClientAuthToken[sizeof(m_sOwnClientAuthToken) - 1] = 0; + strncpy_s( + m_sOwnClientAuthToken, + sizeof(m_sOwnClientAuthToken), + originAuthInfo["token"].GetString(), + sizeof(m_sOwnClientAuthToken) - 1); spdlog::info("Northstar origin authentication completed successfully!"); } else @@ -347,7 +227,7 @@ void MasterServerManager::RequestServerList() goto REQUEST_END_CLEANUP; } - // todo: verify json props are fine before adding to m_vRemoteServers + // todo: verify json props are fine before adding to m_remoteServers if (!serverObj.HasMember("id") || !serverObj["id"].IsString() || !serverObj.HasMember("name") || !serverObj["name"].IsString() || !serverObj.HasMember("description") || !serverObj["description"].IsString() || !serverObj.HasMember("map") || !serverObj["map"].IsString() || !serverObj.HasMember("playlist") || @@ -491,10 +371,6 @@ void MasterServerManager::RequestMainMenuPromos() { spdlog::error("Failed reading masterserver response: got fastify error response"); spdlog::error(readBuffer); - if (mainMenuPromoJson["error"].HasMember("enum")) - s_authfail_reason = std::string(mainMenuPromoJson["error"]["enum"].GetString()); - else - s_authfail_reason = std::string("No error message provided"); goto REQUEST_END_CLEANUP; } @@ -559,7 +435,7 @@ void MasterServerManager::RequestMainMenuPromos() requestThread.detach(); } -void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken) +void MasterServerManager::AuthenticateWithOwnServer(const char* uid, const char* playerToken) { // dont wait, just stop if we're trying to do 2 auth requests at once if (m_bAuthenticatingWithGameServer) @@ -615,10 +491,12 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken { spdlog::error("Failed reading masterserver response: got fastify error response"); spdlog::error(readBuffer); + if (authInfoJson["error"].HasMember("enum")) - s_authfail_reason = std::string(authInfoJson["error"]["enum"].GetString()); + m_sAuthFailureReason = authInfoJson["error"]["enum"].GetString(); else - s_authfail_reason = std::string("No error message provided"); + m_sAuthFailureReason = "No error message provided"; + goto REQUEST_END_CLEANUP; } @@ -636,9 +514,8 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken goto REQUEST_END_CLEANUP; } - AuthData newAuthData {}; - strncpy(newAuthData.uid, authInfoJson["id"].GetString(), sizeof(newAuthData.uid)); - newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; + RemoteAuthData newAuthData {}; + strncpy_s(newAuthData.uid, sizeof(newAuthData.uid), authInfoJson["id"].GetString(), sizeof(newAuthData.uid) - 1); newAuthData.pdataSize = authInfoJson["persistentData"].GetArray().Size(); newAuthData.pdata = new char[newAuthData.pdataSize]; @@ -658,9 +535,10 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken newAuthData.pdata[i++] = static_cast<char>(byte.GetUint()); } - std::lock_guard<std::mutex> guard(g_ServerAuthenticationManager->m_authDataMutex); - g_ServerAuthenticationManager->m_authData.clear(); - g_ServerAuthenticationManager->m_authData.insert(std::make_pair(authInfoJson["authToken"].GetString(), newAuthData)); + std::lock_guard<std::mutex> guard(g_pServerAuthentication->m_AuthDataMutex); + g_pServerAuthentication->m_RemoteAuthenticationData.clear(); + g_pServerAuthentication->m_RemoteAuthenticationData.insert( + std::make_pair(authInfoJson["authToken"].GetString(), newAuthData)); m_bSuccessfullyAuthenticatedWithGameServer = true; } @@ -679,7 +557,7 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken if (m_bNewgameAfterSelfAuth) { // pretty sure this is threadsafe? - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "ns_end_reauth_and_leave_to_lobby", cmd_source_t::kCommandSrcCode); + R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "ns_end_reauth_and_leave_to_lobby", R2::cmd_source_t::kCommandSrcCode); m_bNewgameAfterSelfAuth = false; } @@ -689,7 +567,7 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken requestThread.detach(); } -void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password) +void MasterServerManager::AuthenticateWithServer(const char* uid, const char* playerToken, const char* serverId, const char* password) { // dont wait, just stop if we're trying to do 2 auth requests at once if (m_bAuthenticatingWithGameServer) @@ -766,10 +644,12 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c { spdlog::error("Failed reading masterserver response: got fastify error response"); spdlog::error(readBuffer); + if (connectionInfoJson["error"].HasMember("enum")) - s_authfail_reason = std::string(connectionInfoJson["error"]["enum"].GetString()); + m_sAuthFailureReason = connectionInfoJson["error"]["enum"].GetString(); else - s_authfail_reason = std::string("No error message provided"); + m_sAuthFailureReason = "No error message provided"; + goto REQUEST_END_CLEANUP; } @@ -791,8 +671,11 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c m_pendingConnectionInfo.ip.S_un.S_addr = inet_addr(connectionInfoJson["ip"].GetString()); m_pendingConnectionInfo.port = (unsigned short)connectionInfoJson["port"].GetUint(); - strncpy(m_pendingConnectionInfo.authToken, connectionInfoJson["authToken"].GetString(), 31); - m_pendingConnectionInfo.authToken[31] = 0; + strncpy_s( + m_pendingConnectionInfo.authToken, + sizeof(m_pendingConnectionInfo.authToken), + connectionInfoJson["authToken"].GetString(), + sizeof(m_pendingConnectionInfo.authToken) - 1); m_bHasPendingConnectionInfo = true; m_bSuccessfullyAuthenticatedWithGameServer = true; @@ -814,36 +697,267 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c requestThread.detach(); } -void MasterServerManager::AddSelfToServerList( - int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password) +void MasterServerManager::WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize) { - if (!Cvar_ns_report_server_to_masterserver->GetBool()) + // still call this if we don't have a server id, since lobbies that aren't port forwarded need to be able to call it + m_bSavingPersistentData = true; + if (!pdataSize) + { + spdlog::warn("attempted to write pdata of size 0!"); return; + } + + std::string strPlayerId(playerId); + std::string strPdata(pdata, pdataSize); + + std::thread requestThread( + [this, strPlayerId, strPdata, pdataSize] + { + CURL* curl = curl_easy_init(); + SetCommonHttpClientOptions(curl); + + std::string readBuffer; + curl_easy_setopt( + curl, + CURLOPT_URL, + fmt::format( + "{}/accounts/write_persistence?id={}&serverId={}", + Cvar_ns_masterserver_hostname->GetString(), + strPlayerId, + m_sOwnServerId) + .c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + + curl_mime* mime = curl_mime_init(curl); + curl_mimepart* part = curl_mime_addpart(mime); + + curl_mime_data(part, strPdata.c_str(), pdataSize); + curl_mime_name(part, "pdata"); + curl_mime_filename(part, "file.pdata"); + curl_mime_type(part, "application/octet-stream"); + + curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); + + CURLcode result = curl_easy_perform(curl); + + if (result == CURLcode::CURLE_OK) + m_bSuccessfullyConnected = true; + else + m_bSuccessfullyConnected = false; - if (!Cvar_ns_report_sp_server_to_masterserver->GetBool() && !strncmp(map, "sp_", 3)) + curl_easy_cleanup(curl); + + m_bSavingPersistentData = false; + }); + + requestThread.detach(); +} + +void ConCommand_ns_fetchservers(const CCommand& args) +{ + g_pMasterServerManager->RequestServerList(); +} + +MasterServerManager::MasterServerManager() : m_pendingConnectionInfo {}, m_sOwnServerId {""}, m_sOwnClientAuthToken {""} {} + +ON_DLL_LOAD_RELIESON("engine.dll", MasterServer, (ConCommand, ServerPresence), (CModule module)) +{ + g_pMasterServerManager = new MasterServerManager; + + Cvar_ns_masterserver_hostname = new ConVar("ns_masterserver_hostname", "127.0.0.1", FCVAR_NONE, ""); + Cvar_ns_curl_log_enable = new ConVar("ns_curl_log_enable", "0", FCVAR_NONE, "Whether curl should log to the console"); + + RegisterConCommand("ns_fetchservers", ConCommand_ns_fetchservers, "Fetch all servers from the masterserver", FCVAR_CLIENTDLL); + + MasterServerPresenceReporter* presenceReporter = new MasterServerPresenceReporter; + g_pServerPresence->AddPresenceReporter(presenceReporter); +} + +void MasterServerPresenceReporter::CreatePresence(const ServerPresence* pServerPresence) +{ + m_nNumRegistrationAttempts = 0; +} + +void MasterServerPresenceReporter::ReportPresence(const ServerPresence* pServerPresence) +{ + // make a copy of presence for multithreading purposes + ServerPresence threadedPresence(pServerPresence); + + if (!*g_pMasterServerManager->m_sOwnServerId) { - m_bRequireClientAuth = false; - return; + // Don't try if we've reached the max registration attempts. + // In the future, we should probably allow servers to re-authenticate after a while if the MS was down. + if (m_nNumRegistrationAttempts >= MAX_REGISTRATION_ATTEMPTS) + { + return; + } + + // Make sure to wait til the cooldown is over for DUPLICATE_SERVER failures. + if (Tier0::Plat_FloatTime() < m_fNextAddServerAttemptTime) + { + return; + } + + // If we're not running any InternalAddServer() attempt in the background. + if (!addServerFuture.valid()) + { + // Launch an attempt to add the local server to the master server. + InternalAddServer(pServerPresence); + } + } + else + { + // If we're not running any InternalUpdateServer() attempt in the background. + if (!updateServerFuture.valid()) + { + // Launch an attempt to update the local server on the master server. + InternalUpdateServer(pServerPresence); + } } +} - m_bRequireClientAuth = true; +void MasterServerPresenceReporter::DestroyPresence(const ServerPresence* pServerPresence) +{ + // Don't call this if we don't have a server id. + if (!*g_pMasterServerManager->m_sOwnServerId) + { + return; + } - std::string strName(name); - std::string strDescription(description); - std::string strMap(map); - std::string strPlaylist(playlist); - std::string strPassword(password); + // Not bothering with better thread safety in this case since DestroyPresence() is called when the game is shutting down. + *g_pMasterServerManager->m_sOwnServerId = 0; std::thread requestThread( - [this, port, authPort, strName, strDescription, strMap, strPlaylist, maxPlayers, strPassword] + [this] + { + CURL* curl = curl_easy_init(); + SetCommonHttpClientOptions(curl); + + std::string readBuffer; + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt( + curl, + CURLOPT_URL, + fmt::format( + "{}/server/remove_server?id={}", Cvar_ns_masterserver_hostname->GetString(), g_pMasterServerManager->m_sOwnServerId) + .c_str()); + + CURLcode result = curl_easy_perform(curl); + curl_easy_cleanup(curl); + }); + + requestThread.detach(); +} + +void MasterServerPresenceReporter::RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) +{ + // Check if we're already running an InternalAddServer() call in the background. + // If so, grab the result if it's ready. + if (addServerFuture.valid()) + { + std::future_status status = addServerFuture.wait_for(0ms); + if (status != std::future_status::ready) + { + // Still running, no need to do anything. + return; + } + + // Check the result. + auto resultData = addServerFuture.get(); + + g_pMasterServerManager->m_bSuccessfullyConnected = resultData.result != MasterServerReportPresenceResult::FailedNoConnect; + + switch (resultData.result) + { + case MasterServerReportPresenceResult::Success: + // Copy over the server id and auth token granted by the MS. + strncpy_s( + g_pMasterServerManager->m_sOwnServerId, + sizeof(g_pMasterServerManager->m_sOwnServerId), + resultData.id.value().c_str(), + sizeof(g_pMasterServerManager->m_sOwnServerId) - 1); + strncpy_s( + g_pMasterServerManager->m_sOwnServerAuthToken, + sizeof(g_pMasterServerManager->m_sOwnServerAuthToken), + resultData.serverAuthToken.value().c_str(), + sizeof(g_pMasterServerManager->m_sOwnServerAuthToken) - 1); + break; + case MasterServerReportPresenceResult::FailedNoRetry: + case MasterServerReportPresenceResult::FailedNoConnect: + // If we failed to connect to the master server, or failed with no retry, stop trying. + m_nNumRegistrationAttempts = MAX_REGISTRATION_ATTEMPTS; + break; + case MasterServerReportPresenceResult::Failed: + ++m_nNumRegistrationAttempts; + break; + case MasterServerReportPresenceResult::FailedDuplicateServer: + ++m_nNumRegistrationAttempts; + // Wait at least twenty seconds until we re-attempt to add the server. + m_fNextAddServerAttemptTime = Tier0::Plat_FloatTime() + 20.0f; + break; + } + + if (m_nNumRegistrationAttempts >= MAX_REGISTRATION_ATTEMPTS) + { + spdlog::error("Reached max ms server registration attempts."); + } + } + else if (updateServerFuture.valid()) + { + // Check if the InternalUpdateServer() call completed. + std::future_status status = updateServerFuture.wait_for(0ms); + if (status != std::future_status::ready) { - // Keep track of attempted connects in case of DUPLICATE_SERVER response - int retry_counter = 0; + // Still running, no need to do anything. + return; + } + + auto resultData = updateServerFuture.get(); + if (resultData.result == MasterServerReportPresenceResult::Success) + { + if (resultData.id) + { + strncpy_s( + g_pMasterServerManager->m_sOwnServerId, + sizeof(g_pMasterServerManager->m_sOwnServerId), + resultData.id.value().c_str(), + sizeof(g_pMasterServerManager->m_sOwnServerId) - 1); + } + + if (resultData.serverAuthToken) + { + strncpy_s( + g_pMasterServerManager->m_sOwnServerAuthToken, + sizeof(g_pMasterServerManager->m_sOwnServerAuthToken), + resultData.serverAuthToken.value().c_str(), + sizeof(g_pMasterServerManager->m_sOwnServerAuthToken) - 1); + } + } + } +} + +void MasterServerPresenceReporter::InternalAddServer(const ServerPresence* pServerPresence) +{ + const ServerPresence threadedPresence(pServerPresence); + // Never call this with an ongoing InternalAddServer() call. + assert(!addServerFuture.valid()); + + g_pMasterServerManager->m_sOwnServerId[0] = 0; + g_pMasterServerManager->m_sOwnServerAuthToken[0] = 0; - START_REQUEST: - m_sOwnServerId[0] = 0; - m_sOwnServerAuthToken[0] = 0; + std::string modInfo = g_pMasterServerManager->m_sOwnModInfoJson; + std::string hostname = Cvar_ns_masterserver_hostname->GetString(); + spdlog::info("Attempting to register the local server to the master server."); + + addServerFuture = std::async( + std::launch::async, + [threadedPresence, modInfo, hostname] + { CURL* curl = curl_easy_init(); SetCommonHttpClientOptions(curl); @@ -855,7 +969,22 @@ void MasterServerManager::AddSelfToServerList( curl_mime* mime = curl_mime_init(curl); curl_mimepart* part = curl_mime_addpart(mime); - curl_mime_data(part, m_sOwnModInfoJson.c_str(), m_sOwnModInfoJson.size()); + // Lambda to quickly cleanup resources and return a value. + auto ReturnCleanup = + [curl, mime](MasterServerReportPresenceResult result, const char* id = "", const char* serverAuthToken = "") + { + curl_easy_cleanup(curl); + curl_mime_free(mime); + + MasterServerPresenceReporter::ReportPresenceResultData data; + data.result = result; + data.id = id; + data.serverAuthToken = serverAuthToken; + + return data; + }; + + curl_mime_data(part, modInfo.c_str(), modInfo.size()); curl_mime_name(part, "modinfo"); curl_mime_filename(part, "modinfo.json"); curl_mime_type(part, "application/json"); @@ -864,25 +993,26 @@ void MasterServerManager::AddSelfToServerList( // format every paramter because computers hate me { - char* nameEscaped = curl_easy_escape(curl, strName.c_str(), strName.length()); - char* descEscaped = curl_easy_escape(curl, strDescription.c_str(), strDescription.length()); - char* mapEscaped = curl_easy_escape(curl, strMap.c_str(), strMap.length()); - char* playlistEscaped = curl_easy_escape(curl, strPlaylist.c_str(), strPlaylist.length()); - char* passwordEscaped = curl_easy_escape(curl, strPassword.c_str(), strPassword.length()); + char* nameEscaped = curl_easy_escape(curl, threadedPresence.m_sServerName.c_str(), NULL); + char* descEscaped = curl_easy_escape(curl, threadedPresence.m_sServerDesc.c_str(), NULL); + char* mapEscaped = curl_easy_escape(curl, threadedPresence.m_MapName, NULL); + char* playlistEscaped = curl_easy_escape(curl, threadedPresence.m_PlaylistName, NULL); + char* passwordEscaped = curl_easy_escape(curl, threadedPresence.m_Password, NULL); curl_easy_setopt( curl, CURLOPT_URL, fmt::format( - "{}/server/add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", - Cvar_ns_masterserver_hostname->GetString(), - port, - authPort, + "{}/server/" + "add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", + hostname.c_str(), + threadedPresence.m_iPort, + threadedPresence.m_iAuthPort, nameEscaped, descEscaped, mapEscaped, playlistEscaped, - maxPlayers, + threadedPresence.m_iMaxPlayers, passwordEscaped) .c_str()); @@ -897,23 +1027,23 @@ void MasterServerManager::AddSelfToServerList( if (result == CURLcode::CURLE_OK) { - m_bSuccessfullyConnected = true; - rapidjson_document serverAddedJson; serverAddedJson.Parse(readBuffer.c_str()); + // If we could not parse the JSON or it isn't an object, assume the MS is either wrong or we're completely out of date. + // No retry. if (serverAddedJson.HasParseError()) { spdlog::error( "Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(serverAddedJson.GetParseError())); - goto REQUEST_END_CLEANUP; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); } if (!serverAddedJson.IsObject()) { spdlog::error("Failed reading masterserver authentication response: root object is not an object"); - goto REQUEST_END_CLEANUP; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); } if (serverAddedJson.HasMember("error")) @@ -921,488 +1051,168 @@ void MasterServerManager::AddSelfToServerList( spdlog::error("Failed reading masterserver response: got fastify error response"); spdlog::error(readBuffer); - // Check for enum member in JSON - if (serverAddedJson["error"].HasMember("enum")) + // If this is DUPLICATE_SERVER, we'll retry adding the server every 20 seconds. + // The master server will only update its internal server list and clean up dead servers on certain events. + // And then again, only if a player requests the server list after the cooldown (1 second by default), or a server is + // added/updated/removed. In any case this needs to be fixed in the master server rewrite. + if (serverAddedJson["error"].HasMember("enum") && + strcmp(serverAddedJson["error"]["enum"].GetString(), "DUPLICATE_SERVER") == 0) { - // Check for DUPLICATE_SERVER error response, stop if we tried 10 times - if (strncmp(serverAddedJson["error"]["enum"].GetString(), "DUPLICATE_SERVER", 17) == 0 && retry_counter < 10) - { - - spdlog::info("Retrying request in 10 seconds."); - // Incremement retry counter - retry_counter++; - - // Sleep for 10 seconds - std::this_thread::sleep_for(std::chrono::seconds(10)); - - // curl cleanup to retry request - curl_easy_cleanup(curl); - curl_mime_free(mime); - - // go to beginning and retry - goto START_REQUEST; - } + spdlog::error("Cooling down while the master server cleans the dead server entry, if any."); + return ReturnCleanup(MasterServerReportPresenceResult::FailedDuplicateServer); } - goto REQUEST_END_CLEANUP; + + // Retry until we reach max retries. + return ReturnCleanup(MasterServerReportPresenceResult::Failed); } if (!serverAddedJson["success"].IsTrue()) { spdlog::error("Adding server to masterserver failed: \"success\" is not true"); - goto REQUEST_END_CLEANUP; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); } if (!serverAddedJson.HasMember("id") || !serverAddedJson["id"].IsString() || !serverAddedJson.HasMember("serverAuthToken") || !serverAddedJson["serverAuthToken"].IsString()) { spdlog::error("Failed reading masterserver response: malformed json object"); - goto REQUEST_END_CLEANUP; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoRetry); } - strncpy(m_sOwnServerId, serverAddedJson["id"].GetString(), sizeof(m_sOwnServerId)); - m_sOwnServerId[sizeof(m_sOwnServerId) - 1] = 0; - - strncpy(m_sOwnServerAuthToken, serverAddedJson["serverAuthToken"].GetString(), sizeof(m_sOwnServerAuthToken)); - m_sOwnServerAuthToken[sizeof(m_sOwnServerAuthToken) - 1] = 0; - - spdlog::info("Succesfully added server to the master server."); - - // heartbeat thread - // ideally this should actually be done in main thread, rather than on it's own thread, so it'd stop if server freezes - std::thread heartbeatThread( - [this] - { - Sleep(5000); - - // defensive check, as m_ownServer could be set to null during the Sleep(5000) above - if (!*m_sOwnServerId) - return; - - do - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); - - // send all registration info so we have all necessary info to reregister our server if masterserver goes down, - // without a restart this isn't threadsafe :terror: - { - char* escapedNameNew = curl_easy_escape(curl, g_MasterServerManager->m_sUnicodeServerName.c_str(), NULL); - char* escapedDescNew = curl_easy_escape(curl, g_MasterServerManager->m_sUnicodeServerDesc.c_str(), NULL); - char* escapedMapNew = curl_easy_escape(curl, g_pHostState->m_levelName, NULL); - char* escapedPlaylistNew = curl_easy_escape(curl, GetCurrentPlaylistName(), NULL); - char* escapedPasswordNew = curl_easy_escape(curl, Cvar_ns_server_password->GetString(), NULL); - - int maxPlayers = 6; - char* maxPlayersVar = GetCurrentPlaylistVar("max_players", false); - if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this - maxPlayers = std::stoi(maxPlayersVar); - - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/server/" - "update_values?id={}&port={}&authPort={}&name={}&description={}&map={}&playlist={}&playerCount={}&" - "maxPlayers={}&password={}", - Cvar_ns_masterserver_hostname->GetString(), - m_sOwnServerId, - Cvar_hostport->GetInt(), - Cvar_ns_player_auth_port->GetInt(), - escapedNameNew, - escapedDescNew, - escapedMapNew, - escapedPlaylistNew, - g_ServerAuthenticationManager->m_additionalPlayerData.size(), - maxPlayers, - escapedPasswordNew) - .c_str()); - - curl_free(escapedNameNew); - curl_free(escapedDescNew); - curl_free(escapedMapNew); - curl_free(escapedPlaylistNew); - curl_free(escapedPasswordNew); - } - - curl_mime* mime = curl_mime_init(curl); - curl_mimepart* part = curl_mime_addpart(mime); - - curl_mime_data(part, m_sOwnModInfoJson.c_str(), m_sOwnModInfoJson.size()); - curl_mime_name(part, "modinfo"); - curl_mime_filename(part, "modinfo.json"); - curl_mime_type(part, "application/json"); - - curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); - - CURLcode result = curl_easy_perform(curl); - - // defensive check, as m_sOwnServerId could be set to null before this request gets processed - if (!*m_sOwnServerId) - return; - - if (result == CURLcode::CURLE_OK) - { - rapidjson_document serverAddedJson; - serverAddedJson.Parse(readBuffer.c_str()); - - if (!serverAddedJson.HasParseError() && serverAddedJson.IsObject()) - { - if (serverAddedJson.HasMember("id") && serverAddedJson["id"].IsString()) - { - strncpy(m_sOwnServerId, serverAddedJson["id"].GetString(), sizeof(m_sOwnServerId)); - m_sOwnServerId[sizeof(m_sOwnServerId) - 1] = 0; - } - - if (serverAddedJson.HasMember("serverAuthToken") && serverAddedJson["serverAuthToken"].IsString()) - { - strncpy( - m_sOwnServerAuthToken, - serverAddedJson["serverAuthToken"].GetString(), - sizeof(m_sOwnServerAuthToken)); - m_sOwnServerAuthToken[sizeof(m_sOwnServerAuthToken) - 1] = 0; - } - } - } - else - spdlog::warn("Heartbeat failed with error {}", curl_easy_strerror(result)); - - curl_easy_cleanup(curl); - Sleep(10000); - } while (*m_sOwnServerId); - }); - - heartbeatThread.detach(); + spdlog::info("Successfully registered the local server to the master server."); + return ReturnCleanup( + MasterServerReportPresenceResult::Success, + serverAddedJson["id"].GetString(), + serverAddedJson["serverAuthToken"].GetString()); } else { spdlog::error("Failed adding self to server list: error {}", curl_easy_strerror(result)); - m_bSuccessfullyConnected = false; + return ReturnCleanup(MasterServerReportPresenceResult::FailedNoConnect); } - - REQUEST_END_CLEANUP: - curl_easy_cleanup(curl); - curl_mime_free(mime); }); - - requestThread.detach(); } -void MasterServerManager::UpdateServerMapAndPlaylist(char* map, char* playlist, int maxPlayers) +void MasterServerPresenceReporter::InternalUpdateServer(const ServerPresence* pServerPresence) { - // dont call this if we don't have a server id - if (!*m_sOwnServerId) - return; + const ServerPresence threadedPresence(pServerPresence); - std::string strMap(map); - std::string strPlaylist(playlist); + // Never call this with an ongoing InternalUpdateServer() call. + assert(!updateServerFuture.valid()); - std::thread requestThread( - [this, strMap, strPlaylist, maxPlayers] + const std::string serverId = g_pMasterServerManager->m_sOwnServerId; + const std::string hostname = Cvar_ns_masterserver_hostname->GetString(); + const std::string modinfo = g_pMasterServerManager->m_sOwnModInfoJson; + + updateServerFuture = std::async( + std::launch::async, + [threadedPresence, serverId, hostname, modinfo] { CURL* curl = curl_easy_init(); SetCommonHttpClientOptions(curl); + // Lambda to quickly cleanup resources and return a value. + auto ReturnCleanup = [curl](MasterServerReportPresenceResult result, const char* id = "", const char* serverAuthToken = "") + { + curl_easy_cleanup(curl); + + MasterServerPresenceReporter::ReportPresenceResultData data; + data.result = result; + + if (id != nullptr) + { + data.id = id; + } + + if (serverAuthToken != nullptr) + { + data.serverAuthToken = serverAuthToken; + } + + return data; + }; + std::string readBuffer; curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); - // escape params + // send all registration info so we have all necessary info to reregister our server if masterserver goes down, + // without a restart this isn't threadsafe :terror: { - char* mapEscaped = curl_easy_escape(curl, strMap.c_str(), strMap.length()); - char* playlistEscaped = curl_easy_escape(curl, strPlaylist.c_str(), strPlaylist.length()); + char* nameEscaped = curl_easy_escape(curl, threadedPresence.m_sServerName.c_str(), NULL); + char* descEscaped = curl_easy_escape(curl, threadedPresence.m_sServerDesc.c_str(), NULL); + char* mapEscaped = curl_easy_escape(curl, threadedPresence.m_MapName, NULL); + char* playlistEscaped = curl_easy_escape(curl, threadedPresence.m_PlaylistName, NULL); + char* passwordEscaped = curl_easy_escape(curl, threadedPresence.m_Password, NULL); curl_easy_setopt( curl, CURLOPT_URL, fmt::format( - "{}/server/update_values?id={}&map={}&playlist={}&maxPlayers={}", - Cvar_ns_masterserver_hostname->GetString(), - m_sOwnServerId, + "{}/server/" + "update_values?id={}&port={}&authPort={}&name={}&description={}&map={}&playlist={}&playerCount={}&" + "maxPlayers={}&password={}", + hostname.c_str(), + serverId.c_str(), + threadedPresence.m_iPort, + threadedPresence.m_iAuthPort, + nameEscaped, + descEscaped, mapEscaped, playlistEscaped, - maxPlayers) + threadedPresence.m_iPlayerCount, + threadedPresence.m_iMaxPlayers, + passwordEscaped) .c_str()); + curl_free(nameEscaped); + curl_free(descEscaped); curl_free(mapEscaped); curl_free(playlistEscaped); + curl_free(passwordEscaped); } - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; - else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::UpdateServerPlayerCount(int playerCount) -{ - // dont call this if we don't have a server id - if (!*m_sOwnServerId) - return; - - std::thread requestThread( - [this, playerCount] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/server/update_values?id={}&playerCount={}", Cvar_ns_masterserver_hostname->GetString(), m_sOwnServerId, playerCount) - .c_str()); - - CURLcode result = curl_easy_perform(curl); - - if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; - else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); - }); - - requestThread.detach(); -} - -void MasterServerManager::WritePlayerPersistentData(char* playerId, char* pdata, size_t pdataSize) -{ - // still call this if we don't have a server id, since lobbies that aren't port forwarded need to be able to call it - m_bSavingPersistentData = true; - if (!pdataSize) - { - spdlog::warn("attempted to write pdata of size 0!"); - return; - } - - std::string strPlayerId(playerId); - std::string strPdata(pdata, pdataSize); - - std::thread requestThread( - [this, strPlayerId, strPdata, pdataSize] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); - - std::string readBuffer; - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format( - "{}/accounts/write_persistence?id={}&serverId={}", - Cvar_ns_masterserver_hostname->GetString(), - strPlayerId, - m_sOwnServerId) - .c_str()); - curl_easy_setopt(curl, CURLOPT_POST, 1L); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_mime* mime = curl_mime_init(curl); curl_mimepart* part = curl_mime_addpart(mime); - curl_mime_data(part, strPdata.c_str(), pdataSize); - curl_mime_name(part, "pdata"); - curl_mime_filename(part, "file.pdata"); - curl_mime_type(part, "application/octet-stream"); + curl_mime_data(part, modinfo.c_str(), modinfo.size()); + curl_mime_name(part, "modinfo"); + curl_mime_filename(part, "modinfo.json"); + curl_mime_type(part, "application/json"); curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); CURLcode result = curl_easy_perform(curl); if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; - else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); - - m_bSavingPersistentData = false; - }); - - requestThread.detach(); -} - -void MasterServerManager::RemoveSelfFromServerList() -{ - // dont call this if we don't have a server id - if (!*m_sOwnServerId || !Cvar_ns_report_server_to_masterserver->GetBool()) - return; + { + rapidjson_document serverAddedJson; + serverAddedJson.Parse(readBuffer.c_str()); - std::thread requestThread( - [this] - { - CURL* curl = curl_easy_init(); - SetCommonHttpClientOptions(curl); + const char* updatedId = nullptr; + const char* updatedAuthToken = nullptr; - std::string readBuffer; - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt( - curl, - CURLOPT_URL, - fmt::format("{}/server/remove_server?id={}", Cvar_ns_masterserver_hostname->GetString(), m_sOwnServerId).c_str()); + if (!serverAddedJson.HasParseError() && serverAddedJson.IsObject()) + { + if (serverAddedJson.HasMember("id") && serverAddedJson["id"].IsString()) + { + updatedId = serverAddedJson["id"].GetString(); + } - *m_sOwnServerId = 0; - CURLcode result = curl_easy_perform(curl); + if (serverAddedJson.HasMember("serverAuthToken") && serverAddedJson["serverAuthToken"].IsString()) + { + updatedAuthToken = serverAddedJson["serverAuthToken"].GetString(); + } + } - if (result == CURLcode::CURLE_OK) - m_bSuccessfullyConnected = true; + return ReturnCleanup(MasterServerReportPresenceResult::Success, updatedId, updatedAuthToken); + } else - m_bSuccessfullyConnected = false; - - curl_easy_cleanup(curl); + { + spdlog::warn("Heartbeat failed with error {}", curl_easy_strerror(result)); + return ReturnCleanup(MasterServerReportPresenceResult::Failed); + } }); - - requestThread.detach(); -} - -void ConCommand_ns_fetchservers(const CCommand& args) -{ - g_MasterServerManager->RequestServerList(); -} - -void CHostState__State_NewGameHook(CHostState* hostState) -{ - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "exec autoexec_ns_server", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - - // need to do this to ensure we don't go to private match - if (g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame) - SetCurrentPlaylist("tdm"); - - // net_data_block_enabled is required for sp, force it if we're on an sp map - // sucks for security but just how it be - if (!strncmp(g_pHostState->m_levelName, "sp_", 3)) - { - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "net_data_block_enabled 1", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - } - - CHostState__State_NewGame(hostState); - - int maxPlayers = 6; - char* maxPlayersVar = GetCurrentPlaylistVar("max_players", false); - if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this - maxPlayers = std::stoi(maxPlayersVar); - - // Copy new server name cvar to source - Cvar_hostname->SetValue(Cvar_ns_server_name->GetString()); - // This calls the function that converts unicode strings from servername and serverdesc to UTF-8 - UpdateServerInfoFromUnicodeToUTF8(); - - g_MasterServerManager->AddSelfToServerList( - Cvar_hostport->GetInt(), - Cvar_ns_player_auth_port->GetInt(), - (char*)Cvar_ns_server_name->GetString(), - (char*)Cvar_ns_server_desc->GetString(), - hostState->m_levelName, - (char*)GetCurrentPlaylistName(), - maxPlayers, - (char*)Cvar_ns_server_password->GetString()); - g_ServerAuthenticationManager->StartPlayerAuthServer(); - g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = false; -} - -void CHostState__State_ChangeLevelMPHook(CHostState* hostState) -{ - int maxPlayers = 6; - char* maxPlayersVar = GetCurrentPlaylistVar("max_players", false); - if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this - maxPlayers = std::stoi(maxPlayersVar); - - // net_data_block_enabled is required for sp, force it if we're on an sp map - // sucks for security but just how it be - if (!strncmp(g_pHostState->m_levelName, "sp_", 3)) - { - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "net_data_block_enabled 1", cmd_source_t::kCommandSrcCode); - Cbuf_Execute(); - } - - g_MasterServerManager->UpdateServerMapAndPlaylist(hostState->m_levelName, (char*)GetCurrentPlaylistName(), maxPlayers); - CHostState__State_ChangeLevelMP(hostState); -} - -void CHostState__State_ChangeLevelSPHook(CHostState* hostState) -{ - // is this even called? genuinely i don't think so - // from what i can tell, it's not called on mp=>sp change or sp=>sp change - // so idk it's fucked - - int maxPlayers = 6; - char* maxPlayersVar = GetCurrentPlaylistVar("max_players", false); - if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this - maxPlayers = std::stoi(maxPlayersVar); - - g_MasterServerManager->UpdateServerMapAndPlaylist(hostState->m_levelName, (char*)GetCurrentPlaylistName(), maxPlayers); - CHostState__State_ChangeLevelSP(hostState); -} - -void CHostState__State_GameShutdownHook(CHostState* hostState) -{ - g_MasterServerManager->RemoveSelfFromServerList(); - g_ServerAuthenticationManager->StopPlayerAuthServer(); - - CHostState__State_GameShutdown(hostState); -} - -MasterServerManager::MasterServerManager() : m_pendingConnectionInfo {}, m_sOwnServerId {""}, m_sOwnClientAuthToken {""} {} - -void InitialiseSharedMasterServer(HMODULE baseAddress) -{ - Cvar_ns_masterserver_hostname = new ConVar("ns_masterserver_hostname", "127.0.0.1", FCVAR_NONE, ""); - // unfortunately lib doesn't let us specify a port and still have https work - // Cvar_ns_masterserver_port = new ConVar("ns_masterserver_port", "8080", FCVAR_NONE, ""); - - Cvar_ns_server_name = new ConVar("ns_server_name", "Unnamed Northstar Server", FCVAR_GAMEDLL, ""); - Cvar_ns_server_desc = new ConVar("ns_server_desc", "Default server description", FCVAR_GAMEDLL, ""); - Cvar_ns_server_password = new ConVar("ns_server_password", "", FCVAR_GAMEDLL, ""); - Cvar_ns_report_server_to_masterserver = new ConVar("ns_report_server_to_masterserver", "1", FCVAR_GAMEDLL, ""); - Cvar_ns_report_sp_server_to_masterserver = new ConVar("ns_report_sp_server_to_masterserver", "0", FCVAR_GAMEDLL, ""); - - Cvar_ns_curl_log_enable = new ConVar("ns_curl_log_enable", "0", FCVAR_NONE, ""); - - Cvar_hostname = *(ConVar**)((char*)baseAddress + 0x1315bae8); - - 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 + 0x16E520, - CHostState__State_ChangeLevelMPHook, - reinterpret_cast<LPVOID*>(&CHostState__State_ChangeLevelMP)); - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x16E5D0, - CHostState__State_ChangeLevelSPHook, - reinterpret_cast<LPVOID*>(&CHostState__State_ChangeLevelSP)); - ENABLER_CREATEHOOK( - hook, - (char*)baseAddress + 0x16E640, - CHostState__State_GameShutdownHook, - reinterpret_cast<LPVOID*>(&CHostState__State_GameShutdown)); } |