diff --git a/src/Empostor.Server/Net/State/Game.Data.cs b/src/Empostor.Server/Net/State/Game.Data.cs index 7c3281f..3036fd4 100644 --- a/src/Empostor.Server/Net/State/Game.Data.cs +++ b/src/Empostor.Server/Net/State/Game.Data.cs @@ -76,6 +76,18 @@ namespace Empostor.Server.Net.State return default; } + private enum GameDataResult + { + /// Keep the current message in the packet and continue with the next one. + Continue, + + /// Remove the current message from the packet and continue with the next one. + Remove, + + /// Drop the whole packet (a cheat was detected and the connection should not relay it). + Abort, + } + public async ValueTask HandleGameDataAsync(IMessageReader parent, ClientPlayer sender, bool toPlayer) { // Find target player. @@ -93,240 +105,271 @@ namespace Empostor.Server.Net.State _logger.LogTrace("Received GameData for target {0}.", targetId); } - // Parse GameData messages. + var startPosition = parent.Position; + while (parent.Position < parent.Length) { using var reader = parent.ReadMessage(); - switch (reader.Tag) - { - case GameDataTag.DataFlag: - { - var netId = reader.ReadPackedUInt32(); - if (_allObjects.TryGetValue(netId, out var obj)) - { - await obj.DeserializeAsync(sender, target, reader, false); - } - else - { - _logger.LogWarning("{Code} - Received DataFlag for unregistered NetId {NetId}", Code, netId); - } - - break; - } - - case GameDataTag.RpcFlag: - { - var netId = reader.ReadPackedUInt32(); - if (_allObjects.TryGetValue(netId, out var obj)) - { - if (!await obj.HandleRpcAsync(sender, target, (RpcCalls)reader.ReadByte(), reader)) - { - parent.RemoveMessage(reader); - continue; - } - } - else - { - _logger.LogWarning("{Code} - Received RpcFlag for unregistered NetId {NetId}", Code, netId); - } - - break; - } - - case GameDataTag.SpawnFlag: - { - // Only the host is allowed to spawn objects. - if (!sender.IsHost) - { - if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.SpawnFlag)), CheatCategory.MustBeHost, "Tried to send SpawnFlag as non-host.")) - { - return false; - } - } - - var objectId = reader.ReadPackedUInt32(); - if (SpawnableObjects.TryGetValue(objectId, out var spawnableObjectType)) - { - var innerNetObject = (InnerNetObject)ActivatorUtilities.CreateInstance(_serviceProvider, spawnableObjectType, this); - var ownerClientId = reader.ReadPackedInt32(); - - innerNetObject.SpawnFlags = (SpawnFlags)reader.ReadByte(); - - var components = innerNetObject.GetComponentsInChildren(); - var componentsCount = reader.ReadPackedInt32(); - - if (componentsCount != components.Count) - { - _logger.LogError( - "Children didn't match for spawnable {0}, name {1} ({2} != {3})", - objectId, - innerNetObject.GetType().Name, - componentsCount, - components.Count); - continue; - } - - _logger.LogDebug( - "Spawning {0} components, SpawnFlags {1}", - innerNetObject.GetType().Name, - innerNetObject.SpawnFlags); - - for (var i = 0; i < componentsCount; i++) - { - var obj = components[i]; - - obj.NetId = reader.ReadPackedUInt32(); - obj.OwnerId = ownerClientId; - - _logger.LogDebug( - "- {0}, NetId {1}, OwnerId {2}", - obj.GetType().Name, - obj.NetId, - obj.OwnerId); - - if (!AddNetObject(obj)) - { - _logger.LogTrace("Failed to AddNetObject, it already exists."); - - obj.NetId = uint.MaxValue; - break; - } - - using var readerSub = reader.ReadMessage(); - if (readerSub.Length > 0) - { - await obj.DeserializeAsync(sender, target, readerSub, true); - } - - await OnSpawnAsync(sender, obj); - } - - continue; - } - - _logger.LogWarning("Couldn't find spawnable object {ObjectId}", objectId); - break; - } - - // Only the host is allowed to despawn objects. - case GameDataTag.DespawnFlag: - { - var netId = reader.ReadPackedUInt32(); - if (_allObjects.TryGetValue(netId, out var obj)) - { - if (sender.Client.Id != obj.OwnerId && !sender.IsHost) - { - _logger.LogWarning( - "Player {0} ({1}) tried to send DespawnFlag for {2} but was denied.", - sender.Client.Name, - sender.Client.Id, - netId); - return false; - } - - RemoveNetObject(obj); - await OnDestroyAsync(obj); - _logger.LogDebug("Destroyed InnerNetObject {0} ({1}), OwnerId {2}", obj.GetType().Name, netId, obj.OwnerId); - } - else - { - _logger.LogDebug( - "Player {0} ({1}) sent DespawnFlag for unregistered NetId {2}.", - sender.Client.Name, - sender.Client.Id, - netId); - } - - break; - } - - case GameDataTag.SceneChangeFlag: - { - // Sender is only allowed to change his own scene. - var clientId = reader.ReadPackedInt32(); - var scene = reader.ReadString(); - - if (clientId != sender.Client.Id) - { - _logger.LogWarning( - "Player {0} ({1}) tried to send SceneChangeFlag for another player.", - sender.Client.Name, - sender.Client.Id); - return false; - } - - // According to game assembly, sender is only allowed to send OnlineGame. - if (scene != "OnlineGame") - { - _logger.LogWarning( - "Player {PlayerName} ({ClientId}) tried to send SceneChangeFlag with disallowed scene \"{Scene}\".", - sender.Client.Name, - sender.Client.Id, - scene); - return false; - } - - sender.Scene = scene; - - _logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene); - - await SyncServerObjectsAsync(sender); - await SpawnPlayerInfoAsync(sender); - - break; - } - - case GameDataTag.ReadyFlag: - { - var clientId = reader.ReadPackedInt32(); - - if (clientId != sender.Client.Id) - { - _logger.LogWarning( - "Player {0} ({1}) tried to send ReadyFlag for another player.", - sender.Client.Name, - sender.Client.Id); - return false; - } - - _logger.LogTrace("> IsReady {0}", clientId); - break; - } - - case GameDataTag.ConsoleDeclareClientPlatformFlag: - { - var clientId = reader.ReadPackedInt32(); - var platform = (RuntimePlatform)reader.ReadPackedInt32(); - - if (clientId != sender.Client.Id) - { - if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.ConsoleDeclareClientPlatformFlag)), CheatCategory.Ownership, "Client sent info with wrong client id")) - { - return false; - } - } - - sender.Platform = platform; - - break; - } - - default: - { - _logger.LogWarning("{Code} - Bad GameData tag {Tag}", Code, reader.Tag); - break; - } - } - if (sender.Client.Player == null) { - // Disconnect handler was probably invoked, cancel the rest. return false; } + + if (toPlayer && (target == null || !_players.ContainsKey(target.Client.Id))) + { + return false; + } + + GameDataResult result; + + try + { + result = await HandleGameDataInnerAsync(reader, sender, target); + } + catch (Exception ex) + { + _logger.LogError(ex, "{Code} - Error while handling a GameData message, skipping it", Code); + parent.RemoveMessage(reader); + continue; + } + + if (result == GameDataResult.Abort) + { + // A cheat was detected, drop the whole packet. + return false; + } + + if (result == GameDataResult.Remove) + { + parent.RemoveMessage(reader); + } } - return true; + return parent.Length > startPosition; + } + + private async ValueTask HandleGameDataInnerAsync(IMessageReader reader, ClientPlayer sender, ClientPlayer? target) + { + switch (reader.Tag) + { + case GameDataTag.DataFlag: + { + var netId = reader.ReadPackedUInt32(); + if (_allObjects.TryGetValue(netId, out var obj)) + { + await obj.DeserializeAsync(sender, target, reader, false); + } + else + { + _logger.LogWarning("{Code} - Received DataFlag for unregistered NetId {NetId}", Code, netId); + } + + return GameDataResult.Continue; + } + + case GameDataTag.RpcFlag: + { + var netId = reader.ReadPackedUInt32(); + if (_allObjects.TryGetValue(netId, out var obj)) + { + if (!await obj.HandleRpcAsync(sender, target, (RpcCalls)reader.ReadByte(), reader)) + { + return GameDataResult.Remove; + } + } + else + { + _logger.LogWarning("{Code} - Received RpcFlag for unregistered NetId {NetId}", Code, netId); + } + + return GameDataResult.Continue; + } + + case GameDataTag.SpawnFlag: + { + // Only the host is allowed to spawn objects. + if (!sender.IsHost) + { + if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.SpawnFlag)), CheatCategory.MustBeHost, "Tried to send SpawnFlag as non-host.")) + { + return GameDataResult.Abort; + } + } + + var objectId = reader.ReadPackedUInt32(); + if (SpawnableObjects.TryGetValue(objectId, out var spawnableObjectType)) + { + var innerNetObject = (InnerNetObject)ActivatorUtilities.CreateInstance(_serviceProvider, spawnableObjectType, this); + var ownerClientId = reader.ReadPackedInt32(); + + innerNetObject.SpawnFlags = (SpawnFlags)reader.ReadByte(); + + var components = innerNetObject.GetComponentsInChildren(); + var componentsCount = reader.ReadPackedInt32(); + + if (componentsCount != components.Count) + { + _logger.LogError( + "Children didn't match for spawnable {0}, name {1} ({2} != {3})", + objectId, + innerNetObject.GetType().Name, + componentsCount, + components.Count); + return GameDataResult.Continue; + } + + _logger.LogDebug( + "Spawning {0} components, SpawnFlags {1}", + innerNetObject.GetType().Name, + innerNetObject.SpawnFlags); + + for (var i = 0; i < componentsCount; i++) + { + var obj = components[i]; + + obj.NetId = reader.ReadPackedUInt32(); + obj.OwnerId = ownerClientId; + + _logger.LogDebug( + "- {0}, NetId {1}, OwnerId {2}", + obj.GetType().Name, + obj.NetId, + obj.OwnerId); + + if (!AddNetObject(obj)) + { + _logger.LogTrace("Failed to AddNetObject, it already exists."); + + obj.NetId = uint.MaxValue; + break; + } + + using var readerSub = reader.ReadMessage(); + if (readerSub.Length > 0) + { + await obj.DeserializeAsync(sender, target, readerSub, true); + } + + await OnSpawnAsync(sender, obj); + } + + return GameDataResult.Continue; + } + + _logger.LogWarning("Couldn't find spawnable object {ObjectId}", objectId); + return GameDataResult.Continue; + } + + // Only the host is allowed to despawn objects. + case GameDataTag.DespawnFlag: + { + var netId = reader.ReadPackedUInt32(); + if (_allObjects.TryGetValue(netId, out var obj)) + { + if (sender.Client.Id != obj.OwnerId && !sender.IsHost) + { + _logger.LogWarning( + "Player {0} ({1}) tried to send DespawnFlag for {2} but was denied.", + sender.Client.Name, + sender.Client.Id, + netId); + return GameDataResult.Abort; + } + + RemoveNetObject(obj); + await OnDestroyAsync(obj); + _logger.LogDebug("Destroyed InnerNetObject {0} ({1}), OwnerId {2}", obj.GetType().Name, netId, obj.OwnerId); + } + else + { + _logger.LogDebug( + "Player {0} ({1}) sent DespawnFlag for unregistered NetId {2}.", + sender.Client.Name, + sender.Client.Id, + netId); + } + + return GameDataResult.Continue; + } + + case GameDataTag.SceneChangeFlag: + { + // Sender is only allowed to change his own scene. + var clientId = reader.ReadPackedInt32(); + var scene = reader.ReadString(); + + if (clientId != sender.Client.Id) + { + _logger.LogWarning( + "Player {0} ({1}) tried to send SceneChangeFlag for another player.", + sender.Client.Name, + sender.Client.Id); + return GameDataResult.Abort; + } + + // According to game assembly, sender is only allowed to send OnlineGame. + if (scene != "OnlineGame") + { + _logger.LogWarning( + "Player {PlayerName} ({ClientId}) tried to send SceneChangeFlag with disallowed scene \"{Scene}\".", + sender.Client.Name, + sender.Client.Id, + scene); + return GameDataResult.Abort; + } + + sender.Scene = scene; + + _logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene); + + await SyncServerObjectsAsync(sender); + await SpawnPlayerInfoAsync(sender); + + return GameDataResult.Continue; + } + + case GameDataTag.ReadyFlag: + { + var clientId = reader.ReadPackedInt32(); + + if (clientId != sender.Client.Id) + { + _logger.LogWarning( + "Player {0} ({1}) tried to send ReadyFlag for another player.", + sender.Client.Name, + sender.Client.Id); + return GameDataResult.Abort; + } + + _logger.LogTrace("> IsReady {0}", clientId); + return GameDataResult.Continue; + } + + case GameDataTag.ConsoleDeclareClientPlatformFlag: + { + var clientId = reader.ReadPackedInt32(); + var platform = (RuntimePlatform)reader.ReadPackedInt32(); + + if (clientId != sender.Client.Id) + { + if (await sender.Client.ReportCheatAsync(new CheatContext(nameof(GameDataTag.ConsoleDeclareClientPlatformFlag)), CheatCategory.Ownership, "Client sent info with wrong client id")) + { + return GameDataResult.Abort; + } + } + + sender.Platform = platform; + + return GameDataResult.Continue; + } + + default: + { + _logger.LogWarning("{Code} - Bad GameData tag {Tag}", Code, reader.Tag); + return GameDataResult.Continue; + } + } } private async ValueTask OnSpawnAsync(ClientPlayer sender, InnerNetObject netObj)