+++ /dev/null
-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
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;
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);
}
}
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);
}
var game = _gameManager.Find(gameCode);
if (game == null)
{
- Connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing));
+ Player.SendDisconnectReason(DisconnectReason.GameMissing);
return;
}
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)
+++ /dev/null
-using System.Collections.Concurrent;
-using Impostor.Server.Exceptions;
-using Serilog;
-
-namespace Impostor.Server.Net
-{
- public class ClientManager
- {
- private static readonly ILogger Logger = Log.ForContext<ClientManager>();
-
- private readonly ConcurrentDictionary<int, Client> _clients;
- private readonly object _idLock;
- private int _idLast;
-
- public ClientManager()
- {
- _clients = new ConcurrentDictionary<int, Client>();
- _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
+++ /dev/null
-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
+++ /dev/null
-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<Game>();
-
- private readonly GameManager _gameManager;
- private readonly ConcurrentDictionary<int, ClientPlayer> _players;
- private readonly HashSet<IPAddress> _bannedIps;
-
- public Game(GameManager gameManager, int code, GameOptionsData options)
- {
- _gameManager = gameManager;
- _players = new ConcurrentDictionary<int, ClientPlayer>();
- _bannedIps = new HashSet<IPAddress>();
-
- 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
+++ /dev/null
-using System.Collections.Concurrent;
-using Impostor.Shared.Innersloth;
-using Serilog;
-
-namespace Impostor.Server.Net
-{
- public class GameManager
- {
- private static readonly ILogger Logger = Log.ForContext<GameManager>();
-
- private readonly ConcurrentDictionary<int, Game> _games;
-
- public GameManager()
- {
- _games = new ConcurrentDictionary<int, Game>();
- }
-
- 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
+++ /dev/null
-namespace Impostor.Server.Net
-{
- public enum GameStates : byte
- {
- NotStarted = 0,
- Started = 1,
- Ended = 2,
- Destroyed = 3
- }
-}
\ No newline at end of file
--- /dev/null
+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<ClientManager>();
+
+ private readonly ConcurrentDictionary<int, Client> _clients;
+ private readonly object _idLock;
+ private int _idLast;
+
+ public ClientManager()
+ {
+ _clients = new ConcurrentDictionary<int, Client>();
+ _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
--- /dev/null
+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<GameManager>();
+
+ private readonly ConcurrentDictionary<int, Game> _games;
+
+ public GameManager()
+ {
+ _games = new ConcurrentDictionary<int, Game>();
+ }
+
+ 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
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;
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;
}
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
+++ /dev/null
-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
+++ /dev/null
-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
+++ /dev/null
-namespace Impostor.Server.Net.Response
-{
- public enum MessageFlag : byte
- {
- DisconnectReason = 1
- }
-}
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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<Game>();
+
+ private readonly GameManager _gameManager;
+ private readonly ConcurrentDictionary<int, ClientPlayer> _players;
+ private readonly HashSet<IPAddress> _bannedIps;
+
+ public Game(GameManager gameManager, int code, GameOptionsData options)
+ {
+ _gameManager = gameManager;
+ _players = new ConcurrentDictionary<int, ClientPlayer>();
+ _bannedIps = new HashSet<IPAddress>();
+
+ 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; }
+
+ /// <summary>
+ /// Send a message to all players except one.
+ /// </summary>
+ /// <param name="message">The message to send.</param>
+ /// <param name="sender">
+ /// The player to exclude from sending the message.
+ /// Set to null to send a message to everyone.
+ /// </param>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Send a message to a specific player.
+ /// </summary>
+ /// <param name="message">The message to send.</param>
+ /// <param name="playerId"></param>
+ 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
{
public enum GameStates : byte
{
- NotStarted,
- Started,
- Ended,
- Destroyed
+ NotStarted = 0,
+ Started = 1,
+ Ended = 2,
+ Destroyed = 3
}
}
\ No newline at end of file