From dfee2592868c27e8c9879c0b6a3a13f6d8692171 Mon Sep 17 00:00:00 2001 From: Preston Peek <36671440+dprestonpeek@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:29:43 -0500 Subject: [PATCH 1/4] Offline mode with serverconfig keyword Added offline keyword prevented kicking when not properly authenticated --- .gitignore | 1 + include/Settings.h | 1 + src/LuaAPI.cpp | 8 +++++++ src/Settings.cpp | 2 ++ src/TConfig.cpp | 5 +++++ src/THeartbeatThread.cpp | 46 +++++++++++++++++++++----------------- src/TNetwork.cpp | 48 +++++++++++++++++++++------------------- vcpkg.json | 1 + 8 files changed, 69 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 4b06a998..007184c7 100644 --- a/.gitignore +++ b/.gitignore @@ -478,3 +478,4 @@ callgrind.* notes/* compile_commands.json nohup.out +vcpkg.json diff --git a/include/Settings.h b/include/Settings.h index 016bef14..4254877f 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -79,6 +79,7 @@ struct Settings { General_Map, General_AuthKey, General_Private, + General_Offline, General_Port, General_MaxCars, General_LogChat, diff --git a/src/LuaAPI.cpp b/src/LuaAPI.cpp index fe16fe71..06368120 100644 --- a/src/LuaAPI.cpp +++ b/src/LuaAPI.cpp @@ -325,6 +325,14 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) { beammp_lua_error("set invalid argument [2] expected boolean"); } break; + case 8: // Offline play + if (NewValue.is()) { + Application::Settings.set(Settings::Key::General_Offline, NewValue.as()); + beammp_info(std::string("Set `Offline` to ") + (Application::Settings.getAsBool(Settings::Key::General_Offline) ? "true" : "false")); + } else { + beammp_lua_error("set invalid argument [2] expected boolean"); + } + break; default: beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this."); break; diff --git a/src/Settings.cpp b/src/Settings.cpp index a8dfdb84..3d1320e1 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -28,6 +28,7 @@ Settings::Settings() { { General_Map, std::string("/levels/gridmap_v2/info.json") }, { General_AuthKey, std::string("") }, { General_Private, true }, + { General_Offline, false }, { General_Port, 30814 }, { General_MaxCars, 1 }, { General_LogChat, true }, @@ -47,6 +48,7 @@ Settings::Settings() { { { "General", "Map" }, { General_Map, READ_WRITE } }, { { "General", "AuthKey" }, { General_AuthKey, NO_ACCESS } }, { { "General", "Private" }, { General_Private, READ_ONLY } }, + { { "General", "Offline" }, { General_Offline, READ_ONLY } }, { { "General", "Port" }, { General_Port, READ_ONLY } }, { { "General", "MaxCars" }, { General_MaxCars, READ_WRITE } }, { { "General", "LogChat" }, { General_LogChat, READ_ONLY } }, diff --git a/src/TConfig.cpp b/src/TConfig.cpp index 74e28468..cf5b5d05 100644 --- a/src/TConfig.cpp +++ b/src/TConfig.cpp @@ -34,6 +34,8 @@ static constexpr std::string_view StrDebug = "Debug"; static constexpr std::string_view EnvStrDebug = "BEAMMP_DEBUG"; static constexpr std::string_view StrPrivate = "Private"; static constexpr std::string_view EnvStrPrivate = "BEAMMP_PRIVATE"; +static constexpr std::string_view StrOffline = "Offline"; +//static constexpr std::string_view EnvStrOffline = "BEAMMP_OFFLINE"; static constexpr std::string_view StrPort = "Port"; static constexpr std::string_view EnvStrPort = "BEAMMP_PORT"; static constexpr std::string_view StrMaxCars = "MaxCars"; @@ -132,6 +134,7 @@ void TConfig::FlushToFile() { SetComment(data["General"][StrLogChat.data()].comments(), " Whether to log chat messages in the console / log"); data["General"][StrDebug.data()] = Application::Settings.getAsBool(Settings::Key::General_Debug); data["General"][StrPrivate.data()] = Application::Settings.getAsBool(Settings::Key::General_Private); + data["General"][StrOffline.data()] = Application::Settings.getAsBool(Settings::Key::General_Offline); SetComment(data["General"][StrInformationPacket.data()].comments(), " Whether to allow unconnected clients to get the public server information without joining"); data["General"][StrInformationPacket.data()] = Application::Settings.getAsBool(Settings::Key::General_InformationPacket); data["General"][StrAllowGuests.data()] = Application::Settings.getAsBool(Settings::Key::General_AllowGuests); @@ -246,6 +249,7 @@ void TConfig::ParseFromFile(std::string_view name) { // Read into new Settings Singleton TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug); TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private); + TryReadValue(data, "General", StrOffline, EnvStrPrivate, Settings::Key::General_Offline); TryReadValue(data, "General", StrInformationPacket, EnvStrInformationPacket, Settings::Key::General_InformationPacket); if (Env::Get(Env::Key::PROVIDER_PORT_ENV).has_value()) { TryReadValue(data, "General", StrPort, Env::Get(Env::Key::PROVIDER_PORT_ENV).value(), Settings::Key::General_Port); @@ -300,6 +304,7 @@ void TConfig::PrintDebug() { } beammp_debug(std::string(StrDebug) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Debug) ? "true" : "false")); beammp_debug(std::string(StrPrivate) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false")); + beammp_debug(std::string(StrOffline) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_Offline) ? "true" : "false")); beammp_debug(std::string(StrInformationPacket) + ": " + std::string(Application::Settings.getAsBool(Settings::Key::General_InformationPacket) ? "true" : "false")); beammp_debug(std::string(StrPort) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port))); beammp_debug(std::string(StrMaxCars) + ": " + std::to_string(Application::Settings.getAsInt(Settings::Key::General_MaxCars))); diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 36cdde1c..b7168d28 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -110,30 +110,35 @@ void THeartbeatThread::operator()() { } } else { if (!Application::Settings.getAsBool(Settings::Key::General_Private)) { - beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work."); + if (Application::Settings.getAsBool(Settings::Key::General_Offline)) { + beammp_warn("Server is in Offline Mode. Your server will not appear in the global server list. Direct connect will still work."); + } else { + beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work."); + } } } - - if (Ok && !isAuth && !Application::Settings.getAsBool(Settings::Key::General_Private)) { - if (Status == "2000") { - beammp_info(("Authenticated! " + Message)); - isAuth = true; - } else if (Status == "200") { - beammp_info(("Resumed authenticated session! " + Message)); - isAuth = true; - } else { - if (Message.empty()) { - Message = "Backend didn't provide a reason."; + if (!Application::Settings.getAsBool(Settings::Key::General_Offline)) { + if (Ok && !isAuth && !Application::Settings.getAsBool(Settings::Key::General_Private)) { + if (Status == "2000") { + beammp_info(("Authenticated! " + Message)); + isAuth = true; + } else if (Status == "200") { + beammp_info(("Resumed authenticated session! " + Message)); + isAuth = true; + } else { + if (Message.empty()) { + Message = "Backend didn't provide a reason."; + } + beammp_error("Backend REFUSED the auth key. Reason: " + Message); } - beammp_error("Backend REFUSED the auth key. Reason: " + Message); } - } - if (isAuth || Application::Settings.getAsBool(Settings::Key::General_Private)) { - Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); - } - if (!Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates) && UpdateReminderTimePassed.count() > UpdateReminderTimeout.count()) { - LastUpdateReminderTime = std::chrono::high_resolution_clock::now(); - Application::CheckForUpdates(); + if (isAuth || Application::Settings.getAsBool(Settings::Key::General_Private)) { + Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); + } + if (!Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates) && UpdateReminderTimePassed.count() > UpdateReminderTimeout.count()) { + LastUpdateReminderTime = std::chrono::high_resolution_clock::now(); + Application::CheckForUpdates(); + } } } } @@ -145,6 +150,7 @@ std::string THeartbeatThread::GenerateCall() { { "port", std::to_string(Application::Settings.getAsInt(Settings::Key::General_Port)) }, { "map", Application::Settings.getAsString(Settings::Key::General_Map) }, { "private", Application::Settings.getAsBool(Settings::Key::General_Private) ? "true" : "false" }, + { "offline", Application::Settings.getAsBool(Settings::Key::General_Offline) ? "true" : "false" }, { "version", Application::ServerVersionString() }, { "clientversion", Application::ClientMinimumVersion().AsString() }, { "name", Application::Settings.getAsString(Settings::Key::General_Name) }, diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index 24fc37c6..56c025f6 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -362,32 +362,34 @@ std::shared_ptr TNetwork::Authentication(TConnection&& RawConnection) { return nullptr; } - beammp_debug("Response from authentication backend: " + AuthResStr); - - try { - nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr); - - if (AuthRes["username"].is_string() && AuthRes["roles"].is_string() - && AuthRes["guest"].is_boolean() && AuthRes["identifiers"].is_array()) { - - Client->SetName(AuthRes["username"]); - Client->SetRoles(AuthRes["roles"]); - Client->SetIsGuest(AuthRes["guest"]); - for (const auto& ID : AuthRes["identifiers"]) { - auto Raw = std::string(ID); - auto SepIndex = Raw.find(':'); - Client->SetIdentifier(Raw.substr(0, SepIndex), Raw.substr(SepIndex + 1)); + if (!Application::Settings.getAsBool(Settings::Key::General_Offline)) { + beammp_debug("Response from authentication backend: " + AuthResStr); + + try { + nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr); + + if (AuthRes["username"].is_string() && AuthRes["roles"].is_string() + && AuthRes["guest"].is_boolean() && AuthRes["identifiers"].is_array()) { + + Client->SetName(AuthRes["username"]); + Client->SetRoles(AuthRes["roles"]); + Client->SetIsGuest(AuthRes["guest"]); + for (const auto& ID : AuthRes["identifiers"]) { + auto Raw = std::string(ID); + auto SepIndex = Raw.find(':'); + Client->SetIdentifier(Raw.substr(0, SepIndex), Raw.substr(SepIndex + 1)); + } + } else { + beammp_error("Invalid authentication data received from authentication backend"); + ClientKick(*Client, "Invalid authentication data!"); + return nullptr; } - } else { - beammp_error("Invalid authentication data received from authentication backend"); - ClientKick(*Client, "Invalid authentication data!"); + } catch (const std::exception& e) { + beammp_errorf("Client sent invalid key. Error was: {}", e.what()); + // TODO: we should really clarify that this was a backend response or parsing error + ClientKick(*Client, "Invalid key! Please restart your game."); return nullptr; } - } catch (const std::exception& e) { - beammp_errorf("Client sent invalid key. Error was: {}", e.what()); - // TODO: we should really clarify that this was a backend response or parsing error - ClientKick(*Client, "Invalid key! Please restart your game."); - return nullptr; } beammp_debug("Name -> " + Client->GetName() + ", Guest -> " + std::to_string(Client->IsGuest()) + ", Roles -> " + Client->GetRoles()); diff --git a/vcpkg.json b/vcpkg.json index cc7e74f8..44dc9f56 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -10,6 +10,7 @@ "doctest", "fmt", "libzip", + "lua", "nlohmann-json", "openssl", "rapidjson", From 87d1c017dbb433cb76e3be573565188f0f0a75fa Mon Sep 17 00:00:00 2001 From: Preston Peek <36671440+dprestonpeek@users.noreply.github.com> Date: Mon, 3 Mar 2025 18:23:20 -0500 Subject: [PATCH 2/4] Fix an EnvStr error, gameplay unaffected Also added some comments (I said I added comments in my recent BeamMP commit, but said comments were actually in the server project. woops) --- src/TConfig.cpp | 4 ++-- src/THeartbeatThread.cpp | 4 +++- src/TNetwork.cpp | 15 +++++++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/TConfig.cpp b/src/TConfig.cpp index cf5b5d05..562281b6 100644 --- a/src/TConfig.cpp +++ b/src/TConfig.cpp @@ -35,7 +35,7 @@ static constexpr std::string_view EnvStrDebug = "BEAMMP_DEBUG"; static constexpr std::string_view StrPrivate = "Private"; static constexpr std::string_view EnvStrPrivate = "BEAMMP_PRIVATE"; static constexpr std::string_view StrOffline = "Offline"; -//static constexpr std::string_view EnvStrOffline = "BEAMMP_OFFLINE"; +static constexpr std::string_view EnvStrOffline = "BEAMMP_OFFLINE"; static constexpr std::string_view StrPort = "Port"; static constexpr std::string_view EnvStrPort = "BEAMMP_PORT"; static constexpr std::string_view StrMaxCars = "MaxCars"; @@ -249,7 +249,7 @@ void TConfig::ParseFromFile(std::string_view name) { // Read into new Settings Singleton TryReadValue(data, "General", StrDebug, EnvStrDebug, Settings::Key::General_Debug); TryReadValue(data, "General", StrPrivate, EnvStrPrivate, Settings::Key::General_Private); - TryReadValue(data, "General", StrOffline, EnvStrPrivate, Settings::Key::General_Offline); + TryReadValue(data, "General", StrOffline, EnvStrOffline, Settings::Key::General_Offline); TryReadValue(data, "General", StrInformationPacket, EnvStrInformationPacket, Settings::Key::General_InformationPacket); if (Env::Get(Env::Key::PROVIDER_PORT_ENV).has_value()) { TryReadValue(data, "General", StrPort, Env::Get(Env::Key::PROVIDER_PORT_ENV).value(), Settings::Key::General_Port); diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index b7168d28..9f2967bb 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -111,12 +111,14 @@ void THeartbeatThread::operator()() { } else { if (!Application::Settings.getAsBool(Settings::Key::General_Private)) { if (Application::Settings.getAsBool(Settings::Key::General_Offline)) { - beammp_warn("Server is in Offline Mode. Your server will not appear in the global server list. Direct connect will still work."); + // TODO-Preston: find out if we ever actually get here when in offline mode? This message may not be necessary + beammp_warn("Server is in Offline Mode. Your server will not appear in the global server list. Direct connect will still work with LAN connections."); } else { beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work."); } } } + // Don't do anything requiring an internet connection if we are in offline mode. if (!Application::Settings.getAsBool(Settings::Key::General_Offline)) { if (Ok && !isAuth && !Application::Settings.getAsBool(Settings::Key::General_Private)) { if (Status == "2000") { diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index 56c025f6..d309cbae 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -362,11 +362,18 @@ std::shared_ptr TNetwork::Authentication(TConnection&& RawConnection) { return nullptr; } - if (!Application::Settings.getAsBool(Settings::Key::General_Offline)) { - beammp_debug("Response from authentication backend: " + AuthResStr); + // If we are in "offline mode", we want to do something different for auth + if (Application::Settings.getAsBool(Settings::Key::General_Offline)) { + // skip auth altogether. + // this prevents players from getting kicked for not being logged in. + + // TODO: authenticate using a different method here. - try { - nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr); + } else { + beammp_debug("Response from authentication backend: " + AuthResStr); + + try { + nlohmann::json AuthRes = nlohmann::json::parse(AuthResStr); if (AuthRes["username"].is_string() && AuthRes["roles"].is_string() && AuthRes["guest"].is_boolean() && AuthRes["identifiers"].is_array()) { From 42f1c3520f944e6d469f93c0fab316cd79d621c0 Mon Sep 17 00:00:00 2001 From: Preston Peek <36671440+dprestonpeek@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:58:04 -0500 Subject: [PATCH 3/4] Heartbeat early out when in offline mode --- src/THeartbeatThread.cpp | 51 +++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 9f2967bb..9a8a03e8 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -61,6 +61,11 @@ void THeartbeatThread::operator()() { nlohmann::json Doc; bool Ok = false; + + if (Application::Settings.getAsBool(Settings::Key::General_Offline)) { + beammp_warn("Server is in Offline Mode. Your server will not appear in the global server list. Direct connect will still work with LAN connections."); + return; + } for (const auto& Url : Application::GetBackendUrlsInOrder()) { T = Http::POST(Url + Target, Body, "application/json", &ResponseCode, { { "api-v", "2" } }); @@ -110,37 +115,29 @@ void THeartbeatThread::operator()() { } } else { if (!Application::Settings.getAsBool(Settings::Key::General_Private)) { - if (Application::Settings.getAsBool(Settings::Key::General_Offline)) { - // TODO-Preston: find out if we ever actually get here when in offline mode? This message may not be necessary - beammp_warn("Server is in Offline Mode. Your server will not appear in the global server list. Direct connect will still work with LAN connections."); - } else { - beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work."); - } + beammp_warn("Backend failed to respond to a heartbeat. Your server may temporarily disappear from the server list. This is not an error, and will likely resolve itself soon. Direct connect will still work."); } } - // Don't do anything requiring an internet connection if we are in offline mode. - if (!Application::Settings.getAsBool(Settings::Key::General_Offline)) { - if (Ok && !isAuth && !Application::Settings.getAsBool(Settings::Key::General_Private)) { - if (Status == "2000") { - beammp_info(("Authenticated! " + Message)); - isAuth = true; - } else if (Status == "200") { - beammp_info(("Resumed authenticated session! " + Message)); - isAuth = true; - } else { - if (Message.empty()) { - Message = "Backend didn't provide a reason."; - } - beammp_error("Backend REFUSED the auth key. Reason: " + Message); + if (Ok && !isAuth && !Application::Settings.getAsBool(Settings::Key::General_Private)) { + if (Status == "2000") { + beammp_info(("Authenticated! " + Message)); + isAuth = true; + } else if (Status == "200") { + beammp_info(("Resumed authenticated session! " + Message)); + isAuth = true; + } else { + if (Message.empty()) { + Message = "Backend didn't provide a reason."; } + beammp_error("Backend REFUSED the auth key. Reason: " + Message); } - if (isAuth || Application::Settings.getAsBool(Settings::Key::General_Private)) { - Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); - } - if (!Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates) && UpdateReminderTimePassed.count() > UpdateReminderTimeout.count()) { - LastUpdateReminderTime = std::chrono::high_resolution_clock::now(); - Application::CheckForUpdates(); - } + } + if (isAuth || Application::Settings.getAsBool(Settings::Key::General_Private)) { + Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); + } + if (!Application::Settings.getAsBool(Settings::Key::Misc_ImScaredOfUpdates) && UpdateReminderTimePassed.count() > UpdateReminderTimeout.count()) { + LastUpdateReminderTime = std::chrono::high_resolution_clock::now(); + Application::CheckForUpdates(); } } } From 158d67b4ffb28f208009f1c2262a5e02da7472d2 Mon Sep 17 00:00:00 2001 From: Preston Peek <36671440+dprestonpeek@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:42:58 -0500 Subject: [PATCH 4/4] Revert ignore and vcpkg.json --- .gitignore | 1 - vcpkg.json | 1 - 2 files changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 007184c7..4b06a998 100644 --- a/.gitignore +++ b/.gitignore @@ -478,4 +478,3 @@ callgrind.* notes/* compile_commands.json nohup.out -vcpkg.json diff --git a/vcpkg.json b/vcpkg.json index 44dc9f56..cc7e74f8 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -10,7 +10,6 @@ "doctest", "fmt", "libzip", - "lua", "nlohmann-json", "openssl", "rapidjson",