]> git.deb.at Git - rhonda/impostor.git/commitdiff
Update recorder and add replayer
authorAeonLucid <aeonlucid@outlook.com>
Thu, 22 Oct 2020 20:15:34 +0000 (22:15 +0200)
committerAeonLucid <aeonlucid@outlook.com>
Thu, 22 Oct 2020 20:15:34 +0000 (22:15 +0200)
30 files changed:
src/Impostor.Api/Games/IGameCodeFactory.cs [new file with mode: 0644]
src/Impostor.Api/Impostor.Api.csproj
src/Impostor.Api/Net/IClient.cs
src/Impostor.Api/Net/IConnection.cs
src/Impostor.Api/Net/IHazelConnection.cs [new file with mode: 0644]
src/Impostor.Server/Net/Client.cs
src/Impostor.Server/Net/ClientBase.Api.cs [deleted file]
src/Impostor.Server/Net/ClientBase.cs
src/Impostor.Server/Net/Factories/ClientFactory.cs
src/Impostor.Server/Net/Factories/IClientFactory.cs
src/Impostor.Server/Net/GameCodeFactory.cs [new file with mode: 0644]
src/Impostor.Server/Net/Hazel/HazelConnection.Api.cs [deleted file]
src/Impostor.Server/Net/Hazel/HazelConnection.cs
src/Impostor.Server/Net/Manager/ClientManager.Api.cs [deleted file]
src/Impostor.Server/Net/Manager/ClientManager.cs
src/Impostor.Server/Net/Manager/GameManager.Api.cs [deleted file]
src/Impostor.Server/Net/Manager/GameManager.cs
src/Impostor.Server/Net/Matchmaker.cs
src/Impostor.Server/Net/State/Game.Outgoing.cs
src/Impostor.Server/Net/State/Game.cs
src/Impostor.Server/Program.cs
src/Impostor.Server/Properties/AssemblyInfo.cs
src/Impostor.Server/Recorder/ClientRecorder.cs
src/Impostor.Server/Recorder/PacketRecorder.cs
src/Impostor.Server/Recorder/RecordedPacketType.cs
src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj [new file with mode: 0644]
src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs [new file with mode: 0644]
src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs [new file with mode: 0644]
src/Impostor.Tools.ServerReplay/Program.cs [new file with mode: 0644]
src/Impostor.sln

diff --git a/src/Impostor.Api/Games/IGameCodeFactory.cs b/src/Impostor.Api/Games/IGameCodeFactory.cs
new file mode 100644 (file)
index 0000000..f264fe0
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Impostor.Api.Games
+{
+    public interface IGameCodeFactory
+    {
+        GameCode Create();
+    }
+}
\ No newline at end of file
index 3ff2516256b9eabf424600d8199b9e62ff33b23a..973c5acd85eff3cf30f742c67dd2f0134defdf63 100644 (file)
@@ -9,9 +9,9 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0"/>
-        <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0-rc.1.20451.14"/>
-        <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0-rc.1.20451.14"/>
+        <PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
+        <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0-rc.1.20451.14" />
+        <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0-rc.1.20451.14" />
         <PackageReference Include="Nullable" Version="1.3.0">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
index 137ace0813c388fb4792917b255557ce04807db1..b882d2a95a082221b3a38c144c6e50d57e480ecb 100644 (file)
@@ -1,4 +1,6 @@
 using System.Collections.Generic;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
 
 namespace Impostor.Api.Net
 {
@@ -30,7 +32,7 @@ namespace Impostor.Api.Net
         /// <remarks>
         ///     Null when the client was not registered by the matchmaker.
         /// </remarks>
-        IConnection? Connection { get; }
+        IHazelConnection? Connection { get; }
 
         /// <summary>
         ///     Gets a value indicating whether the client is a bot.
@@ -56,5 +58,9 @@ namespace Impostor.Api.Net
         ///     Gets or sets the current game data of the <see cref="IClient"/>.
         /// </summary>
         IClientPlayer? Player { get; }
+
+        ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
+
+        ValueTask HandleDisconnectAsync(string reason);
     }
 }
\ No newline at end of file
index 3c8e1f5ee474cd029c18d1f5254ce0d8944d10ff..94f9b8be380f95d6e72b2bae3a436493161fab70 100644 (file)
@@ -1,34 +1,7 @@
-using System.Net;
-using System.Threading.Tasks;
-using Impostor.Api.Net.Messages;
-
-namespace Impostor.Api.Net
+namespace Impostor.Api.Net
 {
-    /// <summary>
-    ///     Represents the connection of the client.
-    /// </summary>
     public interface IConnection
     {
-        /// <summary>
-        ///     Gets the IP endpoint of the client.
-        /// </summary>
-        IPEndPoint EndPoint { get; }
-
-        /// <summary>
-        ///     Gets a value indicating whether the client is connected to the server.
-        /// </summary>
-        bool IsConnected { get; }
-
-        /// <summary>
-        ///     Gets the client of the connection.
-        /// </summary>
-        IClient? Client { get; }
-
-        /// <summary>
-        ///     Sends a message writer to the connection.
-        /// </summary>
-        /// <param name="writer">The message.</param>
-        /// <returns></returns>
-        ValueTask SendAsync(IMessageWriter writer);
+        
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Api/Net/IHazelConnection.cs b/src/Impostor.Api/Net/IHazelConnection.cs
new file mode 100644 (file)
index 0000000..a39e0b5
--- /dev/null
@@ -0,0 +1,34 @@
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Api.Net
+{
+    /// <summary>
+    ///     Represents the connection of the client.
+    /// </summary>
+    public interface IHazelConnection
+    {
+        /// <summary>
+        ///     Gets the IP endpoint of the client.
+        /// </summary>
+        IPEndPoint EndPoint { get; }
+
+        /// <summary>
+        ///     Gets a value indicating whether the client is connected to the server.
+        /// </summary>
+        bool IsConnected { get; }
+
+        /// <summary>
+        ///     Gets the client of the connection.
+        /// </summary>
+        IClient? Client { get; set; }
+
+        /// <summary>
+        ///     Sends a message writer to the connection.
+        /// </summary>
+        /// <param name="writer">The message.</param>
+        /// <returns></returns>
+        ValueTask SendAsync(IMessageWriter writer);
+    }
+}
\ No newline at end of file
index 68068ebb0b91a9b4cd5b9b82571c9ade19ac9ccf..361906bbfdc6e3b2395347974e67acdb03eef252 100644 (file)
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
 using Impostor.Api.Games;
 using Impostor.Api.Innersloth;
 using Impostor.Api.Innersloth.Data;
+using Impostor.Api.Net;
 using Impostor.Api.Net.Messages;
 using Impostor.Api.Net.Messages.C2S;
 using Impostor.Api.Net.Messages.S2C;
@@ -20,7 +21,7 @@ namespace Impostor.Server.Net
         private readonly ClientManager _clientManager;
         private readonly GameManager _gameManager;
 
-        public Client(ILogger<Client> logger, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection)
+        public Client(ILogger<Client> logger, ClientManager clientManager, GameManager gameManager, string name, IHazelConnection connection)
             : base(name, connection)
         {
             _logger = logger;
@@ -148,7 +149,7 @@ namespace Impostor.Server.Net
                     var readerCopy = reader.Slice(reader.Position);
 
                     // TODO: Return value, either a bool (to cancel) or a writer (to cancel (null) or modify/overwrite).
-                    await Player.Game.HandleGameData(readerCopy, Player, toPlayer);
+                    // await Player.Game.HandleGameData(readerCopy, Player, toPlayer);
 
                     // Broadcast packet to all other players.
                     using (var writer = MessageWriter.Get(messageType))
diff --git a/src/Impostor.Server/Net/ClientBase.Api.cs b/src/Impostor.Server/Net/ClientBase.Api.cs
deleted file mode 100644 (file)
index 4b34987..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-using Impostor.Api.Net;
-
-namespace Impostor.Server.Net
-{
-    internal abstract partial class ClientBase : IClient
-    {
-        IConnection IClient.Connection => Connection;
-
-        IClientPlayer IClient.Player => Player;
-    }
-}
\ No newline at end of file
index 9ca4d36f34e6d45b551e9bac1b18ec1473c2d88e..de23a39d0dd12a55f9b520f61cc1bed85a37822e 100644 (file)
@@ -1,15 +1,15 @@
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Threading.Tasks;
+using Impostor.Api.Net;
 using Impostor.Api.Net.Messages;
-using Impostor.Server.Net.Hazel;
 using Impostor.Server.Net.State;
 
 namespace Impostor.Server.Net
 {
-    internal abstract partial class ClientBase
+    internal abstract class ClientBase : IClient
     {
-        protected ClientBase(string name, HazelConnection connection)
+        protected ClientBase(string name, IHazelConnection connection)
         {
             Name = name;
             Connection = connection;
@@ -20,7 +20,7 @@ namespace Impostor.Server.Net
 
         public string Name { get; }
 
-        public HazelConnection Connection { get; }
+        public IHazelConnection Connection { get; }
 
         public bool IsBot => false;
 
@@ -28,6 +28,8 @@ namespace Impostor.Server.Net
 
         public ClientPlayer Player { get; set; }
 
+        IClientPlayer IClient.Player => Player;
+
         public abstract ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
 
         public abstract ValueTask HandleDisconnectAsync(string reason);
index 632781b39daf3a2b4f350617bd2c5b20f25373ae..c04044cd5847a31a3ff6d0b27d9e39371aa1d422 100644 (file)
@@ -1,4 +1,5 @@
 using System;
+using Impostor.Api.Net;
 using Impostor.Server.Net.Hazel;
 using Microsoft.Extensions.DependencyInjection;
 
@@ -14,7 +15,7 @@ namespace Impostor.Server.Net.Factories
             _serviceProvider = serviceProvider;
         }
 
-        public ClientBase Create(HazelConnection connection, string name, int clientVersion)
+        public ClientBase Create(IHazelConnection connection, string name, int clientVersion)
         {
             var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, connection);
             connection.Client = client;
index f090f749d0e0942e5711a61db7ecc76b9b12ecb0..6859ae3c950dc9dc3cdc1ec2b956fdf5f95a1693 100644 (file)
@@ -1,9 +1,9 @@
-using Impostor.Server.Net.Hazel;
+using Impostor.Api.Net;
 
 namespace Impostor.Server.Net.Factories
 {
     internal interface IClientFactory
     {
-        ClientBase Create(HazelConnection connection, string name, int clientVersion);
+        ClientBase Create(IHazelConnection connection, string name, int clientVersion);
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/GameCodeFactory.cs b/src/Impostor.Server/Net/GameCodeFactory.cs
new file mode 100644 (file)
index 0000000..2a0553f
--- /dev/null
@@ -0,0 +1,12 @@
+using Impostor.Api.Games;
+
+namespace Impostor.Server.Net
+{
+    public class GameCodeFactory : IGameCodeFactory
+    {
+        public GameCode Create()
+        {
+            return GameCode.Create();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Hazel/HazelConnection.Api.cs b/src/Impostor.Server/Net/Hazel/HazelConnection.Api.cs
deleted file mode 100644 (file)
index c7056d2..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-using Impostor.Api.Net;
-
-namespace Impostor.Server.Net.Hazel
-{
-    internal partial class HazelConnection : IConnection
-    {
-        IClient IConnection.Client => Client;
-    }
-}
\ No newline at end of file
index a1520b8cd029ca8b322354150435142915427d8e..3e92594de634f2a4b77d4494fbf9f79326d092d8 100644 (file)
@@ -1,12 +1,13 @@
 using System.Net;
 using System.Threading.Tasks;
+using Impostor.Api.Net;
 using Impostor.Api.Net.Messages;
 using Impostor.Hazel;
 using Microsoft.Extensions.Logging;
 
 namespace Impostor.Server.Net.Hazel
 {
-    internal partial class HazelConnection
+    internal class HazelConnection : IHazelConnection
     {
         private readonly ILogger<HazelConnection> _logger;
 
@@ -24,7 +25,7 @@ namespace Impostor.Server.Net.Hazel
 
         public bool IsConnected => InnerConnection.State == ConnectionState.Connected;
 
-        public ClientBase Client { get; set; }
+        public IClient Client { get; set; }
 
         public ValueTask SendAsync(IMessageWriter writer)
         {
diff --git a/src/Impostor.Server/Net/Manager/ClientManager.Api.cs b/src/Impostor.Server/Net/Manager/ClientManager.Api.cs
deleted file mode 100644 (file)
index 6cbe5bf..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Collections.Generic;
-using Impostor.Api.Net;
-using Impostor.Api.Net.Manager;
-
-namespace Impostor.Server.Net.Manager
-{
-    internal partial class ClientManager : IClientManager
-    {
-        IEnumerable<IClient> IClientManager.Clients => _clients.Values;
-    }
-}
\ No newline at end of file
index 31f0a731179178b898dcc2b2600abfd7ea31dcc7..3c4e8e80a1aef39bd4f300bdc084c5716ca4de37 100644 (file)
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
 using Impostor.Api.Innersloth;
 using Impostor.Api.Innersloth.Data;
 using Impostor.Api.Net;
+using Impostor.Api.Net.Manager;
 using Impostor.Api.Net.Messages;
 using Impostor.Api.Net.Messages.S2C;
 using Impostor.Hazel;
@@ -15,7 +16,7 @@ using Microsoft.Extensions.Logging;
 
 namespace Impostor.Server.Net.Manager
 {
-    internal partial class ClientManager
+    internal class ClientManager : IClientManager
     {
         public static HashSet<int> SupportedVersions { get; } = new HashSet<int>
         {
@@ -37,6 +38,8 @@ namespace Impostor.Server.Net.Manager
 
         public IEnumerable<ClientBase> Clients => _clients.Values;
 
+        IEnumerable<IClient> IClientManager.Clients => _clients.Values;
+
         public int NextId()
         {
             var clientId = Interlocked.Increment(ref _idLast);
@@ -53,7 +56,7 @@ namespace Impostor.Server.Net.Manager
             return clientId;
         }
 
-        public async ValueTask RegisterConnectionAsync(HazelConnection connection, string name, int clientVersion)
+        public async ValueTask RegisterConnectionAsync(IHazelConnection connection, string name, int clientVersion)
         {
             if (name.Length > 10)
             {
diff --git a/src/Impostor.Server/Net/Manager/GameManager.Api.cs b/src/Impostor.Server/Net/Manager/GameManager.Api.cs
deleted file mode 100644 (file)
index 8ddb19b..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Impostor.Api.Games;
-using Impostor.Api.Games.Managers;
-
-namespace Impostor.Server.Net.Manager
-{
-    internal partial class GameManager : IGameManager
-    {
-        IEnumerable<IGame> IGameManager.Games => _games.Select(kv => kv.Value);
-
-        IGame IGameManager.Find(GameCode code) => Find(code);
-    }
-}
\ No newline at end of file
index a47fc9d1da55d636bc96814607bc458b1527a028..bc65c250031cb84e44eff8ee26a30b8332f8554f 100644 (file)
@@ -8,6 +8,7 @@ using Impostor.Api;
 using Impostor.Api.Events;
 using Impostor.Api.Events.Managers;
 using Impostor.Api.Games;
+using Impostor.Api.Games.Managers;
 using Impostor.Api.Innersloth;
 using Impostor.Api.Innersloth.Data;
 using Impostor.Server.Data;
@@ -20,7 +21,7 @@ using Microsoft.Extensions.Options;
 
 namespace Impostor.Server.Net.Manager
 {
-    internal partial class GameManager
+    internal class GameManager : IGameManager
     {
         private readonly ILogger<GameManager> _logger;
         private readonly INodeLocator _nodeLocator;
@@ -28,18 +29,24 @@ namespace Impostor.Server.Net.Manager
         private readonly ConcurrentDictionary<int, Game> _games;
         private readonly IServiceProvider _serviceProvider;
         private readonly IEventManager _eventManager;
+        private readonly IGameCodeFactory _gameCodeFactory;
 
-        public GameManager(ILogger<GameManager> logger, IOptions<ServerConfig> config, INodeLocator nodeLocator, IServiceProvider serviceProvider, IEventManager eventManager)
+        public GameManager(ILogger<GameManager> logger, IOptions<ServerConfig> config, INodeLocator nodeLocator, IServiceProvider serviceProvider, IEventManager eventManager, IGameCodeFactory gameCodeFactory)
         {
             _logger = logger;
             _nodeLocator = nodeLocator;
             _serviceProvider = serviceProvider;
             _eventManager = eventManager;
+            _gameCodeFactory = gameCodeFactory;
             _publicIp = new IPEndPoint(IPAddress.Parse(config.Value.PublicIp), config.Value.PublicPort);
             _games = new ConcurrentDictionary<int, Game>();
         }
 
-        public async ValueTask<Game> CreateAsync(GameOptionsData options)
+        IEnumerable<IGame> IGameManager.Games => _games.Select(kv => kv.Value);
+
+        IGame IGameManager.Find(GameCode code) => Find(code);
+
+        public async ValueTask<IGame> CreateAsync(GameOptionsData options)
         {
             // TODO: Prevent duplicates when using server redirector using INodeProvider.
             var (success, game) = await TryCreateAsync(options);
@@ -57,9 +64,9 @@ namespace Impostor.Server.Net.Manager
             return game;
         }
 
-        public async ValueTask<(bool success, Game game)> TryCreateAsync(GameOptionsData options)
+        private async ValueTask<(bool success, Game game)> TryCreateAsync(GameOptionsData options)
         {
-            var gameCode = GameCode.Create();
+            var gameCode = _gameCodeFactory.Create();
             var gameCodeStr = gameCode.Code;
             var game = ActivatorUtilities.CreateInstance<Game>(_serviceProvider, _publicIp, gameCode, options);
 
index 54a05811baa418a5cc657e8bf27b234263ff4156..1c1bcf39f0be4fe7082e3fa234eee0d1d901f40a 100644 (file)
@@ -4,6 +4,7 @@ using System.Net.Sockets;
 using System.Threading.Tasks;
 using Impostor.Hazel;
 using Impostor.Hazel.Udp;
+using Impostor.Server.Net.Hazel;
 using Impostor.Server.Net.Manager;
 using Microsoft.Extensions.Logging;
 
@@ -19,7 +20,7 @@ namespace Impostor.Server.Net
         public Matchmaker(
             ILogger<Matchmaker> logger,
             ClientManager clientManager,
-            ILogger<Net.Hazel.HazelConnection> connectionLogger)
+            ILogger<HazelConnection> connectionLogger)
         {
             _logger = logger;
             _clientManager = clientManager;
@@ -52,7 +53,7 @@ namespace Impostor.Server.Net
             var clientVersion = e.HandshakeData.ReadInt32();
             var name = e.HandshakeData.ReadString();
 
-            var connection = new Net.Hazel.HazelConnection(e.Connection, _connectionLogger);
+            var connection = new HazelConnection(e.Connection, _connectionLogger);
 
             // Register client
             await _clientManager.RegisterConnectionAsync(connection, name, clientVersion);
index 14c493b0efd26e85c94257bb9d60c6980e89d95b..57e3a623eb0f26dad2e0990b5e95a20f6ac65473 100644 (file)
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
 using System.Threading.Tasks;
 using Impostor.Api.Innersloth.Data;
 using Impostor.Api.Net;
@@ -30,9 +31,9 @@ namespace Impostor.Server.Net.State
 
         public async ValueTask SendToAsync(IMessageWriter writer, int id)
         {
-            if (TryGetPlayer(id, out var player) && player.Client.Connection is HazelConnection hazelConnection)
+            if (TryGetPlayer(id, out var player))
             {
-                await hazelConnection.InnerConnection.SendAsync(writer);
+                await player.Client.Connection.SendAsync(writer);
             }
         }
 
index 5aa792543f8acd5ffb94b070262ae6409c309e97..2110c0442388fc3d2fb329de375c18b4bd11ab27 100644 (file)
@@ -27,7 +27,6 @@ namespace Impostor.Server.Net.State
         private readonly IServiceProvider _serviceProvider;
         private readonly GameManager _gameManager;
         private readonly ClientManager _clientManager;
-        private readonly Matchmaker _matchmaker;
         private readonly ConcurrentDictionary<int, ClientPlayer> _players;
         private readonly HashSet<IPAddress> _bannedIps;
         private readonly IEventManager _eventManager;
@@ -35,11 +34,9 @@ namespace Impostor.Server.Net.State
         public Game(
             IServiceProvider serviceProvider,
             GameManager gameManager,
-            INodeLocator nodeLocator,
             IPEndPoint publicIp,
             GameCode code,
             GameOptionsData options,
-            Matchmaker matchmaker,
             ClientManager clientManager,
             IEventManager eventManager)
         {
@@ -53,7 +50,6 @@ namespace Impostor.Server.Net.State
             HostId = -1;
             GameState = GameStates.NotStarted;
             Options = options;
-            _matchmaker = matchmaker;
             _clientManager = clientManager;
             _eventManager = eventManager;
             Items = new ConcurrentDictionary<object, object>();
@@ -103,13 +99,11 @@ namespace Impostor.Server.Net.State
             return SendToAllExceptAsync(message, player.Client.Id);
         }
 
-        private IEnumerable<Connection> GetConnections(Func<IClientPlayer, bool> filter)
+        private IEnumerable<IHazelConnection> GetConnections(Func<IClientPlayer, bool> filter)
         {
             return Players
                 .Where(filter)
-                .Select(p => p.Client.Connection)
-                .OfType<HazelConnection>()
-                .Select(c => c.InnerConnection);
+                .Select(p => p.Client.Connection);
         }
     }
 }
\ No newline at end of file
index f85e23d6b5f94bcc5b2f987866400bfd394a08fc..b3f8279e586ee1a77bb0efc9aed2784effc209bb 100644 (file)
@@ -1,5 +1,6 @@
 using System;
 using Impostor.Api.Events.Managers;
+using Impostor.Api.Games;
 using Impostor.Api.Games.Managers;
 using Impostor.Api.Net.Manager;
 using Impostor.Server.Data;
@@ -169,6 +170,7 @@ namespace Impostor.Server
                         services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
                     }
 
+                    services.AddSingleton<IGameCodeFactory, GameCodeFactory>();
                     services.AddSingleton<IEventManager, EventManager>();
                     services.AddSingleton<Matchmaker>();
                     services.AddHostedService<MatchmakerService>();
index eb567801bd83b5132dff66030a6e7de76d4bc368..99d6f86e8473ce101a39f266610bf34ae72530a6 100644 (file)
@@ -1,3 +1,4 @@
 using System.Runtime.CompilerServices;
 
-[assembly:InternalsVisibleTo("Impostor.Tests")]
\ No newline at end of file
+[assembly:InternalsVisibleTo("Impostor.Tests")]
+[assembly:InternalsVisibleTo("Impostor.Tools.ServerReplay")]
\ No newline at end of file
index a03be754acbdd7a22fb782a7c84ea08eb32efe62..b41a687b1ab7ee864f25aac6cd2d1aa0ea18526f 100644 (file)
@@ -10,17 +10,53 @@ namespace Impostor.Server.Recorder
     internal class ClientRecorder : Client
     {
         private readonly PacketRecorder _recorder;
+        private bool _isFirst;
+        private bool _createdGame;
 
         public ClientRecorder(ILogger<Client> logger, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection, PacketRecorder recorder)
             : base(logger, clientManager, gameManager, name, connection)
         {
             _recorder = recorder;
+            _isFirst = true;
+            _createdGame = false;
         }
 
         public override async ValueTask HandleMessageAsync(IMessageReader reader, MessageType messageType)
         {
-            await _recorder.WriteMessageAsync(this, reader.Tag, reader.Buffer);
+            var messageCopy = reader.Slice(0);
+
+            // Trigger connect event.
+            if (_isFirst)
+            {
+                _isFirst = false;
+
+                await _recorder.WriteConnectAsync(this);
+            }
+
+            // Check if we were in-game before handling the message.
+            var inGame = Player?.Game != null;
+
+            // Handle the message.
             await base.HandleMessageAsync(reader, messageType);
+
+            // Player created a game.
+            if (reader.Tag == MessageFlags.HostGame)
+            {
+                _createdGame = true;
+            }
+            else if (reader.Tag == MessageFlags.JoinGame && _createdGame)
+            {
+                _createdGame = false;
+
+                // We created a game and are now in-game, stored that event.
+                if (!inGame && Player?.Game != null)
+                {
+                    await _recorder.WriteGameCreatedAsync(this, Player.Game.Code);
+                }
+            }
+
+            // Trigger message event.
+            await _recorder.WriteMessageAsync(this, messageCopy, messageType);
         }
 
         public override async ValueTask HandleDisconnectAsync(string reason)
index a949e5835af24d6ebd2ff49fa5ff67a2f0a76d27..23be21a5b64ee8d76fe4375f7f8b2743cad2232c 100644 (file)
@@ -2,6 +2,8 @@
 using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
+using Impostor.Api.Games;
+using Impostor.Api.Net.Messages;
 using Impostor.Server.Data;
 using Impostor.Server.Net;
 using Microsoft.Extensions.Logging;
@@ -32,17 +34,16 @@ namespace Impostor.Server.Recorder
             _writer = File.Open(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
         }
 
-        public async Task WriteMessageAsync(ClientRecorder client, byte tag, ReadOnlyMemory<byte> buffer)
+        public async Task WriteConnectAsync(ClientRecorder client)
         {
-            _logger.LogTrace("Writing Message.");
+            _logger.LogTrace("Writing Connect.");
 
             var context = _pool.Get();
 
             try
             {
-                WriteHeader(context, RecordedPacketType.Message);
-                WriteClient(context, client);
-                WritePacket(context, tag, buffer.Span);
+                WriteHeader(context, RecordedPacketType.Connect);
+                WriteClient(context, client, true);
                 WriteLength(context);
 
                 await WriteAsync(context.Stream);
@@ -62,7 +63,49 @@ namespace Impostor.Server.Recorder
             try
             {
                 WriteHeader(context, RecordedPacketType.Disconnect);
-                WriteClient(context, client);
+                WriteClient(context, client, false);
+                WriteLength(context);
+
+                await WriteAsync(context.Stream);
+            }
+            finally
+            {
+                _pool.Return(context);
+            }
+        }
+
+        public async Task WriteMessageAsync(ClientRecorder client, IMessageReader reader, MessageType messageType)
+        {
+            _logger.LogTrace("Writing Message.");
+
+            var context = _pool.Get();
+
+            try
+            {
+                WriteHeader(context, RecordedPacketType.Message);
+                WriteClient(context, client, false);
+                WritePacket(context, reader, messageType);
+                WriteLength(context);
+
+                await WriteAsync(context.Stream);
+            }
+            finally
+            {
+                _pool.Return(context);
+            }
+        }
+
+        public async Task WriteGameCreatedAsync(ClientRecorder client, GameCode gameCode)
+        {
+            _logger.LogTrace("Writing GameCreated {0}.", gameCode);
+
+            var context = _pool.Get();
+
+            try
+            {
+                WriteHeader(context, RecordedPacketType.GameCreated);
+                WriteClient(context, client, false);
+                WriteGameCode(context, gameCode);
                 WriteLength(context);
 
                 await WriteAsync(context.Stream);
@@ -80,20 +123,32 @@ namespace Impostor.Server.Recorder
             context.Writer.Write((byte) type);
         }
 
-        private static void WriteClient(PacketSerializationContext context, ClientBase client)
+        private static void WriteClient(PacketSerializationContext context, ClientBase client, bool full)
         {
             var addressBytes = client.Connection.EndPoint.Address.GetAddressBytes();
 
-            context.Writer.Write((byte) addressBytes.Length);
-            context.Writer.Write(addressBytes);
-            context.Writer.Write((ushort) client.Connection.EndPoint.Port);
+            context.Writer.Write(client.Id);
+
+            if (full)
+            {
+                context.Writer.Write((byte) addressBytes.Length);
+                context.Writer.Write(addressBytes);
+                context.Writer.Write((ushort) client.Connection.EndPoint.Port);
+                context.Writer.Write(client.Name);
+            }
+        }
+
+        private static void WritePacket(PacketSerializationContext context, IMessageReader reader, MessageType messageType)
+        {
+            context.Writer.Write((byte) messageType);
+            context.Writer.Write((byte) reader.Tag);
+            context.Writer.Write((int) reader.Buffer.Length);
+            context.Writer.Write(reader.Buffer.Span);
         }
 
-        private static void WritePacket(PacketSerializationContext context, byte tag, ReadOnlySpan<byte> buffer)
+        private static void WriteGameCode(PacketSerializationContext context, in GameCode gameCode)
         {
-            context.Writer.Write((byte) tag);
-            context.Writer.Write((int) buffer.Length);
-            context.Writer.Write(buffer);
+            context.Writer.Write(gameCode.Code);
         }
 
         private static void WriteLength(PacketSerializationContext context)
index 24e645e1e2edfeba9388fa5218a39a1c0a9e4689..a8a20bca2a994ab154a725af5d3e0a6f2a99ff21 100644 (file)
@@ -2,7 +2,9 @@
 {
     internal enum RecordedPacketType : byte
     {
-        Message = 1,
+        Connect = 1,
         Disconnect = 2,
+        Message = 3,
+        GameCreated = 4
     }
 }
\ No newline at end of file
diff --git a/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj b/src/Impostor.Tools.ServerReplay/Impostor.Tools.ServerReplay.csproj
new file mode 100644 (file)
index 0000000..53ce7bf
--- /dev/null
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net5.0</TargetFramework>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\Impostor.Server\Impostor.Server.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
+    </ItemGroup>
+
+</Project>
diff --git a/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs b/src/Impostor.Tools.ServerReplay/Mocks/MockGameCodeFactory.cs
new file mode 100644 (file)
index 0000000..1111b7e
--- /dev/null
@@ -0,0 +1,14 @@
+using Impostor.Api.Games;
+
+namespace Impostor.Tools.ServerReplay.Mocks
+{
+    public class MockGameCodeFactory : IGameCodeFactory
+    {
+        public GameCode Result { get; set; }
+
+        public GameCode Create()
+        {
+            return Result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs b/src/Impostor.Tools.ServerReplay/Mocks/MockHazelConnection.cs
new file mode 100644 (file)
index 0000000..941fdb1
--- /dev/null
@@ -0,0 +1,26 @@
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+
+namespace Impostor.Tools.ServerReplay.Mocks
+{
+    public class MockHazelConnection : IHazelConnection
+    {
+        public MockHazelConnection(IPEndPoint endPoint)
+        {
+            EndPoint = endPoint;
+            IsConnected = true;
+            Client = null;
+        }
+
+        public IPEndPoint EndPoint { get; }
+        public bool IsConnected { get; }
+        public IClient? Client { get; set; }
+
+        public ValueTask SendAsync(IMessageWriter writer)
+        {
+            return ValueTask.CompletedTask;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Tools.ServerReplay/Program.cs b/src/Impostor.Tools.ServerReplay/Program.cs
new file mode 100644 (file)
index 0000000..adb9b19
--- /dev/null
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using Impostor.Api.Events.Managers;
+using Impostor.Api.Games;
+using Impostor.Api.Games.Managers;
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Messages;
+using Impostor.Api.Net.Messages.C2S;
+using Impostor.Hazel;
+using Impostor.Server.Events;
+using Impostor.Server.Net;
+using Impostor.Server.Net.Factories;
+using Impostor.Server.Net.Manager;
+using Impostor.Server.Net.Redirector;
+using Impostor.Server.Recorder;
+using Impostor.Tools.ServerReplay.Mocks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Serilog;
+using ILogger = Serilog.ILogger;
+
+namespace Impostor.Tools.ServerReplay
+{
+    internal static class Program
+    {
+        private const string GameRecorderPath = "C:\\DevData\\ImpostorSessions";
+
+        private static readonly ILogger Logger = Log.ForContext(typeof(Program));
+        private static readonly Dictionary<int, IHazelConnection> Connections = new Dictionary<int, IHazelConnection>();
+        private static readonly Dictionary<int, GameOptionsData> GameOptions = new Dictionary<int, GameOptionsData>();
+
+        private static ServiceProvider _serviceProvider;
+
+        private static MockGameCodeFactory _gameCodeFactory;
+        private static ClientManager _clientManager;
+        private static GameManager _gameManager;
+
+        private static async Task Main(string[] args)
+        {
+            Log.Logger = new LoggerConfiguration()
+                .MinimumLevel.Verbose()
+                .WriteTo.Console()
+                .CreateLogger();
+
+            // Create service provider.
+            _serviceProvider = BuildServices();
+
+            // Create required instances.
+            _gameCodeFactory = _serviceProvider.GetRequiredService<MockGameCodeFactory>();
+            _clientManager = _serviceProvider.GetRequiredService<ClientManager>();
+            _gameManager = _serviceProvider.GetRequiredService<GameManager>();
+
+            foreach (var file in Directory.GetFiles(GameRecorderPath))
+            {
+                await using (var stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
+                using (var reader = new BinaryReader(stream))
+                {
+                    await ParseSession(reader);
+                }
+            }
+        }
+
+        private static ServiceProvider BuildServices()
+        {
+            var services = new ServiceCollection();
+
+            services.AddLogging(builder =>
+            {
+                builder.ClearProviders();
+                builder.AddSerilog();
+            });
+
+            services.AddSingleton<GameManager>();
+            services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
+
+            services.AddSingleton<MockGameCodeFactory>();
+            services.AddSingleton<IGameCodeFactory>(p => p.GetRequiredService<MockGameCodeFactory>());
+
+            services.AddSingleton<ClientManager>();
+            services.AddSingleton<IClientFactory, ClientFactory<Client>>();
+            services.AddSingleton<INodeLocator, NodeLocatorNoOp>();
+            services.AddSingleton<IEventManager, EventManager>();
+
+            return services.BuildServiceProvider();
+        }
+
+        private static async Task ParseSession(BinaryReader reader)
+        {
+            while (reader.BaseStream.Position < reader.BaseStream.Length)
+            {
+                var dataLength = reader.ReadInt32();
+                var data = reader.ReadBytes(dataLength - 4);
+
+                await using (var stream = new MemoryStream(data))
+                using (var readerInner = new BinaryReader(stream))
+                {
+                    await ParsePacket(readerInner);
+                }
+            }
+        }
+
+        private static async Task ParsePacket(BinaryReader reader)
+        {
+            var dataType = (RecordedPacketType) reader.ReadByte();
+
+            // Read client id.
+            var clientId = reader.ReadInt32();
+
+            switch (dataType)
+            {
+                case RecordedPacketType.Connect:
+                    // Read data.
+                    var addressLength = reader.ReadByte();
+                    var addressBytes = reader.ReadBytes(addressLength);
+                    var addressPort = reader.ReadUInt16();
+                    var address = new IPEndPoint(new IPAddress(addressBytes), addressPort);
+                    var name = reader.ReadString();
+
+                    // Create and register connection.
+                    var connection = new MockHazelConnection(address);
+
+                    await _clientManager.RegisterConnectionAsync(connection, name, 50516550);
+
+                    // Store reference for ourselfs.
+                    Connections.Add(clientId, connection);
+                    break;
+
+                case RecordedPacketType.Disconnect:
+                    await Connections[clientId].Client!.HandleDisconnectAsync(null);
+                    Connections.Remove(clientId);
+                    break;
+
+                case RecordedPacketType.Message:
+                    var messageType = (MessageType) reader.ReadByte();
+                    var tag = reader.ReadByte();
+                    var length = reader.ReadInt32();
+                    var buffer = reader.ReadBytes(length);
+                    var message = new MessageReader(tag, buffer);
+
+                    if (tag == MessageFlags.HostGame)
+                    {
+                        GameOptions.Add(clientId, Message00HostGameC2S.Deserialize(message));
+                    }
+                    else
+                    {
+                        await Connections[clientId].Client!.HandleMessageAsync(message, messageType);
+                    }
+                    break;
+
+                case RecordedPacketType.GameCreated:
+                    _gameCodeFactory.Result = GameCode.From(reader.ReadString());
+
+                    await _gameManager.CreateAsync(GameOptions[clientId]);
+
+                    GameOptions.Remove(clientId);
+                    break;
+
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+        }
+    }
+}
\ No newline at end of file
index 3d60abbcaf4e6edbd533f2d183ecd3a410ad0fde..c04c378310f1fe4b14a1e00abc6dd25c6026242a 100644 (file)
@@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Client", "Impostor
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Client.App", "Impostor.Client.App\Impostor.Client.App.csproj", "{3DF86F12-7099-44F6-B98B-A148213D60B1}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impostor.Tools.ServerReplay", "Impostor.Tools.ServerReplay\Impostor.Tools.ServerReplay.csproj", "{4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -125,6 +127,14 @@ Global
                {3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|Any CPU.Build.0 = Release|Any CPU
                {3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|x86.ActiveCfg = Release|Any CPU
                {3DF86F12-7099-44F6-B98B-A148213D60B1}.Release|x86.Build.0 = Release|Any CPU
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Debug|x86.Build.0 = Debug|Any CPU
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|Any CPU.Build.0 = Release|Any CPU
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|x86.ActiveCfg = Release|Any CPU
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99}.Release|x86.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
@@ -140,5 +150,6 @@ Global
                {ECBCAA3B-B974-41CF-AFFC-6F5AA4C42FA7} = {36AA9913-E6EA-4A6C-90E6-2FD3CC2E3124}
                {BE44C4A8-A202-4B63-A3BA-AC0AB5E7A0EB} = {9F1919B0-915B-4749-9944-697DF7E7F67F}
                {3DF86F12-7099-44F6-B98B-A148213D60B1} = {9F1919B0-915B-4749-9944-697DF7E7F67F}
+               {4DB56ADD-6D3D-4D0E-A047-9D7E7D40EF99} = {56DD9707-D811-4056-9E2C-8A9CC2479B07}
        EndGlobalSection
 EndGlobal