From: AeonLucid Date: Fri, 25 Sep 2020 20:02:03 +0000 (+0200) Subject: Refactor code X-Git-Tag: v1.1.0~1^2~46 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=3f3f54576072914399570bc389a2916d4de7867f;p=rhonda%2Fimpostor.git Refactor code --- diff --git a/src/Impostor.Server/Extensions/ConnectionExtensions.cs b/src/Impostor.Server/Extensions/ConnectionExtensions.cs deleted file mode 100644 index 21ef19e..0000000 --- a/src/Impostor.Server/Extensions/ConnectionExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Hazel; -using Impostor.Server.Net.Response; - -namespace Impostor.Server.Extensions -{ - internal static class ConnectionExtensions - { - public static void Send(this Connection connection, MessageBase message) - { - using (message) - { - connection.Send(message.Write()); - } - } - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs index b3a041e..72e3558 100644 --- a/src/Impostor.Server/Net/Client.cs +++ b/src/Impostor.Server/Net/Client.cs @@ -1,9 +1,9 @@ using System; using Hazel; using Impostor.Server.Data; -using Impostor.Server.Extensions; -using Impostor.Server.Net.Response; -using Impostor.Shared.Innersloth; +using Impostor.Server.Net.Manager; +using Impostor.Server.Net.Messages; +using Impostor.Server.Net.State; using Impostor.Shared.Innersloth.Data; using Serilog; using ILogger = Serilog.ILogger; @@ -85,7 +85,7 @@ namespace Impostor.Server.Net catch (Exception ex) { Logger.Error(ex, "Exception caught in client data handler."); - Connection.Send(new Message1DisconnectReason(DisconnectReason.Custom, DisconnectMessages.Error)); + Player.SendDisconnectReason(DisconnectReason.Custom, DisconnectMessages.Error); } } @@ -100,23 +100,20 @@ namespace Impostor.Server.Net case RequestFlag.HostGame: { // Read game settings. - var gameInfoBytes = message.ReadBytesAndSize(); - var gameInfo = GameOptionsData.Deserialize(gameInfoBytes); + var gameInfo = Message00HostGame.Deserialize(message); // Create game. var game = _gameManager.Create(this, gameInfo); if (game == null) { - Connection.Send(new Message1DisconnectReason(DisconnectReason.ServerFull)); + Player.SendDisconnectReason(DisconnectReason.ServerFull); return; } // Code in the packet below will be used in JoinGame. using (var writer = MessageWriter.Get(SendOption.Reliable)) { - writer.StartMessage(0); - writer.Write(game.Code); - writer.EndMessage(); + Message00HostGame.Serialize(writer, game.Code); Connection.Send(writer); } @@ -130,7 +127,7 @@ namespace Impostor.Server.Net var game = _gameManager.Find(gameCode); if (game == null) { - Connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing)); + Player.SendDisconnectReason(DisconnectReason.GameMissing); return; } @@ -240,17 +237,19 @@ namespace Impostor.Server.Net Logger.Warning("Server received unknown flag {0}.", flag); break; } - + +#if DEBUG if (flag != RequestFlag.GameData && flag != RequestFlag.GameDataTo && flag != RequestFlag.EndGame && message.Position < message.Length) { - Logger.Warning("Server did not consume all bytes from {0} ({1} < {2}).", - flag, - message.Position, + Logger.Warning("Server did not consume all bytes from {0} ({1} < {2}).", + flag, + message.Position, message.Length); } +#endif } private void OnDisconnected(object sender, DisconnectedEventArgs e) diff --git a/src/Impostor.Server/Net/ClientManager.cs b/src/Impostor.Server/Net/ClientManager.cs deleted file mode 100644 index 2a43d14..0000000 --- a/src/Impostor.Server/Net/ClientManager.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Concurrent; -using Impostor.Server.Exceptions; -using Serilog; - -namespace Impostor.Server.Net -{ - public class ClientManager - { - private static readonly ILogger Logger = Log.ForContext(); - - private readonly ConcurrentDictionary _clients; - private readonly object _idLock; - private int _idLast; - - public ClientManager() - { - _clients = new ConcurrentDictionary(); - _idLock = new object(); - _idLast = 0; - } - - // No idea what a good way for this is. - public int NextId() - { - lock (_idLock) - { - // 3 Attempts. - for (var i = 0; i < 3; i++) - { - // It is important that ids start from 1, a 0 id causes issues. - var result = ++_idLast; - - if (_idLast == int.MaxValue) - { - _idLast = 0; - } - - if (_clients.ContainsKey(_idLast)) - { - continue; - } - - return result; - } - - throw new AmongUsException("Unable to generate a client id."); - } - } - - public void Add(Client client) - { - Logger.Information("Client connected."); - - _clients.TryAdd(client.Id, client); - } - - public void Remove(Client client) - { - Logger.Information("Client disconnected."); - - _clients.TryRemove(client.Id, out _); - } - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/ClientPlayer.cs b/src/Impostor.Server/Net/ClientPlayer.cs deleted file mode 100644 index a0b7866..0000000 --- a/src/Impostor.Server/Net/ClientPlayer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Impostor.Shared.Innersloth.Data; - -namespace Impostor.Server.Net -{ - public class ClientPlayer - { - public ClientPlayer(Client client) - { - Client = client; - } - - public Client Client { get; } - public Game Game { get; set; } - public LimboStates LimboState { get; set; } - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Game.cs b/src/Impostor.Server/Net/Game.cs deleted file mode 100644 index be982d7..0000000 --- a/src/Impostor.Server/Net/Game.cs +++ /dev/null @@ -1,387 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using Hazel; -using Impostor.Server.Data; -using Impostor.Server.Exceptions; -using Impostor.Server.Extensions; -using Impostor.Server.Net.Response; -using Impostor.Shared.Innersloth; -using Impostor.Shared.Innersloth.Data; -using Serilog; -using ILogger = Serilog.ILogger; - -namespace Impostor.Server.Net -{ - public class Game - { - private static readonly ILogger Logger = Log.ForContext(); - - private readonly GameManager _gameManager; - private readonly ConcurrentDictionary _players; - private readonly HashSet _bannedIps; - - public Game(GameManager gameManager, int code, GameOptionsData options) - { - _gameManager = gameManager; - _players = new ConcurrentDictionary(); - _bannedIps = new HashSet(); - - Code = code; - CodeStr = GameCode.IntToGameName(code); - HostId = -1; - GameState = GameStates.NotStarted; - Options = options; - } - - public int Code { get; } - public string CodeStr { get; } - public bool IsPublic { get; private set; } - public int HostId { get; private set; } - public GameStates GameState { get; private set; } - public GameOptionsData Options { get; } - - public void SendToAllExcept(MessageWriter message, ClientPlayer sender) - { - foreach (var (_, player) in _players.Where(x => x.Value != sender)) - { - if (player.Client.Connection.State != ConnectionState.Connected) - { - Logger.Warning("[{0}] Tried to sent data to a disconnected player ({1}).", sender?.Client.Id, player.Client.Id); - continue; - } - - player.Client.Send(message); - } - } - - public void SendTo(MessageWriter message, int playerId) - { - if (_players.TryGetValue(playerId, out var player)) - { - if (player.Client.Connection.State != ConnectionState.Connected) - { - Logger.Warning("[{0}] Sending data to {1} failed, player is not connected.", CodeStr, player.Client.Id); - return; - } - - player.Client.Send(message); - } - else - { - Logger.Warning("[{0}] Sending data to {1} failed, player does not exist.", CodeStr, playerId); - } - } - - public void HandleStartGame(MessageReader message) - { - GameState = GameStates.Started; - - using (var packet = MessageWriter.Get(SendOption.Reliable)) - { - packet.CopyFrom(message); - SendToAllExcept(packet, null); - } - } - - public void HandleJoinGame(ClientPlayer sender) - { - if (_bannedIps.Contains(sender.Client.Connection.EndPoint.Address)) - { - sender.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.Banned)); - return; - } - - switch (GameState) - { - case GameStates.NotStarted: - HandleJoinGameNew(sender); - break; - case GameStates.Ended: - HandleJoinGameNext(sender); - break; - case GameStates.Started: - sender.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.GameStarted)); - return; - case GameStates.Destroyed: - sender.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.Custom, DisconnectMessages.Destroyed)); - return; - default: - throw new ArgumentOutOfRangeException(); - } - } - - public void HandleEndGame(MessageReader message) - { - GameState = GameStates.Ended; - - // Broadcast end of the game. - using (var packet = MessageWriter.Get(SendOption.Reliable)) - { - packet.CopyFrom(message); - SendToAllExcept(packet, null); - } - - // Remove all players from this game. - foreach (var player in _players) - { - player.Value.Game = null; - } - - _players.Clear(); - } - - public void HandleAlterGame(MessageReader message, ClientPlayer sender, bool isPublic) - { - IsPublic = isPublic; - - using (var packet = MessageWriter.Get(SendOption.Reliable)) - { - packet.CopyFrom(message); - SendToAllExcept(packet, sender); - } - } - - public void HandleRemovePlayer(int playerId, DisconnectReason reason) - { - if (_players.TryRemove(playerId, out var player)) - { - player.Game = null; - } - - Logger.Information("{0} - Player {1} ({2}) has left.", CodeStr, player?.Client.Name, playerId); - - // Game is empty, remove it. - if (_players.Count == 0) - { - GameState = GameStates.Destroyed; - - // Remove instance reference. - _gameManager.Remove(Code); - return; - } - - // Host migration. - if (HostId == playerId) - { - var newHost = _players.First().Value; - HostId = newHost.Client.Id; - Logger.Information("{0} - Assigned {1} ({2}) as new host.", CodeStr, newHost.Client.Name, newHost.Client.Id); - } - - using (var packet = MessageWriter.Get(SendOption.Reliable)) - { - WriteRemovePlayerMessage(packet, false, playerId, reason); - SendToAllExcept(packet, player); - } - } - - public void HandleKickPlayer(int playerId, bool isBan) - { - _players.TryGetValue(playerId, out var p); - Logger.Information("{0} - Player {1} ({2}) has left.", CodeStr, p?.Client.Name, playerId); - - using (var message = MessageWriter.Get(SendOption.Reliable)) - { - WriteKickPlayerMessage(message, false, playerId, isBan); - SendToAllExcept(message, null); - - if (_players.TryRemove(playerId, out var player)) - { - player.Game = null; - - if (isBan) - { - _bannedIps.Add(player.Client.Connection.EndPoint.Address); - } - } - - WriteRemovePlayerMessage(message, true, playerId, isBan - ? DisconnectReason.Banned - : DisconnectReason.Kicked); - SendToAllExcept(message, player); - } - } - - private void HandleJoinGameNew(ClientPlayer sender) - { - Logger.Information("{0} - Player {1} ({2}) is joining.", CodeStr, sender.Client.Name, sender.Client.Id); - - // Store player. - if (!_players.TryAdd(sender.Client.Id, sender)) - { - throw new AmongUsException("Failed to add player to game."); - } - - // Assign player to this game for future packets. - sender.Game = this; - - // Assign hostId if none is set. - if (HostId == -1) - { - HostId = sender.Client.Id; - } - - if (HostId == sender.Client.Id) - { - sender.LimboState = LimboStates.NotLimbo; - } - - using (var message = MessageWriter.Get(SendOption.Reliable)) - { - WriteJoinedGameMessage(message, false, sender); - WriteAlterGameMessage(message, false); - - sender.Client.Send(message); - - BroadcastJoinMessage(message, true, sender); - } - } - - private void HandleJoinGameNext(ClientPlayer sender) - { - Logger.Information("{0} - Player {1} ({2}) is rejoining.", CodeStr, sender.Client.Name, sender.Client.Id); - - if (sender.Client.Id == HostId) - { - GameState = GameStates.NotStarted; - HandleJoinGameNew(sender); - - using (var message = MessageWriter.Get(SendOption.Reliable)) - { - foreach (var (_, player) in _players.Where(x => x.Value != sender)) - { - WriteJoinedGameMessage(message, true, player); - WriteAlterGameMessage(message, false); - player.Client.Send(message); - } - } - - return; - } - - if (_players.Count >= 9) - { - sender.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.GameFull)); - return; - } - - // Store player. - if (!_players.TryAdd(sender.Client.Id, sender)) - { - throw new AmongUsException("Failed to add player to game."); - } - - // Assign player to this game for future packets. - sender.Game = this; - - // Limbo, yes. - sender.LimboState = LimboStates.WaitingForHost; - - using (var packet = MessageWriter.Get(SendOption.Reliable)) - { - WriteWaitForHostMessage(packet, false, sender); - sender.Client.Send(packet); - - BroadcastJoinMessage(packet, true, sender); - } - } - - private void WriteRemovePlayerMessage(MessageWriter message, bool clear, int playerId, DisconnectReason reason) - { - // Only a subset of DisconnectReason shows an unique message. - // ExitGame, Banned and Kicked. - if (clear) - { - message.Clear(SendOption.Reliable); - } - - message.StartMessage((byte) RequestFlag.RemovePlayer); - message.Write(Code); - message.Write(playerId); - message.Write(HostId); - message.Write((byte) reason); - message.EndMessage(); - } - - private void WriteJoinedGameMessage(MessageWriter message, bool clear, ClientPlayer player) - { - if (clear) - { - message.Clear(SendOption.Reliable); - } - - message.StartMessage((byte) RequestFlag.JoinedGame); - message.Write(Code); - message.Write(player.Client.Id); - message.Write(HostId); - message.WritePacked(_players.Count - 1); - - foreach (var (_, p) in _players.Where(x => x.Value != player)) - { - message.WritePacked(p.Client.Id); - } - - message.EndMessage(); - } - - private void WriteAlterGameMessage(MessageWriter message, bool clear) - { - if (clear) - { - message.Clear(SendOption.Reliable); - } - - message.StartMessage((byte) RequestFlag.AlterGame); - message.Write(Code); - message.Write((byte) AlterGameTags.ChangePrivacy); - message.Write(IsPublic); - message.EndMessage(); - } - - private void WriteKickPlayerMessage(MessageWriter message, bool clear, int playerId, bool isBan) - { - if (clear) - { - message.Clear(SendOption.Reliable); - } - - message.StartMessage((byte) RequestFlag.KickPlayer); - message.Write(Code); - message.WritePacked(playerId); - message.Write(isBan); - message.EndMessage(); - } - - private void WriteWaitForHostMessage(MessageWriter message, bool clear, ClientPlayer player) - { - if (clear) - { - message.Clear(SendOption.Reliable); - } - - message.StartMessage((byte) RequestFlag.WaitForHost); - message.Write(Code); - message.Write(player.Client.Id); - message.EndMessage(); - } - - private void BroadcastJoinMessage(MessageWriter message, bool clear, ClientPlayer player) - { - if (clear) - { - message.Clear(SendOption.Reliable); - } - - message.StartMessage((byte) RequestFlag.JoinGame); - message.Write(Code); - message.Write(player.Client.Id); - message.Write(HostId); - message.EndMessage(); - - SendToAllExcept(message, player); - } - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/GameManager.cs b/src/Impostor.Server/Net/GameManager.cs deleted file mode 100644 index 664f640..0000000 --- a/src/Impostor.Server/Net/GameManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Concurrent; -using Impostor.Shared.Innersloth; -using Serilog; - -namespace Impostor.Server.Net -{ - public class GameManager - { - private static readonly ILogger Logger = Log.ForContext(); - - private readonly ConcurrentDictionary _games; - - public GameManager() - { - _games = new ConcurrentDictionary(); - } - - public Game Create(Client owner, GameOptionsData options) - { - var gameCode = GameCode.GenerateCode(6); - var game = new Game(this, gameCode, options); - - if (_games.TryAdd(gameCode, game)) - { - Logger.Debug("Created game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode); - return game; - } - - Logger.Warning("Failed to create game."); - return null; - } - - public Game Find(int gameCode) - { - _games.TryGetValue(gameCode, out var game); - return game; - } - - public void Remove(int gameCode) - { - Logger.Debug("Remove game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode); - _games.TryRemove(gameCode, out _); - } - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/GameStates.cs b/src/Impostor.Server/Net/GameStates.cs deleted file mode 100644 index f24ecc5..0000000 --- a/src/Impostor.Server/Net/GameStates.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Impostor.Server.Net -{ - public enum GameStates : byte - { - NotStarted = 0, - Started = 1, - Ended = 2, - Destroyed = 3 - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Manager/ClientManager.cs b/src/Impostor.Server/Net/Manager/ClientManager.cs new file mode 100644 index 0000000..b4f7684 --- /dev/null +++ b/src/Impostor.Server/Net/Manager/ClientManager.cs @@ -0,0 +1,64 @@ +using System.Collections.Concurrent; +using Impostor.Server.Exceptions; +using Serilog; + +namespace Impostor.Server.Net.Manager +{ + public class ClientManager + { + private static readonly ILogger Logger = Log.ForContext(); + + private readonly ConcurrentDictionary _clients; + private readonly object _idLock; + private int _idLast; + + public ClientManager() + { + _clients = new ConcurrentDictionary(); + _idLock = new object(); + _idLast = 0; + } + + // No idea what a good way for this is. + public int NextId() + { + lock (_idLock) + { + // 3 Attempts. + for (var i = 0; i < 3; i++) + { + // It is important that ids start from 1, a 0 id causes issues. + var result = ++_idLast; + + if (_idLast == int.MaxValue) + { + _idLast = 0; + } + + if (_clients.ContainsKey(_idLast)) + { + continue; + } + + return result; + } + + throw new AmongUsException("Unable to generate a client id."); + } + } + + public void Add(Client client) + { + Logger.Information("Client connected."); + + _clients.TryAdd(client.Id, client); + } + + public void Remove(Client client) + { + Logger.Information("Client disconnected."); + + _clients.TryRemove(client.Id, out _); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Manager/GameManager.cs b/src/Impostor.Server/Net/Manager/GameManager.cs new file mode 100644 index 0000000..0b2b251 --- /dev/null +++ b/src/Impostor.Server/Net/Manager/GameManager.cs @@ -0,0 +1,46 @@ +using System.Collections.Concurrent; +using Impostor.Server.Net.State; +using Impostor.Shared.Innersloth; +using Serilog; + +namespace Impostor.Server.Net.Manager +{ + public class GameManager + { + private static readonly ILogger Logger = Log.ForContext(); + + private readonly ConcurrentDictionary _games; + + public GameManager() + { + _games = new ConcurrentDictionary(); + } + + public Game Create(Client owner, GameOptionsData options) + { + var gameCode = GameCode.GenerateCode(6); + var game = new Game(this, gameCode, options); + + if (_games.TryAdd(gameCode, game)) + { + Logger.Debug("Created game with code {0} ({1}).", game.CodeStr, gameCode); + return game; + } + + Logger.Warning("Failed to create game."); + return null; + } + + public Game Find(int gameCode) + { + _games.TryGetValue(gameCode, out var game); + return game; + } + + public void Remove(int gameCode) + { + Logger.Debug("Remove game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode); + _games.TryRemove(gameCode, out _); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Matchmaker.cs b/src/Impostor.Server/Net/Matchmaker.cs index 8acc9d6..5a1bec7 100644 --- a/src/Impostor.Server/Net/Matchmaker.cs +++ b/src/Impostor.Server/Net/Matchmaker.cs @@ -1,8 +1,8 @@ using System.Net; using Hazel; using Hazel.Udp; -using Impostor.Server.Extensions; -using Impostor.Server.Net.Response; +using Impostor.Server.Net.Manager; +using Impostor.Server.Net.Messages; using Impostor.Shared.Innersloth.Data; using Serilog; using ILogger = Serilog.ILogger; @@ -39,7 +39,11 @@ namespace Impostor.Server.Net if (clientVersion != 50516550) { - e.Connection.Send(new Message1DisconnectReason(DisconnectReason.IncorrectVersion)); + using (var packet = MessageWriter.Get(SendOption.Reliable)) + { + Message01JoinGame.SerializeError(packet, false, DisconnectReason.IncorrectVersion); + e.Connection.Send(packet); + } return; } diff --git a/src/Impostor.Server/Net/Messages/Message00HostGame.cs b/src/Impostor.Server/Net/Messages/Message00HostGame.cs new file mode 100644 index 0000000..392bc38 --- /dev/null +++ b/src/Impostor.Server/Net/Messages/Message00HostGame.cs @@ -0,0 +1,60 @@ +using System.IO; +using Hazel; +using Impostor.Shared.Innersloth; +using Impostor.Shared.Innersloth.Data; + +namespace Impostor.Server.Net.Messages +{ + internal static class Message00HostGame + { + public static void Serialize(MessageWriter writer, int gameCode) + { + writer.StartMessage(MessageFlags.HostGame); + writer.Write(gameCode); + writer.EndMessage(); + } + + public static GameOptionsData Deserialize(MessageReader reader) + { + var bytes = reader.ReadBytesAndSize(); + + using (var stream = new MemoryStream(bytes)) + using (var binary = new BinaryReader(stream)) + { + var result = new GameOptionsData + { + Version = binary.ReadByte(), + MaxPlayers = binary.ReadByte(), + Keywords = (GameKeywords) binary.ReadUInt32(), + MapId = binary.ReadByte(), + PlayerSpeedMod = binary.ReadSingle(), + CrewLightMod = binary.ReadSingle(), + ImpostorLightMod = binary.ReadSingle(), + KillCooldown = binary.ReadSingle(), + NumCommonTasks = binary.ReadByte(), + NumLongTasks = binary.ReadByte(), + NumShortTasks = binary.ReadByte(), + NumEmergencyMeetings = binary.ReadInt32(), + NumImpostors = binary.ReadByte(), + KillDistance = binary.ReadByte(), + DiscussionTime = binary.ReadInt32(), + VotingTime = binary.ReadInt32(), + IsDefaults = binary.ReadBoolean() + }; + + if (result.Version > 1) + { + result.EmergencyCooldown = binary.ReadByte(); + } + + if (result.Version > 2) + { + result.ConfirmImpostor = binary.ReadBoolean(); + result.VisualTasks = binary.ReadBoolean(); + } + + return result; + } + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Messages/Message01JoinGame.cs b/src/Impostor.Server/Net/Messages/Message01JoinGame.cs new file mode 100644 index 0000000..badee4f --- /dev/null +++ b/src/Impostor.Server/Net/Messages/Message01JoinGame.cs @@ -0,0 +1,46 @@ +using System; +using Hazel; +using Impostor.Shared.Innersloth.Data; + +namespace Impostor.Server.Net.Messages +{ + internal static class Message01JoinGame + { + public static void SerializeJoin(MessageWriter writer, bool clear, int gameCode, int playerId, int hostId) + { + if (clear) + { + writer.Clear(SendOption.Reliable); + } + + writer.StartMessage(MessageFlags.JoinGame); + writer.Write(gameCode); + writer.Write(playerId); + writer.Write(hostId); + writer.EndMessage(); + } + + public static void SerializeError(MessageWriter writer, bool clear, DisconnectReason reason, string message = null) + { + if (clear) + { + writer.Clear(SendOption.Reliable); + } + + writer.StartMessage(MessageFlags.JoinGame); + writer.Write((int) reason); + + if (reason == DisconnectReason.Custom) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + + writer.Write(message); + } + + writer.EndMessage(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Messages/Message04RemovePlayer.cs b/src/Impostor.Server/Net/Messages/Message04RemovePlayer.cs new file mode 100644 index 0000000..b572c21 --- /dev/null +++ b/src/Impostor.Server/Net/Messages/Message04RemovePlayer.cs @@ -0,0 +1,25 @@ +using Hazel; +using Impostor.Shared.Innersloth.Data; + +namespace Impostor.Server.Net.Messages +{ + internal static class Message04RemovePlayer + { + public static void Serialize(MessageWriter writer, bool clear, int gameCode, int playerId, int hostId, DisconnectReason reason) + { + // Only a subset of DisconnectReason shows an unique message. + // ExitGame, Banned and Kicked. + if (clear) + { + writer.Clear(SendOption.Reliable); + } + + writer.StartMessage(MessageFlags.RemovePlayer); + writer.Write(gameCode); + writer.Write(playerId); + writer.Write(hostId); + writer.Write((byte) reason); + writer.EndMessage(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Messages/Message07JoinedGame.cs b/src/Impostor.Server/Net/Messages/Message07JoinedGame.cs new file mode 100644 index 0000000..9fa08e7 --- /dev/null +++ b/src/Impostor.Server/Net/Messages/Message07JoinedGame.cs @@ -0,0 +1,28 @@ +using Hazel; + +namespace Impostor.Server.Net.Messages +{ + internal static class Message07JoinedGame + { + public static void Serialize(MessageWriter writer, bool clear, int gameCode, int playerId, int hostId, int[] otherPlayerIds) + { + if (clear) + { + writer.Clear(SendOption.Reliable); + } + + writer.StartMessage(MessageFlags.JoinedGame); + writer.Write(gameCode); + writer.Write(playerId); + writer.Write(hostId); + writer.WritePacked(otherPlayerIds.Length); + + foreach (var id in otherPlayerIds) + { + writer.WritePacked(id); + } + + writer.EndMessage(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Messages/Message10AlterGame.cs b/src/Impostor.Server/Net/Messages/Message10AlterGame.cs new file mode 100644 index 0000000..3b77548 --- /dev/null +++ b/src/Impostor.Server/Net/Messages/Message10AlterGame.cs @@ -0,0 +1,19 @@ +using Hazel; + +namespace Impostor.Server.Net.Messages +{ + internal static class Message10AlterGame + { + public static void Serialize(MessageWriter writer, bool clear, int gameCode) + { + if (clear) + { + writer.Clear(SendOption.Reliable); + } + + writer.StartMessage(MessageFlags.HostGame); + writer.Write(gameCode); + writer.EndMessage(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Messages/Message11KickPlayer.cs b/src/Impostor.Server/Net/Messages/Message11KickPlayer.cs new file mode 100644 index 0000000..dde4726 --- /dev/null +++ b/src/Impostor.Server/Net/Messages/Message11KickPlayer.cs @@ -0,0 +1,21 @@ +using Hazel; + +namespace Impostor.Server.Net.Messages +{ + internal static class Message11KickPlayer + { + public static void Serialize(MessageWriter writer, bool clear, int gameCode, int playerId, bool isBan) + { + if (clear) + { + writer.Clear(SendOption.Reliable); + } + + writer.StartMessage(MessageFlags.KickPlayer); + writer.Write(gameCode); + writer.WritePacked(playerId); + writer.Write(isBan); + writer.EndMessage(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Messages/Message12WaitForHost.cs b/src/Impostor.Server/Net/Messages/Message12WaitForHost.cs new file mode 100644 index 0000000..e09f4b4 --- /dev/null +++ b/src/Impostor.Server/Net/Messages/Message12WaitForHost.cs @@ -0,0 +1,20 @@ +using Hazel; + +namespace Impostor.Server.Net.Messages +{ + internal static class Message12WaitForHost + { + public static void Serialize(MessageWriter writer, bool clear, int gameCode, int playerId) + { + if (clear) + { + writer.Clear(SendOption.Reliable); + } + + writer.StartMessage(MessageFlags.WaitForHost); + writer.Write(gameCode); + writer.Write(playerId); + writer.EndMessage(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Messages/MessageFlags.cs b/src/Impostor.Server/Net/Messages/MessageFlags.cs new file mode 100644 index 0000000..595c02e --- /dev/null +++ b/src/Impostor.Server/Net/Messages/MessageFlags.cs @@ -0,0 +1,22 @@ +namespace Impostor.Server.Net.Messages +{ + internal static class MessageFlags + { + public const byte HostGame = 0; + public const byte JoinGame = 1; + public const byte StartGame = 2; + public const byte RemoveGame = 3; + public const byte RemovePlayer = 4; + public const byte GameData = 5; + public const byte GameDataTo = 6; + public const byte JoinedGame = 7; + public const byte EndGame = 8; + public const byte AlterGame = 10; + public const byte KickPlayer = 11; + public const byte WaitForHost = 12; + public const byte Redirect = 13; + public const byte ReselectServer = 14; + public const byte GetGameList = 9; + public const byte GetGameListV2 = 16; + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Response/Message1DisconnectReason.cs b/src/Impostor.Server/Net/Response/Message1DisconnectReason.cs deleted file mode 100644 index 0ab5be4..0000000 --- a/src/Impostor.Server/Net/Response/Message1DisconnectReason.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Hazel; -using Impostor.Shared.Innersloth.Data; - -namespace Impostor.Server.Net.Response -{ - public class Message1DisconnectReason : MessageBase - { - private readonly DisconnectReason _reason; - private readonly string _message; - - // Notes: - // - Specifying no reason does something with ban minutes left. - // - (?) You were disconnected because Among Us was suspended by another app. - public Message1DisconnectReason(DisconnectReason reason, string message = null) : base(SendOption.Reliable, MessageFlag.DisconnectReason) - { - _reason = reason; - _message = message; - } - - protected override void WriteMessage(MessageWriter writer) - { - writer.Write((int) _reason); - - if (_reason == DisconnectReason.Custom) - { - writer.Write(_message); - } - } - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Response/MessageBase.cs b/src/Impostor.Server/Net/Response/MessageBase.cs deleted file mode 100644 index d9fceab..0000000 --- a/src/Impostor.Server/Net/Response/MessageBase.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Hazel; - -namespace Impostor.Server.Net.Response -{ - public abstract class MessageBase : IDisposable - { - private readonly MessageWriter _writer; - private readonly MessageFlag _flag; - - protected MessageBase(SendOption option, MessageFlag flag) - { - _writer = MessageWriter.Get(option); - _flag = flag; - } - - public MessageWriter Write() - { - _writer.StartMessage((byte) _flag); - WriteMessage(_writer); - _writer.EndMessage(); - return _writer; - } - - protected abstract void WriteMessage(MessageWriter writer); - - public void Dispose() - { - _writer.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Response/MessageFlag.cs b/src/Impostor.Server/Net/Response/MessageFlag.cs deleted file mode 100644 index a29135d..0000000 --- a/src/Impostor.Server/Net/Response/MessageFlag.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Impostor.Server.Net.Response -{ - public enum MessageFlag : byte - { - DisconnectReason = 1 - } -} \ No newline at end of file diff --git a/src/Impostor.Server/Net/State/ClientPlayer.cs b/src/Impostor.Server/Net/State/ClientPlayer.cs new file mode 100644 index 0000000..242d6b9 --- /dev/null +++ b/src/Impostor.Server/Net/State/ClientPlayer.cs @@ -0,0 +1,27 @@ +using Hazel; +using Impostor.Server.Net.Messages; +using Impostor.Shared.Innersloth.Data; + +namespace Impostor.Server.Net.State +{ + public class ClientPlayer + { + public ClientPlayer(Client client) + { + Client = client; + } + + public Client Client { get; } + public Game Game { get; set; } + public LimboStates LimboState { get; set; } + + public void SendDisconnectReason(DisconnectReason reason, string message = null) + { + using (var packet = MessageWriter.Get(SendOption.Reliable)) + { + Message01JoinGame.SerializeError(packet, false, reason, message); + Client.Connection.Send(packet); + } + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/State/Game.Incoming.cs b/src/Impostor.Server/Net/State/Game.Incoming.cs new file mode 100644 index 0000000..1e7155f --- /dev/null +++ b/src/Impostor.Server/Net/State/Game.Incoming.cs @@ -0,0 +1,226 @@ +using System; +using System.Linq; +using Hazel; +using Impostor.Server.Data; +using Impostor.Server.Exceptions; +using Impostor.Shared.Innersloth.Data; + +namespace Impostor.Server.Net.State +{ + public partial class Game + { + public void HandleStartGame(MessageReader message) + { + GameState = GameStates.Started; + + using (var packet = MessageWriter.Get(SendOption.Reliable)) + { + packet.CopyFrom(message); + SendToAllExcept(packet, null); + } + } + + public void HandleJoinGame(ClientPlayer sender) + { + if (_bannedIps.Contains(sender.Client.Connection.EndPoint.Address)) + { + sender.SendDisconnectReason(DisconnectReason.Banned); + return; + } + + switch (GameState) + { + case GameStates.NotStarted: + HandleJoinGameNew(sender); + break; + case GameStates.Ended: + HandleJoinGameNext(sender); + break; + case GameStates.Started: + sender.SendDisconnectReason(DisconnectReason.GameStarted); + return; + case GameStates.Destroyed: + sender.SendDisconnectReason(DisconnectReason.Custom, DisconnectMessages.Destroyed); + return; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public void HandleEndGame(MessageReader message) + { + GameState = GameStates.Ended; + + // Broadcast end of the game. + using (var packet = MessageWriter.Get(SendOption.Reliable)) + { + packet.CopyFrom(message); + SendToAllExcept(packet, null); + } + + // Remove all players from this game. + foreach (var player in _players) + { + player.Value.Game = null; + } + + _players.Clear(); + } + + public void HandleAlterGame(MessageReader message, ClientPlayer sender, bool isPublic) + { + IsPublic = isPublic; + + using (var packet = MessageWriter.Get(SendOption.Reliable)) + { + packet.CopyFrom(message); + SendToAllExcept(packet, sender); + } + } + + public void HandleRemovePlayer(int playerId, DisconnectReason reason) + { + if (_players.TryRemove(playerId, out var player)) + { + player.Game = null; + } + + Logger.Information("{0} - Player {1} ({2}) has left.", CodeStr, player?.Client.Name, playerId); + + // Game is empty, remove it. + if (_players.Count == 0) + { + GameState = GameStates.Destroyed; + + // Remove instance reference. + _gameManager.Remove(Code); + return; + } + + // Host migration. + if (HostId == playerId) + { + var newHost = _players.First().Value; + HostId = newHost.Client.Id; + Logger.Information("{0} - Assigned {1} ({2}) as new host.", CodeStr, newHost.Client.Name, newHost.Client.Id); + } + + using (var packet = MessageWriter.Get(SendOption.Reliable)) + { + WriteRemovePlayerMessage(packet, false, playerId, reason); + SendToAllExcept(packet, player); + } + } + + public void HandleKickPlayer(int playerId, bool isBan) + { + _players.TryGetValue(playerId, out var p); + Logger.Information("{0} - Player {1} ({2}) has left.", CodeStr, p?.Client.Name, playerId); + + using (var message = MessageWriter.Get(SendOption.Reliable)) + { + WriteKickPlayerMessage(message, false, playerId, isBan); + SendToAllExcept(message, null); + + if (_players.TryRemove(playerId, out var player)) + { + player.Game = null; + + if (isBan) + { + _bannedIps.Add(player.Client.Connection.EndPoint.Address); + } + } + + WriteRemovePlayerMessage(message, true, playerId, isBan + ? DisconnectReason.Banned + : DisconnectReason.Kicked); + SendToAllExcept(message, player); + } + } + + private void HandleJoinGameNew(ClientPlayer sender) + { + Logger.Information("{0} - Player {1} ({2}) is joining.", CodeStr, sender.Client.Name, sender.Client.Id); + + // Store player. + if (!_players.TryAdd(sender.Client.Id, sender)) + { + throw new AmongUsException("Failed to add player to game."); + } + + // Assign player to this game for future packets. + sender.Game = this; + + // Assign hostId if none is set. + if (HostId == -1) + { + HostId = sender.Client.Id; + } + + if (HostId == sender.Client.Id) + { + sender.LimboState = LimboStates.NotLimbo; + } + + using (var message = MessageWriter.Get(SendOption.Reliable)) + { + WriteJoinedGameMessage(message, false, sender); + WriteAlterGameMessage(message, false); + + sender.Client.Send(message); + + BroadcastJoinMessage(message, true, sender); + } + } + + private void HandleJoinGameNext(ClientPlayer sender) + { + Logger.Information("{0} - Player {1} ({2}) is rejoining.", CodeStr, sender.Client.Name, sender.Client.Id); + + if (sender.Client.Id == HostId) + { + GameState = GameStates.NotStarted; + HandleJoinGameNew(sender); + + using (var message = MessageWriter.Get(SendOption.Reliable)) + { + foreach (var (_, player) in _players.Where(x => x.Value != sender)) + { + WriteJoinedGameMessage(message, true, player); + WriteAlterGameMessage(message, false); + player.Client.Send(message); + } + } + + return; + } + + if (_players.Count >= 9) + { + sender.SendDisconnectReason(DisconnectReason.GameFull); + return; + } + + // Store player. + if (!_players.TryAdd(sender.Client.Id, sender)) + { + throw new AmongUsException("Failed to add player to game."); + } + + // Assign player to this game for future packets. + sender.Game = this; + + // Limbo, yes. + sender.LimboState = LimboStates.WaitingForHost; + + using (var packet = MessageWriter.Get(SendOption.Reliable)) + { + WriteWaitForHostMessage(packet, false, sender); + sender.Client.Send(packet); + + BroadcastJoinMessage(packet, true, sender); + } + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/State/Game.Outgoing.cs b/src/Impostor.Server/Net/State/Game.Outgoing.cs new file mode 100644 index 0000000..c03b3b4 --- /dev/null +++ b/src/Impostor.Server/Net/State/Game.Outgoing.cs @@ -0,0 +1,40 @@ +using System.Linq; +using Hazel; +using Impostor.Server.Net.Messages; +using Impostor.Shared.Innersloth.Data; + +namespace Impostor.Server.Net.State +{ + public partial class Game + { + private void WriteRemovePlayerMessage(MessageWriter message, bool clear, int playerId, DisconnectReason reason) + { + Message04RemovePlayer.Serialize(message, clear, Code, playerId, HostId, reason); + } + + private void WriteJoinedGameMessage(MessageWriter message, bool clear, ClientPlayer player) + { + var playerIds = _players + .Where(x => x.Value != player) + .Select(x => x.Key) + .ToArray(); + + Message07JoinedGame.Serialize(message, clear, Code, player.Client.Id, HostId, playerIds); + } + + private void WriteAlterGameMessage(MessageWriter message, bool clear) + { + Message10AlterGame.Serialize(message, clear, Code); + } + + private void WriteKickPlayerMessage(MessageWriter message, bool clear, int playerId, bool isBan) + { + Message11KickPlayer.Serialize(message, clear, Code, playerId, isBan); + } + + private void WriteWaitForHostMessage(MessageWriter message, bool clear, ClientPlayer player) + { + Message12WaitForHost.Serialize(message, clear, Code, player.Client.Id); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/State/Game.cs b/src/Impostor.Server/Net/State/Game.cs new file mode 100644 index 0000000..fe043a1 --- /dev/null +++ b/src/Impostor.Server/Net/State/Game.cs @@ -0,0 +1,95 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Hazel; +using Impostor.Server.Net.Manager; +using Impostor.Server.Net.Messages; +using Impostor.Shared.Innersloth; +using Impostor.Shared.Innersloth.Data; +using Serilog; +using ILogger = Serilog.ILogger; + +namespace Impostor.Server.Net.State +{ + public partial class Game + { + private static readonly ILogger Logger = Log.ForContext(); + + private readonly GameManager _gameManager; + private readonly ConcurrentDictionary _players; + private readonly HashSet _bannedIps; + + public Game(GameManager gameManager, int code, GameOptionsData options) + { + _gameManager = gameManager; + _players = new ConcurrentDictionary(); + _bannedIps = new HashSet(); + + Code = code; + CodeStr = GameCode.IntToGameName(code); + HostId = -1; + GameState = GameStates.NotStarted; + Options = options; + } + + public int Code { get; } + public string CodeStr { get; } + public bool IsPublic { get; private set; } + public int HostId { get; private set; } + public GameStates GameState { get; private set; } + public GameOptionsData Options { get; } + + /// + /// Send a message to all players except one. + /// + /// The message to send. + /// + /// The player to exclude from sending the message. + /// Set to null to send a message to everyone. + /// + public void SendToAllExcept(MessageWriter message, ClientPlayer sender) + { + foreach (var (_, player) in _players.Where(x => x.Value != sender)) + { + if (player.Client.Connection.State != ConnectionState.Connected) + { + Logger.Warning("[{0}] Tried to sent data to a disconnected player ({1}).", sender?.Client.Id, player.Client.Id); + continue; + } + + player.Client.Send(message); + } + } + + /// + /// Send a message to a specific player. + /// + /// The message to send. + /// + public void SendTo(MessageWriter message, int playerId) + { + if (_players.TryGetValue(playerId, out var player)) + { + if (player.Client.Connection.State != ConnectionState.Connected) + { + Logger.Warning("[{0}] Sending data to {1} failed, player is not connected.", CodeStr, player.Client.Id); + return; + } + + player.Client.Send(message); + } + else + { + Logger.Warning("[{0}] Sending data to {1} failed, player does not exist.", CodeStr, playerId); + } + } + + private void BroadcastJoinMessage(MessageWriter message, bool clear, ClientPlayer player) + { + Message01JoinGame.SerializeJoin(message, clear, Code, player.Client.Id, HostId); + + SendToAllExcept(message, player); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/Data/GameStates.cs b/src/Impostor.Shared/Innersloth/Data/GameStates.cs index ec2a2f2..7f165e9 100644 --- a/src/Impostor.Shared/Innersloth/Data/GameStates.cs +++ b/src/Impostor.Shared/Innersloth/Data/GameStates.cs @@ -2,9 +2,9 @@ { public enum GameStates : byte { - NotStarted, - Started, - Ended, - Destroyed + NotStarted = 0, + Started = 1, + Ended = 2, + Destroyed = 3 } } \ No newline at end of file