From 639382be1d0af9f456d5b3ca2c7bc268ab8e1e8e Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Thu, 9 Oct 2025 00:15:21 +0100 Subject: [PATCH 01/17] Remove check for if race is in progress when advertising servers to game --- Bombd/Core/RoomManager.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Bombd/Core/RoomManager.cs b/Bombd/Core/RoomManager.cs index 1bd9038..da3ae24 100644 --- a/Bombd/Core/RoomManager.cs +++ b/Bombd/Core/RoomManager.cs @@ -253,10 +253,6 @@ public List SearchRooms(GameAttributes attributes, Platform id, // before advertising the session. if (!room.Simulation.HasRaceSettings) return false; - - // If the race is already in progress, don't advertise the session - if (room.Simulation.RaceState >= RaceState.LoadingIntoRace || !room.Simulation.CanJoinAsRacer()) - return false; } foreach (KeyValuePair attribute in attributes) From f134da1dfc1ed99cd8019eac21c1cda967b0bafc Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Sun, 12 Oct 2025 00:54:42 +0100 Subject: [PATCH 02/17] Exclude away players from StartEvent --- Bombd/Core/SimServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bombd/Core/SimServer.cs b/Bombd/Core/SimServer.cs index 58c2aad..80b0821 100644 --- a/Bombd/Core/SimServer.cs +++ b/Bombd/Core/SimServer.cs @@ -381,7 +381,7 @@ private void StartEvent() if (IsModNation) { int trackId = _raceSettings.Value.CreationId; - List playerIds = _players.Where(player => !player.IsSpectator) + List playerIds = _players.Where(player => !player.IsSpectator && player.State.Away != 0) .Select(player => player.State.PlayerConnectId).ToList(); BombdServer.Comms.NotifyEventStarted(trackId, playerIds); } From 29cd97536d0cbd4fa01ff0cc8e55286d9d7cfe2d Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Sun, 12 Oct 2025 02:07:57 +0100 Subject: [PATCH 03/17] Away status no longer forces you into the game --- Bombd/Core/SimServer.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Bombd/Core/SimServer.cs b/Bombd/Core/SimServer.cs index 80b0821..2c7f3c6 100644 --- a/Bombd/Core/SimServer.cs +++ b/Bombd/Core/SimServer.cs @@ -152,6 +152,22 @@ private void SwitchToSpectator(GamePlayer player) UpdateRaceSetup(); } + private void SwitchToRacer(GamePlayer player) + { + if (!player.IsSpectator) return; + + player.IsSpectator = false; + if (IsKarting) BroadcastSessionInfo(); + else + { + Broadcast(new NetMessageSessionInfo(PlayerSessionOperation.SwitchSpectatorToRacer, player.UserId), + NetMessageType.PlayerSessionInfo); + } + + if (RaceState == RaceState.GameroomCountdown) + UpdateRaceSetup(); + } + private void BroadcastSessionInfo() { // In ModNation, the session messages seem to only be switches and joins rather than their actual states, @@ -381,7 +397,7 @@ private void StartEvent() if (IsModNation) { int trackId = _raceSettings.Value.CreationId; - List playerIds = _players.Where(player => !player.IsSpectator && player.State.Away != 0) + List playerIds = _players.Where(player => !player.IsSpectator) .Select(player => player.State.PlayerConnectId).ToList(); BombdServer.Comms.NotifyEventStarted(trackId, playerIds); } @@ -1437,6 +1453,11 @@ public void OnNetworkMessage(GamePlayer player, uint senderNameUid, NetMessageTy // Patch our existing player state with the new message player.State.Update(state); + + if (player.State.Away != 0) + SwitchToSpectator(player); + else + SwitchToRacer(player); // If we're not in a gameroom, there's no GameroomReady event, so wait until we've received // the player config and the second player state update to finish our "connecting" process. @@ -1694,7 +1715,7 @@ private void SimUpdate() if (_raceSettings != null && room.State < RoomState.RaceInProgress) { int numReadyPlayers = - _players.Count(x => (x.State.Flags & PlayerStateFlags.GameRoomReady) != 0); + _players.Count(x => (x.State.Flags & PlayerStateFlags.GameRoomReady) != 0 && !x.IsSpectator); bool hasMinPlayers = numReadyPlayers >= _raceSettings.Value.MinHumans; // Karting, series races, and ranked races don't allow the "owner" to start the race From 63d7c6e7711be933225b97b29dd56d4636c5f1e8 Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Sun, 12 Oct 2025 02:13:11 +0100 Subject: [PATCH 04/17] Add code comments --- Bombd/Core/SimServer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Bombd/Core/SimServer.cs b/Bombd/Core/SimServer.cs index 2c7f3c6..92a4824 100644 --- a/Bombd/Core/SimServer.cs +++ b/Bombd/Core/SimServer.cs @@ -1454,6 +1454,7 @@ public void OnNetworkMessage(GamePlayer player, uint senderNameUid, NetMessageTy // Patch our existing player state with the new message player.State.Update(state); + // Check if player is away, if they are switch them to spectator so they do not join the race if (player.State.Away != 0) SwitchToSpectator(player); else From 9899335f82237f62ef12ccfafbc241800cdde070 Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Sun, 12 Oct 2025 12:08:52 +0100 Subject: [PATCH 05/17] Implement matchmaking filter to tell game when a lobby is in progress --- Bombd/Core/RoomManager.cs | 6 ++++-- Bombd/Data/Matchmaking/ModNation.xml | 4 +++- Bombd/Properties/launchSettings.json | 21 ++++++++++++++------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Bombd/Core/RoomManager.cs b/Bombd/Core/RoomManager.cs index da3ae24..60a6cc3 100644 --- a/Bombd/Core/RoomManager.cs +++ b/Bombd/Core/RoomManager.cs @@ -110,7 +110,8 @@ public GameRoom CreateRoom(CreateGameRequest request) request.Attributes["__JOIN_MODE"] = "OPEN"; request.Attributes["__MM_MODE_G"] = "OPEN"; request.Attributes["__MM_MODE_P"] = "OPEN"; - + request.Attributes["IS_LOCKED"] = "0"; + // Set default server type if none was provided, although this // generally shouldn't happen request.Attributes.TryAdd("SERVER_TYPE", "kartPark"); @@ -281,7 +282,8 @@ public void UpdateRoom(GameRoom room, EventSettings settings) attr["__MM_MODE_G"] = visibility; attr["__MM_MODE_P"] = visibility; attr["__JOIN_MODE"] = visibility; - + attr["IS_LOCKED"] = (room.Simulation.RaceState >= RaceState.LoadingIntoRace || !room.Simulation.CanJoinAsRacer()) ? "1" : "0"; + attr["__MAX_PLAYERS"] = settings.MaxHumans.ToString(); // TODO: Adjust player counts on Karting? diff --git a/Bombd/Data/Matchmaking/ModNation.xml b/Bombd/Data/Matchmaking/ModNation.xml index f692ebc..96f72a3 100644 --- a/Bombd/Data/Matchmaking/ModNation.xml +++ b/Bombd/Data/Matchmaking/ModNation.xml @@ -36,4 +36,6 @@ kartPark competitive - \ No newline at end of file + + + \ No newline at end of file diff --git a/Bombd/Properties/launchSettings.json b/Bombd/Properties/launchSettings.json index 5f149f4..63be22c 100644 --- a/Bombd/Properties/launchSettings.json +++ b/Bombd/Properties/launchSettings.json @@ -1,10 +1,17 @@ { - "profiles": { - "Bombd": { - "commandName": "Project" - }, - "Docker": { - "commandName": "Docker" + "profiles": { + "Bombd": { + "commandName": "Project" + }, + "Docker": { + "commandName": "Docker" + }, + "WSL": { + "commandName": "WSL2", + "environmentVariables": { + "LD_LIBRARY_PATH": "/usr/local/lib64:/usr/local/lib:/usr/lib", + "OPENSSL_CONF": "/etc/ssl/openssl.cnf" + } + } } - } } \ No newline at end of file From 67aae72d55be1581048c7b9031f73a039007c9de Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Sun, 12 Oct 2025 13:08:04 +0100 Subject: [PATCH 06/17] Update attribute state at start and end of race --- Bombd/Core/SimServer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Bombd/Core/SimServer.cs b/Bombd/Core/SimServer.cs index 92a4824..91a4fbe 100644 --- a/Bombd/Core/SimServer.cs +++ b/Bombd/Core/SimServer.cs @@ -436,6 +436,8 @@ private void SetCurrentGameroomState(RoomState state) { BroadcastPlayerState(); SwitchAllToRacers(); + if (_raceSettings != null) + Room.UpdateAttributes(_raceSettings.Value); break; } case RoomState.RaceInProgress: @@ -443,6 +445,8 @@ private void SetCurrentGameroomState(RoomState state) StartEvent(); BroadcastSessionInfo(); BroadcastPlayerState(); + if (_raceSettings != null) + Room.UpdateAttributes(_raceSettings.Value); break; } case RoomState.Ready: From 63fae2f829d78a858b991488cfb3cfe19367c2a7 Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Sun, 12 Oct 2025 14:12:47 +0100 Subject: [PATCH 07/17] Fix away issues --- Bombd/Core/SimServer.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Bombd/Core/SimServer.cs b/Bombd/Core/SimServer.cs index 91a4fbe..d3e3e23 100644 --- a/Bombd/Core/SimServer.cs +++ b/Bombd/Core/SimServer.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Bombd.Extensions; using Bombd.Globals; using Bombd.Helpers; @@ -125,7 +126,7 @@ public bool IsRoomReady() private void SwitchAllToRacers() { - foreach (GamePlayer player in _players) + foreach (GamePlayer player in _players.Where(x => x.State.Away == 0)) player.IsSpectator = false; foreach (PlayerInfo info in _playerInfos) info.Operation = PlayerJoinStatus.RacerPending; @@ -271,8 +272,9 @@ public void OnPlayerLeft(GamePlayer player, bool disconnected) if (player.UserId == Owner) { var random = new Random(); - int index = random.Next(0, _players.Count); - var randomPlayer = _players[index]; + var availablePlayers = _players.Where(x => x.State.Away == 0).ToArray(); + int index = random.Next(0, availablePlayers.Length); + var randomPlayer = availablePlayers[index]; Owner = randomPlayer.UserId; if (_raceSettings != null) @@ -1720,7 +1722,7 @@ private void SimUpdate() if (_raceSettings != null && room.State < RoomState.RaceInProgress) { int numReadyPlayers = - _players.Count(x => (x.State.Flags & PlayerStateFlags.GameRoomReady) != 0 && !x.IsSpectator); + _players.Count(x => (x.State.Flags & PlayerStateFlags.GameRoomReady) != 0 && x.State.Away == 0); bool hasMinPlayers = numReadyPlayers >= _raceSettings.Value.MinHumans; // Karting, series races, and ranked races don't allow the "owner" to start the race From 7d6191541fcd6b570cd7155481a755fee21f9754 Mon Sep 17 00:00:00 2001 From: derole Date: Mon, 13 Oct 2025 13:26:16 +0100 Subject: [PATCH 08/17] More detailed exception logging --- Bombd/Protocols/TCP/SslConnection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Bombd/Protocols/TCP/SslConnection.cs b/Bombd/Protocols/TCP/SslConnection.cs index f5a6bf5..f34e1e3 100644 --- a/Bombd/Protocols/TCP/SslConnection.cs +++ b/Bombd/Protocols/TCP/SslConnection.cs @@ -207,9 +207,10 @@ private async void DoBlockAndReceive() } while (offset < payloadSize); } } - catch (Exception) + catch (Exception ex) { Logger.LogError("An error occurred while reading from socket. Closing connection."); + Logger.LogDebug(ex.ToString()); Disconnect(); return; } From 769a9a29e24f1180550be88f5b58499e497aaf07 Mon Sep 17 00:00:00 2001 From: derole Date: Mon, 13 Oct 2025 13:37:05 +0100 Subject: [PATCH 09/17] Add debugging profile required for development in WSL2, Add ability to adjust LogLevel via config --- Bombd/Core/BombdConfig.cs | 5 +++++ Bombd/Logging/Logger.cs | 9 +++------ Bombd/Program.cs | 2 ++ Bombd/Properties/launchSettings.json | 21 ++++++++++++++------- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Bombd/Core/BombdConfig.cs b/Bombd/Core/BombdConfig.cs index c8b43a9..9ad24a8 100644 --- a/Bombd/Core/BombdConfig.cs +++ b/Bombd/Core/BombdConfig.cs @@ -66,6 +66,11 @@ static BombdConfig() /// How many times each service performs an update in a second. /// public int TickRate { get; set; } = 15; + + /// + /// The maximum log level to log messages from. + /// + public string MaxLogLevel { get; set; } = Enum.GetName(LogLevel.Info); /// /// Whether or not connections from LittleBigPlanet Karting are allowed. diff --git a/Bombd/Logging/Logger.cs b/Bombd/Logging/Logger.cs index 7a0785c..4a1cffc 100644 --- a/Bombd/Logging/Logger.cs +++ b/Bombd/Logging/Logger.cs @@ -6,13 +6,8 @@ namespace Bombd.Logging; public class Logger { -#if DEBUG - private const LogLevel MaxLevel = LogLevel.Trace; -#else - private const LogLevel MaxLevel = LogLevel.Info; -#endif - private static readonly ConcurrentQueue LogQueue = new(); + private static LogLevel MaxLevel = LogLevel.Info; static Logger() { @@ -48,6 +43,8 @@ private static ConsoleColor GetLogColor(LogLevel level) }; } + public static void SetLogMaxLevel(LogLevel level) => MaxLevel = level; + public static void LogError(string message) => Log(LogLevel.Error, message); public static void LogWarning(string message) => Log(LogLevel.Warning, message); public static void LogInfo(string message) => Log(LogLevel.Info, message); diff --git a/Bombd/Program.cs b/Bombd/Program.cs index b64baa0..4816c5d 100644 --- a/Bombd/Program.cs +++ b/Bombd/Program.cs @@ -3,6 +3,8 @@ using Bombd.Services; using Directory = Bombd.Services.Directory; +Logger.SetLogMaxLevel(Enum.Parse(BombdConfig.Instance.MaxLogLevel)); + string certificate = BombdConfig.Instance.PfxCertificate; if (string.IsNullOrEmpty(certificate)) { diff --git a/Bombd/Properties/launchSettings.json b/Bombd/Properties/launchSettings.json index 5f149f4..63be22c 100644 --- a/Bombd/Properties/launchSettings.json +++ b/Bombd/Properties/launchSettings.json @@ -1,10 +1,17 @@ { - "profiles": { - "Bombd": { - "commandName": "Project" - }, - "Docker": { - "commandName": "Docker" + "profiles": { + "Bombd": { + "commandName": "Project" + }, + "Docker": { + "commandName": "Docker" + }, + "WSL": { + "commandName": "WSL2", + "environmentVariables": { + "LD_LIBRARY_PATH": "/usr/local/lib64:/usr/local/lib:/usr/lib", + "OPENSSL_CONF": "/etc/ssl/openssl.cnf" + } + } } - } } \ No newline at end of file From 62aa9cbf577c7c7593195e94b80c42c291074dad Mon Sep 17 00:00:00 2001 From: derole Date: Mon, 13 Oct 2025 13:58:53 +0100 Subject: [PATCH 10/17] Make consistent with rest of codebase --- Bombd/Protocols/TCP/SslConnection.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Bombd/Protocols/TCP/SslConnection.cs b/Bombd/Protocols/TCP/SslConnection.cs index f34e1e3..a35b8f5 100644 --- a/Bombd/Protocols/TCP/SslConnection.cs +++ b/Bombd/Protocols/TCP/SslConnection.cs @@ -149,9 +149,10 @@ public override void Send(ArraySegment data, PacketType type) offset += size; } while (offset < messageSize); } - catch (Exception) + catch (Exception e) { Logger.LogError("An error occurred during send. Closing connection."); + Logger.LogDebug(e.ToString()); Disconnect(); } } @@ -207,10 +208,10 @@ private async void DoBlockAndReceive() } while (offset < payloadSize); } } - catch (Exception ex) + catch (Exception e) { Logger.LogError("An error occurred while reading from socket. Closing connection."); - Logger.LogDebug(ex.ToString()); + Logger.LogDebug(e.ToString()); Disconnect(); return; } From 01a4a292f157a16c2a8cd41366a7280299c90dc0 Mon Sep 17 00:00:00 2001 From: derole Date: Mon, 13 Oct 2025 14:05:47 +0100 Subject: [PATCH 11/17] Debug logging for loglevel --- Bombd/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Bombd/Program.cs b/Bombd/Program.cs index 4816c5d..871dacf 100644 --- a/Bombd/Program.cs +++ b/Bombd/Program.cs @@ -3,7 +3,9 @@ using Bombd.Services; using Directory = Bombd.Services.Directory; -Logger.SetLogMaxLevel(Enum.Parse(BombdConfig.Instance.MaxLogLevel)); +var logLevel = Enum.Parse(BombdConfig.Instance.MaxLogLevel); +Logger.SetLogMaxLevel(logLevel); +Logger.LogDebug($"Max log level is now {BombdConfig.Instance.MaxLogLevel} ({logLevel})"); string certificate = BombdConfig.Instance.PfxCertificate; if (string.IsNullOrEmpty(certificate)) From 33fa756deda9ee6a6367cbf73afd2380785222e7 Mon Sep 17 00:00:00 2001 From: derole Date: Mon, 13 Oct 2025 14:12:52 +0100 Subject: [PATCH 12/17] Update loglevel debug --- Bombd/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bombd/Program.cs b/Bombd/Program.cs index 871dacf..f49ee55 100644 --- a/Bombd/Program.cs +++ b/Bombd/Program.cs @@ -5,7 +5,7 @@ var logLevel = Enum.Parse(BombdConfig.Instance.MaxLogLevel); Logger.SetLogMaxLevel(logLevel); -Logger.LogDebug($"Max log level is now {BombdConfig.Instance.MaxLogLevel} ({logLevel})"); +Logger.LogInfo($"Max log level is now {BombdConfig.Instance.MaxLogLevel} ({logLevel})"); string certificate = BombdConfig.Instance.PfxCertificate; if (string.IsNullOrEmpty(certificate)) From c1c95c4aba9b27325c8dc9609d01df043d828c81 Mon Sep 17 00:00:00 2001 From: derole Date: Mon, 13 Oct 2025 14:19:19 +0100 Subject: [PATCH 13/17] Remove conditionals for logging --- Bombd/Logging/Logger.cs | 4 ---- Bombd/Program.cs | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Bombd/Logging/Logger.cs b/Bombd/Logging/Logger.cs index 4a1cffc..392d5c0 100644 --- a/Bombd/Logging/Logger.cs +++ b/Bombd/Logging/Logger.cs @@ -49,10 +49,8 @@ private static ConsoleColor GetLogColor(LogLevel level) public static void LogWarning(string message) => Log(LogLevel.Warning, message); public static void LogInfo(string message) => Log(LogLevel.Info, message); - [Conditional("DEBUG")] public static void LogDebug(string message) => Log(LogLevel.Debug, message); - [Conditional("DEBUG")] public static void LogTrace(string message) => Log(LogLevel.Trace, message); public static void Log(LogLevel level, string message) @@ -64,10 +62,8 @@ public static void Log(LogLevel level, string message) public static void LogWarning(Type type, string message) => Log(type, LogLevel.Warning, message); public static void LogInfo(Type type, string message) => Log(type, LogLevel.Info, message); - [Conditional("DEBUG")] public static void LogDebug(Type type, string message) => Log(type, LogLevel.Debug, message); - [Conditional("DEBUG")] public static void LogTrace(Type type, string message) => Log(type, LogLevel.Trace, message); public static void Log(Type type, LogLevel level, string message) diff --git a/Bombd/Program.cs b/Bombd/Program.cs index f49ee55..4816c5d 100644 --- a/Bombd/Program.cs +++ b/Bombd/Program.cs @@ -3,9 +3,7 @@ using Bombd.Services; using Directory = Bombd.Services.Directory; -var logLevel = Enum.Parse(BombdConfig.Instance.MaxLogLevel); -Logger.SetLogMaxLevel(logLevel); -Logger.LogInfo($"Max log level is now {BombdConfig.Instance.MaxLogLevel} ({logLevel})"); +Logger.SetLogMaxLevel(Enum.Parse(BombdConfig.Instance.MaxLogLevel)); string certificate = BombdConfig.Instance.PfxCertificate; if (string.IsNullOrEmpty(certificate)) From 367a3c85dd8bbe4ff9cb0a9e40d0f828d67d6d4e Mon Sep 17 00:00:00 2001 From: derole Date: Tue, 14 Oct 2025 01:07:43 +0100 Subject: [PATCH 14/17] Temp for debugging --- Bombd/Core/BombdService.cs | 1 + Bombd/Protocols/ConnectionBase.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Bombd/Core/BombdService.cs b/Bombd/Core/BombdService.cs index 80b6ad6..2d7eb87 100644 --- a/Bombd/Core/BombdService.cs +++ b/Bombd/Core/BombdService.cs @@ -167,6 +167,7 @@ public bool Login(ConnectionBase connection, NetcodeTransaction request, Netcode int userId = CryptoHelper.StringHash32Upper(ticket.Username + (isRPCN ? "RPCN" : "PSN")); if (UserInfo.ContainsKey(userId)) { + Logger.LogError(_type, $"User already has a session with state {UserInfo[userId].State}"); response.Error = "alreadyLoggedIn"; return false; } diff --git a/Bombd/Protocols/ConnectionBase.cs b/Bombd/Protocols/ConnectionBase.cs index b93d847..16e2ab9 100644 --- a/Bombd/Protocols/ConnectionBase.cs +++ b/Bombd/Protocols/ConnectionBase.cs @@ -11,7 +11,7 @@ public abstract class ConnectionBase public readonly IServer Server; public readonly BombdService Service; - protected ConnectionState State = ConnectionState.Disconnected; + public ConnectionState State = ConnectionState.Disconnected; protected ConnectionBase(BombdService service, IServer server) { From 4268f2205abd59526e11899e0e02461757920744 Mon Sep 17 00:00:00 2001 From: derole Date: Wed, 15 Oct 2025 17:20:03 +0100 Subject: [PATCH 15/17] Replace SemaphoreSlim with a traditional lock to fix double-lock that occurs that can cause a deadlock on the GameServer tick thread in some rare cases, remove logging for alreadyLoggedIn state --- Bombd/Core/BombdService.cs | 1 - Bombd/Services/GameServer.cs | 510 +++++++++++++++++------------------ 2 files changed, 243 insertions(+), 268 deletions(-) diff --git a/Bombd/Core/BombdService.cs b/Bombd/Core/BombdService.cs index 2d7eb87..80b6ad6 100644 --- a/Bombd/Core/BombdService.cs +++ b/Bombd/Core/BombdService.cs @@ -167,7 +167,6 @@ public bool Login(ConnectionBase connection, NetcodeTransaction request, Netcode int userId = CryptoHelper.StringHash32Upper(ticket.Username + (isRPCN ? "RPCN" : "PSN")); if (UserInfo.ContainsKey(userId)) { - Logger.LogError(_type, $"User already has a session with state {UserInfo[userId].State}"); response.Error = "alreadyLoggedIn"; return false; } diff --git a/Bombd/Services/GameServer.cs b/Bombd/Services/GameServer.cs index 6416417..c5de833 100644 --- a/Bombd/Services/GameServer.cs +++ b/Bombd/Services/GameServer.cs @@ -26,7 +26,7 @@ public class GameServer : BombdService private readonly Dictionary _reservationGroups = new(); private readonly List _playerJoinQueue = []; private readonly List _playerLeaveQueue = []; - private readonly SemaphoreSlim _playerLock = new(1, 1); + private readonly object _playerLock = new(); private readonly List _playerMigrationGroups = []; public event EventHandler? OnPlayerJoined; public event EventHandler? OnPlayerLeft; @@ -41,11 +41,10 @@ public void NotifyHotSeatReset() public void UpdateGuestStatuses(GamePlayer player, GuestStatusBlock block) { - _playerLock.Wait(); - try + lock (_playerLock) { player.Room.UpdateGuestStatuses(player, block); - + // Tell everybody else in the gameroom about any guests // that were either attached or detached var gamemanager = Bombd.GetService(); @@ -53,14 +52,14 @@ public void UpdateGuestStatuses(GamePlayer player, GuestStatusBlock block) { bool wasAttached = guestStatus.Status == GuestStatusCode.AttachSuccess; bool wasDetached = guestStatus.Status == GuestStatusCode.Detached; - + if (!wasAttached && !wasDetached) continue; - + var transaction = NetcodeTransaction.MakeRequest("gamemanager", wasAttached ? "guestJoined" : "guestLeft"); transaction["gamename"] = player.Room.Game.GameName; transaction["playername"] = player.Username; transaction["guestname"] = guestStatus.Username; - + foreach (var peer in player.Room.Game.Players) { if (peer == player) continue; @@ -68,22 +67,17 @@ public void UpdateGuestStatuses(GamePlayer player, GuestStatusBlock block) } } } - finally - { - _playerLock.Release(); - } } public bool ReserveSlotsInGame(string gameName, int numSlots, [MaybeNullWhen(false)] out string reservationKey) { reservationKey = null; - _playerLock.Wait(); - try + lock (_playerLock) { GameRoom? room = Bombd.RoomManager.GetRoomByName(gameName); if (room == null) return false; if (room.NumFreeSlots < numSlots) return false; - + var slots = new Queue(); for (int i = 0; i < numSlots; ++i) { @@ -101,25 +95,20 @@ public bool ReserveSlotsInGame(string gameName, int numSlots, [MaybeNullWhen(fal return true; } - finally - { - _playerLock.Release(); - } } public void AddMigrationGroup(GameMigrationRequest request) { - _playerLock.Wait(); - try + lock (_playerLock) { // This shouldn't be normally possible, so we don't need to send back errors or anything, just return. GameRoom? currentRoom = Bombd.RoomManager.GetRoomByUser(request.HostUserId); if (currentRoom == null) return; - + var gamemanager = Bombd.GetService(); bool isCreatingGame = string.IsNullOrEmpty(request.GameName); bool isJoiningGame = !isCreatingGame; - + GameRoom migratedRoom; if (isCreatingGame) { @@ -128,7 +117,7 @@ public void AddMigrationGroup(GameMigrationRequest request) Attributes = request.Attributes!, OwnerUserId = request.HostUserId, Platform = request.Platform - }); + }); } else { @@ -142,13 +131,13 @@ public void AddMigrationGroup(GameMigrationRequest request) } } - + // Fairly sure with migration requests, guests are only ever attached when // starting a game from the co-op menu, so it should only ever be the "host's" single guest. string? guest = request.Guest; int numSlotsRequired = request.PlayerIdList.Count; if (guest != null) numSlotsRequired++; - + // If we're creating the game, we'll always have enough slots // So we have to make sure there are when joining if (isJoiningGame) @@ -159,9 +148,9 @@ public void AddMigrationGroup(GameMigrationRequest request) transaction.Error = JoinFailReason.NotEnoughSlots; gamemanager.SendTransaction(request.HostUserId, transaction); return; - } + } } - + var group = new MigrationGroup { OldRoom = currentRoom, @@ -171,22 +160,22 @@ public void AddMigrationGroup(GameMigrationRequest request) OwnerGuest = request.Guest, Players = [] }; - + // Make sure we get a slot for the guest if (guest != null) { migratedRoom.RequestSlot(out int guestId); group.OwnerGuestId = guestId; } - + foreach (GenericInt32 playerId in request.PlayerIdList) { // If the player isn't in the game, just ignore them if (!currentRoom.IsPlayerInGame(playerId)) continue; - + GamePlayer player = currentRoom.GetPlayer(playerId); ConnectionBase connection = UserInfo[player.UserId]; - + migratedRoom.RequestSlot(out int slot); group.Players.Add(new MigratingPlayer { @@ -206,16 +195,11 @@ public void AddMigrationGroup(GameMigrationRequest request) _playerMigrationGroups.Add(group); } - finally - { - _playerLock.Release(); - } } public void AddPlayerToLeaveQueue(int userId, string username, string reason) { - _playerLock.Wait(); - try + lock (_playerLock) { _playerLeaveQueue.Add(new PlayerLeaveRequest { @@ -224,43 +208,34 @@ public void AddPlayerToLeaveQueue(int userId, string username, string reason) Reason = reason }); } - finally - { - _playerLock.Release(); - } } public void AddPlayerToJoinQueue(PlayerJoinRequest request) { - _playerLock.Wait(); - try + lock (_playerLock) { _playerJoinQueue.Add(request); } - finally - { - _playerLock.Release(); - } } private void HandlePlayerLeaveRequests() { - _playerLock.Wait(); - - foreach (PlayerLeaveRequest request in _playerLeaveQueue) + lock (_playerLock) { - GamePlayer? player = Bombd.RoomManager.GetPlayerInRoom(request.UserId); - if (player == null) + foreach (PlayerLeaveRequest request in _playerLeaveQueue) { - Logger.LogWarning($"{request.Username} tried to leave a game, but they aren't in one!"); - continue; + GamePlayer? player = Bombd.RoomManager.GetPlayerInRoom(request.UserId); + if (player == null) + { + Logger.LogWarning($"{request.Username} tried to leave a game, but they aren't in one!"); + continue; + } + + LeaveGameInternal(player, request.Reason); } - - LeaveGameInternal(player, request.Reason); - } - _playerLeaveQueue.Clear(); - _playerLock.Release(); + _playerLeaveQueue.Clear(); + } } private void LeaveGameInternal(GamePlayer player, string reason) @@ -295,259 +270,260 @@ private void LeaveGameInternal(GamePlayer player, string reason) private void HandlePlayerJoinRequests() { - _playerLock.Wait(); - for (int i = 0; i < _playerJoinQueue.Count; ++i) + lock (_playerLock) { - PlayerJoinRequest request = _playerJoinQueue[i]; - if (TimeHelper.LocalTime > request.Timestamp + JoinTimeout) + for (int i = 0; i < _playerJoinQueue.Count; ++i) { - Logger.LogWarning( - "A player took too long to join and was disconnected!"); - _playerJoinQueue.RemoveAt(i--); - if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) - pendingConnection.Disconnect(); - continue; - } - - // This generally shouldn't happen if someone isn't manually making requests, - // but still make sure to handle these cases. - GameRoom? gameRoom = Bombd.RoomManager.GetRoomByName(request.GameName); - if (gameRoom == null) - { - Logger.LogWarning( - "A player tried to join a game room, but the room doesn't exist."); - _playerJoinQueue.RemoveAt(i--); - if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) - pendingConnection.Disconnect(); - continue; - } + PlayerJoinRequest request = _playerJoinQueue[i]; + if (TimeHelper.LocalTime > request.Timestamp + JoinTimeout) + { + Logger.LogWarning( + "A player took too long to join and was disconnected!"); + _playerJoinQueue.RemoveAt(i--); + if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) + pendingConnection.Disconnect(); + continue; + } - // Wait until the game room is ready to join before letting the player in - if (!gameRoom.IsReadyToJoin(request.UserId)) continue; - - // Karting doesn't use migrations and just switches games, so make sure we leave the old room. - // TEMP: Wait until player has left? - GamePlayer? existingPlayer = Bombd.RoomManager.GetPlayerInRoom(request.UserId); - if (existingPlayer != null) LeaveGameInternal(existingPlayer, "gameMigration"); + // This generally shouldn't happen if someone isn't manually making requests, + // but still make sure to handle these cases. + GameRoom? gameRoom = Bombd.RoomManager.GetRoomByName(request.GameName); + if (gameRoom == null) + { + Logger.LogWarning( + "A player tried to join a game room, but the room doesn't exist."); + _playerJoinQueue.RemoveAt(i--); + if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) + pendingConnection.Disconnect(); + continue; + } - if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? connection)) - { - if (!connection.IsAuthenticated) continue; + // Wait until the game room is ready to join before letting the player in + if (!gameRoom.IsReadyToJoin(request.UserId)) continue; + + // Karting doesn't use migrations and just switches games, so make sure we leave the old room. + // TEMP: Wait until player has left? + GamePlayer? existingPlayer = Bombd.RoomManager.GetPlayerInRoom(request.UserId); + if (existingPlayer != null) LeaveGameInternal(existingPlayer, "gameMigration"); - GamePlayer? player; - if (request.ReservationKey != null) + if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? connection)) { - // Make sure the reservation key actually exists - if (!_reservationGroups.TryGetValue(request.ReservationKey, out ReservationGroup group)) - { - Logger.LogWarning( - "A player tried to join a game room with an invalid reservation key!"); - _playerJoinQueue.RemoveAt(i--); - if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) - pendingConnection.Disconnect(); - continue; - } - - // Shouldn't happen, but if it does, make sure we don't mismatch reservations - if (group.Room != gameRoom) + if (!connection.IsAuthenticated) continue; + + GamePlayer? player; + if (request.ReservationKey != null) { - Logger.LogWarning( - "A player tried to join a game room with a mismatched reservation key!"); - _playerJoinQueue.RemoveAt(i--); - if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) - pendingConnection.Disconnect(); - continue; + // Make sure the reservation key actually exists + if (!_reservationGroups.TryGetValue(request.ReservationKey, out ReservationGroup group)) + { + Logger.LogWarning( + "A player tried to join a game room with an invalid reservation key!"); + _playerJoinQueue.RemoveAt(i--); + if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) + pendingConnection.Disconnect(); + continue; + } + + // Shouldn't happen, but if it does, make sure we don't mismatch reservations + if (group.Room != gameRoom) + { + Logger.LogWarning( + "A player tried to join a game room with a mismatched reservation key!"); + _playerJoinQueue.RemoveAt(i--); + if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) + pendingConnection.Disconnect(); + continue; + } + + Logger.LogDebug($"{request.Username} is joining room using a reservation (guest={request.Guest})"); + + // Reservations in Karting are a bit wonky right now, and I can't really test it, + // so we'll need this check to prevent any exceptions + string? guest = request.Guest; + int numSlotsRequired = 1; + if (guest != null) numSlotsRequired++; + if (group.Slots.Count < numSlotsRequired) + { + Logger.LogWarning("A player tried to join a game room with a reservation that doesn't have enough slots!"); + continue; + } + ; + + int playerId = group.Slots.Dequeue(); + if (guest == null) + { + player = Bombd.RoomManager.JoinRoom(connection.Username, connection.UserId, playerId, + group.Room); + } + else + { + int guestId = group.Slots.Dequeue(); + player = Bombd.RoomManager.JoinRoomWithGuest(connection.Username, guest, connection.UserId, playerId, + guestId, group.Room); + } + + if (group.Slots.Count == 0) + { + Logger.LogDebug($"Destroying reservation {request.ReservationKey} since all slots have been used!"); + _reservationGroups.Remove(request.ReservationKey); + } } - - Logger.LogDebug($"{request.Username} is joining room using a reservation (guest={request.Guest})"); - - // Reservations in Karting are a bit wonky right now, and I can't really test it, - // so we'll need this check to prevent any exceptions - string? guest = request.Guest; - int numSlotsRequired = 1; - if (guest != null) numSlotsRequired++; - if (group.Slots.Count < numSlotsRequired) - { - Logger.LogWarning("A player tried to join a game room with a reservation that doesn't have enough slots!"); - continue; - }; + else player = Bombd.RoomManager.RequestJoinRoom(connection.Username, connection.UserId, gameRoom, request.Guest); - int playerId = group.Slots.Dequeue(); - if (guest == null) + if (player != null) { - player = Bombd.RoomManager.JoinRoom(connection.Username, connection.UserId, playerId, - group.Room); + // For convenience, we attach the send method directly to the game player object, + // so that no lookups have to be performed within the simulation server environment. + player.Send = (bytes, type) => SendMessage(player.UserId, bytes, type); + player.Disconnect = () => Disconnect(player.UserId); + + Logger.LogInfo($"{player.Username} joined {gameRoom.Game.GameName}."); + + // Make sure to tell both the simulation instance and anything subscribed to game events + // about the new player that joined. + gameRoom.Simulation.OnPlayerJoin(player); + OnPlayerJoined?.Invoke(this, new PlayerJoinEventArgs + { + Room = gameRoom, + Player = player, + WasMigration = false + }); } else { - int guestId = group.Slots.Dequeue(); - player = Bombd.RoomManager.JoinRoomWithGuest(connection.Username, guest, connection.UserId, playerId, - guestId, group.Room); + Logger.LogWarning( + $"{connection.Username} tried to join {gameRoom.Game.GameName}, but operation failed."); + if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) + pendingConnection.Disconnect(); } - if (group.Slots.Count == 0) - { - Logger.LogDebug($"Destroying reservation {request.ReservationKey} since all slots have been used!"); - _reservationGroups.Remove(request.ReservationKey); - } + _playerJoinQueue.RemoveAt(i--); } - else player = Bombd.RoomManager.RequestJoinRoom(connection.Username, connection.UserId, gameRoom, request.Guest); - - if (player != null) - { - // For convenience, we attach the send method directly to the game player object, - // so that no lookups have to be performed within the simulation server environment. - player.Send = (bytes, type) => SendMessage(player.UserId, bytes, type); - player.Disconnect = () => Disconnect(player.UserId); - - Logger.LogInfo($"{player.Username} joined {gameRoom.Game.GameName}."); - - // Make sure to tell both the simulation instance and anything subscribed to game events - // about the new player that joined. - gameRoom.Simulation.OnPlayerJoin(player); - OnPlayerJoined?.Invoke(this, new PlayerJoinEventArgs - { - Room = gameRoom, - Player = player, - WasMigration = false - }); - } - else - { - Logger.LogWarning( - $"{connection.Username} tried to join {gameRoom.Game.GameName}, but operation failed."); - if (UserInfo.TryGetValue(request.UserId, out ConnectionBase? pendingConnection)) - pendingConnection.Disconnect(); - } - - _playerJoinQueue.RemoveAt(i--); } - } - _playerLock.Release(); + } } private void HandlePlayerMigrationRequests() { - _playerLock.Wait(); - int time = TimeHelper.LocalTime; - for (int i = 0; i < _playerMigrationGroups.Count; ++i) + lock (_playerLock) { - MigrationGroup group = _playerMigrationGroups[i]; - foreach (MigratingPlayer player in group.Players) + int time = TimeHelper.LocalTime; + for (int i = 0; i < _playerMigrationGroups.Count; ++i) { - if (player.Status >= MigrationStatus.Migrated) continue; - if (time > group.Timestamp + MigrationTimeout) + MigrationGroup group = _playerMigrationGroups[i]; + foreach (MigratingPlayer player in group.Players) { - player.Status = MigrationStatus.MigrationFailed; - continue; - } + if (player.Status >= MigrationStatus.Migrated) continue; + if (time > group.Timestamp + MigrationTimeout) + { + player.Status = MigrationStatus.MigrationFailed; + continue; + } - if (!UserInfo.TryGetValue(player.UserId, out ConnectionBase? connection)) - { - if (player.Status == MigrationStatus.WaitingForDisconnect) - player.Status = MigrationStatus.WaitingForConnect; + if (!UserInfo.TryGetValue(player.UserId, out ConnectionBase? connection)) + { + if (player.Status == MigrationStatus.WaitingForDisconnect) + player.Status = MigrationStatus.WaitingForConnect; - continue; - } + continue; + } - if (player.Status == MigrationStatus.WaitingForConnect && connection.IsAuthenticated) - player.Status = MigrationStatus.Migrated; - } + if (player.Status == MigrationStatus.WaitingForConnect && connection.IsAuthenticated) + player.Status = MigrationStatus.Migrated; + } - bool isMigrationComplete = group.Players.All(player => player.Status >= MigrationStatus.Migrated); - if (!isMigrationComplete) continue; + bool isMigrationComplete = group.Players.All(player => player.Status >= MigrationStatus.Migrated); + if (!isMigrationComplete) continue; - // Now that all users are connected to the gameserver, let's add them to the game room - GameRoom room = group.NewRoom; - foreach (MigratingPlayer player in group.Players) - { - bool isOwner = player.UserId == group.Owner; - string? guest = isOwner ? group.OwnerGuest : null; - - // In case the player closed their game during migration or if something else caused a disconnection. - if (!UserInfo.TryGetValue(player.UserId, out ConnectionBase? connection) || !connection.IsAuthenticated) + // Now that all users are connected to the gameserver, let's add them to the game room + GameRoom room = group.NewRoom; + foreach (MigratingPlayer player in group.Players) { - player.Status = MigrationStatus.MigrationFailed; - group.NewRoom.FreeSlot(player.NewPlayerId); + bool isOwner = player.UserId == group.Owner; + string? guest = isOwner ? group.OwnerGuest : null; + + // In case the player closed their game during migration or if something else caused a disconnection. + if (!UserInfo.TryGetValue(player.UserId, out ConnectionBase? connection) || !connection.IsAuthenticated) + { + player.Status = MigrationStatus.MigrationFailed; + group.NewRoom.FreeSlot(player.NewPlayerId); + if (isOwner && guest != null) + group.NewRoom.FreeSlot(group.OwnerGuestId); + continue; + } + + GamePlayer gamePlayer; if (isOwner && guest != null) - group.NewRoom.FreeSlot(group.OwnerGuestId); - continue; - } + { + gamePlayer = Bombd.RoomManager.JoinRoomWithGuest(connection.Username, guest, connection.UserId, + player.NewPlayerId, group.OwnerGuestId, group.NewRoom); + } + else + { + gamePlayer = Bombd.RoomManager.JoinRoom(connection.Username, connection.UserId, + player.NewPlayerId, group.NewRoom); + } - GamePlayer gamePlayer; - if (isOwner && guest != null) - { - gamePlayer = Bombd.RoomManager.JoinRoomWithGuest(connection.Username, guest, connection.UserId, - player.NewPlayerId, group.OwnerGuestId, group.NewRoom); + gamePlayer.Send = (bytes, type) => SendMessage(gamePlayer.UserId, bytes, type); + gamePlayer.Disconnect = () => Disconnect(player.UserId); + Logger.LogInfo($"{gamePlayer.Username} migrated to {room.Game.GameName}."); + room.Simulation.OnPlayerJoin(gamePlayer); + OnPlayerJoined?.Invoke(this, new PlayerJoinEventArgs + { + Room = room, + Player = gamePlayer, + WasMigration = true + }); } - else + + List migratedPlayers = group.Players + .Where(player => player.Status == MigrationStatus.Migrated) + .Select(player => new GenericInt32(player.OldPlayerId)).ToList(); + + List unmigratedPlayers = group.Players + .Where(player => player.Status == MigrationStatus.MigrationFailed) + .Select(player => new GenericInt32(player.OldPlayerId)).ToList(); + + // Tell the old room about the migration that just occurred + var gamemanager = Bombd.GetService(); + var transaction = NetcodeTransaction.MakeRequest("gamemanager", "gameMigrationOccured"); + transaction["numPlayersMigrated"] = migratedPlayers.Count.ToString(); + transaction["numPlayersNotMigrated"] = unmigratedPlayers.Count.ToString(); + transaction["playersNotMigrated"] = Convert.ToBase64String(NetworkWriter.Serialize(unmigratedPlayers)); + transaction["playersMigrated"] = Convert.ToBase64String(NetworkWriter.Serialize(migratedPlayers)); + foreach (GamePlayer player in group.OldRoom.Game.Players) + gamemanager.SendTransaction(player.UserId, transaction); + + // If any of the old players are still connected to the gamemanager + // tell them that they failed to migrate + foreach (MigratingPlayer player in group.Players) { - gamePlayer = Bombd.RoomManager.JoinRoom(connection.Username, connection.UserId, - player.NewPlayerId, group.NewRoom); + if (player.Status != MigrationStatus.MigrationFailed) continue; + transaction = NetcodeTransaction.MakeRequest("gamemanager", "gameMigrationFailure"); + transaction.Error = "timeout"; + SendTransaction(player.UserId, transaction); } - - gamePlayer.Send = (bytes, type) => SendMessage(gamePlayer.UserId, bytes, type); - gamePlayer.Disconnect = () => Disconnect(player.UserId); - Logger.LogInfo($"{gamePlayer.Username} migrated to {room.Game.GameName}."); - room.Simulation.OnPlayerJoin(gamePlayer); - OnPlayerJoined?.Invoke(this, new PlayerJoinEventArgs - { - Room = room, - Player = gamePlayer, - WasMigration = true - }); - } - List migratedPlayers = group.Players - .Where(player => player.Status == MigrationStatus.Migrated) - .Select(player => new GenericInt32(player.OldPlayerId)).ToList(); - - List unmigratedPlayers = group.Players - .Where(player => player.Status == MigrationStatus.MigrationFailed) - .Select(player => new GenericInt32(player.OldPlayerId)).ToList(); - - // Tell the old room about the migration that just occurred - var gamemanager = Bombd.GetService(); - var transaction = NetcodeTransaction.MakeRequest("gamemanager", "gameMigrationOccured"); - transaction["numPlayersMigrated"] = migratedPlayers.Count.ToString(); - transaction["numPlayersNotMigrated"] = unmigratedPlayers.Count.ToString(); - transaction["playersNotMigrated"] = Convert.ToBase64String(NetworkWriter.Serialize(unmigratedPlayers)); - transaction["playersMigrated"] = Convert.ToBase64String(NetworkWriter.Serialize(migratedPlayers)); - foreach (GamePlayer player in group.OldRoom.Game.Players) - gamemanager.SendTransaction(player.UserId, transaction); - - // If any of the old players are still connected to the gamemanager - // tell them that they failed to migrate - foreach (MigratingPlayer player in group.Players) - { - if (player.Status != MigrationStatus.MigrationFailed) continue; - transaction = NetcodeTransaction.MakeRequest("gamemanager", "gameMigrationFailure"); - transaction.Error = "timeout"; - SendTransaction(player.UserId, transaction); + _playerMigrationGroups.RemoveAt(i--); } - - _playerMigrationGroups.RemoveAt(i--); } - - _playerLock.Release(); } private void ClearExpiredReservations() { - _playerLock.Wait(); - - int time = TimeHelper.LocalTime; - foreach ((string? key, ReservationGroup group) in _reservationGroups.ToList()) + lock (_playerLock) { - if (time <= group.Timestamp + ReservationTimeout) continue; - - while (group.Slots.TryDequeue(out int slot)) - group.Room.FreeSlot(slot); - _reservationGroups.Remove(key); + int time = TimeHelper.LocalTime; + foreach ((string? key, ReservationGroup group) in _reservationGroups.ToList()) + { + if (time <= group.Timestamp + ReservationTimeout) continue; + + while (group.Slots.TryDequeue(out int slot)) + group.Room.FreeSlot(slot); + _reservationGroups.Remove(key); + } } - - _playerLock.Release(); } protected override void OnGamedata(ConnectionBase connection, ArraySegment data) From 09ca685e493280d25af61d871e40e93372b2ab70 Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:00:28 +0100 Subject: [PATCH 16/17] Fix stale sockets getting stuck showing as connected indefinitely for SSL servers --- Bombd/Protocols/TCP/SslConnection.cs | 68 +++++++++++++++++----------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/Bombd/Protocols/TCP/SslConnection.cs b/Bombd/Protocols/TCP/SslConnection.cs index f5a6bf5..9f59ebc 100644 --- a/Bombd/Protocols/TCP/SslConnection.cs +++ b/Bombd/Protocols/TCP/SslConnection.cs @@ -13,7 +13,9 @@ public class SslConnection : ConnectionBase { private const int MaxMessageSize = 8192; private const int KeepAliveFrequency = 3000; - + private const int ReadTimeout = 10_000; + private const int WriteTimeout = 10_000; + // u32 MsgLength // char Md5Digest[16] // char Protocol @@ -47,12 +49,12 @@ public async void Connect(Socket socket) try { _sslStream = new SslStream(new NetworkStream(_socket, false), false); - + // Prevent lingering connections, the game should periodically send keep alive packets // in response to our own. - _sslStream.ReadTimeout = 10_000; - _sslStream.WriteTimeout = 10_000; - + _sslStream.ReadTimeout = ReadTimeout; + _sslStream.WriteTimeout = WriteTimeout; + await _sslStream.AuthenticateAsServerAsync(_server.Certificate, false, SslProtocols.Ssl3, false); _keepAliveTimer = new Timer(KeepAliveFrequency); @@ -66,7 +68,7 @@ public async void Connect(Socket socket) return; } - DoBlockAndReceive(); + DoBlockAndReceive(ReadTimeout); } public override void Disconnect() @@ -157,7 +159,7 @@ public override void Send(ArraySegment data, PacketType type) } } - private async void DoBlockAndReceive() + private async void DoBlockAndReceive(int readTimeout) { while (State != ConnectionState.Disconnected) { @@ -165,20 +167,23 @@ private async void DoBlockAndReceive() PacketType type; try { - int len = await _sslStream.ReadAsync(_recv.AsMemory(0, MessageHeaderSize)); - - // Socket has been shutdown on the other side - if (len == 0) + using (var cTokenSrc = CreateCancellationTokenTimeout(readTimeout)) { - Disconnect(); - return; - } - - if (len != MessageHeaderSize) - { - Logger.LogError("Received message with invalid header. Closing connection."); - Disconnect(); - return; + int len = await _sslStream.ReadAsync(_recv.AsMemory(0, MessageHeaderSize), cTokenSrc.Token); + + // Socket has been shutdown on the other side + if (len == 0) + { + Disconnect(); + return; + } + + if (len != MessageHeaderSize) + { + Logger.LogError("Received message with invalid header. Closing connection."); + Disconnect(); + return; + } } payloadSize = ((_recv[0] << 24) | (_recv[1] << 16) | (_recv[2] << 8) | _recv[3]) - @@ -196,14 +201,18 @@ private async void DoBlockAndReceive() int offset = 0; do { - len = await _sslStream.ReadAsync(_recv.AsMemory(offset, payloadSize - offset)); - if (len == 0) + using (var cTokenSrc = CreateCancellationTokenTimeout(readTimeout)) { - Disconnect(); - return; - } + int len = await _sslStream.ReadAsync(_recv.AsMemory(offset, payloadSize - offset), cTokenSrc.Token); - offset += len; + if (len == 0) + { + Disconnect(); + return; + } + + offset += len; + } } while (offset < payloadSize); } } @@ -224,4 +233,11 @@ private async void DoBlockAndReceive() } } } + + private CancellationTokenSource CreateCancellationTokenTimeout(int timeout) + { + var cTokenSrc = new CancellationTokenSource(); + cTokenSrc.CancelAfter(timeout); + return cTokenSrc; + } } \ No newline at end of file From 96bc50f907d42901e5ad8b1e69fda314e0a4bb2b Mon Sep 17 00:00:00 2001 From: derole <38840902+derole1@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:01:42 +0100 Subject: [PATCH 17/17] Fix stale sockets when a client uncleanly disconnects from an SSL service --- Bombd/Protocols/TCP/SslConnection.cs | 68 +++++++++++++++++----------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/Bombd/Protocols/TCP/SslConnection.cs b/Bombd/Protocols/TCP/SslConnection.cs index f5a6bf5..9f59ebc 100644 --- a/Bombd/Protocols/TCP/SslConnection.cs +++ b/Bombd/Protocols/TCP/SslConnection.cs @@ -13,7 +13,9 @@ public class SslConnection : ConnectionBase { private const int MaxMessageSize = 8192; private const int KeepAliveFrequency = 3000; - + private const int ReadTimeout = 10_000; + private const int WriteTimeout = 10_000; + // u32 MsgLength // char Md5Digest[16] // char Protocol @@ -47,12 +49,12 @@ public async void Connect(Socket socket) try { _sslStream = new SslStream(new NetworkStream(_socket, false), false); - + // Prevent lingering connections, the game should periodically send keep alive packets // in response to our own. - _sslStream.ReadTimeout = 10_000; - _sslStream.WriteTimeout = 10_000; - + _sslStream.ReadTimeout = ReadTimeout; + _sslStream.WriteTimeout = WriteTimeout; + await _sslStream.AuthenticateAsServerAsync(_server.Certificate, false, SslProtocols.Ssl3, false); _keepAliveTimer = new Timer(KeepAliveFrequency); @@ -66,7 +68,7 @@ public async void Connect(Socket socket) return; } - DoBlockAndReceive(); + DoBlockAndReceive(ReadTimeout); } public override void Disconnect() @@ -157,7 +159,7 @@ public override void Send(ArraySegment data, PacketType type) } } - private async void DoBlockAndReceive() + private async void DoBlockAndReceive(int readTimeout) { while (State != ConnectionState.Disconnected) { @@ -165,20 +167,23 @@ private async void DoBlockAndReceive() PacketType type; try { - int len = await _sslStream.ReadAsync(_recv.AsMemory(0, MessageHeaderSize)); - - // Socket has been shutdown on the other side - if (len == 0) + using (var cTokenSrc = CreateCancellationTokenTimeout(readTimeout)) { - Disconnect(); - return; - } - - if (len != MessageHeaderSize) - { - Logger.LogError("Received message with invalid header. Closing connection."); - Disconnect(); - return; + int len = await _sslStream.ReadAsync(_recv.AsMemory(0, MessageHeaderSize), cTokenSrc.Token); + + // Socket has been shutdown on the other side + if (len == 0) + { + Disconnect(); + return; + } + + if (len != MessageHeaderSize) + { + Logger.LogError("Received message with invalid header. Closing connection."); + Disconnect(); + return; + } } payloadSize = ((_recv[0] << 24) | (_recv[1] << 16) | (_recv[2] << 8) | _recv[3]) - @@ -196,14 +201,18 @@ private async void DoBlockAndReceive() int offset = 0; do { - len = await _sslStream.ReadAsync(_recv.AsMemory(offset, payloadSize - offset)); - if (len == 0) + using (var cTokenSrc = CreateCancellationTokenTimeout(readTimeout)) { - Disconnect(); - return; - } + int len = await _sslStream.ReadAsync(_recv.AsMemory(offset, payloadSize - offset), cTokenSrc.Token); - offset += len; + if (len == 0) + { + Disconnect(); + return; + } + + offset += len; + } } while (offset < payloadSize); } } @@ -224,4 +233,11 @@ private async void DoBlockAndReceive() } } } + + private CancellationTokenSource CreateCancellationTokenTimeout(int timeout) + { + var cTokenSrc = new CancellationTokenSource(); + cTokenSrc.CancelAfter(timeout); + return cTokenSrc; + } } \ No newline at end of file