From: AeonLucid Date: Sun, 20 Sep 2020 23:28:19 +0000 (+0200) Subject: Rename project files X-Git-Tag: v1.0.0~6 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=af3687a213bb4930dbcb27ab23f3f4a562b3b5e9;p=rhonda%2Fimpostor.git Rename project files --- diff --git a/src/AmongUs.Agent/AmongUs.Agent.vcxproj b/src/AmongUs.Agent/AmongUs.Agent.vcxproj deleted file mode 100644 index 9350e62..0000000 --- a/src/AmongUs.Agent/AmongUs.Agent.vcxproj +++ /dev/null @@ -1,94 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - 16.0 - Win32Proj - {097be795-88e9-4e7b-92f3-f6dddbe3f012} - AmongUsAgent - 10.0 - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - true - $(ProjectDir)build\$(Platform)\$(Configuration)\ - $(ProjectDir)build\$(Platform)\$(Configuration)\Intermediate\ - - - false - $(ProjectDir)build\$(Platform)\$(Configuration)\ - $(ProjectDir)build\$(Platform)\$(Configuration)\Intermediate\ - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/AmongUs.Agent/AmongUs.Agent.vcxproj.filters b/src/AmongUs.Agent/AmongUs.Agent.vcxproj.filters deleted file mode 100644 index 85ba1c7..0000000 --- a/src/AmongUs.Agent/AmongUs.Agent.vcxproj.filters +++ /dev/null @@ -1,30 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - - - Header Files - - - \ No newline at end of file diff --git a/src/AmongUs.Agent/logger.cpp b/src/AmongUs.Agent/logger.cpp deleted file mode 100644 index 4b45c1d..0000000 --- a/src/AmongUs.Agent/logger.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "logger.h" -#include -#include - -HANDLE h_out = nullptr, h_old_out = nullptr; -HANDLE h_err = nullptr, h_old_err = nullptr; -HANDLE h_in = nullptr, h_old_in = nullptr; - -namespace Logger -{ - void Attach() - { - h_old_out = GetStdHandle(STD_OUTPUT_HANDLE); - h_old_err = GetStdHandle(STD_ERROR_HANDLE); - h_old_in = GetStdHandle(STD_INPUT_HANDLE); - - AllocConsole() && AttachConsole(GetCurrentProcessId()); - - h_out = GetStdHandle(STD_OUTPUT_HANDLE); - h_err = GetStdHandle(STD_ERROR_HANDLE); - h_in = GetStdHandle(STD_INPUT_HANDLE); - - SetConsoleMode(h_out, - ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); - - SetConsoleMode(h_in, - ENABLE_INSERT_MODE | ENABLE_EXTENDED_FLAGS | - ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE); - } - - void Detach() - { - if (h_out && h_err && h_in) { - FreeConsole(); - - if (h_old_out) - { - SetStdHandle(STD_OUTPUT_HANDLE, h_old_out); - } - - if (h_old_err) - { - SetStdHandle(STD_ERROR_HANDLE, h_old_err); - } - - if (h_old_in) - { - SetStdHandle(STD_INPUT_HANDLE, h_old_in); - } - } - } - - bool Print(const char* fmt, ...) - { - if (!h_out) - { - return false; - } - - char buf[1024]; - va_list va; - - va_start(va, fmt); - _vsnprintf_s(buf, 1024, fmt, va); - va_end(va); - - return !!WriteConsoleA(h_out, buf, static_cast(strlen(buf)), nullptr, nullptr); - } - - char ReadKey() - { - if (!h_in) - { - return -1; - } - - auto key = char{ 0 }; - auto keysread = DWORD{ 0 }; - - ReadConsoleA(h_in, &key, 1, &keysread, nullptr); - - return key; - } -}; \ No newline at end of file diff --git a/src/AmongUs.Agent/logger.h b/src/AmongUs.Agent/logger.h deleted file mode 100644 index fc22e15..0000000 --- a/src/AmongUs.Agent/logger.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -namespace Logger -{ - void Attach(); - void Detach(); - bool Print(const char* fmt, ...); - char ReadKey(); -}; \ No newline at end of file diff --git a/src/AmongUs.Agent/main.cpp b/src/AmongUs.Agent/main.cpp deleted file mode 100644 index 40b5ff0..0000000 --- a/src/AmongUs.Agent/main.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include "logger.h" - -DWORD WINAPI OnDllAttach(LPVOID base) -{ - Logger::Attach(); - Logger::Print("[AmongUs.Agent] DLL Attached\n"); - - FreeLibraryAndExitThread(static_cast(base), 1); -} - -BOOL WINAPI OnDllDetach() -{ - Logger::Print("[AmongUs.Agent] DLL Detached\n"); - Logger::Detach(); - - return true; -} - -BOOL WINAPI DllMain( - const _In_ HINSTANCE hinstDll, - const _In_ DWORD fdwReason, - const _In_opt_ LPVOID lpvReserved -) -{ - switch (fdwReason) - { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hinstDll); - CreateThread(nullptr, 0, OnDllAttach, hinstDll, 0, nullptr); - return true; - - case DLL_PROCESS_DETACH: - if (lpvReserved == nullptr) - return OnDllDetach(); - return true; - default: - return true; - } -} \ No newline at end of file diff --git a/src/AmongUs.Client/AmongUs.Client.csproj b/src/AmongUs.Client/AmongUs.Client.csproj deleted file mode 100644 index be6c4f5..0000000 --- a/src/AmongUs.Client/AmongUs.Client.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Exe - netcoreapp3.1 - - - - - - - diff --git a/src/AmongUs.Client/Program.cs b/src/AmongUs.Client/Program.cs deleted file mode 100644 index a643609..0000000 --- a/src/AmongUs.Client/Program.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.IO; -using AmongUs.Shared.Innersloth; - -namespace AmongUs.Client -{ - internal static class Program - { - private static void Main(string[] args) - { - 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", "192.168.1.211", new [] - { - new ServerInfo("Private-Master-1", "192.168.1.211", 22023) - }); - - using (var file = File.Open(regionFile, FileMode.Create, FileAccess.Write)) - using (var writer = new BinaryWriter(file)) - { - region.Serialize(writer); - } - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Server/AmongUs.Server.csproj b/src/AmongUs.Server/AmongUs.Server.csproj deleted file mode 100644 index 77f0970..0000000 --- a/src/AmongUs.Server/AmongUs.Server.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - Exe - netcoreapp3.1 - - - - - - - - - - - - diff --git a/src/AmongUs.Server/Data/DisconnectMessages.cs b/src/AmongUs.Server/Data/DisconnectMessages.cs deleted file mode 100644 index ab3be88..0000000 --- a/src/AmongUs.Server/Data/DisconnectMessages.cs +++ /dev/null @@ -1,12 +0,0 @@ -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."; - - public const string Destroyed = "The game you tried to join is being destroyed. " + - "Please create a new game."; - } -} \ No newline at end of file diff --git a/src/AmongUs.Server/Exceptions/AmongUsException.cs b/src/AmongUs.Server/Exceptions/AmongUsException.cs deleted file mode 100644 index 1009b87..0000000 --- a/src/AmongUs.Server/Exceptions/AmongUsException.cs +++ /dev/null @@ -1,24 +0,0 @@ -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/Extensions/ConnectionExtensions.cs b/src/AmongUs.Server/Extensions/ConnectionExtensions.cs deleted file mode 100644 index 63bb31c..0000000 --- a/src/AmongUs.Server/Extensions/ConnectionExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using AmongUs.Server.Net.Response; -using Hazel; - -namespace AmongUs.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/AmongUs.Server/Net/Client.cs b/src/AmongUs.Server/Net/Client.cs deleted file mode 100644 index 0596666..0000000 --- a/src/AmongUs.Server/Net/Client.cs +++ /dev/null @@ -1,273 +0,0 @@ -using System; -using AmongUs.Server.Data; -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 Client - { - private static readonly ILogger Logger = Log.ForContext(); - - private readonly ClientManager _clientManager; - private readonly GameManager _gameManager; - - public Client(ClientManager clientManager, GameManager gameManager, int id, string name, Connection connection) - { - _clientManager = clientManager; - _gameManager = gameManager; - 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; } - - public void Send(MessageWriter writer) - { - Connection.Send(writer); - } - - private bool IsPacketAllowed(MessageReader message, bool hostOnly) - { - var game = Player.Game; - if (game == null) - { - return false; - } - - // GameCode must match code of the current game assigned to the player. - if (message.ReadInt32() != game.Code) - { - return false; - } - - // Some packets should only be sent by the host of the game. - if (hostOnly) - { - if (game.HostId == Id) - { - return true; - } - - Logger.Warning("[{0}] Client sent packet only allowed by the host ({1}).", Id, game.HostId); - return false; - } - - return true; - } - - private void OnDataReceived(DataReceivedEventArgs e) - { - try - { - while (true) - { - if (e.Message.Position >= e.Message.Length) - { - break; - } - - OnMessageReceived(e.Message.ReadMessage(), e.SendOption); - } - } - 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, SendOption sendOption) - { - var flag = (RequestFlag) message.Tag; - - Logger.Verbose("[{0}] Server got {1}.", Id, flag); - - switch (flag) - { - case RequestFlag.HostGame: - { - // Read game settings. - var gameInfoBytes = message.ReadBytesAndSize(); - var gameInfo = GameOptionsData.Deserialize(gameInfoBytes); - - // Create game. - var game = _gameManager.Create(this, gameInfo); - if (game == null) - { - Connection.Send(new Message1DisconnectReason(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(); - - Connection.Send(writer); - } - break; - } - - case RequestFlag.JoinGame: - { - var gameCode = message.ReadInt32(); - var unknown = message.ReadByte(); - var game = _gameManager.Find(gameCode); - if (game == null) - { - Connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing)); - return; - } - - game.HandleJoinGame(Player); - break; - } - - case RequestFlag.StartGame: - { - if (!IsPacketAllowed(message, true)) - { - return; - } - - Player.Game.HandleStartGame(message); - break; - } - - // No idea how this flag is triggered. - case RequestFlag.RemoveGame: - break; - - case RequestFlag.RemovePlayer: - { - if (!IsPacketAllowed(message, true)) - { - return; - } - - var playerId = message.ReadPackedInt32(); - var reason = message.ReadByte(); - - Player.Game.HandleRemovePlayer(playerId, (DisconnectReason) reason); - break; - } - - case RequestFlag.GameData: - case RequestFlag.GameDataTo: - { - if (!IsPacketAllowed(message, false)) - { - return; - } - - // Broadcast packet to all other players. - using (var writer = MessageWriter.Get(sendOption)) - { - if (flag == RequestFlag.GameDataTo) - { - var target = message.ReadPackedInt32(); - writer.CopyFrom(message); - Player.Game.SendTo(writer, target); - } - else - { - writer.CopyFrom(message); - Player.Game.SendToAllExcept(writer, Player); - } - } - break; - } - - case RequestFlag.EndGame: - { - if (!IsPacketAllowed(message, true)) - { - return; - } - - Player.Game.HandleEndGame(message); - break; - } - - case RequestFlag.AlterGame: - { - if (!IsPacketAllowed(message, true)) - { - return; - } - - if (message.ReadByte() != (byte) AlterGameTags.ChangePrivacy) - { - return; - } - - var isPublic = message.ReadByte() == 1; - - Player.Game.HandleAlterGame(message, Player, isPublic); - break; - } - - case RequestFlag.KickPlayer: - { - if (!IsPacketAllowed(message, true)) - { - return; - } - - var playerId = message.ReadPackedInt32(); - var isBan = message.ReadBoolean(); - - Player.Game.HandleKickPlayer(playerId, isBan); - break; - } - - default: - Logger.Warning("Server received unknown flag {0}.", flag); - break; - } - - 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, - message.Length); - } - } - - private void OnDisconnected(object sender, DisconnectedEventArgs e) - { - try - { - if (Player.Game != null) - { - Player.Game.HandleRemovePlayer(Id, DisconnectReason.ExitGame); - } - } - catch (Exception ex) - { - Logger.Error(ex, "Exception caught in client disconnection."); - } - - _clientManager.Remove(this); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Server/Net/ClientManager.cs b/src/AmongUs.Server/Net/ClientManager.cs deleted file mode 100644 index 9a3cb7b..0000000 --- a/src/AmongUs.Server/Net/ClientManager.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Concurrent; -using AmongUs.Server.Exceptions; -using Serilog; - -namespace AmongUs.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/AmongUs.Server/Net/ClientPlayer.cs b/src/AmongUs.Server/Net/ClientPlayer.cs deleted file mode 100644 index 1b9322c..0000000 --- a/src/AmongUs.Server/Net/ClientPlayer.cs +++ /dev/null @@ -1,16 +0,0 @@ -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/Game.cs b/src/AmongUs.Server/Net/Game.cs deleted file mode 100644 index 723280c..0000000 --- a/src/AmongUs.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 AmongUs.Server.Data; -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 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/AmongUs.Server/Net/GameManager.cs b/src/AmongUs.Server/Net/GameManager.cs deleted file mode 100644 index 100e146..0000000 --- a/src/AmongUs.Server/Net/GameManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Collections.Concurrent; -using AmongUs.Shared.Innersloth; -using Serilog; - -namespace AmongUs.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/AmongUs.Server/Net/GameStates.cs b/src/AmongUs.Server/Net/GameStates.cs deleted file mode 100644 index a50e9e3..0000000 --- a/src/AmongUs.Server/Net/GameStates.cs +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index 5c2dc77..0000000 --- a/src/AmongUs.Server/Net/Matchmaker.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Net; -using AmongUs.Server.Extensions; -using AmongUs.Server.Net.Response; -using AmongUs.Shared.Innersloth.Data; -using Hazel; -using Hazel.Udp; -using Serilog; -using ILogger = Serilog.ILogger; - -namespace AmongUs.Server.Net -{ - public class Matchmaker - { - private static readonly ILogger Logger = Log.ForContext(); - - private readonly GameManager _gameManager; - private readonly ClientManager _clientManager; - private readonly UdpConnectionListener _connection; - - public Matchmaker(IPAddress ip, int port) - { - _gameManager = new GameManager(); - _clientManager = new ClientManager(); - _connection = new UdpConnectionListener(new IPEndPoint(ip, port), IPMode.IPv4, s => - { - Logger.Warning("Log from Hazel: {0}", s); - }); - - _connection.NewConnection += OnNewConnection; - } - - private void OnNewConnection(NewConnectionEventArgs e) - { - // Handshake. - var clientVersion = e.HandshakeData.ReadInt32(); - var clientName = e.HandshakeData.ReadString(); - - e.HandshakeData.Recycle(); - - if (clientVersion != 50516550) - { - e.Connection.Send(new Message1DisconnectReason(DisconnectReason.IncorrectVersion)); - return; - } - - // Register client. - _clientManager.Add(new Client(_clientManager, _gameManager, _clientManager.NextId(), clientName, e.Connection)); - } - - public void Start() - { - _connection.Start(); - } - - public void Stop() - { - _connection.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Server/Net/Response/Message1DisconnectReason.cs b/src/AmongUs.Server/Net/Response/Message1DisconnectReason.cs deleted file mode 100644 index 86362aa..0000000 --- a/src/AmongUs.Server/Net/Response/Message1DisconnectReason.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AmongUs.Shared.Innersloth.Data; -using Hazel; - -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, 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.Server/Net/Response/MessageBase.cs b/src/AmongUs.Server/Net/Response/MessageBase.cs deleted file mode 100644 index dd18790..0000000 --- a/src/AmongUs.Server/Net/Response/MessageBase.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Hazel; - -namespace AmongUs.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/AmongUs.Server/Net/Response/MessageFlag.cs b/src/AmongUs.Server/Net/Response/MessageFlag.cs deleted file mode 100644 index 68cbe4b..0000000 --- a/src/AmongUs.Server/Net/Response/MessageFlag.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace AmongUs.Server.Net.Response -{ - public enum MessageFlag : byte - { - DisconnectReason = 1 - } -} \ No newline at end of file diff --git a/src/AmongUs.Server/Program.cs b/src/AmongUs.Server/Program.cs deleted file mode 100644 index 7857696..0000000 --- a/src/AmongUs.Server/Program.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Net; -using System.Threading; -using AmongUs.Server.Net; -using Serilog; - -namespace AmongUs.Server -{ - internal static class Program - { - private static readonly ManualResetEvent QuitEvent = new ManualResetEvent(false); - - private static void Main(string[] args) - { - // Listen for CTRL+C. - Console.CancelKeyPress += (sender, e) => - { - e.Cancel = true; - QuitEvent.Set(); - }; - - // Configure logger. - Log.Logger = new LoggerConfiguration() -#if DEBUG - .MinimumLevel.Verbose() -#else - .MinimumLevel.Information() -#endif - .WriteTo.Console() - .CreateLogger(); - - // Initialize matchmaker. - var matchMaker = new Matchmaker(IPAddress.Any, 22023); - matchMaker.Start(); - Log.Logger.Information("Matchmaker is running on *:22023."); - QuitEvent.WaitOne(); - Log.Logger.Warning("Matchmaker is shutting down!"); - matchMaker.Stop(); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/AmongUs.Shared.csproj b/src/AmongUs.Shared/AmongUs.Shared.csproj deleted file mode 100644 index 8642d92..0000000 --- a/src/AmongUs.Shared/AmongUs.Shared.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netcoreapp3.1 - - - diff --git a/src/AmongUs.Shared/Innersloth/Data/AlterGameTags.cs b/src/AmongUs.Shared/Innersloth/Data/AlterGameTags.cs deleted file mode 100644 index a89c628..0000000 --- a/src/AmongUs.Shared/Innersloth/Data/AlterGameTags.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace AmongUs.Shared.Innersloth.Data -{ - public enum AlterGameTags : byte - { - ChangePrivacy = 1 - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/Data/DisconnectReason.cs b/src/AmongUs.Shared/Innersloth/Data/DisconnectReason.cs deleted file mode 100644 index 8d5149d..0000000 --- a/src/AmongUs.Shared/Innersloth/Data/DisconnectReason.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace AmongUs.Shared.Innersloth.Data -{ - public enum DisconnectReason - { - ExitGame = 0, - Destroy = 16, - // The game you tried to join is full. - // Check with the host to see if you can join next round. - GameFull = 1, - // The game you tried to join already started. - // Check with the host to see if you can join next round. - GameStarted = 2, - // Could not find the game you're looking for. - GameMissing = 3, - IncorrectGame = 18, - // For these a message can be given, specifying an empty message shows - // "An unknown error disconnected you from the server." - CustomMessage1 = 4, - Custom = 8, - // CustomMessage3 = 11, - // CustomMessage4 = 12, - // CustomMessage5 = 13, - // CustomMessage6 = 14, - // CustomMessage7 = 15, - // You are running an older version of the game. - // Please update to play with others. - IncorrectVersion = 5, - // You cannot rejoin that room. - // You were banned - Banned = 6, - // You can rejoin if the room hasn't started - // You were kicked - Kicked = 7, - // Server refused username: %USERNAME% - InvalidName = 9, - // You were banned for hacking. - // Please stop. - Hacking = 10, - // You disconnected from the host. - // If this happens often, check your WiFi strength. - // - // You disconnected from the server. - // If this happens often, check your network strength. - // This may also be a server issue. - Error = 17, - // The server stopped this game. Possibly due to inactivity. - ServerRequest = 19, - // The Among Us servers are overloaded. - // Sorry! Please try again later! - ServerFull = 20, - FocusLostBackground = 207, - IntentionalLeaving = 208, - FocusLost = 209, - NewConnection = 210 - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/Data/GameKeywords.cs b/src/AmongUs.Shared/Innersloth/Data/GameKeywords.cs deleted file mode 100644 index 5478153..0000000 --- a/src/AmongUs.Shared/Innersloth/Data/GameKeywords.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace AmongUs.Shared.Innersloth.Data -{ - [Flags] - public enum GameKeywords : uint - { - All = 0, - Other = 1, - Spanish = 2, - Korean = 4, - Russian = 8, - Portuguese = 16, - Arabic = 32, - Filipone = 64, - Polish = 128, - English = 256 - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/Data/GameStates.cs b/src/AmongUs.Shared/Innersloth/Data/GameStates.cs deleted file mode 100644 index b43c7a0..0000000 --- a/src/AmongUs.Shared/Innersloth/Data/GameStates.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AmongUs.Shared.Innersloth.Data -{ - public enum GameStates : byte - { - NotStarted, - Started, - Ended, - Destroyed - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/Data/LimboStates.cs b/src/AmongUs.Shared/Innersloth/Data/LimboStates.cs deleted file mode 100644 index e81e4da..0000000 --- a/src/AmongUs.Shared/Innersloth/Data/LimboStates.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AmongUs.Shared.Innersloth.Data -{ - public enum LimboStates - { - PreSpawn, - NotLimbo, - WaitingForHost, - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/Data/RequestFlag.cs b/src/AmongUs.Shared/Innersloth/Data/RequestFlag.cs deleted file mode 100644 index 8d82426..0000000 --- a/src/AmongUs.Shared/Innersloth/Data/RequestFlag.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace AmongUs.Shared.Innersloth.Data -{ - public enum RequestFlag : byte - { - HostGame = 0, - JoinGame = 1, - StartGame = 2, - RemoveGame = 3, - RemovePlayer = 4, - GameData = 5, - GameDataTo = 6, - JoinedGame = 7, - EndGame = 8, - GetGameList = 9, - AlterGame = 10, - KickPlayer = 11, - WaitForHost = 12, - Redirect = 13, - ReselectServer = 14, - GetGameListV2 = 16, - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/GameCode.cs b/src/AmongUs.Shared/Innersloth/GameCode.cs deleted file mode 100644 index 152048b..0000000 --- a/src/AmongUs.Shared/Innersloth/GameCode.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Buffers.Binary; -using System.Linq; -using System.Security.Cryptography; -using System.Text; - -namespace AmongUs.Shared.Innersloth -{ - public static class GameCode - { - private const string V2 = "QWXRTYLPESDFGHUJKZOCVBINMA"; - private static readonly int[] V2Map = { - 25, - 21, - 19, - 10, - 8, - 11, - 12, - 13, - 22, - 15, - 16, - 6, - 24, - 23, - 18, - 7, - 0, - 3, - 9, - 4, - 14, - 20, - 1, - 2, - 5, - 17 - }; - private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider(); - - public static string IntToGameName(int input) - { - // V2. - if (input < -1) - { - return IntToGameNameV2(input); - } - - // V1. - Span code = stackalloc byte[4]; - BinaryPrimitives.WriteInt32LittleEndian(code, input); - return Encoding.UTF8.GetString(code.Slice(0, 4)); - } - - private static string IntToGameNameV2(int input) - { - var a = input & 0x3FF; - var b = (input >> 10) & 0xFFFFF; - - return new string(new [] - { - V2[a % 26], - V2[a / 26], - V2[b % 26], - V2[b / 26 % 26], - V2[b / (26 * 26) % 26], - V2[b / (26 * 26 * 26) % 26] - }); - } - - public static int GameNameToInt(string code) - { - var upper = code.ToUpperInvariant(); - if (upper.Any(x => !char.IsLetter(x))) - { - return -1; - } - - var len = code.Length; - if (len == 6) - { - return GameNameToIntV2(upper); - } - - if (len == 4) - { - return code[0] | ((code[1] | ((code[2] | (code[3] << 8)) << 8)) << 8); - } - - return -1; - } - - private static int GameNameToIntV2(string code) - { - var a = V2Map[code[0] - 65]; - var b = V2Map[code[1] - 65]; - var c = V2Map[code[2] - 65]; - var d = V2Map[code[3] - 65]; - var e = V2Map[code[4] - 65]; - var f = V2Map[code[5] - 65]; - - var one = (a + 26 * b) & 0x3FF; - var two = (c + 26 * (d + 26 * (e + 26 * f))); - - return (int) (one | ((two << 10) & 0x3FFFFC00) | 0x80000000); - } - - public static int GenerateCode(int len) - { - if (len != 4 && len != 6) - { - throw new ArgumentException("should be 4 or 6", nameof(len)); - } - - // Generate random bytes. - Span data = stackalloc byte[len]; - Random.GetBytes(data); - - // Convert to their char representation. - Span dataChar = stackalloc char[len]; - for (var i = 0; i < len; i++) - { - dataChar[i] = V2[V2Map[data[i] % 26]]; - } - - return GameNameToInt(new string(dataChar)); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/GameOptionsData.cs b/src/AmongUs.Shared/Innersloth/GameOptionsData.cs deleted file mode 100644 index 4fd19f6..0000000 --- a/src/AmongUs.Shared/Innersloth/GameOptionsData.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.IO; -using AmongUs.Shared.Innersloth.Data; - -namespace AmongUs.Shared.Innersloth -{ - public class GameOptionsData - { - public byte Version { get; set; } - public byte MaxPlayers { get; set; } - public GameKeywords Keywords { get; set; } - public byte MapId { get; set; } - public float PlayerSpeedMod { get; set; } - public float CrewLightMod { get; set; } - public float ImpostorLightMod { get; set; } - public float KillCooldown { get; set; } - public int NumCommonTasks { get; set; } - public int NumLongTasks { get; set; } - public int NumShortTasks { get; set; } - public int NumEmergencyMeetings { get; set; } - public int EmergencyCooldown { get; set; } - public int NumImpostors { get; set; } - public bool GhostsDoTasks { get; set; } - public int KillDistance { get; set; } - public int DiscussionTime { get; set; } - public int VotingTime { get; set; } - public bool ConfirmImpostor { get; set; } - public bool VisualTasks { get; set; } - public bool IsDefaults { get; set; } - - public void Serialize(BinaryWriter writer, byte version) - { - throw new NotImplementedException(); - } - - public static GameOptionsData Deserialize(byte[] bytes) - { - using (var stream = new MemoryStream(bytes)) - using (var reader = new BinaryReader(stream)) - { - var result = new GameOptionsData(); - - result.Version = reader.ReadByte(); - result.MaxPlayers = reader.ReadByte(); - result.Keywords = (GameKeywords) reader.ReadUInt32(); - result.MapId = reader.ReadByte(); - result.PlayerSpeedMod = reader.ReadSingle(); - result.CrewLightMod = reader.ReadSingle(); - result.ImpostorLightMod = reader.ReadSingle(); - result.KillCooldown = reader.ReadSingle(); - result.NumCommonTasks = reader.ReadByte(); - result.NumLongTasks = reader.ReadByte(); - result.NumShortTasks = reader.ReadByte(); - result.NumEmergencyMeetings = reader.ReadInt32(); - result.NumImpostors = reader.ReadByte(); - result.KillDistance = reader.ReadByte(); - result.DiscussionTime = reader.ReadInt32(); - result.VotingTime = reader.ReadInt32(); - result.IsDefaults = reader.ReadBoolean(); - - if (result.Version > 1) - { - result.EmergencyCooldown = reader.ReadByte(); - } - - if (result.Version > 2) - { - result.ConfirmImpostor = reader.ReadBoolean(); - result.VisualTasks = reader.ReadBoolean(); - } - - return result; - } - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/RegionInfo.cs b/src/AmongUs.Shared/Innersloth/RegionInfo.cs deleted file mode 100644 index ce23e5d..0000000 --- a/src/AmongUs.Shared/Innersloth/RegionInfo.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace AmongUs.Shared.Innersloth -{ - public class RegionInfo - { - public RegionInfo(string name, string ping, IReadOnlyList servers) - { - Name = name; - Ping = ping; - Servers = servers; - } - - public string Name { get; } - public string Ping { get; } - public IReadOnlyList Servers { get; } - - public void Serialize(BinaryWriter writer) - { - writer.Write(0); - writer.Write(Name); - writer.Write(Ping); - writer.Write(Servers.Count); - - foreach (var server in Servers) - { - server.Serialize(writer); - } - } - - public static RegionInfo Deserialize(BinaryReader reader) - { - var unknown = reader.ReadInt32(); - var name = reader.ReadString(); - var ping = reader.ReadString(); - var servers = new List(); - var serverCount = reader.ReadInt32(); - - for (var i = 0; i < serverCount; i++) - { - servers.Add(ServerInfo.Deserialize(reader)); - } - - return new RegionInfo(name, ping, servers); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Innersloth/ServerInfo.cs b/src/AmongUs.Shared/Innersloth/ServerInfo.cs deleted file mode 100644 index 2be6924..0000000 --- a/src/AmongUs.Shared/Innersloth/ServerInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.IO; -using System.Net; - -namespace AmongUs.Shared.Innersloth -{ - public class ServerInfo - { - public string Name { get; } - public string Ip { get; } - public ushort Port { get; } - - public ServerInfo(string name, string ip, ushort port) - { - Name = name; - Ip = ip; - Port = port; - } - - public void Serialize(BinaryWriter writer) - { - writer.Write(Name); - writer.Write(IPAddress.Parse(Ip).GetAddressBytes()); - writer.Write(Port); - writer.Write(0); - } - - public static ServerInfo Deserialize(BinaryReader reader) - { - var name = reader.ReadString(); - var ip = new IPAddress(reader.ReadBytes(4)).ToString(); - var port = reader.ReadUInt16(); - var unknown = reader.ReadInt32(); - - return new ServerInfo(name, ip, port); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Shared/Utils/HexUtils.cs b/src/AmongUs.Shared/Utils/HexUtils.cs deleted file mode 100644 index 55b9cc0..0000000 --- a/src/AmongUs.Shared/Utils/HexUtils.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Text; - -namespace AmongUs.Shared.Utils -{ - public static class HexUtils - { - public static string HexDump(byte[] bytes, int bytesPerLine = 16) - { - if (bytes == null) return ""; - int bytesLength = bytes.Length; - - char[] HexChars = "0123456789ABCDEF".ToCharArray(); - - int firstHexColumn = - 8 // 8 characters for the address - + 3; // 3 spaces - - int firstCharColumn = firstHexColumn - + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space - + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th - + 2; // 2 spaces - - int lineLength = firstCharColumn - + bytesPerLine // - characters to show the ascii value - + Environment.NewLine.Length; // Carriage return and line feed (should normally be 2) - - char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); - int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine; - StringBuilder result = new StringBuilder(expectedLines * lineLength); - - for (int i = 0; i < bytesLength; i += bytesPerLine) - { - line[0] = HexChars[(i >> 28) & 0xF]; - line[1] = HexChars[(i >> 24) & 0xF]; - line[2] = HexChars[(i >> 20) & 0xF]; - line[3] = HexChars[(i >> 16) & 0xF]; - line[4] = HexChars[(i >> 12) & 0xF]; - line[5] = HexChars[(i >> 8) & 0xF]; - line[6] = HexChars[(i >> 4) & 0xF]; - line[7] = HexChars[(i >> 0) & 0xF]; - - int hexColumn = firstHexColumn; - int charColumn = firstCharColumn; - - for (int j = 0; j < bytesPerLine; j++) - { - if (j > 0 && (j & 7) == 0) hexColumn++; - if (i + j >= bytesLength) - { - line[hexColumn] = ' '; - line[hexColumn + 1] = ' '; - line[charColumn] = ' '; - } - else - { - byte b = bytes[i + j]; - line[hexColumn] = HexChars[(b >> 4) & 0xF]; - line[hexColumn + 1] = HexChars[b & 0xF]; - line[charColumn] = (b < 32 ? '·' : (char)b); - } - hexColumn += 3; - charColumn++; - } - result.Append(line); - } - return result.ToString(); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Tests/AmongUs.Tests.csproj b/src/AmongUs.Tests/AmongUs.Tests.csproj deleted file mode 100644 index a754dfb..0000000 --- a/src/AmongUs.Tests/AmongUs.Tests.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netcoreapp3.1 - - false - - - - - - - - - - - - - - diff --git a/src/AmongUs.Tests/GameCodeTests.cs b/src/AmongUs.Tests/GameCodeTests.cs deleted file mode 100644 index 7fc6582..0000000 --- a/src/AmongUs.Tests/GameCodeTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using AmongUs.Shared.Innersloth; -using Xunit; - -namespace AmongUs.Tests -{ - public class GameCodeTests - { - [Fact] - public void CodeV1() - { - const string code = "ABCD"; - const int codeInt = 0x44434241; - - Assert.Equal(code, GameCode.IntToGameName(codeInt)); - Assert.Equal(codeInt, GameCode.GameNameToInt(code)); - } - - [Fact] - public void CodeV2() - { - const string code = "ABCDEF"; - const int codeInt = -1943683525; - - Assert.Equal(code, GameCode.IntToGameName(codeInt)); - Assert.Equal(codeInt, GameCode.GameNameToInt(code)); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Tools.Proxy/AmongUs.Tools.Proxy.csproj b/src/AmongUs.Tools.Proxy/AmongUs.Tools.Proxy.csproj deleted file mode 100644 index 1a89dbd..0000000 --- a/src/AmongUs.Tools.Proxy/AmongUs.Tools.Proxy.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - net452 - - - - - - - - - - - diff --git a/src/AmongUs.Tools.Proxy/HexUtils.cs b/src/AmongUs.Tools.Proxy/HexUtils.cs deleted file mode 100644 index c2a3398..0000000 --- a/src/AmongUs.Tools.Proxy/HexUtils.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Text; - -namespace AmongUs.Tools.Proxy -{ - public static class HexUtils - { - public static string HexDump(byte[] bytes, int bytesPerLine = 16) - { - if (bytes == null) return ""; - int bytesLength = bytes.Length; - - char[] HexChars = "0123456789ABCDEF".ToCharArray(); - - int firstHexColumn = - 8 // 8 characters for the address - + 3; // 3 spaces - - int firstCharColumn = firstHexColumn - + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space - + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th - + 2; // 2 spaces - - int lineLength = firstCharColumn - + bytesPerLine // - characters to show the ascii value - + Environment.NewLine.Length; // Carriage return and line feed (should normally be 2) - - char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); - int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine; - StringBuilder result = new StringBuilder(expectedLines * lineLength); - - for (int i = 0; i < bytesLength; i += bytesPerLine) - { - line[0] = HexChars[(i >> 28) & 0xF]; - line[1] = HexChars[(i >> 24) & 0xF]; - line[2] = HexChars[(i >> 20) & 0xF]; - line[3] = HexChars[(i >> 16) & 0xF]; - line[4] = HexChars[(i >> 12) & 0xF]; - line[5] = HexChars[(i >> 8) & 0xF]; - line[6] = HexChars[(i >> 4) & 0xF]; - line[7] = HexChars[(i >> 0) & 0xF]; - - int hexColumn = firstHexColumn; - int charColumn = firstCharColumn; - - for (int j = 0; j < bytesPerLine; j++) - { - if (j > 0 && (j & 7) == 0) hexColumn++; - if (i + j >= bytesLength) - { - line[hexColumn] = ' '; - line[hexColumn + 1] = ' '; - line[charColumn] = ' '; - } - else - { - byte b = bytes[i + j]; - line[hexColumn] = HexChars[(b >> 4) & 0xF]; - line[hexColumn + 1] = HexChars[b & 0xF]; - line[charColumn] = (b < 32 ? '·' : (char)b); - } - hexColumn += 3; - charColumn++; - } - result.Append(line); - } - return result.ToString(); - } - } -} \ No newline at end of file diff --git a/src/AmongUs.Tools.Proxy/Program.cs b/src/AmongUs.Tools.Proxy/Program.cs deleted file mode 100644 index 6136f50..0000000 --- a/src/AmongUs.Tools.Proxy/Program.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Hazel; -using Hazel.Udp; -using PcapDotNet.Core; -using PcapDotNet.Packets; - -namespace AmongUs.Tools.Proxy -{ - internal static class Program - { - private const string DeviceName = "Intel(R) I211 Gigabit Network Connection"; - - private static readonly Dictionary TagMap = new Dictionary - { - {0, "HostGame"}, - {1, "JoinGame"}, - {2, "StartGame"}, - {3, "RemoveGame"}, - {4, "RemovePlayer"}, - {5, "GameData"}, - {6, "GameDataTo"}, - {7, "JoinedGame"}, - {8, "EndGame"}, - {9, "GetGameList"}, - {10, "AlterGame"}, - {11, "KickPlayer"}, - {12, "WaitForHost"}, - {13, "Redirect"}, - {14, "ReselectServer"}, - {16, "GetGameListV2"} - }; - - private static void Main(string[] args) - { - var devices = LivePacketDevice.AllLocalMachine; - if (devices.Count == 0) - { - Console.WriteLine("No interfaces found! Make sure WinPcap is installed."); - return; - } - - var device = devices.FirstOrDefault(x => x.Description.Contains(DeviceName)); - if (device == null) - { - Console.WriteLine("Unable to find configured device."); - return; - } - - using (var communicator = device.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 1000)) - { - using (var filter = communicator.CreateFilter("udp and port 22023")) - { - communicator.SetFilter(filter); - } - - communicator.ReceivePackets(0, PacketHandler); - } - } - - private static void PacketHandler(Packet packet) - { - var ip = packet.Ethernet.IpV4; - var ipSrc = ip.Source.ToString(); - var udp = ip.Udp; - - // True if this is our own packet. - using (var stream = udp.Payload.ToMemoryStream()) - { - var reader = MessageReader.Get(stream.ToArray()); - if (reader.Buffer[0] == (byte) SendOption.Reliable) - { - reader.Offset = 3; - reader.Length = udp.Payload.Length - 3; - reader.Position = 0; - } - else if (reader.Buffer[0] == (byte) UdpSendOption.Acknowledgement || - reader.Buffer[0] == (byte) UdpSendOption.Ping || - reader.Buffer[0] == (byte) UdpSendOption.Hello || - reader.Buffer[0] == (byte) UdpSendOption.Disconnect) - { - return; - } - else - { - reader.Offset = 1; - reader.Length = udp.Payload.Length - 1; - reader.Position = 0; - } - - var isSent = ipSrc.StartsWith("192."); - - while (true) - { - if (reader.Position >= reader.Length) - { - break; - } - - var message = reader.ReadMessage(); - if (isSent) - { - HandleToServer(ipSrc, message); - } - else - { - HandleToClient(ipSrc, message); - } - - if (message.Position < message.Length) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("- Did not consume all bytes."); - } - } - } - } - - private static void HandleToClient(string source, MessageReader packet) - { - var tagName = TagMap.ContainsKey(packet.Tag) ? TagMap[packet.Tag] : "Unknown"; - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine($"{source,-15} Client received: {packet.Tag,-2} {tagName}"); - - switch (packet.Tag) - { - case 14: - case 13: - packet.Position = packet.Length; - break; - case 0: - Console.WriteLine("- GameCode " + packet.ReadInt32()); - break; - case 5: - case 6: - Console.WriteLine(HexUtils.HexDump(packet.Buffer.Take(packet.Length).ToArray())); - packet.Position = packet.Length; - break; - case 7: - Console.WriteLine("- GameCode " + packet.ReadInt32()); - Console.WriteLine("- PlayerId " + packet.ReadInt32()); - Console.WriteLine("- Host " + packet.ReadInt32()); - var playerCount = packet.ReadPackedInt32(); - Console.WriteLine("- PlayerCount " + playerCount); - for (var i = 0; i < playerCount; i++) - { - Console.WriteLine("- PlayerId " + packet.ReadPackedInt32()); - } - break; - case 10: - Console.WriteLine("- GameCode " + packet.ReadInt32()); - Console.WriteLine("- Flag " + packet.ReadSByte()); - Console.WriteLine("- Value " + packet.ReadBoolean()); - break; - } - } - - private static void HandleToServer(string source, MessageReader packet) - { - var tagName = TagMap.ContainsKey(packet.Tag) ? TagMap[packet.Tag] : "Unknown"; - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine($"{source,-15} Server received: {packet.Tag,-2} {tagName}"); - - switch (packet.Tag) - { - case 0: - Console.WriteLine("- GameInfo length " + packet.ReadBytesAndSize().Length); - break; - case 1: - Console.WriteLine("- GameCode " + packet.ReadInt32()); - Console.WriteLine("- Unknown " + packet.ReadByte()); - break; - case 5: - case 6: - Console.WriteLine("- GameCode " + packet.ReadInt32()); - Console.WriteLine(HexUtils.HexDump(packet.Buffer.Take(packet.Length).ToArray())); - packet.Position = packet.Length; - break; - } - } - } -} diff --git a/src/AmongUsServer.sln b/src/AmongUsServer.sln deleted file mode 100644 index 391da3d..0000000 --- a/src/AmongUsServer.sln +++ /dev/null @@ -1,97 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30503.244 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AmongUs.Agent", "AmongUs.Agent\AmongUs.Agent.vcxproj", "{097BE795-88E9-4E7B-92F3-F6DDDBE3F012}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmongUs.Server", "AmongUs.Server\AmongUs.Server.csproj", "{1B0390AF-A4F3-4FE4-B093-708B0135C0B3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmongUs.Client", "AmongUs.Client\AmongUs.Client.csproj", "{24F07173-D4A7-41C6-BA51-4516AB68343C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmongUs.Shared", "AmongUs.Shared\AmongUs.Shared.csproj", "{1D109A96-FADF-41B9-A845-457A7A5C0C5A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "submodules", "submodules", "{DFA3CC6A-A27C-4713-876D-A55912C78C45}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hazel", "..\submodules\Hazel-Networking\Hazel\Hazel.csproj", "{02CFBD30-D77D-400F-94B2-700F60EFDD7F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmongUs.Tests", "AmongUs.Tests\AmongUs.Tests.csproj", "{C1385C67-A7DD-46D2-80F6-FA428B85FB22}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{56DD9707-D811-4056-9E2C-8A9CC2479B07}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AmongUs.Tools.Proxy", "AmongUs.Tools.Proxy\AmongUs.Tools.Proxy.csproj", "{D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Debug|x86.ActiveCfg = Debug|Win32 - {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Debug|x86.Build.0 = Debug|Win32 - {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Release|Any CPU.ActiveCfg = Release|Win32 - {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Release|x86.ActiveCfg = Release|Win32 - {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Release|x86.Build.0 = Release|Win32 - {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x86.ActiveCfg = Debug|Any CPU - {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x86.Build.0 = Debug|Any CPU - {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|Any CPU.Build.0 = Release|Any CPU - {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x86.ActiveCfg = Release|Any CPU - {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x86.Build.0 = Release|Any CPU - {24F07173-D4A7-41C6-BA51-4516AB68343C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24F07173-D4A7-41C6-BA51-4516AB68343C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24F07173-D4A7-41C6-BA51-4516AB68343C}.Debug|x86.ActiveCfg = Debug|Any CPU - {24F07173-D4A7-41C6-BA51-4516AB68343C}.Debug|x86.Build.0 = Debug|Any CPU - {24F07173-D4A7-41C6-BA51-4516AB68343C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24F07173-D4A7-41C6-BA51-4516AB68343C}.Release|Any CPU.Build.0 = Release|Any CPU - {24F07173-D4A7-41C6-BA51-4516AB68343C}.Release|x86.ActiveCfg = Release|Any CPU - {24F07173-D4A7-41C6-BA51-4516AB68343C}.Release|x86.Build.0 = Release|Any CPU - {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Debug|x86.ActiveCfg = Debug|Any CPU - {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Debug|x86.Build.0 = Debug|Any CPU - {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Release|Any CPU.Build.0 = Release|Any CPU - {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Release|x86.ActiveCfg = Release|Any CPU - {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Release|x86.Build.0 = Release|Any CPU - {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|x86.ActiveCfg = Debug|Any CPU - {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|x86.Build.0 = Debug|Any CPU - {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|Any CPU.Build.0 = Release|Any CPU - {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|x86.ActiveCfg = Release|Any CPU - {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|x86.Build.0 = Release|Any CPU - {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|x86.ActiveCfg = Debug|Any CPU - {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|x86.Build.0 = Debug|Any CPU - {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|Any CPU.Build.0 = Release|Any CPU - {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|x86.ActiveCfg = Release|Any CPU - {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|x86.Build.0 = Release|Any CPU - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|x86.ActiveCfg = Debug|Any CPU - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|x86.Build.0 = Debug|Any CPU - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|Any CPU.Build.0 = Release|Any CPU - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|x86.ActiveCfg = Release|Any CPU - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {06245255-E585-483C-A4AD-71B7B568C6C8} - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {02CFBD30-D77D-400F-94B2-700F60EFDD7F} = {DFA3CC6A-A27C-4713-876D-A55912C78C45} - {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D} = {56DD9707-D811-4056-9E2C-8A9CC2479B07} - EndGlobalSection -EndGlobal diff --git a/src/Impostor.Agent/Impostor.Agent.vcxproj b/src/Impostor.Agent/Impostor.Agent.vcxproj new file mode 100644 index 0000000..9350e62 --- /dev/null +++ b/src/Impostor.Agent/Impostor.Agent.vcxproj @@ -0,0 +1,94 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + 16.0 + Win32Proj + {097be795-88e9-4e7b-92f3-f6dddbe3f012} + AmongUsAgent + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + true + $(ProjectDir)build\$(Platform)\$(Configuration)\ + $(ProjectDir)build\$(Platform)\$(Configuration)\Intermediate\ + + + false + $(ProjectDir)build\$(Platform)\$(Configuration)\ + $(ProjectDir)build\$(Platform)\$(Configuration)\Intermediate\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Impostor.Agent/Impostor.Agent.vcxproj.filters b/src/Impostor.Agent/Impostor.Agent.vcxproj.filters new file mode 100644 index 0000000..85ba1c7 --- /dev/null +++ b/src/Impostor.Agent/Impostor.Agent.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/src/Impostor.Agent/logger.cpp b/src/Impostor.Agent/logger.cpp new file mode 100644 index 0000000..4b45c1d --- /dev/null +++ b/src/Impostor.Agent/logger.cpp @@ -0,0 +1,84 @@ +#include "logger.h" +#include +#include + +HANDLE h_out = nullptr, h_old_out = nullptr; +HANDLE h_err = nullptr, h_old_err = nullptr; +HANDLE h_in = nullptr, h_old_in = nullptr; + +namespace Logger +{ + void Attach() + { + h_old_out = GetStdHandle(STD_OUTPUT_HANDLE); + h_old_err = GetStdHandle(STD_ERROR_HANDLE); + h_old_in = GetStdHandle(STD_INPUT_HANDLE); + + AllocConsole() && AttachConsole(GetCurrentProcessId()); + + h_out = GetStdHandle(STD_OUTPUT_HANDLE); + h_err = GetStdHandle(STD_ERROR_HANDLE); + h_in = GetStdHandle(STD_INPUT_HANDLE); + + SetConsoleMode(h_out, + ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); + + SetConsoleMode(h_in, + ENABLE_INSERT_MODE | ENABLE_EXTENDED_FLAGS | + ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE); + } + + void Detach() + { + if (h_out && h_err && h_in) { + FreeConsole(); + + if (h_old_out) + { + SetStdHandle(STD_OUTPUT_HANDLE, h_old_out); + } + + if (h_old_err) + { + SetStdHandle(STD_ERROR_HANDLE, h_old_err); + } + + if (h_old_in) + { + SetStdHandle(STD_INPUT_HANDLE, h_old_in); + } + } + } + + bool Print(const char* fmt, ...) + { + if (!h_out) + { + return false; + } + + char buf[1024]; + va_list va; + + va_start(va, fmt); + _vsnprintf_s(buf, 1024, fmt, va); + va_end(va); + + return !!WriteConsoleA(h_out, buf, static_cast(strlen(buf)), nullptr, nullptr); + } + + char ReadKey() + { + if (!h_in) + { + return -1; + } + + auto key = char{ 0 }; + auto keysread = DWORD{ 0 }; + + ReadConsoleA(h_in, &key, 1, &keysread, nullptr); + + return key; + } +}; \ No newline at end of file diff --git a/src/Impostor.Agent/logger.h b/src/Impostor.Agent/logger.h new file mode 100644 index 0000000..fc22e15 --- /dev/null +++ b/src/Impostor.Agent/logger.h @@ -0,0 +1,9 @@ +#pragma once + +namespace Logger +{ + void Attach(); + void Detach(); + bool Print(const char* fmt, ...); + char ReadKey(); +}; \ No newline at end of file diff --git a/src/Impostor.Agent/main.cpp b/src/Impostor.Agent/main.cpp new file mode 100644 index 0000000..40b5ff0 --- /dev/null +++ b/src/Impostor.Agent/main.cpp @@ -0,0 +1,40 @@ +#include +#include "logger.h" + +DWORD WINAPI OnDllAttach(LPVOID base) +{ + Logger::Attach(); + Logger::Print("[AmongUs.Agent] DLL Attached\n"); + + FreeLibraryAndExitThread(static_cast(base), 1); +} + +BOOL WINAPI OnDllDetach() +{ + Logger::Print("[AmongUs.Agent] DLL Detached\n"); + Logger::Detach(); + + return true; +} + +BOOL WINAPI DllMain( + const _In_ HINSTANCE hinstDll, + const _In_ DWORD fdwReason, + const _In_opt_ LPVOID lpvReserved +) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hinstDll); + CreateThread(nullptr, 0, OnDllAttach, hinstDll, 0, nullptr); + return true; + + case DLL_PROCESS_DETACH: + if (lpvReserved == nullptr) + return OnDllDetach(); + return true; + default: + return true; + } +} \ No newline at end of file diff --git a/src/Impostor.Client/Impostor.Client.csproj b/src/Impostor.Client/Impostor.Client.csproj new file mode 100644 index 0000000..6794e8f --- /dev/null +++ b/src/Impostor.Client/Impostor.Client.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp3.1 + + + + + + + diff --git a/src/Impostor.Client/Program.cs b/src/Impostor.Client/Program.cs new file mode 100644 index 0000000..7cd684c --- /dev/null +++ b/src/Impostor.Client/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; +using Impostor.Shared.Innersloth; + +namespace Impostor.Client +{ + internal static class Program + { + private static void Main(string[] args) + { + 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", "192.168.1.211", new [] + { + new ServerInfo("Private-Master-1", "192.168.1.211", 22023) + }); + + using (var file = File.Open(regionFile, FileMode.Create, FileAccess.Write)) + using (var writer = new BinaryWriter(file)) + { + region.Serialize(writer); + } + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Data/DisconnectMessages.cs b/src/Impostor.Server/Data/DisconnectMessages.cs new file mode 100644 index 0000000..1c16173 --- /dev/null +++ b/src/Impostor.Server/Data/DisconnectMessages.cs @@ -0,0 +1,12 @@ +namespace Impostor.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."; + + public const string Destroyed = "The game you tried to join is being destroyed. " + + "Please create a new game."; + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Exceptions/AmongUsException.cs b/src/Impostor.Server/Exceptions/AmongUsException.cs new file mode 100644 index 0000000..0d5ff20 --- /dev/null +++ b/src/Impostor.Server/Exceptions/AmongUsException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace Impostor.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/Impostor.Server/Extensions/ConnectionExtensions.cs b/src/Impostor.Server/Extensions/ConnectionExtensions.cs new file mode 100644 index 0000000..21ef19e --- /dev/null +++ b/src/Impostor.Server/Extensions/ConnectionExtensions.cs @@ -0,0 +1,16 @@ +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/Impostor.Server.csproj b/src/Impostor.Server/Impostor.Server.csproj new file mode 100644 index 0000000..46ef7fa --- /dev/null +++ b/src/Impostor.Server/Impostor.Server.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs new file mode 100644 index 0000000..b3a041e --- /dev/null +++ b/src/Impostor.Server/Net/Client.cs @@ -0,0 +1,273 @@ +using System; +using Hazel; +using Impostor.Server.Data; +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 Client + { + private static readonly ILogger Logger = Log.ForContext(); + + private readonly ClientManager _clientManager; + private readonly GameManager _gameManager; + + public Client(ClientManager clientManager, GameManager gameManager, int id, string name, Connection connection) + { + _clientManager = clientManager; + _gameManager = gameManager; + 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; } + + public void Send(MessageWriter writer) + { + Connection.Send(writer); + } + + private bool IsPacketAllowed(MessageReader message, bool hostOnly) + { + var game = Player.Game; + if (game == null) + { + return false; + } + + // GameCode must match code of the current game assigned to the player. + if (message.ReadInt32() != game.Code) + { + return false; + } + + // Some packets should only be sent by the host of the game. + if (hostOnly) + { + if (game.HostId == Id) + { + return true; + } + + Logger.Warning("[{0}] Client sent packet only allowed by the host ({1}).", Id, game.HostId); + return false; + } + + return true; + } + + private void OnDataReceived(DataReceivedEventArgs e) + { + try + { + while (true) + { + if (e.Message.Position >= e.Message.Length) + { + break; + } + + OnMessageReceived(e.Message.ReadMessage(), e.SendOption); + } + } + 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, SendOption sendOption) + { + var flag = (RequestFlag) message.Tag; + + Logger.Verbose("[{0}] Server got {1}.", Id, flag); + + switch (flag) + { + case RequestFlag.HostGame: + { + // Read game settings. + var gameInfoBytes = message.ReadBytesAndSize(); + var gameInfo = GameOptionsData.Deserialize(gameInfoBytes); + + // Create game. + var game = _gameManager.Create(this, gameInfo); + if (game == null) + { + Connection.Send(new Message1DisconnectReason(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(); + + Connection.Send(writer); + } + break; + } + + case RequestFlag.JoinGame: + { + var gameCode = message.ReadInt32(); + var unknown = message.ReadByte(); + var game = _gameManager.Find(gameCode); + if (game == null) + { + Connection.Send(new Message1DisconnectReason(DisconnectReason.GameMissing)); + return; + } + + game.HandleJoinGame(Player); + break; + } + + case RequestFlag.StartGame: + { + if (!IsPacketAllowed(message, true)) + { + return; + } + + Player.Game.HandleStartGame(message); + break; + } + + // No idea how this flag is triggered. + case RequestFlag.RemoveGame: + break; + + case RequestFlag.RemovePlayer: + { + if (!IsPacketAllowed(message, true)) + { + return; + } + + var playerId = message.ReadPackedInt32(); + var reason = message.ReadByte(); + + Player.Game.HandleRemovePlayer(playerId, (DisconnectReason) reason); + break; + } + + case RequestFlag.GameData: + case RequestFlag.GameDataTo: + { + if (!IsPacketAllowed(message, false)) + { + return; + } + + // Broadcast packet to all other players. + using (var writer = MessageWriter.Get(sendOption)) + { + if (flag == RequestFlag.GameDataTo) + { + var target = message.ReadPackedInt32(); + writer.CopyFrom(message); + Player.Game.SendTo(writer, target); + } + else + { + writer.CopyFrom(message); + Player.Game.SendToAllExcept(writer, Player); + } + } + break; + } + + case RequestFlag.EndGame: + { + if (!IsPacketAllowed(message, true)) + { + return; + } + + Player.Game.HandleEndGame(message); + break; + } + + case RequestFlag.AlterGame: + { + if (!IsPacketAllowed(message, true)) + { + return; + } + + if (message.ReadByte() != (byte) AlterGameTags.ChangePrivacy) + { + return; + } + + var isPublic = message.ReadByte() == 1; + + Player.Game.HandleAlterGame(message, Player, isPublic); + break; + } + + case RequestFlag.KickPlayer: + { + if (!IsPacketAllowed(message, true)) + { + return; + } + + var playerId = message.ReadPackedInt32(); + var isBan = message.ReadBoolean(); + + Player.Game.HandleKickPlayer(playerId, isBan); + break; + } + + default: + Logger.Warning("Server received unknown flag {0}.", flag); + break; + } + + 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, + message.Length); + } + } + + private void OnDisconnected(object sender, DisconnectedEventArgs e) + { + try + { + if (Player.Game != null) + { + Player.Game.HandleRemovePlayer(Id, DisconnectReason.ExitGame); + } + } + catch (Exception ex) + { + Logger.Error(ex, "Exception caught in client disconnection."); + } + + _clientManager.Remove(this); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/ClientManager.cs b/src/Impostor.Server/Net/ClientManager.cs new file mode 100644 index 0000000..2a43d14 --- /dev/null +++ b/src/Impostor.Server/Net/ClientManager.cs @@ -0,0 +1,64 @@ +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 new file mode 100644 index 0000000..a0b7866 --- /dev/null +++ b/src/Impostor.Server/Net/ClientPlayer.cs @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..be982d7 --- /dev/null +++ b/src/Impostor.Server/Net/Game.cs @@ -0,0 +1,387 @@ +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 new file mode 100644 index 0000000..664f640 --- /dev/null +++ b/src/Impostor.Server/Net/GameManager.cs @@ -0,0 +1,45 @@ +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 new file mode 100644 index 0000000..f24ecc5 --- /dev/null +++ b/src/Impostor.Server/Net/GameStates.cs @@ -0,0 +1,10 @@ +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/Matchmaker.cs b/src/Impostor.Server/Net/Matchmaker.cs new file mode 100644 index 0000000..8acc9d6 --- /dev/null +++ b/src/Impostor.Server/Net/Matchmaker.cs @@ -0,0 +1,60 @@ +using System.Net; +using Hazel; +using Hazel.Udp; +using Impostor.Server.Extensions; +using Impostor.Server.Net.Response; +using Impostor.Shared.Innersloth.Data; +using Serilog; +using ILogger = Serilog.ILogger; + +namespace Impostor.Server.Net +{ + public class Matchmaker + { + private static readonly ILogger Logger = Log.ForContext(); + + private readonly GameManager _gameManager; + private readonly ClientManager _clientManager; + private readonly UdpConnectionListener _connection; + + public Matchmaker(IPAddress ip, int port) + { + _gameManager = new GameManager(); + _clientManager = new ClientManager(); + _connection = new UdpConnectionListener(new IPEndPoint(ip, port), IPMode.IPv4, s => + { + Logger.Warning("Log from Hazel: {0}", s); + }); + + _connection.NewConnection += OnNewConnection; + } + + private void OnNewConnection(NewConnectionEventArgs e) + { + // Handshake. + var clientVersion = e.HandshakeData.ReadInt32(); + var clientName = e.HandshakeData.ReadString(); + + e.HandshakeData.Recycle(); + + if (clientVersion != 50516550) + { + e.Connection.Send(new Message1DisconnectReason(DisconnectReason.IncorrectVersion)); + return; + } + + // Register client. + _clientManager.Add(new Client(_clientManager, _gameManager, _clientManager.NextId(), clientName, e.Connection)); + } + + public void Start() + { + _connection.Start(); + } + + public void Stop() + { + _connection.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Net/Response/Message1DisconnectReason.cs b/src/Impostor.Server/Net/Response/Message1DisconnectReason.cs new file mode 100644 index 0000000..0ab5be4 --- /dev/null +++ b/src/Impostor.Server/Net/Response/Message1DisconnectReason.cs @@ -0,0 +1,30 @@ +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 new file mode 100644 index 0000000..d9fceab --- /dev/null +++ b/src/Impostor.Server/Net/Response/MessageBase.cs @@ -0,0 +1,32 @@ +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 new file mode 100644 index 0000000..a29135d --- /dev/null +++ b/src/Impostor.Server/Net/Response/MessageFlag.cs @@ -0,0 +1,7 @@ +namespace Impostor.Server.Net.Response +{ + public enum MessageFlag : byte + { + DisconnectReason = 1 + } +} \ No newline at end of file diff --git a/src/Impostor.Server/Program.cs b/src/Impostor.Server/Program.cs new file mode 100644 index 0000000..9dab491 --- /dev/null +++ b/src/Impostor.Server/Program.cs @@ -0,0 +1,41 @@ +using System; +using System.Net; +using System.Threading; +using Impostor.Server.Net; +using Serilog; + +namespace Impostor.Server +{ + internal static class Program + { + private static readonly ManualResetEvent QuitEvent = new ManualResetEvent(false); + + private static void Main(string[] args) + { + // Listen for CTRL+C. + Console.CancelKeyPress += (sender, e) => + { + e.Cancel = true; + QuitEvent.Set(); + }; + + // Configure logger. + Log.Logger = new LoggerConfiguration() +#if DEBUG + .MinimumLevel.Verbose() +#else + .MinimumLevel.Information() +#endif + .WriteTo.Console() + .CreateLogger(); + + // Initialize matchmaker. + var matchMaker = new Matchmaker(IPAddress.Any, 22023); + matchMaker.Start(); + Log.Logger.Information("Matchmaker is running on *:22023."); + QuitEvent.WaitOne(); + Log.Logger.Warning("Matchmaker is shutting down!"); + matchMaker.Stop(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Impostor.Shared.csproj b/src/Impostor.Shared/Impostor.Shared.csproj new file mode 100644 index 0000000..8642d92 --- /dev/null +++ b/src/Impostor.Shared/Impostor.Shared.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp3.1 + + + diff --git a/src/Impostor.Shared/Innersloth/Data/AlterGameTags.cs b/src/Impostor.Shared/Innersloth/Data/AlterGameTags.cs new file mode 100644 index 0000000..c246183 --- /dev/null +++ b/src/Impostor.Shared/Innersloth/Data/AlterGameTags.cs @@ -0,0 +1,7 @@ +namespace Impostor.Shared.Innersloth.Data +{ + public enum AlterGameTags : byte + { + ChangePrivacy = 1 + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/Data/DisconnectReason.cs b/src/Impostor.Shared/Innersloth/Data/DisconnectReason.cs new file mode 100644 index 0000000..f3f9b66 --- /dev/null +++ b/src/Impostor.Shared/Innersloth/Data/DisconnectReason.cs @@ -0,0 +1,56 @@ +namespace Impostor.Shared.Innersloth.Data +{ + public enum DisconnectReason + { + ExitGame = 0, + Destroy = 16, + // The game you tried to join is full. + // Check with the host to see if you can join next round. + GameFull = 1, + // The game you tried to join already started. + // Check with the host to see if you can join next round. + GameStarted = 2, + // Could not find the game you're looking for. + GameMissing = 3, + IncorrectGame = 18, + // For these a message can be given, specifying an empty message shows + // "An unknown error disconnected you from the server." + CustomMessage1 = 4, + Custom = 8, + // CustomMessage3 = 11, + // CustomMessage4 = 12, + // CustomMessage5 = 13, + // CustomMessage6 = 14, + // CustomMessage7 = 15, + // You are running an older version of the game. + // Please update to play with others. + IncorrectVersion = 5, + // You cannot rejoin that room. + // You were banned + Banned = 6, + // You can rejoin if the room hasn't started + // You were kicked + Kicked = 7, + // Server refused username: %USERNAME% + InvalidName = 9, + // You were banned for hacking. + // Please stop. + Hacking = 10, + // You disconnected from the host. + // If this happens often, check your WiFi strength. + // + // You disconnected from the server. + // If this happens often, check your network strength. + // This may also be a server issue. + Error = 17, + // The server stopped this game. Possibly due to inactivity. + ServerRequest = 19, + // The Among Us servers are overloaded. + // Sorry! Please try again later! + ServerFull = 20, + FocusLostBackground = 207, + IntentionalLeaving = 208, + FocusLost = 209, + NewConnection = 210 + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/Data/GameKeywords.cs b/src/Impostor.Shared/Innersloth/Data/GameKeywords.cs new file mode 100644 index 0000000..daefefe --- /dev/null +++ b/src/Impostor.Shared/Innersloth/Data/GameKeywords.cs @@ -0,0 +1,19 @@ +using System; + +namespace Impostor.Shared.Innersloth.Data +{ + [Flags] + public enum GameKeywords : uint + { + All = 0, + Other = 1, + Spanish = 2, + Korean = 4, + Russian = 8, + Portuguese = 16, + Arabic = 32, + Filipone = 64, + Polish = 128, + English = 256 + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/Data/GameStates.cs b/src/Impostor.Shared/Innersloth/Data/GameStates.cs new file mode 100644 index 0000000..ec2a2f2 --- /dev/null +++ b/src/Impostor.Shared/Innersloth/Data/GameStates.cs @@ -0,0 +1,10 @@ +namespace Impostor.Shared.Innersloth.Data +{ + public enum GameStates : byte + { + NotStarted, + Started, + Ended, + Destroyed + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/Data/LimboStates.cs b/src/Impostor.Shared/Innersloth/Data/LimboStates.cs new file mode 100644 index 0000000..e501614 --- /dev/null +++ b/src/Impostor.Shared/Innersloth/Data/LimboStates.cs @@ -0,0 +1,9 @@ +namespace Impostor.Shared.Innersloth.Data +{ + public enum LimboStates + { + PreSpawn, + NotLimbo, + WaitingForHost, + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/Data/RequestFlag.cs b/src/Impostor.Shared/Innersloth/Data/RequestFlag.cs new file mode 100644 index 0000000..d07eefa --- /dev/null +++ b/src/Impostor.Shared/Innersloth/Data/RequestFlag.cs @@ -0,0 +1,22 @@ +namespace Impostor.Shared.Innersloth.Data +{ + public enum RequestFlag : byte + { + HostGame = 0, + JoinGame = 1, + StartGame = 2, + RemoveGame = 3, + RemovePlayer = 4, + GameData = 5, + GameDataTo = 6, + JoinedGame = 7, + EndGame = 8, + GetGameList = 9, + AlterGame = 10, + KickPlayer = 11, + WaitForHost = 12, + Redirect = 13, + ReselectServer = 14, + GetGameListV2 = 16, + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/GameCode.cs b/src/Impostor.Shared/Innersloth/GameCode.cs new file mode 100644 index 0000000..8f01fde --- /dev/null +++ b/src/Impostor.Shared/Innersloth/GameCode.cs @@ -0,0 +1,130 @@ +using System; +using System.Buffers.Binary; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace Impostor.Shared.Innersloth +{ + public static class GameCode + { + private const string V2 = "QWXRTYLPESDFGHUJKZOCVBINMA"; + private static readonly int[] V2Map = { + 25, + 21, + 19, + 10, + 8, + 11, + 12, + 13, + 22, + 15, + 16, + 6, + 24, + 23, + 18, + 7, + 0, + 3, + 9, + 4, + 14, + 20, + 1, + 2, + 5, + 17 + }; + private static readonly RNGCryptoServiceProvider Random = new RNGCryptoServiceProvider(); + + public static string IntToGameName(int input) + { + // V2. + if (input < -1) + { + return IntToGameNameV2(input); + } + + // V1. + Span code = stackalloc byte[4]; + BinaryPrimitives.WriteInt32LittleEndian(code, input); + return Encoding.UTF8.GetString(code.Slice(0, 4)); + } + + private static string IntToGameNameV2(int input) + { + var a = input & 0x3FF; + var b = (input >> 10) & 0xFFFFF; + + return new string(new [] + { + V2[a % 26], + V2[a / 26], + V2[b % 26], + V2[b / 26 % 26], + V2[b / (26 * 26) % 26], + V2[b / (26 * 26 * 26) % 26] + }); + } + + public static int GameNameToInt(string code) + { + var upper = code.ToUpperInvariant(); + if (upper.Any(x => !char.IsLetter(x))) + { + return -1; + } + + var len = code.Length; + if (len == 6) + { + return GameNameToIntV2(upper); + } + + if (len == 4) + { + return code[0] | ((code[1] | ((code[2] | (code[3] << 8)) << 8)) << 8); + } + + return -1; + } + + private static int GameNameToIntV2(string code) + { + var a = V2Map[code[0] - 65]; + var b = V2Map[code[1] - 65]; + var c = V2Map[code[2] - 65]; + var d = V2Map[code[3] - 65]; + var e = V2Map[code[4] - 65]; + var f = V2Map[code[5] - 65]; + + var one = (a + 26 * b) & 0x3FF; + var two = (c + 26 * (d + 26 * (e + 26 * f))); + + return (int) (one | ((two << 10) & 0x3FFFFC00) | 0x80000000); + } + + public static int GenerateCode(int len) + { + if (len != 4 && len != 6) + { + throw new ArgumentException("should be 4 or 6", nameof(len)); + } + + // Generate random bytes. + Span data = stackalloc byte[len]; + Random.GetBytes(data); + + // Convert to their char representation. + Span dataChar = stackalloc char[len]; + for (var i = 0; i < len; i++) + { + dataChar[i] = V2[V2Map[data[i] % 26]]; + } + + return GameNameToInt(new string(dataChar)); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/GameOptionsData.cs b/src/Impostor.Shared/Innersloth/GameOptionsData.cs new file mode 100644 index 0000000..711edb1 --- /dev/null +++ b/src/Impostor.Shared/Innersloth/GameOptionsData.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using Impostor.Shared.Innersloth.Data; + +namespace Impostor.Shared.Innersloth +{ + public class GameOptionsData + { + public byte Version { get; set; } + public byte MaxPlayers { get; set; } + public GameKeywords Keywords { get; set; } + public byte MapId { get; set; } + public float PlayerSpeedMod { get; set; } + public float CrewLightMod { get; set; } + public float ImpostorLightMod { get; set; } + public float KillCooldown { get; set; } + public int NumCommonTasks { get; set; } + public int NumLongTasks { get; set; } + public int NumShortTasks { get; set; } + public int NumEmergencyMeetings { get; set; } + public int EmergencyCooldown { get; set; } + public int NumImpostors { get; set; } + public bool GhostsDoTasks { get; set; } + public int KillDistance { get; set; } + public int DiscussionTime { get; set; } + public int VotingTime { get; set; } + public bool ConfirmImpostor { get; set; } + public bool VisualTasks { get; set; } + public bool IsDefaults { get; set; } + + public void Serialize(BinaryWriter writer, byte version) + { + throw new NotImplementedException(); + } + + public static GameOptionsData Deserialize(byte[] bytes) + { + using (var stream = new MemoryStream(bytes)) + using (var reader = new BinaryReader(stream)) + { + var result = new GameOptionsData(); + + result.Version = reader.ReadByte(); + result.MaxPlayers = reader.ReadByte(); + result.Keywords = (GameKeywords) reader.ReadUInt32(); + result.MapId = reader.ReadByte(); + result.PlayerSpeedMod = reader.ReadSingle(); + result.CrewLightMod = reader.ReadSingle(); + result.ImpostorLightMod = reader.ReadSingle(); + result.KillCooldown = reader.ReadSingle(); + result.NumCommonTasks = reader.ReadByte(); + result.NumLongTasks = reader.ReadByte(); + result.NumShortTasks = reader.ReadByte(); + result.NumEmergencyMeetings = reader.ReadInt32(); + result.NumImpostors = reader.ReadByte(); + result.KillDistance = reader.ReadByte(); + result.DiscussionTime = reader.ReadInt32(); + result.VotingTime = reader.ReadInt32(); + result.IsDefaults = reader.ReadBoolean(); + + if (result.Version > 1) + { + result.EmergencyCooldown = reader.ReadByte(); + } + + if (result.Version > 2) + { + result.ConfirmImpostor = reader.ReadBoolean(); + result.VisualTasks = reader.ReadBoolean(); + } + + return result; + } + } + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/RegionInfo.cs b/src/Impostor.Shared/Innersloth/RegionInfo.cs new file mode 100644 index 0000000..926bd8d --- /dev/null +++ b/src/Impostor.Shared/Innersloth/RegionInfo.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.IO; + +namespace Impostor.Shared.Innersloth +{ + public class RegionInfo + { + public RegionInfo(string name, string ping, IReadOnlyList servers) + { + Name = name; + Ping = ping; + Servers = servers; + } + + public string Name { get; } + public string Ping { get; } + public IReadOnlyList Servers { get; } + + public void Serialize(BinaryWriter writer) + { + writer.Write(0); + writer.Write(Name); + writer.Write(Ping); + writer.Write(Servers.Count); + + foreach (var server in Servers) + { + server.Serialize(writer); + } + } + + public static RegionInfo Deserialize(BinaryReader reader) + { + var unknown = reader.ReadInt32(); + var name = reader.ReadString(); + var ping = reader.ReadString(); + var servers = new List(); + var serverCount = reader.ReadInt32(); + + for (var i = 0; i < serverCount; i++) + { + servers.Add(ServerInfo.Deserialize(reader)); + } + + return new RegionInfo(name, ping, servers); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Innersloth/ServerInfo.cs b/src/Impostor.Shared/Innersloth/ServerInfo.cs new file mode 100644 index 0000000..c76a7e4 --- /dev/null +++ b/src/Impostor.Shared/Innersloth/ServerInfo.cs @@ -0,0 +1,37 @@ +using System.IO; +using System.Net; + +namespace Impostor.Shared.Innersloth +{ + public class ServerInfo + { + public string Name { get; } + public string Ip { get; } + public ushort Port { get; } + + public ServerInfo(string name, string ip, ushort port) + { + Name = name; + Ip = ip; + Port = port; + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(Name); + writer.Write(IPAddress.Parse(Ip).GetAddressBytes()); + writer.Write(Port); + writer.Write(0); + } + + public static ServerInfo Deserialize(BinaryReader reader) + { + var name = reader.ReadString(); + var ip = new IPAddress(reader.ReadBytes(4)).ToString(); + var port = reader.ReadUInt16(); + var unknown = reader.ReadInt32(); + + return new ServerInfo(name, ip, port); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Shared/Utils/HexUtils.cs b/src/Impostor.Shared/Utils/HexUtils.cs new file mode 100644 index 0000000..04e497b --- /dev/null +++ b/src/Impostor.Shared/Utils/HexUtils.cs @@ -0,0 +1,70 @@ +using System; +using System.Text; + +namespace Impostor.Shared.Utils +{ + public static class HexUtils + { + public static string HexDump(byte[] bytes, int bytesPerLine = 16) + { + if (bytes == null) return ""; + int bytesLength = bytes.Length; + + char[] HexChars = "0123456789ABCDEF".ToCharArray(); + + int firstHexColumn = + 8 // 8 characters for the address + + 3; // 3 spaces + + int firstCharColumn = firstHexColumn + + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space + + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th + + 2; // 2 spaces + + int lineLength = firstCharColumn + + bytesPerLine // - characters to show the ascii value + + Environment.NewLine.Length; // Carriage return and line feed (should normally be 2) + + char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); + int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine; + StringBuilder result = new StringBuilder(expectedLines * lineLength); + + for (int i = 0; i < bytesLength; i += bytesPerLine) + { + line[0] = HexChars[(i >> 28) & 0xF]; + line[1] = HexChars[(i >> 24) & 0xF]; + line[2] = HexChars[(i >> 20) & 0xF]; + line[3] = HexChars[(i >> 16) & 0xF]; + line[4] = HexChars[(i >> 12) & 0xF]; + line[5] = HexChars[(i >> 8) & 0xF]; + line[6] = HexChars[(i >> 4) & 0xF]; + line[7] = HexChars[(i >> 0) & 0xF]; + + int hexColumn = firstHexColumn; + int charColumn = firstCharColumn; + + for (int j = 0; j < bytesPerLine; j++) + { + if (j > 0 && (j & 7) == 0) hexColumn++; + if (i + j >= bytesLength) + { + line[hexColumn] = ' '; + line[hexColumn + 1] = ' '; + line[charColumn] = ' '; + } + else + { + byte b = bytes[i + j]; + line[hexColumn] = HexChars[(b >> 4) & 0xF]; + line[hexColumn + 1] = HexChars[b & 0xF]; + line[charColumn] = (b < 32 ? '·' : (char)b); + } + hexColumn += 3; + charColumn++; + } + result.Append(line); + } + return result.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Tests/GameCodeTests.cs b/src/Impostor.Tests/GameCodeTests.cs new file mode 100644 index 0000000..24e6171 --- /dev/null +++ b/src/Impostor.Tests/GameCodeTests.cs @@ -0,0 +1,28 @@ +using Impostor.Shared.Innersloth; +using Xunit; + +namespace Impostor.Tests +{ + public class GameCodeTests + { + [Fact] + public void CodeV1() + { + const string code = "ABCD"; + const int codeInt = 0x44434241; + + Assert.Equal(code, GameCode.IntToGameName(codeInt)); + Assert.Equal(codeInt, GameCode.GameNameToInt(code)); + } + + [Fact] + public void CodeV2() + { + const string code = "ABCDEF"; + const int codeInt = -1943683525; + + Assert.Equal(code, GameCode.IntToGameName(codeInt)); + Assert.Equal(codeInt, GameCode.GameNameToInt(code)); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Tests/Impostor.Tests.csproj b/src/Impostor.Tests/Impostor.Tests.csproj new file mode 100644 index 0000000..88929eb --- /dev/null +++ b/src/Impostor.Tests/Impostor.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/src/Impostor.Tools.Proxy/HexUtils.cs b/src/Impostor.Tools.Proxy/HexUtils.cs new file mode 100644 index 0000000..79517b4 --- /dev/null +++ b/src/Impostor.Tools.Proxy/HexUtils.cs @@ -0,0 +1,70 @@ +using System; +using System.Text; + +namespace Impostor.Tools.Proxy +{ + public static class HexUtils + { + public static string HexDump(byte[] bytes, int bytesPerLine = 16) + { + if (bytes == null) return ""; + int bytesLength = bytes.Length; + + char[] HexChars = "0123456789ABCDEF".ToCharArray(); + + int firstHexColumn = + 8 // 8 characters for the address + + 3; // 3 spaces + + int firstCharColumn = firstHexColumn + + bytesPerLine * 3 // - 2 digit for the hexadecimal value and 1 space + + (bytesPerLine - 1) / 8 // - 1 extra space every 8 characters from the 9th + + 2; // 2 spaces + + int lineLength = firstCharColumn + + bytesPerLine // - characters to show the ascii value + + Environment.NewLine.Length; // Carriage return and line feed (should normally be 2) + + char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); + int expectedLines = (bytesLength + bytesPerLine - 1) / bytesPerLine; + StringBuilder result = new StringBuilder(expectedLines * lineLength); + + for (int i = 0; i < bytesLength; i += bytesPerLine) + { + line[0] = HexChars[(i >> 28) & 0xF]; + line[1] = HexChars[(i >> 24) & 0xF]; + line[2] = HexChars[(i >> 20) & 0xF]; + line[3] = HexChars[(i >> 16) & 0xF]; + line[4] = HexChars[(i >> 12) & 0xF]; + line[5] = HexChars[(i >> 8) & 0xF]; + line[6] = HexChars[(i >> 4) & 0xF]; + line[7] = HexChars[(i >> 0) & 0xF]; + + int hexColumn = firstHexColumn; + int charColumn = firstCharColumn; + + for (int j = 0; j < bytesPerLine; j++) + { + if (j > 0 && (j & 7) == 0) hexColumn++; + if (i + j >= bytesLength) + { + line[hexColumn] = ' '; + line[hexColumn + 1] = ' '; + line[charColumn] = ' '; + } + else + { + byte b = bytes[i + j]; + line[hexColumn] = HexChars[(b >> 4) & 0xF]; + line[hexColumn + 1] = HexChars[b & 0xF]; + line[charColumn] = (b < 32 ? '·' : (char)b); + } + hexColumn += 3; + charColumn++; + } + result.Append(line); + } + return result.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj b/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj new file mode 100644 index 0000000..1a89dbd --- /dev/null +++ b/src/Impostor.Tools.Proxy/Impostor.Tools.Proxy.csproj @@ -0,0 +1,16 @@ + + + + Exe + net452 + + + + + + + + + + + diff --git a/src/Impostor.Tools.Proxy/Program.cs b/src/Impostor.Tools.Proxy/Program.cs new file mode 100644 index 0000000..539463c --- /dev/null +++ b/src/Impostor.Tools.Proxy/Program.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Hazel; +using Hazel.Udp; +using PcapDotNet.Core; +using PcapDotNet.Packets; + +namespace Impostor.Tools.Proxy +{ + internal static class Program + { + private const string DeviceName = "Intel(R) I211 Gigabit Network Connection"; + + private static readonly Dictionary TagMap = new Dictionary + { + {0, "HostGame"}, + {1, "JoinGame"}, + {2, "StartGame"}, + {3, "RemoveGame"}, + {4, "RemovePlayer"}, + {5, "GameData"}, + {6, "GameDataTo"}, + {7, "JoinedGame"}, + {8, "EndGame"}, + {9, "GetGameList"}, + {10, "AlterGame"}, + {11, "KickPlayer"}, + {12, "WaitForHost"}, + {13, "Redirect"}, + {14, "ReselectServer"}, + {16, "GetGameListV2"} + }; + + private static void Main(string[] args) + { + var devices = LivePacketDevice.AllLocalMachine; + if (devices.Count == 0) + { + Console.WriteLine("No interfaces found! Make sure WinPcap is installed."); + return; + } + + var device = devices.FirstOrDefault(x => x.Description.Contains(DeviceName)); + if (device == null) + { + Console.WriteLine("Unable to find configured device."); + return; + } + + using (var communicator = device.Open(65536, PacketDeviceOpenAttributes.Promiscuous, 1000)) + { + using (var filter = communicator.CreateFilter("udp and port 22023")) + { + communicator.SetFilter(filter); + } + + communicator.ReceivePackets(0, PacketHandler); + } + } + + private static void PacketHandler(Packet packet) + { + var ip = packet.Ethernet.IpV4; + var ipSrc = ip.Source.ToString(); + var udp = ip.Udp; + + // True if this is our own packet. + using (var stream = udp.Payload.ToMemoryStream()) + { + var reader = MessageReader.Get(stream.ToArray()); + if (reader.Buffer[0] == (byte) SendOption.Reliable) + { + reader.Offset = 3; + reader.Length = udp.Payload.Length - 3; + reader.Position = 0; + } + else if (reader.Buffer[0] == (byte) UdpSendOption.Acknowledgement || + reader.Buffer[0] == (byte) UdpSendOption.Ping || + reader.Buffer[0] == (byte) UdpSendOption.Hello || + reader.Buffer[0] == (byte) UdpSendOption.Disconnect) + { + return; + } + else + { + reader.Offset = 1; + reader.Length = udp.Payload.Length - 1; + reader.Position = 0; + } + + var isSent = ipSrc.StartsWith("192."); + + while (true) + { + if (reader.Position >= reader.Length) + { + break; + } + + var message = reader.ReadMessage(); + if (isSent) + { + HandleToServer(ipSrc, message); + } + else + { + HandleToClient(ipSrc, message); + } + + if (message.Position < message.Length) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("- Did not consume all bytes."); + } + } + } + } + + private static void HandleToClient(string source, MessageReader packet) + { + var tagName = TagMap.ContainsKey(packet.Tag) ? TagMap[packet.Tag] : "Unknown"; + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine($"{source,-15} Client received: {packet.Tag,-2} {tagName}"); + + switch (packet.Tag) + { + case 14: + case 13: + packet.Position = packet.Length; + break; + case 0: + Console.WriteLine("- GameCode " + packet.ReadInt32()); + break; + case 5: + case 6: + Console.WriteLine(HexUtils.HexDump(packet.Buffer.Take(packet.Length).ToArray())); + packet.Position = packet.Length; + break; + case 7: + Console.WriteLine("- GameCode " + packet.ReadInt32()); + Console.WriteLine("- PlayerId " + packet.ReadInt32()); + Console.WriteLine("- Host " + packet.ReadInt32()); + var playerCount = packet.ReadPackedInt32(); + Console.WriteLine("- PlayerCount " + playerCount); + for (var i = 0; i < playerCount; i++) + { + Console.WriteLine("- PlayerId " + packet.ReadPackedInt32()); + } + break; + case 10: + Console.WriteLine("- GameCode " + packet.ReadInt32()); + Console.WriteLine("- Flag " + packet.ReadSByte()); + Console.WriteLine("- Value " + packet.ReadBoolean()); + break; + } + } + + private static void HandleToServer(string source, MessageReader packet) + { + var tagName = TagMap.ContainsKey(packet.Tag) ? TagMap[packet.Tag] : "Unknown"; + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine($"{source,-15} Server received: {packet.Tag,-2} {tagName}"); + + switch (packet.Tag) + { + case 0: + Console.WriteLine("- GameInfo length " + packet.ReadBytesAndSize().Length); + break; + case 1: + Console.WriteLine("- GameCode " + packet.ReadInt32()); + Console.WriteLine("- Unknown " + packet.ReadByte()); + break; + case 5: + case 6: + Console.WriteLine("- GameCode " + packet.ReadInt32()); + Console.WriteLine(HexUtils.HexDump(packet.Buffer.Take(packet.Length).ToArray())); + packet.Position = packet.Length; + break; + } + } + } +} diff --git a/src/Impostor.sln b/src/Impostor.sln new file mode 100644 index 0000000..6f6f969 --- /dev/null +++ b/src/Impostor.sln @@ -0,0 +1,97 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30503.244 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Imposter.Agent", "Imposter.Agent\Imposter.Agent.vcxproj", "{097BE795-88E9-4E7B-92F3-F6DDDBE3F012}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imposter.Server", "Imposter.Server\Imposter.Server.csproj", "{1B0390AF-A4F3-4FE4-B093-708B0135C0B3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imposter.Client", "Imposter.Client\Imposter.Client.csproj", "{24F07173-D4A7-41C6-BA51-4516AB68343C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imposter.Shared", "Imposter.Shared\Imposter.Shared.csproj", "{1D109A96-FADF-41B9-A845-457A7A5C0C5A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "submodules", "submodules", "{DFA3CC6A-A27C-4713-876D-A55912C78C45}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hazel", "..\submodules\Hazel-Networking\Hazel\Hazel.csproj", "{02CFBD30-D77D-400F-94B2-700F60EFDD7F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imposter.Tests", "Imposter.Tests\Imposter.Tests.csproj", "{C1385C67-A7DD-46D2-80F6-FA428B85FB22}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{56DD9707-D811-4056-9E2C-8A9CC2479B07}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imposter.Tools.Proxy", "Imposter.Tools.Proxy\Imposter.Tools.Proxy.csproj", "{D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Debug|x86.ActiveCfg = Debug|Win32 + {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Debug|x86.Build.0 = Debug|Win32 + {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Release|Any CPU.ActiveCfg = Release|Win32 + {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Release|x86.ActiveCfg = Release|Win32 + {097BE795-88E9-4E7B-92F3-F6DDDBE3F012}.Release|x86.Build.0 = Release|Win32 + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x86.ActiveCfg = Debug|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Debug|x86.Build.0 = Debug|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|Any CPU.Build.0 = Release|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x86.ActiveCfg = Release|Any CPU + {1B0390AF-A4F3-4FE4-B093-708B0135C0B3}.Release|x86.Build.0 = Release|Any CPU + {24F07173-D4A7-41C6-BA51-4516AB68343C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24F07173-D4A7-41C6-BA51-4516AB68343C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24F07173-D4A7-41C6-BA51-4516AB68343C}.Debug|x86.ActiveCfg = Debug|Any CPU + {24F07173-D4A7-41C6-BA51-4516AB68343C}.Debug|x86.Build.0 = Debug|Any CPU + {24F07173-D4A7-41C6-BA51-4516AB68343C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24F07173-D4A7-41C6-BA51-4516AB68343C}.Release|Any CPU.Build.0 = Release|Any CPU + {24F07173-D4A7-41C6-BA51-4516AB68343C}.Release|x86.ActiveCfg = Release|Any CPU + {24F07173-D4A7-41C6-BA51-4516AB68343C}.Release|x86.Build.0 = Release|Any CPU + {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Debug|x86.ActiveCfg = Debug|Any CPU + {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Debug|x86.Build.0 = Debug|Any CPU + {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Release|Any CPU.Build.0 = Release|Any CPU + {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Release|x86.ActiveCfg = Release|Any CPU + {1D109A96-FADF-41B9-A845-457A7A5C0C5A}.Release|x86.Build.0 = Release|Any CPU + {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|x86.ActiveCfg = Debug|Any CPU + {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Debug|x86.Build.0 = Debug|Any CPU + {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|Any CPU.Build.0 = Release|Any CPU + {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|x86.ActiveCfg = Release|Any CPU + {02CFBD30-D77D-400F-94B2-700F60EFDD7F}.Release|x86.Build.0 = Release|Any CPU + {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|x86.ActiveCfg = Debug|Any CPU + {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Debug|x86.Build.0 = Debug|Any CPU + {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|Any CPU.Build.0 = Release|Any CPU + {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|x86.ActiveCfg = Release|Any CPU + {C1385C67-A7DD-46D2-80F6-FA428B85FB22}.Release|x86.Build.0 = Release|Any CPU + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|x86.ActiveCfg = Debug|Any CPU + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Debug|x86.Build.0 = Debug|Any CPU + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|Any CPU.Build.0 = Release|Any CPU + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|x86.ActiveCfg = Release|Any CPU + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {06245255-E585-483C-A4AD-71B7B568C6C8} + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {02CFBD30-D77D-400F-94B2-700F60EFDD7F} = {DFA3CC6A-A27C-4713-876D-A55912C78C45} + {D16B8DE9-8EE2-4F6A-9352-305D5AB0233D} = {56DD9707-D811-4056-9E2C-8A9CC2479B07} + EndGlobalSection +EndGlobal