diff --git a/GenOnlineService/Controllers/Lobby/LobbyController.cs b/GenOnlineService/Controllers/Lobby/LobbyController.cs index 62fb454..336e93b 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 @@ -542,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(); @@ -648,8 +656,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); } @@ -849,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/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 { diff --git a/GenOnlineService/Controllers/WebSocket/WebSocketController.cs b/GenOnlineService/Controllers/WebSocket/WebSocketController.cs index bab3424..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); @@ -978,7 +981,8 @@ await lobby.RunExclusiveAsync(() => } } - if (lobbyInfo == null) + // only a lobby still in setup can start + if (lobbyInfo == null || lobbyInfo.State != ELobbyState.GAME_SETUP) { return; } 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); } } } 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;