summaryrefslogtreecommitdiff
path: root/Impostor-dev/src/Impostor.Server/Net/State/Game.Data.cs
diff options
context:
space:
mode:
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.cs449
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;
+ }
+ }
+}