aboutsummaryrefslogtreecommitdiff
path: root/NorthstarDedicatedTest/masterserver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'NorthstarDedicatedTest/masterserver.cpp')
-rw-r--r--NorthstarDedicatedTest/masterserver.cpp539
1 files changed, 366 insertions, 173 deletions
diff --git a/NorthstarDedicatedTest/masterserver.cpp b/NorthstarDedicatedTest/masterserver.cpp
index a630fc6e..d34065c0 100644
--- a/NorthstarDedicatedTest/masterserver.cpp
+++ b/NorthstarDedicatedTest/masterserver.cpp
@@ -3,7 +3,6 @@
#include "concommand.h"
#include "gameutils.h"
#include "hookutils.h"
-#include "httplib.h"
#include "serverauthentication.h"
#include "gameutils.h"
#include "rapidjson/document.h"
@@ -13,6 +12,9 @@
#include "modmanager.h"
#include "misccommands.h"
+// 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;
@@ -21,6 +23,9 @@ ConVar* Cvar_ns_server_name;
ConVar* Cvar_ns_server_desc;
ConVar* Cvar_ns_server_password;
+// Source ConVar
+ConVar* Cvar_hostname;
+
MasterServerManager* g_MasterServerManager;
typedef void(*CHostState__State_NewGameType)(CHostState* hostState);
@@ -89,6 +94,18 @@ RemoteServerInfo::RemoteServerInfo(const char* newId, const char* newName, const
maxPlayers = newMaxPlayers;
}
+void MasterServerManager::SetCommonHttpClientOptions(CURL* curl)
+{
+ curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+ //curl_easy_setopt(curl, CURLOPT_STDERR, stdout);
+ if (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);
+ }
+}
+
void MasterServerManager::ClearServerList()
{
// this doesn't really do anything lol, probably isn't threadsafe
@@ -99,6 +116,12 @@ void MasterServerManager::ClearServerList()
m_requestingServerList = false;
}
+size_t CurlWriteToStringBufferCallback(char* contents, size_t size, size_t nmemb, void* userp)
+{
+ ((std::string*)userp)->append((char*)contents, size * nmemb);
+ return size * nmemb;
+}
+
void MasterServerManager::AuthenticateOriginWithMasterServer(char* uid, char* originToken)
{
if (m_bOriginAuthWithMasterServerInProgress)
@@ -111,29 +134,35 @@ 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);
-
spdlog::info("Trying to authenticate with northstar masterserver for user {}", uidStr);
- if (auto result = http.Get(fmt::format("/client/origin_auth?id={}&token={}", uidStr, tokenStr).c_str()))
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
+
+ std::string readBuffer;
+ curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/client/origin_auth?id={}&token={}", Cvar_ns_masterserver_hostname->m_pszString, uidStr, tokenStr).c_str());
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
+
+ CURLcode result = curl_easy_perform(curl);
+
+ if (result == CURLcode::CURLE_OK)
{
m_successfullyConnected = true;
- rapidjson::Document originAuthInfo;
- originAuthInfo.Parse(result->body.c_str());
+ rapidjson_document originAuthInfo;
+ originAuthInfo.Parse(readBuffer.c_str());
if (originAuthInfo.HasParseError())
{
- spdlog::error("Failed reading origin auth info response: encountered parse error \{}\"", rapidjson::GetParseError_En(originAuthInfo.GetParseError()));
+ 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);
+ spdlog::error("Failed reading origin auth info response: malformed response object {}", readBuffer);
goto REQUEST_END_CLEANUP;
}
@@ -148,14 +177,15 @@ void MasterServerManager::AuthenticateOriginWithMasterServer(char* uid, char* or
}
else
{
- spdlog::error("Failed performing northstar origin auth: error {}", HttplibErrorToString(result.error()));
+ spdlog::error("Failed performing northstar origin auth: error {}", curl_easy_strerror(result));
m_successfullyConnected = false;
}
// we goto this instead of returning so we always hit this
- REQUEST_END_CLEANUP:
+ REQUEST_END_CLEANUP:
m_bOriginAuthWithMasterServerInProgress = false;
m_bOriginAuthWithMasterServerDone = true;
+ curl_easy_cleanup(curl);
});
requestThread.detach();
@@ -175,20 +205,26 @@ void MasterServerManager::RequestServerList()
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);
spdlog::info("Requesting server list from {}", Cvar_ns_masterserver_hostname->m_pszString);
- if (auto result = http.Get("/client/servers"))
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
+
+ std::string readBuffer;
+ curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/client/servers", Cvar_ns_masterserver_hostname->m_pszString).c_str());
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
+
+ CURLcode result = curl_easy_perform(curl);
+
+ if (result == CURLcode::CURLE_OK)
{
m_successfullyConnected = true;
- rapidjson::Document serverInfoJson;
- serverInfoJson.Parse(result->body.c_str());
+ rapidjson_document serverInfoJson;
+ serverInfoJson.Parse(readBuffer.c_str());
if (serverInfoJson.HasParseError())
{
@@ -199,7 +235,7 @@ void MasterServerManager::RequestServerList()
if (serverInfoJson.IsObject() && serverInfoJson.HasMember("error"))
{
spdlog::error("Failed reading masterserver response: got fastify error response");
- spdlog::error(result->body);
+ spdlog::error(readBuffer);
goto REQUEST_END_CLEANUP;
}
@@ -209,7 +245,7 @@ void MasterServerManager::RequestServerList()
goto REQUEST_END_CLEANUP;
}
- rapidjson::GenericArray<false, rapidjson::Value> serverArray = serverInfoJson.GetArray();
+ rapidjson::GenericArray<false, rapidjson_document::GenericValue> serverArray = serverInfoJson.GetArray();
spdlog::info("Got {} servers", serverArray.Size());
@@ -222,15 +258,15 @@ void MasterServerManager::RequestServerList()
}
// todo: verify json props are fine before adding to m_remoteServers
- if (!serverObj.HasMember("id") || !serverObj["id"].IsString()
- || !serverObj.HasMember("name") || !serverObj["name"].IsString()
+ 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() )
+ || !serverObj.HasMember("modInfo") || !serverObj["modInfo"].HasMember("Mods") || !serverObj["modInfo"]["Mods"].IsArray())
{
spdlog::error("Failed reading masterserver response: malformed server object");
continue;
@@ -279,20 +315,22 @@ void MasterServerManager::RequestServerList()
spdlog::info("Server {} on map {} with playlist {} has {}/{} players", serverObj["name"].GetString(), serverObj["map"].GetString(), serverObj["playlist"].GetString(), serverObj["playerCount"].GetInt(), serverObj["maxPlayers"].GetInt());
}
- std::sort(m_remoteServers.begin(), m_remoteServers.end(), [](RemoteServerInfo& a, RemoteServerInfo& b) {
+ 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()));
+ spdlog::error("Failed requesting servers: error {}", curl_easy_strerror(result));
m_successfullyConnected = false;
}
// we goto this instead of returning so we always hit this
- REQUEST_END_CLEANUP:
+ REQUEST_END_CLEANUP:
m_requestingServerList = false;
m_scriptRequestingServerList = false;
+ curl_easy_cleanup(curl);
});
requestThread.detach();
@@ -306,18 +344,24 @@ void MasterServerManager::RequestMainMenuPromos()
{
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"))
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
+
+ std::string readBuffer;
+ curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/client/mainmenupromos", Cvar_ns_masterserver_hostname->m_pszString).c_str());
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
+
+ CURLcode result = curl_easy_perform(curl);
+
+ if (result == CURLcode::CURLE_OK)
{
m_successfullyConnected = true;
- rapidjson::Document mainMenuPromoJson;
- mainMenuPromoJson.Parse(result->body.c_str());
+ rapidjson_document mainMenuPromoJson;
+ mainMenuPromoJson.Parse(readBuffer.c_str());
if (mainMenuPromoJson.HasParseError())
{
@@ -334,7 +378,7 @@ void MasterServerManager::RequestMainMenuPromos()
if (mainMenuPromoJson.HasMember("error"))
{
spdlog::error("Failed reading masterserver response: got fastify error response");
- spdlog::error(result->body);
+ spdlog::error(readBuffer);
goto REQUEST_END_CLEANUP;
}
@@ -342,7 +386,7 @@ void MasterServerManager::RequestMainMenuPromos()
!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() ||
@@ -384,13 +428,13 @@ void MasterServerManager::RequestMainMenuPromos()
}
else
{
- spdlog::error("Failed requesting main menu promos: error {}", HttplibErrorToString(result.error()));
+ spdlog::error("Failed requesting main menu promos: error {}", curl_easy_strerror(result));
m_successfullyConnected = false;
}
- REQUEST_END_CLEANUP:
+ REQUEST_END_CLEANUP:
// nothing lol
- return;
+ curl_easy_cleanup(curl);
});
requestThread.detach();
@@ -406,19 +450,28 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken
m_scriptAuthenticatingWithGameServer = true;
m_successfullyAuthenticatedWithGameServer = false;
- std::thread requestThread([this, uid, playerToken]()
+ std::string uidStr(uid);
+ std::string tokenStr(playerToken);
+
+ 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);
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
- if (auto result = http.Post(fmt::format("/client/auth_with_self?id={}&playerToken={}", uid, playerToken).c_str()))
+ std::string readBuffer;
+ curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/client/auth_with_self?id={}&playerToken={}", Cvar_ns_masterserver_hostname->m_pszString, uidStr, tokenStr).c_str());
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
+
+ CURLcode result = curl_easy_perform(curl);
+
+ if (result == CURLcode::CURLE_OK)
{
m_successfullyConnected = true;
- rapidjson::Document authInfoJson;
- authInfoJson.Parse(result->body.c_str());
+ rapidjson_document authInfoJson;
+ authInfoJson.Parse(readBuffer.c_str());
if (authInfoJson.HasParseError())
{
@@ -435,7 +488,7 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken
if (authInfoJson.HasMember("error"))
{
spdlog::error("Failed reading masterserver response: got fastify error response");
- spdlog::error(result->body);
+ spdlog::error(readBuffer);
goto REQUEST_END_CLEANUP;
}
@@ -469,25 +522,25 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken
spdlog::error("Failed reading masterserver authentication response: malformed json object");
goto REQUEST_END_CLEANUP;
}
-
- newAuthData.pdata[i++] = (char)byte.GetUint();
+
+ 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));
-
+
m_successfullyAuthenticatedWithGameServer = true;
}
else
{
- spdlog::error("Failed authenticating with own server: error {}", HttplibErrorToString(result.error()));
+ spdlog::error("Failed authenticating with own server: error {}", curl_easy_strerror(result));
m_successfullyConnected = false;
m_successfullyAuthenticatedWithGameServer = false;
m_scriptAuthenticatingWithGameServer = false;
}
- REQUEST_END_CLEANUP:
+ REQUEST_END_CLEANUP:
m_authenticatingWithGameServer = false;
m_scriptAuthenticatingWithGameServer = false;
@@ -497,6 +550,8 @@ void MasterServerManager::AuthenticateWithOwnServer(char* uid, char* playerToken
Cbuf_AddText(Cbuf_GetCurrentPlayer(), "ns_end_reauth_and_leave_to_lobby", cmd_source_t::kCommandSrcCode);
m_bNewgameAfterSelfAuth = false;
}
+
+ curl_easy_cleanup(curl);
});
requestThread.detach();
@@ -512,25 +567,43 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c
m_scriptAuthenticatingWithGameServer = true;
m_successfullyAuthenticatedWithGameServer = false;
- std::thread requestThread([this, uid, playerToken, serverId, password]()
+ std::string uidStr(uid);
+ std::string tokenStr(playerToken);
+ std::string serverIdStr(serverId);
+ std::string passwordStr(password);
+
+ std::thread requestThread([this, uidStr, tokenStr, serverIdStr, passwordStr]()
{
// 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 \"{}\"", serverIdStr);
+
+ 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);
+
+ {
+ char* escapedPassword = curl_easy_escape(curl, passwordStr.c_str(), passwordStr.length());
+
+ curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/client/auth_with_server?id={}&playerToken={}&server={}&password={}", Cvar_ns_masterserver_hostname->m_pszString, uidStr, tokenStr, serverIdStr, escapedPassword).c_str());
+
+ curl_free(escapedPassword);
+ }
- spdlog::info("Attempting authentication with server of id \"{}\"", serverId);
+ CURLcode result = curl_easy_perform(curl);
- if (auto result = http.Post(fmt::format("/client/auth_with_server?id={}&playerToken={}&server={}&password={}", uid, playerToken, serverId, password).c_str()))
+ if (result == CURLcode::CURLE_OK)
{
m_successfullyConnected = true;
-
- rapidjson::Document connectionInfoJson;
- connectionInfoJson.Parse(result->body.c_str());
+
+ rapidjson_document connectionInfoJson;
+ connectionInfoJson.Parse(readBuffer.c_str());
if (connectionInfoJson.HasParseError())
{
@@ -547,7 +620,7 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c
if (connectionInfoJson.HasMember("error"))
{
spdlog::error("Failed reading masterserver response: got fastify error response");
- spdlog::error(result->body);
+ spdlog::error(readBuffer);
goto REQUEST_END_CLEANUP;
}
@@ -564,7 +637,7 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c
}
m_pendingConnectionInfo.ip.S_un.S_addr = inet_addr(connectionInfoJson["ip"].GetString());
- m_pendingConnectionInfo.port = connectionInfoJson["port"].GetInt();
+ m_pendingConnectionInfo.port = (unsigned short)connectionInfoJson["port"].GetUint();
strncpy(m_pendingConnectionInfo.authToken, connectionInfoJson["authToken"].GetString(), 31);
m_pendingConnectionInfo.authToken[31] = 0;
@@ -574,15 +647,16 @@ void MasterServerManager::AuthenticateWithServer(char* uid, char* playerToken, c
}
else
{
- spdlog::error("Failed authenticating with server: error {}", HttplibErrorToString(result.error()));
+ spdlog::error("Failed authenticating with server: error {}", curl_easy_strerror(result));
m_successfullyConnected = false;
m_successfullyAuthenticatedWithGameServer = false;
m_scriptAuthenticatingWithGameServer = false;
}
- REQUEST_END_CLEANUP:
+ REQUEST_END_CLEANUP:
m_authenticatingWithGameServer = false;
m_scriptAuthenticatingWithGameServer = false;
+ curl_easy_cleanup(curl);
});
requestThread.detach();
@@ -601,114 +675,187 @@ void MasterServerManager::AddSelfToServerList(int port, int authPort, char* name
m_bRequireClientAuth = true;
- 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);
+ std::string strName(name);
+ std::string strDescription(description);
+ std::string strMap(map);
+ std::string strPlaylist(playlist);
+ std::string strPassword(password);
+ std::thread requestThread([this, port, authPort, strName, strDescription, strMap, strPlaylist, maxPlayers, strPassword]
+ {
m_ownServerId[0] = 0;
+ m_ownServerAuthToken[0] = 0;
- 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);
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
- // build modinfo obj
- rapidjson::Document modinfoDoc;
- modinfoDoc.SetObject();
- modinfoDoc.AddMember("Mods", rapidjson::Value(rapidjson::kArrayType), modinfoDoc.GetAllocator());
+ std::string readBuffer;
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
- int currentModIndex = 0;
- for (Mod& mod : g_ModManager->m_loadedMods)
- {
- if (!mod.Enabled || (!mod.RequiredOnClient && !mod.Pdiff.size()))
- continue;
+ curl_mime* mime = curl_mime_init(curl);
+ curl_mimepart* part = curl_mime_addpart(mime);
- 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());
+ curl_mime_data(part, m_ownModInfoJson.c_str(), m_ownModInfoJson.size());
+ curl_mime_name(part, "modinfo");
+ curl_mime_filename(part, "modinfo.json");
+ curl_mime_type(part, "application/json");
- currentModIndex++;
- }
+ curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
- rapidjson::StringBuffer buffer;
- buffer.Clear();
- rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
- modinfoDoc.Accept(writer);
- const char* modInfoString = buffer.GetString();
+ // 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());
+
+ curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/server/add_server?port={}&authPort={}&name={}&description={}&map={}&playlist={}&maxPlayers={}&password={}", Cvar_ns_masterserver_hostname->m_pszString, port, authPort, nameEscaped, descEscaped, mapEscaped, playlistEscaped, maxPlayers, passwordEscaped).c_str());
+
+ curl_free(nameEscaped);
+ curl_free(descEscaped);
+ curl_free(mapEscaped);
+ curl_free(playlistEscaped);
+ curl_free(passwordEscaped);
+ }
- httplib::MultipartFormDataItems requestItems = {
- {"modinfo", std::string(modInfoString, buffer.GetSize()), "modinfo.json", "application/octet-stream"}
- };
+ CURLcode result = curl_easy_perform(curl);
- if (auto result = http.Post(request.c_str(), requestItems))
+ if (result == CURLcode::CURLE_OK)
{
m_successfullyConnected = true;
-
- rapidjson::Document serverAddedJson;
- serverAddedJson.Parse(result->body.c_str());
+
+ rapidjson_document serverAddedJson;
+ serverAddedJson.Parse(readBuffer.c_str());
if (serverAddedJson.HasParseError())
{
spdlog::error("Failed reading masterserver authentication response: encountered parse error \"{}\"", rapidjson::GetParseError_En(serverAddedJson.GetParseError()));
- return;
+ goto REQUEST_END_CLEANUP;
}
if (!serverAddedJson.IsObject())
{
spdlog::error("Failed reading masterserver authentication response: root object is not an object");
- return;
+ goto REQUEST_END_CLEANUP;
}
if (serverAddedJson.HasMember("error"))
{
spdlog::error("Failed reading masterserver response: got fastify error response");
- spdlog::error(result->body);
- return;
+ spdlog::error(readBuffer);
+ goto REQUEST_END_CLEANUP;
}
if (!serverAddedJson["success"].IsTrue())
{
spdlog::error("Adding server to masterserver failed: \"success\" is not true");
- return;
+ goto REQUEST_END_CLEANUP;
}
- if (!serverAddedJson.HasMember("id") || !serverAddedJson["id"].IsString())
+ if (!serverAddedJson.HasMember("id") || !serverAddedJson["id"].IsString() || !serverAddedJson.HasMember("serverAuthToken") || !serverAddedJson["serverAuthToken"].IsString())
{
spdlog::error("Failed reading masterserver response: malformed json object");
- return;
+ goto REQUEST_END_CLEANUP;
}
strncpy(m_ownServerId, serverAddedJson["id"].GetString(), sizeof(m_ownServerId));
m_ownServerId[sizeof(m_ownServerId) - 1] = 0;
+ strncpy(m_ownServerAuthToken, serverAddedJson["serverAuthToken"].GetString(), sizeof(m_ownServerAuthToken));
+ m_ownServerAuthToken[sizeof(m_ownServerAuthToken) - 1] = 0;
// 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);
+ Sleep(5000);
+
+ 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);
- while (*m_ownServerId)
+ // 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:
{
- Sleep(15000);
- http.Post(fmt::format("/server/heartbeat?id={}&playerCount={}", m_ownServerId, g_ServerAuthenticationManager->m_additionalPlayerData.size()).c_str());
+ char* escapedNameNew = curl_easy_escape(curl, Cvar_ns_server_name->m_pszString, NULL);
+ char* escapedDescNew = curl_easy_escape(curl, Cvar_ns_server_desc->m_pszString, 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->m_pszString, 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->m_pszString, m_ownServerId, Cvar_hostport->m_nValue, Cvar_ns_player_auth_port->m_nValue, 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_ownModInfoJson.c_str(), m_ownModInfoJson.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)
+ {
+ rapidjson_document serverAddedJson;
+ serverAddedJson.Parse(readBuffer.c_str());
+
+ if (!serverAddedJson.HasParseError() && serverAddedJson.IsObject())
+ {
+ if (serverAddedJson.HasMember("id") && serverAddedJson["id"].IsString())
+ {
+ strncpy(m_ownServerId, serverAddedJson["id"].GetString(), sizeof(m_ownServerId));
+ m_ownServerId[sizeof(m_ownServerId) - 1] = 0;
+ }
+
+ if (serverAddedJson.HasMember("serverAuthToken") && serverAddedJson["serverAuthToken"].IsString())
+ {
+ strncpy(m_ownServerAuthToken, serverAddedJson["serverAuthToken"].GetString(), sizeof(m_ownServerAuthToken));
+ m_ownServerAuthToken[sizeof(m_ownServerAuthToken) - 1] = 0;
+ }
+ }
+ }
+ else
+ spdlog::warn("Heartbeat failed with error {}", curl_easy_strerror(result));
+
+ curl_easy_cleanup(curl);
+ Sleep(10000);
+ } while (*m_ownServerId);
+ });
heartbeatThread.detach();
}
else
{
- spdlog::error("Failed authenticating with server: error {}", HttplibErrorToString(result.error()));
+ spdlog::error("Failed adding self to server list: error {}", curl_easy_strerror(result));
m_successfullyConnected = false;
}
+
+ REQUEST_END_CLEANUP:
+ curl_easy_cleanup(curl);
+ curl_mime_free(mime);
});
requestThread.detach();
@@ -720,21 +867,38 @@ void MasterServerManager::UpdateServerMapAndPlaylist(char* map, char* playlist,
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);
+ std::string strMap(map);
+ std::string strPlaylist(playlist);
+
+ std::thread requestThread([this, strMap, strPlaylist, maxPlayers]
+ {
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
- // 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()))
+ std::string readBuffer;
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
+
+ // escape params
{
- m_successfullyConnected = true;
+ char* mapEscaped = curl_easy_escape(curl, strMap.c_str(), strMap.length());
+ char* playlistEscaped = curl_easy_escape(curl, strPlaylist.c_str(), strPlaylist.length());
+
+ curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/server/update_values?id={}&map={}&playlist={}&maxPlayers={}", Cvar_ns_masterserver_hostname->m_pszString, m_ownServerId, mapEscaped, playlistEscaped, maxPlayers).c_str());
+
+ curl_free(mapEscaped);
+ curl_free(playlistEscaped);
}
+
+ CURLcode result = curl_easy_perform(curl);
+
+ if (result == CURLcode::CURLE_OK)
+ m_successfullyConnected = true;
else
- {
m_successfullyConnected = false;
- }
+
+ curl_easy_cleanup(curl);
});
requestThread.detach();
@@ -746,21 +910,25 @@ void MasterServerManager::UpdateServerPlayerCount(int playerCount)
if (!*m_ownServerId)
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);
+ std::thread requestThread([this, playerCount]
+ {
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
- // 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()))
- {
+ 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->m_pszString, m_ownServerId, playerCount).c_str());
+
+ CURLcode result = curl_easy_perform(curl);
+
+ if (result == CURLcode::CURLE_OK)
m_successfullyConnected = true;
- }
else
- {
m_successfullyConnected = false;
- }
+
+ curl_easy_cleanup(curl);
});
requestThread.detach();
@@ -776,26 +944,38 @@ void MasterServerManager::WritePlayerPersistentData(char* playerId, char* pdata,
return;
}
- 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);
+ std::string strPlayerId(playerId);
+ std::string strPdata(pdata, pdataSize);
+
+ std::thread requestThread([this, strPlayerId, strPdata, pdataSize]
+ {
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
- httplib::MultipartFormDataItems requestItems = {
- { "pdata", std::string(pdata, pdataSize), "file.pdata", "application/octet-stream"}
- };
+ std::string readBuffer;
+ curl_easy_setopt(curl, CURLOPT_URL, fmt::format("{}/accounts/write_persistence?id={}&serverId={}", Cvar_ns_masterserver_hostname->m_pszString, strPlayerId, m_ownServerId).c_str());
+ curl_easy_setopt(curl, CURLOPT_POST, 1L);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteToStringBufferCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
- // 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))
- {
+ 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_successfullyConnected = true;
- }
else
- {
m_successfullyConnected = false;
- }
+
+ curl_easy_cleanup(curl);
m_savingPersistentData = false;
});
@@ -810,22 +990,23 @@ 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);
+ CURL* curl = curl_easy_init();
+ SetCommonHttpClientOptions(curl);
- // 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()))
- {
+ 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->m_pszString, m_ownServerId).c_str());
+
+ CURLcode result = curl_easy_perform(curl);
+
+ if (result == CURLcode::CURLE_OK)
m_successfullyConnected = true;
- }
else
- {
m_successfullyConnected = false;
- }
- m_ownServerId[0] = 0;
+ curl_easy_cleanup(curl);
});
requestThread.detach();
@@ -852,6 +1033,10 @@ void CHostState__State_NewGameHook(CHostState* hostState)
if (maxPlayersVar) // GetCurrentPlaylistVar can return null so protect against this
maxPlayers = std::stoi(maxPlayersVar);
+ // Copy new server name cvar to source
+ Cvar_hostname->m_pszString = Cvar_ns_server_name->m_pszString;
+ Cvar_hostname->m_StringLength = Cvar_ns_server_name->m_StringLength;
+
g_MasterServerManager->AddSelfToServerList(Cvar_hostport->m_nValue, Cvar_ns_player_auth_port->m_nValue, Cvar_ns_server_name->m_pszString, Cvar_ns_server_desc->m_pszString, hostState->m_levelName, (char*)GetCurrentPlaylistName(), maxPlayers, Cvar_ns_server_password->m_pszString);
g_ServerAuthenticationManager->StartPlayerAuthServer();
g_ServerAuthenticationManager->m_bNeedLocalAuthForNewgame = false;
@@ -887,6 +1072,11 @@ void CHostState__State_GameShutdownHook(CHostState* hostState)
CHostState__State_GameShutdown(hostState);
}
+MasterServerManager::MasterServerManager() : m_pendingConnectionInfo{}, m_ownServerId{ "" }, m_ownClientAuthToken{ "" }
+{
+
+}
+
void InitialiseSharedMasterServer(HMODULE baseAddress)
{
Cvar_ns_masterserver_hostname = RegisterConVar("ns_masterserver_hostname", "127.0.0.1", FCVAR_NONE, "");
@@ -898,6 +1088,9 @@ void InitialiseSharedMasterServer(HMODULE baseAddress)
Cvar_ns_server_password = RegisterConVar("ns_server_password", "", FCVAR_GAMEDLL, "");
Cvar_ns_report_server_to_masterserver = RegisterConVar("ns_report_server_to_masterserver", "1", FCVAR_GAMEDLL, "");
Cvar_ns_report_sp_server_to_masterserver = RegisterConVar("ns_report_sp_server_to_masterserver", "0", FCVAR_GAMEDLL, "");
+
+ Cvar_hostname = *(ConVar**)((char*)baseAddress + 0x1315bae8);
+
g_MasterServerManager = new MasterServerManager;
RegisterConCommand("ns_fetchservers", ConCommand_ns_fetchservers, "", FCVAR_CLIENTDLL);
@@ -907,4 +1100,4 @@ void InitialiseSharedMasterServer(HMODULE baseAddress)
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E5D0, CHostState__State_ChangeLevelMPHook, reinterpret_cast<LPVOID*>(&CHostState__State_ChangeLevelMP));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E520, CHostState__State_ChangeLevelSPHook, reinterpret_cast<LPVOID*>(&CHostState__State_ChangeLevelSP));
ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x16E640, CHostState__State_GameShutdownHook, reinterpret_cast<LPVOID*>(&CHostState__State_GameShutdown));
-} \ No newline at end of file
+}