]> git.deb.at Git - rhonda/impostor.git/commitdiff
Add DateTime to server replays (#349)
authorjs6pak <kubastaron@hotmail.com>
Sun, 14 Mar 2021 20:50:55 +0000 (20:50 +0000)
committerGitHub <noreply@github.com>
Sun, 14 Mar 2021 20:50:55 +0000 (20:50 +0000)
17 files changed:
src/.editorconfig
src/Impostor.Api/IDateTimeProvider.cs [new file with mode: 0644]
src/Impostor.Server/Net/Client.cs
src/Impostor.Server/Net/ClientBase.cs
src/Impostor.Server/Net/Factories/ClientFactory.cs
src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs
src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs
src/Impostor.Server/Net/Redirector/ClientRedirector.cs
src/Impostor.Server/Program.cs
src/Impostor.Server/RealDateTimeProvider.cs [new file with mode: 0644]
src/Impostor.Server/Recorder/ClientRecorder.cs
src/Impostor.Server/Recorder/PacketRecorder.cs
src/Impostor.Server/Recorder/ServerReplayVersion.cs [new file with mode: 0644]
src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs [new file with mode: 0644]
src/Impostor.Tools.ServerReplay/Program.cs
src/Impostor.Tools.ServerReplay/sessions/.gitkeep [new file with mode: 0644]
src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat [deleted file]

index af97d16c25df14bfbea99dca2d552581126af613..38c708527c4be62ff20ebba34c54bc1aaebaee21 100644 (file)
@@ -163,7 +163,7 @@ csharp_new_line_before_open_brace = all
 csharp_new_line_before_else = true
 csharp_new_line_before_catch = true
 csharp_new_line_before_finally = true
-csharp_new_line_before_members_in_object_initializers = true
+# csharp_new_line_before_members_in_object_initializers = true TODO seems like Rider/ReSharper has the value inverted, uncomment when its fixed
 csharp_new_line_before_members_in_anonymous_types = true
 csharp_new_line_between_query_expression_clauses = true
 
@@ -228,3 +228,6 @@ csharp_preserve_single_line_statements = true
 
 # warning RS0037: PublicAPI.txt is missing '#nullable enable'
 dotnet_diagnostic.RS0037.severity = none
+
+# ReSharper properties
+resharper_trailing_comma_in_multiline_lists = true
diff --git a/src/Impostor.Api/IDateTimeProvider.cs b/src/Impostor.Api/IDateTimeProvider.cs
new file mode 100644 (file)
index 0000000..95b3dee
--- /dev/null
@@ -0,0 +1,9 @@
+using System;
+
+namespace Impostor.Api
+{
+    public interface IDateTimeProvider
+    {
+        DateTimeOffset UtcNow { get; }
+    }
+}
index 713ef8831551df9b695a140d2ccc83a80015981f..f26de4026497d67c65abeacd491c02b643aef02b 100644 (file)
@@ -24,8 +24,8 @@ namespace Impostor.Server.Net
         private readonly ClientManager _clientManager;
         private readonly GameManager _gameManager;
 
-        public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, IHazelConnection connection, ISet<Mod> mods)
-            : base(name, connection, mods)
+        public Client(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, int gameVersion, IHazelConnection connection, ISet<Mod> mods)
+            : base(name, gameVersion, connection, mods)
         {
             _logger = logger;
             _antiCheatConfig = antiCheatOptions.Value;
index bae1fb33f72b77b91557ebf62bb827c4d66988d1..7bff06efee2a74f861ba43f5886937d817a865be 100644 (file)
@@ -16,9 +16,10 @@ namespace Impostor.Server.Net
 {
     internal abstract class ClientBase : IClient
     {
-        protected ClientBase(string name, IHazelConnection connection, ISet<Mod> mods)
+        protected ClientBase(string name, int gameVersion, IHazelConnection connection, ISet<Mod> mods)
         {
             Name = name;
+            GameVersion = gameVersion;
             Connection = connection;
             Mods = mods;
             Items = new ConcurrentDictionary<object, object>();
@@ -40,6 +41,8 @@ namespace Impostor.Server.Net
 
         public string Name { get; }
 
+        public int GameVersion { get; }
+
         public ISet<Mod> Mods { get; }
 
         public Dictionary<int, string> ModIdMap { get; }
index ab6eedf615b49c353ca38deac3633cef2861834b..985ab4e0ecdeb438ea811b1d2141c740c25259b4 100644 (file)
@@ -18,9 +18,9 @@ namespace Impostor.Server.Net.Factories
 
         public ClientBase Create(IHazelConnection connection, string name, int clientVersion, ISet<Mod> mods)
         {
-            var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, connection, mods);
+            var client = ActivatorUtilities.CreateInstance<TClient>(_serviceProvider, name, clientVersion, connection, mods);
             connection.Client = client;
             return client;
         }
     }
-}
\ No newline at end of file
+}
index c2759139ae8985ac8246552498fbe28f267c5aff..8391ed08856bd0f3705be853ba3a7ac19cb06d13 100644 (file)
@@ -13,7 +13,6 @@ using Impostor.Api.Net.Messages.Rpcs;
 using Impostor.Server.Events.Player;
 using Impostor.Server.Net.Inner.Objects.Components;
 using Impostor.Server.Net.State;
-using Impostor.Server.Utils;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 
@@ -24,14 +23,14 @@ namespace Impostor.Server.Net.Inner.Objects
         private readonly ILogger<InnerPlayerControl> _logger;
         private readonly IEventManager _eventManager;
         private readonly Game _game;
-        private readonly ServerEnvironment _serverEnvironment;
+        private readonly IDateTimeProvider _dateTimeProvider;
 
-        public InnerPlayerControl(ILogger<InnerPlayerControl> logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game, ServerEnvironment serverEnvironment)
+        public InnerPlayerControl(ILogger<InnerPlayerControl> logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game, IDateTimeProvider dateTimeProvider)
         {
             _logger = logger;
             _eventManager = eventManager;
             _game = game;
-            _serverEnvironment = serverEnvironment;
+            _dateTimeProvider = dateTimeProvider;
 
             Physics = ActivatorUtilities.CreateInstance<InnerPlayerPhysics>(serviceProvider, this, _eventManager, _game);
             NetworkTransform = ActivatorUtilities.CreateInstance<InnerCustomNetworkTransform>(serviceProvider, this, _game);
@@ -520,8 +519,7 @@ namespace Impostor.Server.Net.Inner.Objects
 
         private async ValueTask<bool> HandleMurderPlayer(ClientPlayer sender, IInnerPlayerControl? target)
         {
-            // TODO record replay with timestamps
-            if (!_serverEnvironment.IsReplay && !PlayerInfo.CanMurder(_game))
+            if (!PlayerInfo.CanMurder(_game, _dateTimeProvider))
             {
                 if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder too fast"))
                 {
@@ -537,7 +535,7 @@ namespace Impostor.Server.Net.Inner.Objects
                 }
             }
 
-            PlayerInfo.LastMurder = DateTimeOffset.UtcNow;
+            PlayerInfo.LastMurder = _dateTimeProvider.UtcNow;
 
             if (!target.PlayerInfo.IsDead)
             {
index 410b4af610e8381092702f485f1b0b4efbcfe33f..301fc08815189dd17fc0d4efa102e3542394623d 100644 (file)
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using Impostor.Api;
 using Impostor.Api.Games;
 using Impostor.Api.Innersloth;
 using Impostor.Api.Innersloth.Customization;
@@ -40,14 +41,14 @@ namespace Impostor.Server.Net.Inner.Objects
 
         public DateTimeOffset LastMurder { get; set; }
 
-        public bool CanMurder(IGame game)
+        public bool CanMurder(IGame game, IDateTimeProvider dateTimeProvider)
         {
             if (!IsImpostor)
             {
                 return false;
             }
 
-            return DateTimeOffset.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown;
+            return dateTimeProvider.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown;
         }
 
         public void Serialize(IMessageWriter writer)
index 878a05273997f7a7c347d6465471309434886837..afae57e8ba5e94042dd957882c8aebfcced9a275 100644 (file)
@@ -24,12 +24,13 @@ namespace Impostor.Server.Net.Redirector
 
         public ClientRedirector(
             string name,
+            int gameVersion,
             HazelConnection connection,
             ISet<Mod> mods,
             ClientManager clientManager,
             INodeProvider nodeProvider,
             INodeLocator nodeLocator)
-            : base(name, connection, mods)
+            : base(name, gameVersion, connection, mods)
         {
             _clientManager = clientManager;
             _nodeProvider = nodeProvider;
index 2af8d55043a38787c6cd2669d50372ef975b2a7b..109c6be01d200b7e9012733863896ed018093b17 100644 (file)
@@ -1,6 +1,7 @@
 using System;
 using System.IO;
 using System.Linq;
+using Impostor.Api;
 using Impostor.Api.Events.Managers;
 using Impostor.Api.Games;
 using Impostor.Api.Games.Managers;
@@ -117,6 +118,7 @@ namespace Impostor.Server
                         .Get<AnnouncementsServerConfig>() ?? new AnnouncementsServerConfig();
 
                     services.AddSingleton<ServerEnvironment>();
+                    services.AddSingleton<IDateTimeProvider, RealDateTimeProvider>();
 
                     services.Configure<DebugConfig>(host.Configuration.GetSection(DebugConfig.Section));
                     services.Configure<AntiCheatConfig>(host.Configuration.GetSection(AntiCheatConfig.Section));
diff --git a/src/Impostor.Server/RealDateTimeProvider.cs b/src/Impostor.Server/RealDateTimeProvider.cs
new file mode 100644 (file)
index 0000000..506e0c0
--- /dev/null
@@ -0,0 +1,10 @@
+using System;
+using Impostor.Api;
+
+namespace Impostor.Server
+{
+    public class RealDateTimeProvider : IDateTimeProvider
+    {
+        public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
+    }
+}
index 868aa0a4cb1b5c594ed940a54475ce34743d993c..fe9d94cc670bcf2f6a161d7a07fc0f209aa1c224 100644 (file)
@@ -18,8 +18,8 @@ namespace Impostor.Server.Recorder
         private bool _createdGame;
         private bool _recordAfter;
 
-        public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, HazelConnection connection, ISet<Mod> mods, PacketRecorder recorder)
-            : base(logger, antiCheatOptions, clientManager, gameManager, name, connection, mods)
+        public ClientRecorder(ILogger<Client> logger, IOptions<AntiCheatConfig> antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, int gameVersion, HazelConnection connection, ISet<Mod> mods, PacketRecorder recorder)
+            : base(logger, antiCheatOptions, clientManager, gameManager, name, gameVersion, connection, mods)
         {
             _recorder = recorder;
             _isFirst = true;
index af2ea4f1f51b371b5af3eb1ba5c716063667dcc9..c8efdb35a76007ef64553d0614dfa9fe67686a8b 100644 (file)
@@ -8,6 +8,7 @@ using Impostor.Api.Games;
 using Impostor.Api.Net.Messages;
 using Impostor.Server.Config;
 using Impostor.Server.Net;
+using Impostor.Server.Utils;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.ObjectPool;
@@ -24,6 +25,7 @@ namespace Impostor.Server.Recorder
         private readonly ILogger<PacketRecorder> _logger;
         private readonly ObjectPool<PacketSerializationContext> _pool;
         private readonly Channel<byte[]> _channel;
+        private DateTimeOffset _startTime;
 
         public PacketRecorder(ILogger<PacketRecorder> logger, IOptions<DebugConfig> options, ObjectPool<PacketSerializationContext> pool)
         {
@@ -42,10 +44,13 @@ namespace Impostor.Server.Recorder
 
         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
         {
+            _startTime = DateTimeOffset.UtcNow;
             _logger.LogInformation("PacketRecorder is enabled, writing packets to {0}.", _path);
 
             var writer = File.Open(_path, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
 
+            await WriteFileHeaderAsync();
+
             // Handle messages.
             try
             {
@@ -73,7 +78,7 @@ namespace Impostor.Server.Recorder
 
             try
             {
-                WriteHeader(context, RecordedPacketType.Connect);
+                WritePacketHeader(context, RecordedPacketType.Connect);
                 WriteClient(context, client, true);
                 WriteLength(context);
 
@@ -93,7 +98,7 @@ namespace Impostor.Server.Recorder
 
             try
             {
-                WriteHeader(context, RecordedPacketType.Disconnect);
+                WritePacketHeader(context, RecordedPacketType.Disconnect);
                 WriteClient(context, client, false);
                 context.Writer.Write(reason);
                 WriteLength(context);
@@ -114,7 +119,7 @@ namespace Impostor.Server.Recorder
 
             try
             {
-                WriteHeader(context, RecordedPacketType.Message);
+                WritePacketHeader(context, RecordedPacketType.Message);
                 WriteClient(context, client, false);
                 WritePacket(context, reader, messageType);
                 WriteLength(context);
@@ -135,7 +140,7 @@ namespace Impostor.Server.Recorder
 
             try
             {
-                WriteHeader(context, RecordedPacketType.GameCreated);
+                WritePacketHeader(context, RecordedPacketType.GameCreated);
                 WriteClient(context, client, false);
                 WriteGameCode(context, gameCode);
                 WriteLength(context);
@@ -148,11 +153,33 @@ namespace Impostor.Server.Recorder
             }
         }
 
-        private static void WriteHeader(PacketSerializationContext context, RecordedPacketType type)
+        private async Task WriteFileHeaderAsync()
+        {
+            var context = _pool.Get();
+
+            try
+            {
+                context.Writer.Write((uint)ServerReplayVersion.Initial);
+                context.Writer.Write(_startTime.ToUnixTimeMilliseconds());
+                context.Writer.Write(DotnetUtils.GetVersion());
+
+                await WriteAsync(context.Stream);
+            }
+            finally
+            {
+                _pool.Return(context);
+            }
+        }
+
+        private void WritePacketHeader(PacketSerializationContext context, RecordedPacketType type)
         {
             // Length placeholder.
-            context.Writer.Write((int) 0);
-            context.Writer.Write((byte) type);
+            context.Writer.Write(0);
+
+            // Timestamp relative to recording start time.
+            context.Writer.Write((uint)(DateTimeOffset.UtcNow - _startTime).TotalMilliseconds);
+
+            context.Writer.Write((byte)type);
         }
 
         private static void WriteClient(PacketSerializationContext context, ClientBase client, bool full)
@@ -164,18 +191,19 @@ namespace Impostor.Server.Recorder
 
             if (full)
             {
-                context.Writer.Write((byte) addressBytes.Length);
+                context.Writer.Write((byte)addressBytes.Length);
                 context.Writer.Write(addressBytes);
-                context.Writer.Write((ushort) address.Port);
+                context.Writer.Write((ushort)address.Port);
                 context.Writer.Write(client.Name);
+                context.Writer.Write(client.GameVersion);
             }
         }
 
         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.Length);
+            context.Writer.Write((byte)messageType);
+            context.Writer.Write((byte)reader.Tag);
+            context.Writer.Write((int)reader.Length);
             context.Writer.Write(reader.Buffer, reader.Offset, reader.Length);
         }
 
@@ -189,7 +217,7 @@ namespace Impostor.Server.Recorder
             var length = context.Stream.Position;
 
             context.Stream.Position = 0;
-            context.Writer.Write((int) length);
+            context.Writer.Write((int)length);
             context.Stream.Position = length;
         }
 
diff --git a/src/Impostor.Server/Recorder/ServerReplayVersion.cs b/src/Impostor.Server/Recorder/ServerReplayVersion.cs
new file mode 100644 (file)
index 0000000..648205b
--- /dev/null
@@ -0,0 +1,18 @@
+namespace Impostor.Server.Recorder
+{
+    /// <summary>
+    /// Version of the server replay data format.
+    /// </summary>
+    public enum ServerReplayVersion
+    {
+        /// <summary>
+        /// Initial version
+        /// </summary>
+        Initial = 1,
+
+        /// <summary>
+        /// Latest version
+        /// </summary>
+        Latest = Initial,
+    }
+}
diff --git a/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs b/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs
new file mode 100644 (file)
index 0000000..7bd4beb
--- /dev/null
@@ -0,0 +1,10 @@
+using System;
+using Impostor.Api;
+
+namespace Impostor.Tools.ServerReplay
+{
+    public class FakeDateTimeProvider : IDateTimeProvider
+    {
+        public DateTimeOffset UtcNow { get; set; }
+    }
+}
index c36ed24c6d1d5886905f07df053614600a165048..261d679b628290cc45af74678adb8e89f72b97dd 100644 (file)
@@ -4,6 +4,7 @@ using System.Diagnostics;
 using System.IO;
 using System.Net;
 using System.Threading.Tasks;
+using Impostor.Api;
 using Impostor.Api.Events.Managers;
 using Impostor.Api.Games;
 using Impostor.Api.Games.Managers;
@@ -42,6 +43,7 @@ namespace Impostor.Tools.ServerReplay
         private static MockGameCodeFactory _gameCodeFactory;
         private static ClientManager _clientManager;
         private static GameManager _gameManager;
+        private static FakeDateTimeProvider _fakeDateTimeProvider;
 
         private static async Task Main(string[] args)
         {
@@ -53,7 +55,7 @@ namespace Impostor.Tools.ServerReplay
 
             var stopwatch = Stopwatch.StartNew();
 
-            foreach (var file in Directory.GetFiles(args[0]))
+            foreach (var file in Directory.GetFiles(args[0], "*.dat"))
             {
                 // Clear.
                 Connections.Clear();
@@ -67,6 +69,7 @@ namespace Impostor.Tools.ServerReplay
                 _gameCodeFactory = _serviceProvider.GetRequiredService<MockGameCodeFactory>();
                 _clientManager = _serviceProvider.GetRequiredService<ClientManager>();
                 _gameManager = _serviceProvider.GetRequiredService<GameManager>();
+                _fakeDateTimeProvider = _serviceProvider.GetRequiredService<FakeDateTimeProvider>();
 
                 await using (var stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))
                 using (var reader = new BinaryReader(stream))
@@ -89,6 +92,9 @@ namespace Impostor.Tools.ServerReplay
                 IsReplay = true
             });
 
+            services.AddSingleton<FakeDateTimeProvider>();
+            services.AddSingleton<IDateTimeProvider>(p => p.GetRequiredService<FakeDateTimeProvider>());
+
             services.AddLogging(builder =>
             {
                 builder.ClearProviders();
@@ -114,6 +120,17 @@ namespace Impostor.Tools.ServerReplay
 
         private static async Task ParseSession(BinaryReader reader)
         {
+            var protocolVersion = (ServerReplayVersion)reader.ReadUInt32();
+            if (protocolVersion < ServerReplayVersion.Initial || protocolVersion > ServerReplayVersion.Latest)
+            {
+                throw new NotSupportedException("Session's protocol version is unsupported");
+            }
+
+            var startTime = _fakeDateTimeProvider.UtcNow = DateTimeOffset.FromUnixTimeMilliseconds(reader.ReadInt64());
+            var serverVersion = reader.ReadString();
+
+            Logger.Information("Loaded session (server: {ServerVersion}, recorded at {StartTime})", serverVersion, startTime);
+
             while (reader.BaseStream.Position < reader.BaseStream.Length)
             {
                 var dataLength = reader.ReadInt32();
@@ -122,6 +139,7 @@ namespace Impostor.Tools.ServerReplay
                 await using (var stream = new MemoryStream(data))
                 using (var readerInner = new BinaryReader(stream))
                 {
+                    _fakeDateTimeProvider.UtcNow = startTime + TimeSpan.FromMilliseconds(readerInner.ReadUInt32());
                     await ParsePacket(readerInner);
                 }
             }
@@ -143,11 +161,12 @@ namespace Impostor.Tools.ServerReplay
                     var addressPort = reader.ReadUInt16();
                     var address = new IPEndPoint(new IPAddress(addressBytes), addressPort);
                     var name = reader.ReadString();
+                    var gameVersion = reader.ReadInt32();
 
                     // Create and register connection.
                     var connection = new MockHazelConnection(address);
 
-                    await _clientManager.RegisterConnectionAsync(connection, name, 50516550, null);
+                    await _clientManager.RegisterConnectionAsync(connection, name, gameVersion, null);
 
                     // Store reference for ourselfs.
                     Connections.Add(clientId, connection);
diff --git a/src/Impostor.Tools.ServerReplay/sessions/.gitkeep b/src/Impostor.Tools.ServerReplay/sessions/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat b/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat
deleted file mode 100644 (file)
index e5f441b..0000000
Binary files a/src/Impostor.Tools.ServerReplay/sessions/session_1604255331821_dead_player_exception.dat and /dev/null differ