From: AeonLucid Date: Sun, 20 Sep 2020 04:12:08 +0000 (+0200) Subject: Possible to create / join a game X-Git-Tag: v1.0.0~16 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=bc028b421c20275ea9c9ed4c8e80c28ffe11b184;p=rhonda%2Fimpostor.git Possible to create / join a game --- diff --git a/src/AmongUs.Client/Program.cs b/src/AmongUs.Client/Program.cs index 5a4709d..a643609 100644 --- a/src/AmongUs.Client/Program.cs +++ b/src/AmongUs.Client/Program.cs @@ -10,9 +10,9 @@ namespace AmongUs.Client { var appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "..\\LocalLow"); var regionFile = Path.Combine(appData, "Innersloth", "Among Us", "regionInfo.dat"); - var region = new RegionInfo("Private", "127.0.0.1", new [] + var region = new RegionInfo("Private", "192.168.1.211", new [] { - new ServerInfo("Private-Master-1", "127.0.0.1", 22023) + new ServerInfo("Private-Master-1", "192.168.1.211", 22023) }); using (var file = File.Open(regionFile, FileMode.Create, FileAccess.Write)) diff --git a/src/AmongUs.Server/Data/DisconnectMessages.cs b/src/AmongUs.Server/Data/DisconnectMessages.cs new file mode 100644 index 0000000..4f95954 --- /dev/null +++ b/src/AmongUs.Server/Data/DisconnectMessages.cs @@ -0,0 +1,9 @@ +namespace AmongUs.Server.Data +{ + public static class DisconnectMessages + { + public const string Error = "There was an internal server error. " + + "Check the server console for more information. " + + "Please report the issue on the AmongUsServer GitHub if it keeps happening."; + } +} \ No newline at end of file diff --git a/src/AmongUs.Server/Exceptions/AmongUsException.cs b/src/AmongUs.Server/Exceptions/AmongUsException.cs new file mode 100644 index 0000000..1009b87 --- /dev/null +++ b/src/AmongUs.Server/Exceptions/AmongUsException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace AmongUs.Server.Exceptions +{ + public class AmongUsException : Exception + { + public AmongUsException() + { + } + + protected AmongUsException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + public AmongUsException(string? message) : base(message) + { + } + + public AmongUsException(string? message, Exception? innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/AmongUs.Server/Net/Client.cs b/src/AmongUs.Server/Net/Client.cs index ee23ebb..0ce5551 100644 --- a/src/AmongUs.Server/Net/Client.cs +++ b/src/AmongUs.Server/Net/Client.cs @@ -1,5 +1,6 @@ using System; -using System.Text.Json; +using AmongUs.Server.Data; +using AmongUs.Server.Exceptions; using AmongUs.Server.Extensions; using AmongUs.Server.Net.Request; using AmongUs.Server.Net.Response; @@ -17,46 +18,55 @@ namespace AmongUs.Server.Net private readonly ClientManager _clientManager; private readonly GameManager _gameManager; - private readonly Connection _connection; - private readonly ClientState _state; - private readonly int _version; - private readonly string _name; - public Client(ClientManager clientManager, GameManager gameManager, Connection connection, int version, string name) + public Client(ClientManager clientManager, GameManager gameManager, int id, string name, Connection connection) { _clientManager = clientManager; _gameManager = gameManager; - _connection = connection; - _connection.DataReceived += OnDataReceived; - _connection.Disconnected += OnDisconnected; - _state = new ClientState(_connection); - _version = version; - _name = name; + Id = id; + Name = name; + Connection = connection; + Connection.DataReceived += OnDataReceived; + Connection.Disconnected += OnDisconnected; + Player = new ClientPlayer(this); } + public int Id { get; } + public string Name { get; } + public Connection Connection { get; } + public ClientPlayer Player { get; } + private void OnDataReceived(DataReceivedEventArgs e) { - while (true) + try { - if (e.Message.Position >= e.Message.Length) + while (true) { - break; + if (e.Message.Position >= e.Message.Length) + { + break; + } + + OnMessageReceived(e.Message.ReadMessage(), e.SendOption); } - - OnMessageReceived(e.Message.ReadMessage()); + } + catch (Exception ex) + { + Logger.Error(ex, "Exception caught in client data handler."); + Connection.Send(new Message1DisconnectReason(DisconnectReason.Custom, DisconnectMessages.Error)); } } - private void OnMessageReceived(MessageReader message) + private void OnMessageReceived(MessageReader message, SendOption sendOption) { var flag = (RequestFlag) message.Tag; + Logger.Verbose("[{0}] Server got {1}.", Id, flag); + switch (flag) { - // 101A3813 case RequestFlag.HostGame: - Logger.Debug("Server got host game"); - + { // Read game settings. var gameInfoBytes = message.ReadBytesAndSize(); var gameInfo = GameOptionsData.Deserialize(gameInfoBytes); @@ -65,69 +75,98 @@ namespace AmongUs.Server.Net var game = _gameManager.Create(this, gameInfo); if (game == null) { - _connection.Send(new Message1DisconnectReason(DisconnectReason.ServerFull)); + Connection.Send(new Message1DisconnectReason(DisconnectReason.ServerFull)); return; } - // Code (32) in the packet below will be used in JoinGame. + // Code in the packet below will be used in JoinGame. using (var writer = MessageWriter.Get(SendOption.Reliable)) { writer.StartMessage(0); - writer.Write(32); + writer.Write(game.Code); writer.EndMessage(); - _connection.Send(writer); + Connection.Send(writer); } break; - // 101A388C + } + case RequestFlag.JoinGame: - Logger.Debug("Server got join game"); - + { var gameCode = message.ReadInt32(); - if (gameCode != 32) + var unknown = message.ReadByte(); + var game = _gameManager.Find(gameCode); + if (game == null) { - Logger.Debug("- Code {0}", gameCode, GameCode.IntToGameName(gameCode)); - - _connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing)); + Connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing)); return; } - - // TODO: JoinGame - Logger.Debug("JoinGame {0} {1}", gameCode, message.ReadByte()); + + game.HandleJoinGame(Player); break; + } + // 101A3960 case RequestFlag.StartGame: - Logger.Debug("Server got StartGame"); break; + // 101A39EC case RequestFlag.RemoveGame: - Logger.Debug("Server got RemoveGame"); break; + case RequestFlag.RemovePlayer: - Logger.Debug("Server got RemovePlayer"); break; - // 101A3A15 + case RequestFlag.GameData: - Logger.Debug("Server got GameData"); - break; - // 101A3AAB case RequestFlag.GameDataTo: - Logger.Debug("Server got GameDataTo"); + { + var game = Player.Game; + if (game == null) + { + throw new NullReferenceException("Game was not set for the client."); + } + + var code = message.ReadInt32(); + if (code != game.Code) + { + // Packet was meant for another game. + return; + } + + // Broadcast packet to all other players. + using (var writer = MessageWriter.Get(sendOption)) + { + if (flag == RequestFlag.GameDataTo) + { + var target = message.ReadPackedInt32(); + writer.CopyFrom(message); + game.SendTo(writer, target); + } + else + { + writer.CopyFrom(message); + game.SendToAllExcept(writer, Player); + } + } break; + } + // 101A3BA6 case RequestFlag.JoinedGame: - Logger.Debug("Server got JoinedGame"); break; + // 101A3BD0 case RequestFlag.EndGame: - Logger.Debug("Server got EndGame"); break; + default: - Logger.Debug("Server received unknown {0}", flag); + Logger.Warning("Server received unknown flag {0}.", flag); break; } - if (message.Position < message.Length) + if (flag != RequestFlag.GameData && + flag != RequestFlag.GameDataTo && + message.Position < message.Length) { Logger.Warning("Server did not consume all bytes from {0} ({1} < {2}).", flag, diff --git a/src/AmongUs.Server/Net/ClientManager.cs b/src/AmongUs.Server/Net/ClientManager.cs index a47c183..9a3cb7b 100644 --- a/src/AmongUs.Server/Net/ClientManager.cs +++ b/src/AmongUs.Server/Net/ClientManager.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Concurrent; +using AmongUs.Server.Exceptions; using Serilog; namespace AmongUs.Server.Net @@ -7,25 +8,57 @@ namespace AmongUs.Server.Net { private static readonly ILogger Logger = Log.ForContext(); - private readonly HashSet _clients; + private readonly ConcurrentDictionary _clients; + private readonly object _idLock; + private int _idLast; public ClientManager() { - _clients = new HashSet(); + _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.Add(client); + + _clients.TryAdd(client.Id, client); } public void Remove(Client client) { Logger.Information("Client disconnected."); - - _clients.Remove(client); + + _clients.TryRemove(client.Id, out _); } } } \ No newline at end of file diff --git a/src/AmongUs.Server/Net/ClientPlayer.cs b/src/AmongUs.Server/Net/ClientPlayer.cs new file mode 100644 index 0000000..1b9322c --- /dev/null +++ b/src/AmongUs.Server/Net/ClientPlayer.cs @@ -0,0 +1,16 @@ +using AmongUs.Shared.Innersloth.Data; + +namespace AmongUs.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/AmongUs.Server/Net/ClientState.cs b/src/AmongUs.Server/Net/ClientState.cs deleted file mode 100644 index ca93160..0000000 --- a/src/AmongUs.Server/Net/ClientState.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Hazel; - -namespace AmongUs.Server.Net -{ - public class ClientState - { - private readonly Connection _connection; - - public ClientState(Connection connection) - { - _connection = connection; - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Server/Net/Game.cs b/src/AmongUs.Server/Net/Game.cs index ee8796c..6fb35ef 100644 --- a/src/AmongUs.Server/Net/Game.cs +++ b/src/AmongUs.Server/Net/Game.cs @@ -1,16 +1,143 @@ -using AmongUs.Shared.Innersloth; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using AmongUs.Server.Exceptions; +using AmongUs.Server.Extensions; +using AmongUs.Server.Net.Response; +using AmongUs.Shared.Innersloth; +using AmongUs.Shared.Innersloth.Data; +using Hazel; +using Serilog; +using ILogger = Serilog.ILogger; namespace AmongUs.Server.Net { public class Game { + private static readonly ILogger Logger = Log.ForContext(); + + private readonly ConcurrentDictionary _players; + private int _hostId; + public Game(int code, GameOptionsData options) { Code = code; + CodeStr = GameCode.IntToGameName(code); + GameState = GameStates.NotStarted; Options = options; + + _hostId = -1; + _players = new ConcurrentDictionary(); } public int Code { get; } + public string CodeStr { get; } + public GameStates GameState { get; } public GameOptionsData Options { get; } + + public void SendToAllExcept(MessageWriter message, ClientPlayer sender) + { + foreach (var (_, player) in _players.Where(x => x.Value != sender)) + { + player.Client.Connection.Send(message); + } + } + + public void SendTo(MessageWriter message, int playerId) + { + if (_players.TryGetValue(playerId, out var player)) + { + player.Client.Connection.Send(message); + } + else + { + Logger.Warning("[{0}] Sending data to {1} failed, player does not exist.", CodeStr, playerId); + } + } + + public void HandleJoinGame(ClientPlayer player) + { + switch (GameState) + { + case GameStates.NotStarted: + HandleJoinGameNew(player); + break; + case GameStates.Started: + HandleJoinGameNext(player); + break; + case GameStates.Ended: + case GameStates.Destroyed: + player.Client.Connection.Send(new Message1DisconnectReason(DisconnectReason.GameStarted)); + return; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void HandleJoinGameNew(ClientPlayer player) + { + Logger.Verbose("[{0}] Player joined.", CodeStr); + + // Store player. + if (!_players.TryAdd(player.Client.Id, player)) + { + throw new AmongUsException("Failed to add player to game."); + } + + // Assign player to this game for future packets. + player.Game = this; + + // Assign hostId if none is set. + if (_hostId == -1) + { + _hostId = player.Client.Id; + } + + if (_hostId == player.Client.Id) + { + player.LimboState = LimboStates.NotLimbo; + } + + using (var message = MessageWriter.Get(SendOption.Reliable)) + { + // TODO: WriteJoinedMessage - Move to own method / class + message.StartMessage(7); + 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(); + + message.StartMessage(10); + message.Write(Code); + message.Write((sbyte)1); + message.Write(false); // Private / Public + message.EndMessage(); + + player.Client.Connection.Send(message); + + // TODO: BroadcastJoinMessage - Move to own method / class + message.Clear(SendOption.Reliable); + message.StartMessage(1); + message.Write(Code); + message.Write(player.Client.Id); + message.Write(_hostId); + message.EndMessage(); + + SendToAllExcept(message, player); + } + } + + private void HandleJoinGameNext(ClientPlayer player) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/AmongUs.Server/Net/GameManager.cs b/src/AmongUs.Server/Net/GameManager.cs index 02e476b..e6fe541 100644 --- a/src/AmongUs.Server/Net/GameManager.cs +++ b/src/AmongUs.Server/Net/GameManager.cs @@ -28,9 +28,15 @@ namespace AmongUs.Server.Net return null; } - public void Remove(int key) + public Game Find(int gameCode) { - _games.TryRemove(key, out _); + _games.TryGetValue(gameCode, out var game); + return game; + } + + public void Remove(int gameCode) + { + _games.TryRemove(gameCode, out _); } } } \ No newline at end of file diff --git a/src/AmongUs.Server/Net/GameStates.cs b/src/AmongUs.Server/Net/GameStates.cs new file mode 100644 index 0000000..a50e9e3 --- /dev/null +++ b/src/AmongUs.Server/Net/GameStates.cs @@ -0,0 +1,10 @@ +namespace AmongUs.Server.Net +{ + public enum GameStates : byte + { + NotStarted = 0, + Started = 1, + Ended = 2, + Destroyed = 3 + } +} \ No newline at end of file diff --git a/src/AmongUs.Server/Net/Matchmaker.cs b/src/AmongUs.Server/Net/Matchmaker.cs index d9ceee0..5c2dc77 100644 --- a/src/AmongUs.Server/Net/Matchmaker.cs +++ b/src/AmongUs.Server/Net/Matchmaker.cs @@ -44,7 +44,7 @@ namespace AmongUs.Server.Net } // Register client. - _clientManager.Add(new Client(_clientManager, _gameManager, e.Connection, clientVersion, clientName)); + _clientManager.Add(new Client(_clientManager, _gameManager, _clientManager.NextId(), clientName, e.Connection)); } public void Start() diff --git a/src/AmongUs.Server/Net/Response/Message1DisconnectReason.cs b/src/AmongUs.Server/Net/Response/Message1DisconnectReason.cs index 878f579..86362aa 100644 --- a/src/AmongUs.Server/Net/Response/Message1DisconnectReason.cs +++ b/src/AmongUs.Server/Net/Response/Message1DisconnectReason.cs @@ -6,18 +6,25 @@ namespace AmongUs.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) : base(SendOption.Reliable, MessageFlag.DisconnectReason) + 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/AmongUs.Shared/Innersloth/Data/LimboStates.cs b/src/AmongUs.Shared/Innersloth/Data/LimboStates.cs new file mode 100644 index 0000000..e81e4da --- /dev/null +++ b/src/AmongUs.Shared/Innersloth/Data/LimboStates.cs @@ -0,0 +1,9 @@ +namespace AmongUs.Shared.Innersloth.Data +{ + public enum LimboStates + { + PreSpawn, + NotLimbo, + WaitingForHost, + } +} \ No newline at end of file