From 69db3d144c6e86c6d691dd636d42fdaff54a183f Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:28:18 +0200 Subject: [PATCH 1/6] fix(lobby): Only start a game from a lobby in setup --- GenOnlineService/Controllers/WebSocket/WebSocketController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GenOnlineService/Controllers/WebSocket/WebSocketController.cs b/GenOnlineService/Controllers/WebSocket/WebSocketController.cs index bab3424..4aa59f5 100644 --- a/GenOnlineService/Controllers/WebSocket/WebSocketController.cs +++ b/GenOnlineService/Controllers/WebSocket/WebSocketController.cs @@ -978,7 +978,8 @@ await lobby.RunExclusiveAsync(() => } } - if (lobbyInfo == null) + // only a lobby still in setup can start + if (lobbyInfo == null || lobbyInfo.State != ELobbyState.GAME_SETUP) { return; } From 13c62806760f816c8d673c2498008d77ad9846dc Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:28:35 +0200 Subject: [PATCH 2/6] fix(lobby): Keep the slot state action off human slots --- GenOnlineService/Controllers/Lobby/LobbyController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/GenOnlineService/Controllers/Lobby/LobbyController.cs b/GenOnlineService/Controllers/Lobby/LobbyController.cs index 62fb454..5511631 100644 --- a/GenOnlineService/Controllers/Lobby/LobbyController.cs +++ b/GenOnlineService/Controllers/Lobby/LobbyController.cs @@ -501,7 +501,8 @@ public async Task Post(Int64 lobbyID) internal static bool TryParseSlotState(UInt16 rawValue, out EPlayerType slotState) { slotState = (EPlayerType)rawValue; - return Enum.IsDefined(typeof(EPlayerType), slotState); + // hosts can open, close or fill a slot with AI, but never mark it as a human + return Enum.IsDefined(typeof(EPlayerType), slotState) && slotState != EPlayerType.SLOT_PLAYER; } // Rejects any wire value that isn't a real ELobbyUpdateField member, so an unknown/malformed @@ -648,8 +649,9 @@ internal static bool TryParseLobbyUpdateField(int rawValue, out ELobbyUpdateFiel return null; } + // human slots are left alone; kicking is how a host removes a player LobbyMember? TargetMember = lobby.GetMemberFromSlot(slot_index); - if (TargetMember != null) + if (TargetMember != null && TargetMember.SlotState != EPlayerType.SLOT_PLAYER) { TargetMember.SetPlayerSlotState(slot_state); } From fb91f177bef98c9c8f1809c411a10d5e661a1ada Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:29:21 +0200 Subject: [PATCH 3/6] fix(discord): Stop relayed messages from pinging anyone --- GenOnlineService/Discord.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GenOnlineService/Discord.cs b/GenOnlineService/Discord.cs index 29c5728..85814d9 100644 --- a/GenOnlineService/Discord.cs +++ b/GenOnlineService/Discord.cs @@ -158,7 +158,7 @@ public async Task SendNetworkRoomChat(int roomID, Int64 userID, string strDispla if (channel != null) { string strDiscordMsg = String.Format("[NETWORK ROOM CHAT ID #{0}] {1}", roomID, strFormattedChatMsg); - await channel.SendMessageAsync(strDiscordMsg).ConfigureAwait(true); + await channel.SendMessageAsync(strDiscordMsg, allowedMentions: AllowedMentions.None).ConfigureAwait(true); } } } @@ -751,7 +751,7 @@ public void PushDM(SocketUser user, string strMessage) { if (user != null) { - user.SendMessageAsync(strMessage).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted); + user.SendMessageAsync(strMessage, allowedMentions: AllowedMentions.None).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted); } } catch @@ -799,7 +799,7 @@ public void PushChannelMessage(EDiscordChannelIDs channelID, string strMessage) ISocketMessageChannel? channel = GetChannel(channelID); if (channel != null) { - channel.SendMessageAsync(strMessage).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted); + channel.SendMessageAsync(strMessage, allowedMentions: AllowedMentions.None).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted); } } catch @@ -823,7 +823,7 @@ public async Task PushMessage(SocketUser user, ulong channelToUse, string strMes ISocketMessageChannel channel = (ISocketMessageChannel)discord.GetChannel(channelToUse); if (channel != null) { - RestUserMessage msg = await channel.SendMessageAsync(strMessage).ConfigureAwait(true); + RestUserMessage msg = await channel.SendMessageAsync(strMessage, allowedMentions: AllowedMentions.None).ConfigureAwait(true); } } } From ab9439efcae63e94406bf4d2bcec76b94dc372ca Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:30:21 +0200 Subject: [PATCH 4/6] fix(lobby): Cap chat length and apply create limits to map updates --- GenOnlineService/Controllers/Lobby/LobbyController.cs | 7 +++++++ .../Controllers/WebSocket/WebSocketController.cs | 9 ++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/GenOnlineService/Controllers/Lobby/LobbyController.cs b/GenOnlineService/Controllers/Lobby/LobbyController.cs index 5511631..ed0f299 100644 --- a/GenOnlineService/Controllers/Lobby/LobbyController.cs +++ b/GenOnlineService/Controllers/Lobby/LobbyController.cs @@ -543,6 +543,13 @@ internal static bool TryParseLobbyUpdateField(int rawValue, out ELobbyUpdateFiel bool bOfficialMap = data["map_official"].GetBoolean(); int maxPlayers = data["max_players"].GetInt32(); + // same limits as lobby creation + if ((strMap != null && strMap.Length > 255) || (strMapPath != null && strMapPath.Length > 512)) + { + Response.StatusCode = (int)HttpStatusCode.BadRequest; + return null; + } + if (strMap != null && strMapPath != null) { await using var db = await _dbFactory.CreateDbContextAsync(); diff --git a/GenOnlineService/Controllers/WebSocket/WebSocketController.cs b/GenOnlineService/Controllers/WebSocket/WebSocketController.cs index 4aa59f5..ce70dfd 100644 --- a/GenOnlineService/Controllers/WebSocket/WebSocketController.cs +++ b/GenOnlineService/Controllers/WebSocket/WebSocketController.cs @@ -41,6 +41,9 @@ public WebSocketController(LobbyManager lobbyManager, IDbContextFactory(payload, JsonOpts); - if (chatMessage != null) + if (chatMessage != null && (chatMessage.message?.Length ?? 0) <= MaxChatMessageLength) { if (!sourceUserData.TryConsumeChatMessage()) { @@ -589,7 +592,7 @@ private async Task ProcessWSMessage(UserWebSocketInstance sourceWS, UserSession WebSocketMessage_NetworkRoomChatMessageInbound? chatMessage = JsonSerializer.Deserialize(payload, JsonOpts); - if (chatMessage != null) + if (chatMessage != null && (chatMessage.message?.Length ?? 0) <= MaxChatMessageLength) { if (!sourceUserData.TryConsumeChatMessage()) { @@ -875,7 +878,7 @@ await lobby.RunExclusiveAsync(() => WebSocketMessage_LobbyChatMessageInbound? chatMessage = JsonSerializer.Deserialize(payload, JsonOpts); - if (chatMessage != null) + if (chatMessage != null && (chatMessage.message?.Length ?? 0) <= MaxChatMessageLength) { // get lobby Lobby? playerLobby = _lobbyManager.GetLobby(sourceUserSession.currentLobbyID); From 744722009fe024ad5814b65020a1cd9e5b37fa44 Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:30:39 +0200 Subject: [PATCH 5/6] fix(stats): Ignore stat entries past the known stat list --- .../Controllers/PlayerStats/PlayerStatsController.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/GenOnlineService/Controllers/PlayerStats/PlayerStatsController.cs b/GenOnlineService/Controllers/PlayerStats/PlayerStatsController.cs index 29213fb..a1ac9dd 100644 --- a/GenOnlineService/Controllers/PlayerStats/PlayerStatsController.cs +++ b/GenOnlineService/Controllers/PlayerStats/PlayerStatsController.cs @@ -188,9 +188,16 @@ public async Task Put() // TODO: Update client so this is an associative array and not just in order... if (jsonReqData != null) { + // anything past the known stats is ignored, which also bounds the work per request + int numStats = Enum.GetValues().Length; int stat_id = 0; foreach (JsonElement elem in jsonReqData) { + if (stat_id >= numStats) + { + break; + } + // TODO: do we care about the string stats? they dont seem relevant, its things like system spec try { From ea8f80a9ffe17a00da0836270a679069116d746b Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:31:26 +0200 Subject: [PATCH 6/6] fix(matchmaking): Only let matched players join a quick match lobby --- .../Controllers/Lobby/LobbyController.cs | 7 +++++++ GenOnlineService/LobbyManager.cs | 3 +++ GenOnlineService/MatchmakingManager.cs | 13 +++++++++++++ 3 files changed, 23 insertions(+) diff --git a/GenOnlineService/Controllers/Lobby/LobbyController.cs b/GenOnlineService/Controllers/Lobby/LobbyController.cs index ed0f299..336e93b 100644 --- a/GenOnlineService/Controllers/Lobby/LobbyController.cs +++ b/GenOnlineService/Controllers/Lobby/LobbyController.cs @@ -858,6 +858,13 @@ public async Task Put(Int64 lobbyID) EUserSessionType sessionType = TokenHelper.GetSessionType(this); if (user_id != -1 && SessionHelpers.SessionTypeHasAccessTo(sessionType, ESessionAccessType.Gameplay)) { + if (lobby.LobbyType == ELobbyType.QuickMatch && !lobby.MatchedUserIDs.Contains(user_id)) + { + Response.StatusCode = (int)HttpStatusCode.Forbidden; + result.success = false; + return result; + } + UInt16 userPreferredPort = data["preferred_port"].GetUInt16(); bool bHasMap = data["has_map"].GetBoolean(); EKnownAnticheatID anticheatID = (EKnownAnticheatID)data["anticheat_id"].GetInt32(); diff --git a/GenOnlineService/LobbyManager.cs b/GenOnlineService/LobbyManager.cs index 055afc3..5a677d7 100644 --- a/GenOnlineService/LobbyManager.cs +++ b/GenOnlineService/LobbyManager.cs @@ -708,6 +708,9 @@ public int MaxPlayers public ELobbyType LobbyType { get; private set; } = ELobbyType.CustomGame; + // quick match lobbies only admit the players the matchmaker paired + public HashSet MatchedUserIDs { get; } = new(); + public string Region { get; private set; } = ""; public int EstimatedLatency { get; private set; } = 999999; diff --git a/GenOnlineService/MatchmakingManager.cs b/GenOnlineService/MatchmakingManager.cs index 2a645d2..95d02e3 100644 --- a/GenOnlineService/MatchmakingManager.cs +++ b/GenOnlineService/MatchmakingManager.cs @@ -1266,6 +1266,19 @@ await SendMatchmakingMessage(memberSession, true, playlist.DesiredPlayers, "", 12345, false, true, 10000, false, String.Empty, -5, false, Constants.g_DefaultCameraMaxHeight, dummyHostUser.ExeCRC, dummyHostUser.IniCRC, ELobbyType.QuickMatch, dummyHostUser.AnticheatID); + Lobby? quickMatchLobby = lobbyManager.GetLobby(m_LobbyID); + if (quickMatchLobby != null) + { + foreach (MatchmakingBucketMember member in m_lstMembers) + { + UserSession? memberSession = member.GetAssociatedSession(); + if (memberSession != null) + { + quickMatchLobby.MatchedUserIDs.Add(memberSession.m_UserID); + } + } + } + // tell both to join our lobby WebSocketMessage_MatchmakerJoinLobby joinAction = new WebSocketMessage_MatchmakerJoinLobby(); joinAction.msg_id = (int)EWebSocketMessageID.MATCHMAKING_ACTION_JOIN_PREARRANGED_LOBBY;