Possible to create / join a game

This commit is contained in:
AeonLucid authored and Mike committed 2020-09-20 23:13:57 +02:00
1 parent b65326a4a8
commit bc028b421c
13 files changed
+341 -75

No files matched your search

+2 -2
View File
@@ -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))
@@ -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.";
}
}
@@ -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)
{
}
}
}
+86 -47
View File
@@ -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,
+40 -7
View File
@@ -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<ClientManager>();
private readonly HashSet<Client> _clients;
private readonly ConcurrentDictionary<int, Client> _clients;
private readonly object _idLock;
private int _idLast;
public ClientManager()
{
_clients = new HashSet<Client>();
_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.Add(client);
_clients.TryAdd(client.Id, client);
}
public void Remove(Client client)
{
Logger.Information("Client disconnected.");
_clients.Remove(client);
_clients.TryRemove(client.Id, out _);
}
}
}
+16
View File
@@ -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; }
}
}
-14
View File
@@ -1,14 +0,0 @@
using Hazel;
namespace AmongUs.Server.Net
{
public class ClientState
{
private readonly Connection _connection;
public ClientState(Connection connection)
{
_connection = connection;
}
}
}
+128 -1
View File
@@ -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<Game>();
private readonly ConcurrentDictionary<int, ClientPlayer> _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<int, ClientPlayer>();
}
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();
}
}
}
+8 -2
View File
@@ -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 _);
}
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace AmongUs.Server.Net
{
public enum GameStates : byte
{
NotStarted = 0,
Started = 1,
Ended = 2,
Destroyed = 3
}
}
+1 -1
View File
@@ -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()
@@ -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);
}
}
}
}
@@ -0,0 +1,9 @@
namespace AmongUs.Shared.Innersloth.Data
{
public enum LimboStates
{
PreSpawn,
NotLimbo,
WaitingForHost,
}
}