--- /dev/null
+namespace Impostor.Api.Games
+{
+ public interface IGameCodeFactory
+ {
+ GameCode Create();
+ }
+}
\ No newline at end of file
</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>
using System.Collections.Generic;
+using System.Threading.Tasks;
+using Impostor.Api.Net.Messages;
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.
/// 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
-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
--- /dev/null
+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
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;
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;
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))
+++ /dev/null
-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
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;
public string Name { get; }
- public HazelConnection Connection { get; }
+ public IHazelConnection Connection { get; }
public bool IsBot => false;
public ClientPlayer Player { get; set; }
+ IClientPlayer IClient.Player => Player;
+
public abstract ValueTask HandleMessageAsync(IMessageReader message, MessageType messageType);
public abstract ValueTask HandleDisconnectAsync(string reason);
using System;
+using Impostor.Api.Net;
using Impostor.Server.Net.Hazel;
using Microsoft.Extensions.DependencyInjection;
_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;
-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
--- /dev/null
+using Impostor.Api.Games;
+
+namespace Impostor.Server.Net
+{
+ public class GameCodeFactory : IGameCodeFactory
+ {
+ public GameCode Create()
+ {
+ return GameCode.Create();
+ }
+ }
+}
\ No newline at end of file
+++ /dev/null
-using Impostor.Api.Net;
-
-namespace Impostor.Server.Net.Hazel
-{
- internal partial class HazelConnection : IConnection
- {
- IClient IConnection.Client => Client;
- }
-}
\ No newline at end of file
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;
public bool IsConnected => InnerConnection.State == ConnectionState.Connected;
- public ClientBase Client { get; set; }
+ public IClient Client { get; set; }
public ValueTask SendAsync(IMessageWriter writer)
{
+++ /dev/null
-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
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;
namespace Impostor.Server.Net.Manager
{
- internal partial class ClientManager
+ internal class ClientManager : IClientManager
{
public static HashSet<int> SupportedVersions { get; } = new HashSet<int>
{
public IEnumerable<ClientBase> Clients => _clients.Values;
+ IEnumerable<IClient> IClientManager.Clients => _clients.Values;
+
public int NextId()
{
var clientId = Interlocked.Increment(ref _idLast);
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)
{
+++ /dev/null
-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
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;
namespace Impostor.Server.Net.Manager
{
- internal partial class GameManager
+ internal class GameManager : IGameManager
{
private readonly ILogger<GameManager> _logger;
private readonly INodeLocator _nodeLocator;
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);
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);
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;
public Matchmaker(
ILogger<Matchmaker> logger,
ClientManager clientManager,
- ILogger<Net.Hazel.HazelConnection> connectionLogger)
+ ILogger<HazelConnection> connectionLogger)
{
_logger = logger;
_clientManager = clientManager;
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);
-using System.Linq;
+using System;
+using System.Linq;
using System.Threading.Tasks;
using Impostor.Api.Innersloth.Data;
using Impostor.Api.Net;
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);
}
}
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;
public Game(
IServiceProvider serviceProvider,
GameManager gameManager,
- INodeLocator nodeLocator,
IPEndPoint publicIp,
GameCode code,
GameOptionsData options,
- Matchmaker matchmaker,
ClientManager clientManager,
IEventManager eventManager)
{
HostId = -1;
GameState = GameStates.NotStarted;
Options = options;
- _matchmaker = matchmaker;
_clientManager = clientManager;
_eventManager = eventManager;
Items = new ConcurrentDictionary<object, object>();
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
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;
services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
}
+ services.AddSingleton<IGameCodeFactory, GameCodeFactory>();
services.AddSingleton<IEventManager, EventManager>();
services.AddSingleton<Matchmaker>();
services.AddHostedService<MatchmakerService>();
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
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)
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;
_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);
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);
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)
{
internal enum RecordedPacketType : byte
{
- Message = 1,
+ Connect = 1,
Disconnect = 2,
+ Message = 3,
+ GameCreated = 4
}
}
\ No newline at end of file
--- /dev/null
+<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>
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
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
{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
{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