diff options
Diffstat (limited to 'Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs')
-rw-r--r-- | Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs b/Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs new file mode 100644 index 0000000..a84d9b5 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs @@ -0,0 +1,449 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Innersloth; +using Impostor.Api.Net.Messages; +using Impostor.Api.Net.Messages.S2C; +using Impostor.Hazel; +using Impostor.Server.Events.Meeting; +using Impostor.Server.Events.Player; +using Impostor.Server.Net.Inner; +using Impostor.Server.Net.Inner.Objects; +using Impostor.Server.Net.Inner.Objects.Components; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Impostor.Server.Net.State +{ + internal partial class Game + { + private const int FakeClientId = int.MaxValue - 1; + + /// <summary> + /// Used for global object, spawned by the host. + /// </summary> + private const int InvalidClient = -2; + + /// <summary> + /// Used internally to set the OwnerId to the current ClientId. + /// i.e: <code>ownerId = ownerId == -3 ? this.ClientId : ownerId;</code> + /// </summary> + private const int CurrentClient = -3; + + private static readonly Type[] SpawnableObjects = + { + typeof(InnerShipStatus), // ShipStatus + typeof(InnerMeetingHud), + typeof(InnerLobbyBehaviour), + typeof(InnerGameData), + typeof(InnerPlayerControl), + typeof(InnerShipStatus), // HeadQuarters + typeof(InnerShipStatus), // PlanetMap + typeof(InnerShipStatus), // AprilShipStatus + }; + + private readonly List<InnerNetObject> _allObjects = new List<InnerNetObject>(); + private readonly Dictionary<uint, InnerNetObject> _allObjectsFast = new Dictionary<uint, InnerNetObject>(); + + private int _gamedataInitialized; + private bool _gamedataFakeReceived; + + private async ValueTask OnSpawnAsync(InnerNetObject netObj) + { + switch (netObj) + { + case InnerLobbyBehaviour lobby: + { + GameNet.LobbyBehaviour = lobby; + break; + } + + case InnerGameData data: + { + GameNet.GameData = data; + break; + } + + case InnerVoteBanSystem voteBan: + { + GameNet.VoteBan = voteBan; + break; + } + + case InnerShipStatus shipStatus: + { + GameNet.ShipStatus = shipStatus; + break; + } + + case InnerPlayerControl control: + { + // Hook up InnerPlayerControl <-> IClientPlayer. + if (!TryGetPlayer(control.OwnerId, out var player)) + { + throw new ImpostorException("Failed to find player that spawned the InnerPlayerControl"); + } + + player.Character = control; + player.DisableSpawnTimeout(); + + // Hook up InnerPlayerControl <-> InnerPlayerControl.PlayerInfo. + control.PlayerInfo = GameNet.GameData.GetPlayerById(control.PlayerId)!; + + if (control.PlayerInfo == null) + { + GameNet.GameData.AddPlayer(control); + } + + if (control.PlayerInfo != null) + { + control.PlayerInfo!.Controller = control; + } + + await _eventManager.CallAsync(new PlayerSpawnedEvent(this, player, control)); + + break; + } + + case InnerMeetingHud meetingHud: + { + await _eventManager.CallAsync(new MeetingStartedEvent(this, meetingHud)); + break; + } + } + } + + private async ValueTask OnDestroyAsync(InnerNetObject netObj) + { + switch (netObj) + { + case InnerLobbyBehaviour: + { + GameNet.LobbyBehaviour = null; + break; + } + + case InnerGameData: + { + GameNet.GameData = null; + break; + } + + case InnerVoteBanSystem: + { + GameNet.VoteBan = null; + break; + } + + case InnerShipStatus: + { + GameNet.ShipStatus = null; + break; + } + + case InnerPlayerControl control: + { + // Remove InnerPlayerControl <-> IClientPlayer. + if (TryGetPlayer(control.OwnerId, out var player)) + { + player.Character = null; + } + + await _eventManager.CallAsync(new PlayerDestroyedEvent(this, player, control)); + + break; + } + } + } + + private async ValueTask InitGameDataAsync(ClientPlayer player) + { + if (Interlocked.Exchange(ref _gamedataInitialized, 1) != 0) + { + return; + } + + /* + * The Among Us client on 20.9.22i spawns some components on the host side and + * only spawns these on other clients when someone else connects. This means that we can't + * parse data until someone connects because we don't know which component belongs to the NetId. + * + * We solve this by spawning a fake player and removing the player when the spawn GameData + * is received in HandleGameDataAsync. + */ + using (var message = MessageWriter.Get(MessageType.Reliable)) + { + // Spawn a fake player. + Message01JoinGameS2C.SerializeJoin(message, false, Code, FakeClientId, HostId); + + message.StartMessage(MessageFlags.GameData); + message.Write(Code); + message.StartMessage(GameDataTag.SceneChangeFlag); + message.WritePacked(FakeClientId); + message.Write("OnlineGame"); + message.EndMessage(); + message.EndMessage(); + + await player.Client.Connection.SendAsync(message); + } + } + + public async ValueTask<bool> HandleGameDataAsync(IMessageReader parent, ClientPlayer sender, bool toPlayer) + { + // Find target player. + ClientPlayer target = null; + + if (toPlayer) + { + var targetId = parent.ReadPackedInt32(); + if (targetId == FakeClientId && !_gamedataFakeReceived && sender.IsHost) + { + _gamedataFakeReceived = true; + + // Remove the fake client, we received the data. + using (var message = MessageWriter.Get(MessageType.Reliable)) + { + WriteRemovePlayerMessage(message, false, FakeClientId, (byte)DisconnectReason.ExitGame); + + await sender.Client.Connection.SendAsync(message); + } + } + else if (!TryGetPlayer(targetId, out target)) + { + _logger.LogWarning("Player {0} tried to send GameData to unknown player {1}.", sender.Client.Id, targetId); + return false; + } + + _logger.LogTrace("Received GameData for target {0}.", targetId); + } + + // Parse GameData messages. + while (parent.Position < parent.Length) + { + using var reader = parent.ReadMessage(); + + switch (reader.Tag) + { + case GameDataTag.DataFlag: + { + var netId = reader.ReadPackedUInt32(); + if (_allObjectsFast.TryGetValue(netId, out var obj)) + { + obj.Deserialize(sender, target, reader, false); + } + else + { + _logger.LogWarning("Received DataFlag for unregistered NetId {0}.", netId); + } + + break; + } + + case GameDataTag.RpcFlag: + { + var netId = reader.ReadPackedUInt32(); + if (_allObjectsFast.TryGetValue(netId, out var obj)) + { + await obj.HandleRpc(sender, target, (RpcCalls) reader.ReadByte(), reader); + } + else + { + _logger.LogWarning("Received RpcFlag for unregistered NetId {0}.", netId); + } + + break; + } + + case GameDataTag.SpawnFlag: + { + // Only the host is allowed to despawn objects. + if (!sender.IsHost) + { + throw new ImpostorCheatException("Tried to send SpawnFlag as non-host."); + } + + var objectId = reader.ReadPackedUInt32(); + if (objectId < SpawnableObjects.Length) + { + var innerNetObject = (InnerNetObject) ActivatorUtilities.CreateInstance(_serviceProvider, SpawnableObjects[objectId], this); + var ownerClientId = reader.ReadPackedInt32(); + + // Prevent fake client from being broadcasted. + // TODO: Remove message from stream properly. + if (ownerClientId == FakeClientId) + { + return false; + } + + innerNetObject.SpawnFlags = (SpawnFlags) reader.ReadByte(); + + var components = innerNetObject.GetComponentsInChildren<InnerNetObject>(); + 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) + { + obj.Deserialize(sender, target, readerSub, true); + } + + await OnSpawnAsync(obj); + } + + continue; + } + + _logger.LogError("Couldn't find spawnable object {0}.", objectId); + break; + } + + // Only the host is allowed to despawn objects. + case GameDataTag.DespawnFlag: + { + var netId = reader.ReadPackedUInt32(); + if (_allObjectsFast.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(); + 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; + } + + sender.Scene = reader.ReadString(); + + _logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene); + break; + } + + case GameDataTag.ReadyFlag: + { + var clientId = reader.ReadPackedInt32(); + _logger.LogTrace("> IsReady {0}", clientId); + break; + } + + default: + { + _logger.LogTrace("Bad GameData tag {0}", reader.Tag); + break; + } + } + + if (sender.Client.Player == null) + { + // Disconnect handler was probably invoked, cancel the rest. + return false; + } + } + + return true; + } + + private bool AddNetObject(InnerNetObject obj) + { + if (_allObjectsFast.ContainsKey(obj.NetId)) + { + return false; + } + + _allObjects.Add(obj); + _allObjectsFast.Add(obj.NetId, obj); + return true; + } + + private void RemoveNetObject(InnerNetObject obj) + { + var index = _allObjects.IndexOf(obj); + if (index > -1) + { + _allObjects.RemoveAt(index); + } + + _allObjectsFast.Remove(obj.NetId); + + obj.NetId = uint.MaxValue; + } + + public T FindObjectByNetId<T>(uint netId) + where T : InnerNetObject + { + if (_allObjectsFast.TryGetValue(netId, out var obj)) + { + return (T) obj; + } + + return null; + } + } +} |