From e5684053f4127bd02681ef46e0bfeada83b2f63c Mon Sep 17 00:00:00 2001 From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com> Date: Tue, 28 Dec 2021 04:35:47 +0000 Subject: add retry logic to masterserver requests --- NorthstarDedicatedTest/masterserver.cpp | 1123 +++++++++++++++++-------------- NorthstarDedicatedTest/masterserver.h | 14 + 2 files changed, 621 insertions(+), 516 deletions(-) (limited to 'NorthstarDedicatedTest') diff --git a/NorthstarDedicatedTest/masterserver.cpp b/NorthstarDedicatedTest/masterserver.cpp index 706f60ec..d49252bc 100644 --- a/NorthstarDedicatedTest/masterserver.cpp +++ b/NorthstarDedicatedTest/masterserver.cpp @@ -89,6 +89,18 @@ RemoteServerInfo::RemoteServerInfo(const char* newId, const char* newName, const maxPlayers = newMaxPlayers; } +void MasterServerManager::LazyCreateHttpClient() +{ + if (m_httpClient) + return; + + m_httpClient = new httplib::Client(Cvar_ns_masterserver_hostname->m_pszString); + m_httpClient->set_connection_timeout(25); + m_httpClient->set_read_timeout(25); + m_httpClient->set_write_timeout(25); + m_httpClient->set_tcp_nodelay(true); +} + void MasterServerManager::ClearServerList() { // this doesn't really do anything lol, probably isn't threadsafe @@ -99,6 +111,53 @@ void MasterServerManager::ClearServerList() m_requestingServerList = false; } +bool MasterServerManager::AuthenticateOriginWithMasterServerThread(std::string uidStr, std::string tokenStr) +{ + spdlog::info("Trying to authenticate with northstar masterserver for user {}", uidStr); + + LazyCreateHttpClient(); + if (auto result = m_httpClient->Get(fmt::format("/client/origin_auth?id={}&token={}", uidStr, tokenStr).c_str())) + { + m_successfullyConnected = true; + + rapidjson::Document originAuthInfo; + originAuthInfo.Parse(result->body.c_str()); + + if (originAuthInfo.HasParseError()) + { + spdlog::error("Failed reading origin auth info response: encountered parse error \{}\"", rapidjson::GetParseError_En(originAuthInfo.GetParseError())); + goto REQUEST_END_CLEANUP; + } + + if (!originAuthInfo.IsObject() || !originAuthInfo.HasMember("success")) + { + spdlog::error("Failed reading origin auth info response: malformed response object {}", result->body); + goto REQUEST_END_CLEANUP; + } + + if (originAuthInfo["success"].IsTrue() && originAuthInfo.HasMember("token") && originAuthInfo["token"].IsString()) + { + strncpy(m_ownClientAuthToken, originAuthInfo["token"].GetString(), sizeof(m_ownClientAuthToken)); + m_ownClientAuthToken[sizeof(m_ownClientAuthToken) - 1] = 0; + spdlog::info("Northstar origin authentication completed successfully!"); + } + else + spdlog::error("Northstar origin authentication failed"); + } + else + { + spdlog::error("Failed performing northstar origin auth: error {}", HttplibErrorToString(result.error())); + m_successfullyConnected = false; + } + + // we goto this instead of returning so we always hit this +REQUEST_END_CLEANUP: + m_bOriginAuthWithMasterServerInProgress = false; + m_bOriginAuthWithMasterServerDone = true; + + return m_successfullyConnected; +} + void MasterServerManager::AuthenticateOriginWithMasterServer(char* uid, char* originToken) { if (m_bOriginAuthWithMasterServerInProgress) @@ -111,54 +170,144 @@ void MasterServerManager::AuthenticateOriginWithMasterServer(char* uid, char* or std::thread requestThread([this, uidStr, tokenStr]() { - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); + for (int i = 0; i < 5; i++) + { + if (!AuthenticateOriginWithMasterServerThread(uidStr, tokenStr)) + spdlog::info("retrying..."); + else + return; + } + }); - spdlog::info("Trying to authenticate with northstar masterserver for user {}", uidStr); + requestThread.detach(); +} + +bool MasterServerManager::RequestServerListThread() +{ + // make sure we never have 2 threads writing at once + // i sure do hope this is actually threadsafe + while (m_requestingServerList) + Sleep(100); + + m_requestingServerList = true; + m_scriptRequestingServerList = true; + + + spdlog::info("Requesting server list from {}", Cvar_ns_masterserver_hostname->m_pszString); + + LazyCreateHttpClient(); + if (auto result = m_httpClient->Get("/client/servers")) + { + m_successfullyConnected = true; + + rapidjson::Document serverInfoJson; + serverInfoJson.Parse(result->body.c_str()); + + if (serverInfoJson.HasParseError()) + { + spdlog::error("Failed reading masterserver response: encountered parse error \"{}\"", rapidjson::GetParseError_En(serverInfoJson.GetParseError())); + goto REQUEST_END_CLEANUP; + } + + if (serverInfoJson.IsObject() && serverInfoJson.HasMember("error")) + { + spdlog::error("Failed reading masterserver response: got fastify error response"); + spdlog::error(result->body); + goto REQUEST_END_CLEANUP; + } - if (auto result = http.Get(fmt::format("/client/origin_auth?id={}&token={}", uidStr, tokenStr).c_str())) + if (!serverInfoJson.IsArray()) + { + spdlog::error("Failed reading masterserver response: root object is not an array"); + goto REQUEST_END_CLEANUP; + } + + rapidjson::GenericArray serverArray = serverInfoJson.GetArray(); + + spdlog::info("Got {} servers", serverArray.Size()); + + for (auto& serverObj : serverArray) + { + if (!serverObj.IsObject()) { - m_successfullyConnected = true; + spdlog::error("Failed reading masterserver response: member of server array is not an object"); + goto REQUEST_END_CLEANUP; + } - rapidjson::Document originAuthInfo; - originAuthInfo.Parse(result->body.c_str()); + // 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") || !serverObj["playlist"].IsString() + || !serverObj.HasMember("playerCount") || !serverObj["playerCount"].IsNumber() + || !serverObj.HasMember("maxPlayers") || !serverObj["maxPlayers"].IsNumber() + || !serverObj.HasMember("hasPassword") || !serverObj["hasPassword"].IsBool() + || !serverObj.HasMember("modInfo") || !serverObj["modInfo"].HasMember("Mods") || !serverObj["modInfo"]["Mods"].IsArray()) + { + spdlog::error("Failed reading masterserver response: malformed server object"); + continue; + }; - if (originAuthInfo.HasParseError()) - { - spdlog::error("Failed reading origin auth info response: encountered parse error \{}\"", rapidjson::GetParseError_En(originAuthInfo.GetParseError())); - goto REQUEST_END_CLEANUP; - } + const char* id = serverObj["id"].GetString(); - if (!originAuthInfo.IsObject() || !originAuthInfo.HasMember("success")) - { - spdlog::error("Failed reading origin auth info response: malformed response object {}", result->body); - goto REQUEST_END_CLEANUP; - } + RemoteServerInfo* newServer = nullptr; - if (originAuthInfo["success"].IsTrue() && originAuthInfo.HasMember("token") && originAuthInfo["token"].IsString()) + 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, 32)) { - strncpy(m_ownClientAuthToken, originAuthInfo["token"].GetString(), sizeof(m_ownClientAuthToken)); - m_ownClientAuthToken[sizeof(m_ownClientAuthToken) - 1] = 0; - spdlog::info("Northstar origin authentication completed successfully!"); + server = RemoteServerInfo(id, serverObj["name"].GetString(), serverObj["description"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt(), serverObj["hasPassword"].IsTrue()); + newServer = &server; + createNewServerInfo = false; + break; } - else - spdlog::error("Northstar origin authentication failed"); } - else + + // server didn't exist + if (createNewServerInfo) + newServer = &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()); + + newServer->requiredMods.clear(); + for (auto& requiredMod : serverObj["modInfo"]["Mods"].GetArray()) { - spdlog::error("Failed performing northstar origin auth: error {}", HttplibErrorToString(result.error())); - m_successfullyConnected = false; + RemoteModInfo modInfo; + + if (!requiredMod.HasMember("RequiredOnClient") || !requiredMod["RequiredOnClient"].IsTrue()) + continue; + + if (!requiredMod.HasMember("Name") || !requiredMod["Name"].IsString()) + continue; + modInfo.Name = requiredMod["Name"].GetString(); + + if (!requiredMod.HasMember("Version") || !requiredMod["Version"].IsString()) + continue; + modInfo.Version = requiredMod["Version"].GetString(); + + newServer->requiredMods.push_back(modInfo); } - // we goto this instead of returning so we always hit this - REQUEST_END_CLEANUP: - m_bOriginAuthWithMasterServerInProgress = false; - m_bOriginAuthWithMasterServerDone = true; - }); + spdlog::info("Server {} on map {} with playlist {} has {}/{} players", serverObj["name"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt()); + } - requestThread.detach(); + std::sort(m_remoteServers.begin(), m_remoteServers.end(), [](RemoteServerInfo& a, RemoteServerInfo& b) { + return a.playerCount > b.playerCount; + }); + } + else + { + spdlog::error("Failed requesting servers: error {}", HttplibErrorToString(result.error())); + m_successfullyConnected = false; + } + + // we goto this instead of returning so we always hit this +REQUEST_END_CLEANUP: + m_requestingServerList = false; + m_scriptRequestingServerList = false; + + return m_successfullyConnected; } void MasterServerManager::RequestServerList() @@ -168,134 +317,103 @@ void MasterServerManager::RequestServerList() std::thread requestThread([this]() { - // make sure we never have 2 threads writing at once - // i sure do hope this is actually threadsafe - while (m_requestingServerList) - Sleep(100); + for (int i = 0; i < 3; i++) + { + if (!RequestServerListThread()) + spdlog::info("retrying..."); + else + return; + } + }); - m_requestingServerList = true; - m_scriptRequestingServerList = true; - - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); + requestThread.detach(); +} - spdlog::info("Requesting server list from {}", Cvar_ns_masterserver_hostname->m_pszString); +bool MasterServerManager::RequestMainMenuPromosThread() +{ + while (m_bOriginAuthWithMasterServerInProgress || !m_bOriginAuthWithMasterServerDone) + Sleep(500); - if (auto result = http.Get("/client/servers")) - { - m_successfullyConnected = true; + LazyCreateHttpClient(); + if (auto result = m_httpClient->Get("/client/mainmenupromos")) + { + m_successfullyConnected = true; - rapidjson::Document serverInfoJson; - serverInfoJson.Parse(result->body.c_str()); + rapidjson::Document mainMenuPromoJson; + mainMenuPromoJson.Parse(result->body.c_str()); - if (serverInfoJson.HasParseError()) - { - spdlog::error("Failed reading masterserver response: encountered parse error \"{}\"", rapidjson::GetParseError_En(serverInfoJson.GetParseError())); - goto REQUEST_END_CLEANUP; - } + if (mainMenuPromoJson.HasParseError()) + { + spdlog::error("Failed reading masterserver main menu promos response: encountered parse error \"{}\"", rapidjson::GetParseError_En(mainMenuPromoJson.GetParseError())); + goto REQUEST_END_CLEANUP; + } - if (serverInfoJson.IsObject() && serverInfoJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(result->body); - goto REQUEST_END_CLEANUP; - } + if (!mainMenuPromoJson.IsObject()) + { + spdlog::error("Failed reading masterserver main menu promos response: root object is not an object"); + goto REQUEST_END_CLEANUP; + } - if (!serverInfoJson.IsArray()) - { - spdlog::error("Failed reading masterserver response: root object is not an array"); - goto REQUEST_END_CLEANUP; - } + if (mainMenuPromoJson.HasMember("error")) + { + spdlog::error("Failed reading masterserver response: got fastify error response"); + spdlog::error(result->body); + goto REQUEST_END_CLEANUP; + } + + if (!mainMenuPromoJson.HasMember("newInfo") || !mainMenuPromoJson["newInfo"].IsObject() || + !mainMenuPromoJson["newInfo"].HasMember("Title1") || !mainMenuPromoJson["newInfo"]["Title1"].IsString() || + !mainMenuPromoJson["newInfo"].HasMember("Title2") || !mainMenuPromoJson["newInfo"]["Title2"].IsString() || + !mainMenuPromoJson["newInfo"].HasMember("Title3") || !mainMenuPromoJson["newInfo"]["Title3"].IsString() || + + !mainMenuPromoJson.HasMember("largeButton") || !mainMenuPromoJson["largeButton"].IsObject() || + !mainMenuPromoJson["largeButton"].HasMember("Title") || !mainMenuPromoJson["largeButton"]["Title"].IsString() || + !mainMenuPromoJson["largeButton"].HasMember("Text") || !mainMenuPromoJson["largeButton"]["Text"].IsString() || + !mainMenuPromoJson["largeButton"].HasMember("Url") || !mainMenuPromoJson["largeButton"]["Url"].IsString() || + !mainMenuPromoJson["largeButton"].HasMember("ImageIndex") || !mainMenuPromoJson["largeButton"]["ImageIndex"].IsNumber() || + + !mainMenuPromoJson.HasMember("smallButton1") || !mainMenuPromoJson["smallButton1"].IsObject() || + !mainMenuPromoJson["smallButton1"].HasMember("Title") || !mainMenuPromoJson["smallButton1"]["Title"].IsString() || + !mainMenuPromoJson["smallButton1"].HasMember("Url") || !mainMenuPromoJson["smallButton1"]["Url"].IsString() || + !mainMenuPromoJson["smallButton1"].HasMember("ImageIndex") || !mainMenuPromoJson["smallButton1"]["ImageIndex"].IsNumber() || + + !mainMenuPromoJson.HasMember("smallButton2") || !mainMenuPromoJson["smallButton2"].IsObject() || + !mainMenuPromoJson["smallButton2"].HasMember("Title") || !mainMenuPromoJson["smallButton2"]["Title"].IsString() || + !mainMenuPromoJson["smallButton2"].HasMember("Url") || !mainMenuPromoJson["smallButton2"]["Url"].IsString() || + !mainMenuPromoJson["smallButton2"].HasMember("ImageIndex") || !mainMenuPromoJson["smallButton2"]["ImageIndex"].IsNumber()) + { + spdlog::error("Failed reading masterserver main menu promos response: malformed json object"); + goto REQUEST_END_CLEANUP; + } - rapidjson::GenericArray serverArray = serverInfoJson.GetArray(); + m_MainMenuPromoData.newInfoTitle1 = mainMenuPromoJson["newInfo"]["Title1"].GetString(); + m_MainMenuPromoData.newInfoTitle2 = mainMenuPromoJson["newInfo"]["Title2"].GetString(); + m_MainMenuPromoData.newInfoTitle3 = mainMenuPromoJson["newInfo"]["Title3"].GetString(); - spdlog::info("Got {} servers", serverArray.Size()); + m_MainMenuPromoData.largeButtonTitle = mainMenuPromoJson["largeButton"]["Title"].GetString(); + m_MainMenuPromoData.largeButtonText = mainMenuPromoJson["largeButton"]["Text"].GetString(); + m_MainMenuPromoData.largeButtonUrl = mainMenuPromoJson["largeButton"]["Url"].GetString(); + m_MainMenuPromoData.largeButtonImageIndex = mainMenuPromoJson["largeButton"]["ImageIndex"].GetInt(); - for (auto& serverObj : serverArray) - { - if (!serverObj.IsObject()) - { - spdlog::error("Failed reading masterserver response: member of server array is not an object"); - goto REQUEST_END_CLEANUP; - } - - // 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") || !serverObj["playlist"].IsString() - || !serverObj.HasMember("playerCount") || !serverObj["playerCount"].IsNumber() - || !serverObj.HasMember("maxPlayers") || !serverObj["maxPlayers"].IsNumber() - || !serverObj.HasMember("hasPassword") || !serverObj["hasPassword"].IsBool() - || !serverObj.HasMember("modInfo") || !serverObj["modInfo"].HasMember("Mods") || !serverObj["modInfo"]["Mods"].IsArray() ) - { - spdlog::error("Failed reading masterserver response: malformed server object"); - continue; - }; - - const char* id = serverObj["id"].GetString(); - - RemoteServerInfo* newServer = nullptr; - - 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, 32)) - { - server = RemoteServerInfo(id, serverObj["name"].GetString(), serverObj["description"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt(), serverObj["hasPassword"].IsTrue()); - newServer = &server; - createNewServerInfo = false; - break; - } - } - - // server didn't exist - if (createNewServerInfo) - newServer = &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()); - - newServer->requiredMods.clear(); - for (auto& requiredMod : serverObj["modInfo"]["Mods"].GetArray()) - { - RemoteModInfo modInfo; - - if (!requiredMod.HasMember("RequiredOnClient") || !requiredMod["RequiredOnClient"].IsTrue()) - continue; - - if (!requiredMod.HasMember("Name") || !requiredMod["Name"].IsString()) - continue; - modInfo.Name = requiredMod["Name"].GetString(); - - if (!requiredMod.HasMember("Version") || !requiredMod["Version"].IsString()) - continue; - modInfo.Version = requiredMod["Version"].GetString(); - - newServer->requiredMods.push_back(modInfo); - } - - spdlog::info("Server {} on map {} with playlist {} has {}/{} players", serverObj["name"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt()); - } + m_MainMenuPromoData.smallButton1Title = mainMenuPromoJson["smallButton1"]["Title"].GetString(); + m_MainMenuPromoData.smallButton1Url = mainMenuPromoJson["smallButton1"]["Url"].GetString(); + m_MainMenuPromoData.smallButton1ImageIndex = mainMenuPromoJson["smallButton1"]["ImageIndex"].GetInt(); - std::sort(m_remoteServers.begin(), m_remoteServers.end(), [](RemoteServerInfo& a, RemoteServerInfo& b) { - return a.playerCount > b.playerCount; - }); - } - else - { - spdlog::error("Failed requesting servers: error {}", HttplibErrorToString(result.error())); - m_successfullyConnected = false; - } + m_MainMenuPromoData.smallButton2Title = mainMenuPromoJson["smallButton2"]["Title"].GetString(); + m_MainMenuPromoData.smallButton2Url = mainMenuPromoJson["smallButton2"]["Url"].GetString(); + m_MainMenuPromoData.smallButton2ImageIndex = mainMenuPromoJson["smallButton2"]["ImageIndex"].GetInt(); - // we goto this instead of returning so we always hit this - REQUEST_END_CLEANUP: - m_requestingServerList = false; - m_scriptRequestingServerList = false; - }); + m_bHasMainMenuPromoData = true; + } + else + { + spdlog::error("Failed requesting main menu promos: error {}", HttplibErrorToString(result.error())); + m_successfullyConnected = false; + } - requestThread.detach(); +REQUEST_END_CLEANUP: + // nothing lol + return m_successfullyConnected; } void MasterServerManager::RequestMainMenuPromos() @@ -304,96 +422,107 @@ void MasterServerManager::RequestMainMenuPromos() std::thread requestThread([this]() { - while (m_bOriginAuthWithMasterServerInProgress || !m_bOriginAuthWithMasterServerDone) - Sleep(500); - - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); - - if (auto result = http.Get("/client/mainmenupromos")) + for (int i = 0; i < 3; i++) { - m_successfullyConnected = true; - - rapidjson::Document mainMenuPromoJson; - mainMenuPromoJson.Parse(result->body.c_str()); - - if (mainMenuPromoJson.HasParseError()) - { - spdlog::error("Failed reading masterserver main menu promos response: encountered parse error \"{}\"", rapidjson::GetParseError_En(mainMenuPromoJson.GetParseError())); - goto REQUEST_END_CLEANUP; - } + if (!RequestMainMenuPromosThread()) + spdlog::info("retrying..."); + else + return; + } + }); - if (!mainMenuPromoJson.IsObject()) - { - spdlog::error("Failed reading masterserver main menu promos response: root object is not an object"); - goto REQUEST_END_CLEANUP; - } + requestThread.detach(); +} - if (mainMenuPromoJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(result->body); - goto REQUEST_END_CLEANUP; - } +bool MasterServerManager::AuthenticateWithOwnServerThread(char* uid, char* playerToken) +{ + LazyCreateHttpClient(); + if (auto result = m_httpClient->Post(fmt::format("/client/auth_with_self?id={}&playerToken={}", uid, playerToken).c_str())) + { + m_successfullyConnected = true; - if (!mainMenuPromoJson.HasMember("newInfo") || !mainMenuPromoJson["newInfo"].IsObject() || - !mainMenuPromoJson["newInfo"].HasMember("Title1") || !mainMenuPromoJson["newInfo"]["Title1"].IsString() || - !mainMenuPromoJson["newInfo"].HasMember("Title2") || !mainMenuPromoJson["newInfo"]["Title2"].IsString() || - !mainMenuPromoJson["newInfo"].HasMember("Title3") || !mainMenuPromoJson["newInfo"]["Title3"].IsString() || - - !mainMenuPromoJson.HasMember("largeButton") || !mainMenuPromoJson["largeButton"].IsObject() || - !mainMenuPromoJson["largeButton"].HasMember("Title") || !mainMenuPromoJson["largeButton"]["Title"].IsString() || - !mainMenuPromoJson["largeButton"].HasMember("Text") || !mainMenuPromoJson["largeButton"]["Text"].IsString() || - !mainMenuPromoJson["largeButton"].HasMember("Url") || !mainMenuPromoJson["largeButton"]["Url"].IsString() || - !mainMenuPromoJson["largeButton"].HasMember("ImageIndex") || !mainMenuPromoJson["largeButton"]["ImageIndex"].IsNumber() || - - !mainMenuPromoJson.HasMember("smallButton1") || !mainMenuPromoJson["smallButton1"].IsObject() || - !mainMenuPromoJson["smallButton1"].HasMember("Title") || !mainMenuPromoJson["smallButton1"]["Title"].IsString() || - !mainMenuPromoJson["smallButton1"].HasMember("Url") || !mainMenuPromoJson["smallButton1"]["Url"].IsString() || - !mainMenuPromoJson["smallButton1"].HasMember("ImageIndex") || !mainMenuPromoJson["smallButton1"]["ImageIndex"].IsNumber() || - - !mainMenuPromoJson.HasMember("smallButton2") || !mainMenuPromoJson["smallButton2"].IsObject() || - !mainMenuPromoJson["smallButton2"].HasMember("Title") || !mainMenuPromoJson["smallButton2"]["Title"].IsString() || - !mainMenuPromoJson["smallButton2"].HasMember("Url") || !mainMenuPromoJson["smallButton2"]["Url"].IsString() || - !mainMenuPromoJson["smallButton2"].HasMember("ImageIndex") || !mainMenuPromoJson["smallButton2"]["ImageIndex"].IsNumber()) - { - spdlog::error("Failed reading masterserver main menu promos response: malformed json object"); - goto REQUEST_END_CLEANUP; - } + rapidjson::Document authInfoJson; + authInfoJson.Parse(result->body.c_str()); - m_MainMenuPromoData.newInfoTitle1 = mainMenuPromoJson["newInfo"]["Title1"].GetString(); - m_MainMenuPromoData.newInfoTitle2 = mainMenuPromoJson["newInfo"]["Title2"].GetString(); - m_MainMenuPromoData.newInfoTitle3 = mainMenuPromoJson["newInfo"]["Title3"].GetString(); + if (authInfoJson.HasParseError()) + { + spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(authInfoJson.GetParseError())); + goto REQUEST_END_CLEANUP; + } - m_MainMenuPromoData.largeButtonTitle = mainMenuPromoJson["largeButton"]["Title"].GetString(); - m_MainMenuPromoData.largeButtonText = mainMenuPromoJson["largeButton"]["Text"].GetString(); - m_MainMenuPromoData.largeButtonUrl = mainMenuPromoJson["largeButton"]["Url"].GetString(); - m_MainMenuPromoData.largeButtonImageIndex = mainMenuPromoJson["largeButton"]["ImageIndex"].GetInt(); + if (!authInfoJson.IsObject()) + { + spdlog::error("Failed reading masterserver authentication response: root object is not an object"); + goto REQUEST_END_CLEANUP; + } - m_MainMenuPromoData.smallButton1Title = mainMenuPromoJson["smallButton1"]["Title"].GetString(); - m_MainMenuPromoData.smallButton1Url = mainMenuPromoJson["smallButton1"]["Url"].GetString(); - m_MainMenuPromoData.smallButton1ImageIndex = mainMenuPromoJson["smallButton1"]["ImageIndex"].GetInt(); + if (authInfoJson.HasMember("error")) + { + spdlog::error("Failed reading masterserver response: got fastify error response"); + spdlog::error(result->body); + goto REQUEST_END_CLEANUP; + } - m_MainMenuPromoData.smallButton2Title = mainMenuPromoJson["smallButton2"]["Title"].GetString(); - m_MainMenuPromoData.smallButton2Url = mainMenuPromoJson["smallButton2"]["Url"].GetString(); - m_MainMenuPromoData.smallButton2ImageIndex = mainMenuPromoJson["smallButton2"]["ImageIndex"].GetInt(); + if (!authInfoJson["success"].IsTrue()) + { + spdlog::error("Authentication with masterserver failed: \"success\" is not true"); + goto REQUEST_END_CLEANUP; + } - m_bHasMainMenuPromoData = true; - } - else + if (!authInfoJson.HasMember("success") || !authInfoJson.HasMember("id") || !authInfoJson["id"].IsString() || !authInfoJson.HasMember("authToken") || !authInfoJson["authToken"].IsString() || !authInfoJson.HasMember("persistentData") || !authInfoJson["persistentData"].IsArray()) + { + spdlog::error("Failed reading masterserver authentication response: malformed json object"); + goto REQUEST_END_CLEANUP; + } + + AuthData newAuthData; + strncpy(newAuthData.uid, authInfoJson["id"].GetString(), sizeof(newAuthData.uid)); + newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; + + newAuthData.pdataSize = authInfoJson["persistentData"].GetArray().Size(); + newAuthData.pdata = new char[newAuthData.pdataSize]; + //memcpy(newAuthData.pdata, authInfoJson["persistentData"].GetString(), newAuthData.pdataSize); + + int i = 0; + // note: persistentData is a uint8array because i had problems getting strings to behave, it sucks but it's just how it be unfortunately + // potentially refactor later + for (auto& byte : authInfoJson["persistentData"].GetArray()) + { + if (!byte.IsUint() || byte.GetUint() > 255) { - spdlog::error("Failed requesting main menu promos: error {}", HttplibErrorToString(result.error())); - m_successfullyConnected = false; + spdlog::error("Failed reading masterserver authentication response: malformed json object"); + goto REQUEST_END_CLEANUP; } - REQUEST_END_CLEANUP: - // nothing lol - return; - }); + newAuthData.pdata[i++] = (char)byte.GetUint(); + } - requestThread.detach(); + std::lock_guard guard(g_ServerAuthenticationManager->m_authDataMutex); + g_ServerAuthenticationManager->m_authData.clear(); + g_ServerAuthenticationManager->m_authData.insert(std::make_pair(authInfoJson["authToken"].GetString(), newAuthData)); + + m_successfullyAuthenticatedWithGameServer = true; + } + else + { + spdlog::error("Failed authenticating with own server: error {}", HttplibErrorToString(result.error())); + m_successfullyConnected = false; + m_successfullyAuthenticatedWithGameServer = false; + m_scriptAuthenticatingWithGameServer = false; + } + +REQUEST_END_CLEANUP: + m_authenticatingWithGameServer = false; + m_scriptAuthenticatingWithGameServer = false; + + if (m_bNewgameAfterSelfAuth) + { + // pretty sure this is threadsafe? + Cbuf_AddText(Cbuf_GetCurrentPlayer(), "ns_end_reauth_and_leave_to_lobby", cmd_source_t::kCommandSrcCode); + m_bNewgameAfterSelfAuth = false; + } + + return m_successfullyConnected; } void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken) @@ -408,98 +537,87 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken std::thread requestThread([this, uid, playerToken]() { - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); - - if (auto result = http.Post(fmt::format("/client/auth_with_self?id={}&playerToken={}", uid, playerToken).c_str())) + for (int i = 0; i < 5; i++) { - m_successfullyConnected = true; + if (!AuthenticateWithOwnServerThread(uid, playerToken)) + spdlog::info("retrying..."); + else + return; + } + }); - rapidjson::Document authInfoJson; - authInfoJson.Parse(result->body.c_str()); + requestThread.detach(); +} - if (authInfoJson.HasParseError()) - { - spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(authInfoJson.GetParseError())); - goto REQUEST_END_CLEANUP; - } +bool MasterServerManager::AuthenticateWithServerThread(char* uid, char* playerToken, char* serverId, char* password) +{ + // esnure that any persistence saving is done, so we know masterserver has newest + while (m_savingPersistentData) + Sleep(100); - if (!authInfoJson.IsObject()) - { - spdlog::error("Failed reading masterserver authentication response: root object is not an object"); - goto REQUEST_END_CLEANUP; - } + LazyCreateHttpClient(); + spdlog::info("Attempting authentication with server of id \"{}\"", serverId); - if (authInfoJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(result->body); - goto REQUEST_END_CLEANUP; - } + if (auto result = m_httpClient->Post(fmt::format("/client/auth_with_server?id={}&playerToken={}&server={}&password={}", uid, playerToken, serverId, password).c_str())) + { + m_successfullyConnected = true; - if (!authInfoJson["success"].IsTrue()) - { - spdlog::error("Authentication with masterserver failed: \"success\" is not true"); - goto REQUEST_END_CLEANUP; - } + rapidjson::Document connectionInfoJson; + connectionInfoJson.Parse(result->body.c_str()); - if (!authInfoJson.HasMember("success") || !authInfoJson.HasMember("id") || !authInfoJson["id"].IsString() || !authInfoJson.HasMember("authToken") || !authInfoJson["authToken"].IsString() || !authInfoJson.HasMember("persistentData") || !authInfoJson["persistentData"].IsArray()) - { - spdlog::error("Failed reading masterserver authentication response: malformed json object"); - goto REQUEST_END_CLEANUP; - } + if (connectionInfoJson.HasParseError()) + { + spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(connectionInfoJson.GetParseError())); + goto REQUEST_END_CLEANUP; + } - AuthData newAuthData; - strncpy(newAuthData.uid, authInfoJson["id"].GetString(), sizeof(newAuthData.uid)); - newAuthData.uid[sizeof(newAuthData.uid) - 1] = 0; + if (!connectionInfoJson.IsObject()) + { + spdlog::error("Failed reading masterserver authentication response: root object is not an object"); + goto REQUEST_END_CLEANUP; + } - newAuthData.pdataSize = authInfoJson["persistentData"].GetArray().Size(); - newAuthData.pdata = new char[newAuthData.pdataSize]; - //memcpy(newAuthData.pdata, authInfoJson["persistentData"].GetString(), newAuthData.pdataSize); + if (connectionInfoJson.HasMember("error")) + { + spdlog::error("Failed reading masterserver response: got fastify error response"); + spdlog::error(result->body); + goto REQUEST_END_CLEANUP; + } - int i = 0; - // note: persistentData is a uint8array because i had problems getting strings to behave, it sucks but it's just how it be unfortunately - // potentially refactor later - for (auto& byte : authInfoJson["persistentData"].GetArray()) - { - if (!byte.IsUint() || byte.GetUint() > 255) - { - spdlog::error("Failed reading masterserver authentication response: malformed json object"); - goto REQUEST_END_CLEANUP; - } - - newAuthData.pdata[i++] = (char)byte.GetUint(); - } + if (!connectionInfoJson["success"].IsTrue()) + { + spdlog::error("Authentication with masterserver failed: \"success\" is not true"); + goto REQUEST_END_CLEANUP; + } - std::lock_guard guard(g_ServerAuthenticationManager->m_authDataMutex); - g_ServerAuthenticationManager->m_authData.clear(); - g_ServerAuthenticationManager->m_authData.insert(std::make_pair(authInfoJson["authToken"].GetString(), newAuthData)); - - m_successfullyAuthenticatedWithGameServer = true; - } - else - { - spdlog::error("Failed authenticating with own server: error {}", HttplibErrorToString(result.error())); - m_successfullyConnected = false; - m_successfullyAuthenticatedWithGameServer = false; - m_scriptAuthenticatingWithGameServer = false; - } + 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; + } - REQUEST_END_CLEANUP: - m_authenticatingWithGameServer = false; - m_scriptAuthenticatingWithGameServer = false; + m_pendingConnectionInfo.ip.S_un.S_addr = inet_addr(connectionInfoJson["ip"].GetString()); + m_pendingConnectionInfo.port = connectionInfoJson["port"].GetInt(); - if (m_bNewgameAfterSelfAuth) - { - // pretty sure this is threadsafe? - Cbuf_AddText(Cbuf_GetCurrentPlayer(), "ns_end_reauth_and_leave_to_lobby", cmd_source_t::kCommandSrcCode); - m_bNewgameAfterSelfAuth = false; - } - }); + strncpy(m_pendingConnectionInfo.authToken, connectionInfoJson["authToken"].GetString(), 31); + m_pendingConnectionInfo.authToken[31] = 0; - requestThread.detach(); + m_hasPendingConnectionInfo = true; + m_successfullyAuthenticatedWithGameServer = true; + } + else + { + spdlog::error("Failed authenticating with server: error {}", HttplibErrorToString(result.error())); + m_successfullyConnected = false; + m_successfullyAuthenticatedWithGameServer = false; + m_scriptAuthenticatingWithGameServer = false; + } + +REQUEST_END_CLEANUP: + m_authenticatingWithGameServer = false; + m_scriptAuthenticatingWithGameServer = false; + + return m_successfullyConnected; } void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, char* serverId, char* password) @@ -514,226 +632,186 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c std::thread requestThread([this, uid, playerToken, serverId, password]() { - // esnure that any persistence saving is done, so we know masterserver has newest - while (m_savingPersistentData) - Sleep(100); - - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); - - spdlog::info("Attempting authentication with server of id \"{}\"", serverId); - - if (auto result = http.Post(fmt::format("/client/auth_with_server?id={}&playerToken={}&server={}&password={}", uid, playerToken, serverId, password).c_str())) - { - m_successfullyConnected = true; - - rapidjson::Document connectionInfoJson; - connectionInfoJson.Parse(result->body.c_str()); - - if (connectionInfoJson.HasParseError()) - { - spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(connectionInfoJson.GetParseError())); - goto REQUEST_END_CLEANUP; - } - - if (!connectionInfoJson.IsObject()) - { - spdlog::error("Failed reading masterserver authentication response: root object is not an object"); - goto REQUEST_END_CLEANUP; - } - - if (connectionInfoJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(result->body); - goto REQUEST_END_CLEANUP; - } - - if (!connectionInfoJson["success"].IsTrue()) - { - spdlog::error("Authentication with masterserver failed: \"success\" is not true"); - goto REQUEST_END_CLEANUP; - } - - 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 = inet_addr(connectionInfoJson["ip"].GetString()); - m_pendingConnectionInfo.port = connectionInfoJson["port"].GetInt(); - - strncpy(m_pendingConnectionInfo.authToken, connectionInfoJson["authToken"].GetString(), 31); - m_pendingConnectionInfo.authToken[31] = 0; - - m_hasPendingConnectionInfo = true; - m_successfullyAuthenticatedWithGameServer = true; - } - else + for (int i = 0; i < 5; i++) { - spdlog::error("Failed authenticating with server: error {}", HttplibErrorToString(result.error())); - m_successfullyConnected = false; - m_successfullyAuthenticatedWithGameServer = false; - m_scriptAuthenticatingWithGameServer = false; + if (!AuthenticateWithServerThread(uid, playerToken, serverId, password)) + spdlog::info("retrying..."); + else + return; } - - REQUEST_END_CLEANUP: - m_authenticatingWithGameServer = false; - m_scriptAuthenticatingWithGameServer = false; }); requestThread.detach(); } -void MasterServerManager::AddSelfToServerList(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password) +bool MasterServerManager::AddSelfToServerListThread(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password) { - if (!Cvar_ns_report_server_to_masterserver->m_nValue) - return; + m_ownServerId[0] = 0; - if (!Cvar_ns_report_sp_server_to_masterserver->m_nValue && !strncmp(map, "sp_", 3)) - { - m_bRequireClientAuth = false; - return; - } + std::string request; + if (*password) + request = fmt::format("/server/add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", port, authPort, name, description, map, playlist, maxPlayers, password); + else + request = fmt::format("/server/add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password=", port, authPort, name, description, map, playlist, maxPlayers); - m_bRequireClientAuth = true; + // build modinfo obj + rapidjson::Document modinfoDoc; + modinfoDoc.SetObject(); + modinfoDoc.AddMember("Mods", rapidjson::Value(rapidjson::kArrayType), modinfoDoc.GetAllocator()); - std::thread requestThread([this, port, authPort, name, description, map, playlist, maxPlayers, password] { - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); + int currentModIndex = 0; + for (Mod& mod : g_ModManager->m_loadedMods) + { + if (!mod.Enabled || (!mod.RequiredOnClient && !mod.Pdiff.size())) + continue; - m_ownServerId[0] = 0; + modinfoDoc["Mods"].PushBack(rapidjson::Value(rapidjson::kObjectType), modinfoDoc.GetAllocator()); + modinfoDoc["Mods"][currentModIndex].AddMember("Name", rapidjson::StringRef(&mod.Name[0]), modinfoDoc.GetAllocator()); + modinfoDoc["Mods"][currentModIndex].AddMember("Version", rapidjson::StringRef(&mod.Version[0]), modinfoDoc.GetAllocator()); + modinfoDoc["Mods"][currentModIndex].AddMember("RequiredOnClient", mod.RequiredOnClient, modinfoDoc.GetAllocator()); + modinfoDoc["Mods"][currentModIndex].AddMember("Pdiff", rapidjson::StringRef(&mod.Pdiff[0]), modinfoDoc.GetAllocator()); - std::string request; - if (*password) - request = fmt::format("/server/add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", port, authPort, name, description, map, playlist, maxPlayers, password); - else - request = fmt::format("/server/add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password=", port, authPort, name, description, map, playlist, maxPlayers); + currentModIndex++; + } - // build modinfo obj - rapidjson::Document modinfoDoc; - modinfoDoc.SetObject(); - modinfoDoc.AddMember("Mods", rapidjson::Value(rapidjson::kArrayType), modinfoDoc.GetAllocator()); + rapidjson::StringBuffer buffer; + buffer.Clear(); + rapidjson::Writer writer(buffer); + modinfoDoc.Accept(writer); + const char* modInfoString = buffer.GetString(); - int currentModIndex = 0; - for (Mod& mod : g_ModManager->m_loadedMods) - { - if (!mod.Enabled || (!mod.RequiredOnClient && !mod.Pdiff.size())) - continue; + httplib::MultipartFormDataItems requestItems = { + {"modinfo", std::string(modInfoString, buffer.GetSize()), "modinfo.json", "application/octet-stream"} + }; - modinfoDoc["Mods"].PushBack(rapidjson::Value(rapidjson::kObjectType), modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Name", rapidjson::StringRef(&mod.Name[0]), modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Version", rapidjson::StringRef(&mod.Version[0]), modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("RequiredOnClient", mod.RequiredOnClient, modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Pdiff", rapidjson::StringRef(&mod.Pdiff[0]), modinfoDoc.GetAllocator()); + LazyCreateHttpClient(); + if (auto result = m_httpClient->Post(request.c_str(), requestItems)) + { + m_successfullyConnected = true; - currentModIndex++; - } + rapidjson::Document serverAddedJson; + serverAddedJson.Parse(result->body.c_str()); - rapidjson::StringBuffer buffer; - buffer.Clear(); - rapidjson::Writer writer(buffer); - modinfoDoc.Accept(writer); - const char* modInfoString = buffer.GetString(); + if (serverAddedJson.HasParseError()) + { + spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(serverAddedJson.GetParseError())); + goto REQUEST_END_CLEANUP; + } - httplib::MultipartFormDataItems requestItems = { - {"modinfo", std::string(modInfoString, buffer.GetSize()), "modinfo.json", "application/octet-stream"} - }; + if (!serverAddedJson.IsObject()) + { + spdlog::error("Failed reading masterserver authentication response: root object is not an object"); + goto REQUEST_END_CLEANUP; + } - if (auto result = http.Post(request.c_str(), requestItems)) - { - m_successfullyConnected = true; - - rapidjson::Document serverAddedJson; - serverAddedJson.Parse(result->body.c_str()); + if (serverAddedJson.HasMember("error")) + { + spdlog::error("Failed reading masterserver response: got fastify error response"); + spdlog::error(result->body); + goto REQUEST_END_CLEANUP; + } - if (serverAddedJson.HasParseError()) - { - spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(serverAddedJson.GetParseError())); - return; - } + if (!serverAddedJson["success"].IsTrue()) + { + spdlog::error("Adding server to masterserver failed: \"success\" is not true"); + goto REQUEST_END_CLEANUP; + } - if (!serverAddedJson.IsObject()) - { - spdlog::error("Failed reading masterserver authentication response: root object is not an object"); - return; - } + if (!serverAddedJson.HasMember("id") || !serverAddedJson["id"].IsString()) + { + spdlog::error("Failed reading masterserver response: malformed json object"); + goto REQUEST_END_CLEANUP; + } - if (serverAddedJson.HasMember("error")) - { - spdlog::error("Failed reading masterserver response: got fastify error response"); - spdlog::error(result->body); - return; - } + strncpy(m_ownServerId, serverAddedJson["id"].GetString(), sizeof(m_ownServerId)); + m_ownServerId[sizeof(m_ownServerId) - 1] = 0; - if (!serverAddedJson["success"].IsTrue()) - { - spdlog::error("Adding server to masterserver failed: \"success\" is not true"); - return; - } - if (!serverAddedJson.HasMember("id") || !serverAddedJson["id"].IsString()) + // 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] { + while (*m_ownServerId) + { + Sleep(10000); + for (int i = 0; i < 3; i++) { - spdlog::error("Failed reading masterserver response: malformed json object"); - return; + if (!(int)m_httpClient->Post(fmt::format("/server/heartbeat?id={}&playerCount={}", m_ownServerId, g_ServerAuthenticationManager->m_additionalPlayerData.size()).c_str()).error()) + break; + else + spdlog::warn("retrying heartbeat..."); } + } + }); - strncpy(m_ownServerId, serverAddedJson["id"].GetString(), sizeof(m_ownServerId)); - m_ownServerId[sizeof(m_ownServerId) - 1] = 0; + heartbeatThread.detach(); + } + else + { + spdlog::error("Failed adding self to server list: error {}", HttplibErrorToString(result.error())); + m_successfullyConnected = false; + } +REQUEST_END_CLEANUP: + return m_successfullyConnected; +} - // 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] { - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); +void MasterServerManager::AddSelfToServerList(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password) +{ + if (!Cvar_ns_report_server_to_masterserver->m_nValue) + return; - while (*m_ownServerId) - { - Sleep(15000); - http.Post(fmt::format("/server/heartbeat?id={}&playerCount={}", m_ownServerId, g_ServerAuthenticationManager->m_additionalPlayerData.size()).c_str()); - } - }); + if (!Cvar_ns_report_sp_server_to_masterserver->m_nValue && !strncmp(map, "sp_", 3)) + { + m_bRequireClientAuth = false; + return; + } - heartbeatThread.detach(); - } - else + m_bRequireClientAuth = true; + + std::thread requestThread([this, port, authPort, name, description, map, playlist, maxPlayers, password] + { + for (int i = 0; i < 5; i++) { - spdlog::error("Failed adding self to server list: error {}", HttplibErrorToString(result.error())); - m_successfullyConnected = false; + if (!AddSelfToServerListThread(port, authPort, name, description, map, playlist, maxPlayers, password)) + spdlog::info("retrying..."); + else + return; } }); requestThread.detach(); } +bool MasterServerManager::UpdateServerMapAndPlaylistThread(char* map, char* playlist, int maxPlayers) +{ + // we dont process this at all atm, maybe do later, but atm not necessary + LazyCreateHttpClient(); + if (auto result = m_httpClient->Post(fmt::format("/server/update_values?id={}&map={}&playlist={}&maxPlayers={}", m_ownServerId, map, playlist, maxPlayers).c_str())) + { + m_successfullyConnected = true; + } + else + { + m_successfullyConnected = false; + } + + return m_successfullyConnected; +} + void MasterServerManager::UpdateServerMapAndPlaylist(char* map, char* playlist, int maxPlayers) { // dont call this if we don't have a server id if (!*m_ownServerId) return; - std::thread requestThread([this, map, playlist, maxPlayers] { - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); - - // 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={}&maxPlayers={}", m_ownServerId, map, playlist, maxPlayers).c_str())) - { - m_successfullyConnected = true; - } - else + std::thread requestThread([this, map, playlist, maxPlayers] + { + for (int i = 0; i < 3; i++) { - m_successfullyConnected = false; + if (!UpdateServerMapAndPlaylistThread(map, playlist, maxPlayers)) + spdlog::info("retrying..."); + else + return; } }); @@ -747,13 +825,10 @@ void MasterServerManager::UpdateServerPlayerCount(int playerCount) return; std::thread requestThread([this, playerCount] { - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); // 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())) + LazyCreateHttpClient(); + if (auto result = m_httpClient->Post(fmt::format("/server/update_values?id={}&playerCount={}", m_ownServerId, playerCount).c_str())) { m_successfullyConnected = true; } @@ -766,6 +841,27 @@ void MasterServerManager::UpdateServerPlayerCount(int playerCount) requestThread.detach(); } +bool MasterServerManager::WritePlayerPersistentDataThread(std::string playerIdTemp, char* pdata, size_t pdataSize) +{ + httplib::MultipartFormDataItems requestItems = { + { "pdata", std::string(pdata, pdataSize), "file.pdata", "application/octet-stream"} + }; + + // we dont process this at all atm, maybe do later, but atm not necessary + LazyCreateHttpClient(); + if (auto result = m_httpClient->Post(fmt::format("/accounts/write_persistence?id={}&serverId={}", playerIdTemp, m_ownServerId).c_str(), requestItems)) + { + m_successfullyConnected = true; + } + else + { + m_successfullyConnected = false; + } + + m_savingPersistentData = false; + return m_successfullyConnected; +} + 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 @@ -777,32 +873,36 @@ void MasterServerManager::WritePlayerPersistentData(char* playerId, char* pdata, } std::string playerIdTemp(playerId); - std::thread requestThread([this, playerIdTemp, pdata, pdataSize] { - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); - - httplib::MultipartFormDataItems requestItems = { - { "pdata", std::string(pdata, pdataSize), "file.pdata", "application/octet-stream"} - }; - - // we dont process this at all atm, maybe do later, but atm not necessary - if (auto result = http.Post(fmt::format("/accounts/write_persistence?id={}&serverId={}", playerIdTemp, m_ownServerId).c_str(), requestItems)) - { - m_successfullyConnected = true; - } - else + std::thread requestThread([this, playerIdTemp, pdata, pdataSize] + { + for (int i = 0; i < 5; i++) { - m_successfullyConnected = false; + if (!WritePlayerPersistentDataThread(playerIdTemp, pdata, pdataSize)) + spdlog::info("retrying..."); + else + return; } - - m_savingPersistentData = false; }); requestThread.detach(); } +bool MasterServerManager::RemoveSelfFromServerListThread() +{ + // we dont process this at all atm, maybe do later, but atm not necessary + LazyCreateHttpClient(); + if (auto result = m_httpClient->Delete(fmt::format("/server/remove_server?id={}", m_ownServerId).c_str())) + { + m_successfullyConnected = true; + } + else + { + m_successfullyConnected = false; + } + + m_ownServerId[0] = 0; +} + void MasterServerManager::RemoveSelfFromServerList() { // dont call this if we don't have a server id @@ -810,22 +910,13 @@ void MasterServerManager::RemoveSelfFromServerList() return; std::thread requestThread([this] { - httplib::Client http(Cvar_ns_masterserver_hostname->m_pszString); - http.set_connection_timeout(25); - http.set_read_timeout(25); - http.set_write_timeout(25); - - // 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 + for (int i = 0; i < 5; i++) { - m_successfullyConnected = false; + if (!RemoveSelfFromServerListThread()) + spdlog::info("retrying..."); + else + return; } - - m_ownServerId[0] = 0; }); requestThread.detach(); diff --git a/NorthstarDedicatedTest/masterserver.h b/NorthstarDedicatedTest/masterserver.h index e51477a2..85237f23 100644 --- a/NorthstarDedicatedTest/masterserver.h +++ b/NorthstarDedicatedTest/masterserver.h @@ -1,5 +1,6 @@ #pragma once #include "convar.h" +#include "httplib.h" #include struct RemoteModInfo @@ -66,6 +67,7 @@ class MasterServerManager private: bool m_requestingServerList = false; bool m_authenticatingWithGameServer = false; + httplib::Client* m_httpClient = nullptr; public: char m_ownServerId[33]; @@ -92,6 +94,18 @@ public: bool m_bHasMainMenuPromoData = false; MainMenuPromoData m_MainMenuPromoData; +private: + void LazyCreateHttpClient(); + bool RequestServerListThread(); + bool RequestMainMenuPromosThread(); + bool AuthenticateOriginWithMasterServerThread(std::string uidStr, std::string tokenStr); + bool AuthenticateWithOwnServerThread(char* uid, char* playerToken); + bool AuthenticateWithServerThread(char* uid, char* playerToken, char* serverId, char* password); + bool AddSelfToServerListThread(int port, int authPort, char* name, char* description, char* map, char* playlist, int maxPlayers, char* password); + bool UpdateServerMapAndPlaylistThread(char* map, char* playlist, int playerCount); + bool WritePlayerPersistentDataThread(std::string playerId, char* pdata, size_t pdataSize); + bool RemoveSelfFromServerListThread(); + public: void ClearServerList(); void RequestServerList(); -- cgit v1.2.3