From: js6pak Date: Mon, 10 May 2021 11:08:26 +0000 (+0000) Subject: Add custom message support (#404) X-Git-Tag: v1.4.0~3 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=ce62815dc5990a89b7ff47d817e4ed6c528b4cf1;p=rhonda%2Fimpostor.git Add custom message support (#404) --- diff --git a/src/Impostor.Api/Events/Client/IClientConnectedEvent.cs b/src/Impostor.Api/Events/Client/IClientConnectedEvent.cs new file mode 100644 index 0000000..c638170 --- /dev/null +++ b/src/Impostor.Api/Events/Client/IClientConnectedEvent.cs @@ -0,0 +1,12 @@ +using Impostor.Api.Net; + +namespace Impostor.Api.Events.Client +{ + /// + /// Called just after a is created and connected. + /// + public interface IClientConnectedEvent : IClientEvent + { + IClient Client { get; } + } +} diff --git a/src/Impostor.Api/Events/Client/IClientConnectionEvent.cs b/src/Impostor.Api/Events/Client/IClientConnectionEvent.cs new file mode 100644 index 0000000..17aa8ec --- /dev/null +++ b/src/Impostor.Api/Events/Client/IClientConnectionEvent.cs @@ -0,0 +1,13 @@ +using Impostor.Api.Net; +using Impostor.Api.Net.Messages; + +namespace Impostor.Api.Events.Client +{ + /// + /// Called just before a is registered. + /// + public interface IClientConnectionEvent : IClientEvent + { + IMessageReader HandshakeData { get; } + } +} diff --git a/src/Impostor.Api/Events/Client/IClientEvent.cs b/src/Impostor.Api/Events/Client/IClientEvent.cs new file mode 100644 index 0000000..69184bc --- /dev/null +++ b/src/Impostor.Api/Events/Client/IClientEvent.cs @@ -0,0 +1,9 @@ +using Impostor.Api.Net; + +namespace Impostor.Api.Events.Client +{ + public interface IClientEvent : IEvent + { + IHazelConnection Connection { get; } + } +} diff --git a/src/Impostor.Api/Events/Game/IGamePlayerJoiningEvent.cs b/src/Impostor.Api/Events/Game/IGamePlayerJoiningEvent.cs new file mode 100644 index 0000000..dc326b0 --- /dev/null +++ b/src/Impostor.Api/Events/Game/IGamePlayerJoiningEvent.cs @@ -0,0 +1,15 @@ +using Impostor.Api.Games; +using Impostor.Api.Net; + +namespace Impostor.Api.Events +{ + /// + /// Called just before a joins a game. + /// + public interface IGamePlayerJoiningEvent : IGameEvent + { + IClientPlayer Player { get; } + + GameJoinResult? JoinResult { get; set; } + } +} diff --git a/src/Impostor.Api/Events/MultiDisposable.cs b/src/Impostor.Api/Events/MultiDisposable.cs new file mode 100644 index 0000000..bacaef7 --- /dev/null +++ b/src/Impostor.Api/Events/MultiDisposable.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Impostor.Api.Events +{ + /// + /// Disposes multiple . + /// + public class MultiDisposable : IDisposable + { + private readonly IEnumerable _disposables; + + public MultiDisposable(IEnumerable disposables) + { + _disposables = disposables; + } + + public MultiDisposable(params IDisposable[] disposables) : this(disposables.AsEnumerable()) + { + } + + public void Dispose() + { + foreach (var disposable in _disposables) + { + disposable?.Dispose(); + } + } + } +} diff --git a/src/Impostor.Api/Games/IGame.cs b/src/Impostor.Api/Games/IGame.cs index ecc11f8..8d95a1c 100644 --- a/src/Impostor.Api/Games/IGame.cs +++ b/src/Impostor.Api/Games/IGame.cs @@ -90,5 +90,24 @@ namespace Impostor.Api.Games /// ID of the client. /// A representing the asynchronous operation. ValueTask SendToAsync(IMessageWriter writer, int id); + + /// + /// Creates a MessageWriter with GameData header. + /// + /// Use with caution, recommended only for advanced developers. + /// Net id of the InnerNetObject. + /// Rpc id of the message. + /// Client id of GameDataTo's target. + /// Message type of the message. + /// A that you can fill with rpc data and send using . + IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int? targetClientId, MessageType type = MessageType.Reliable); + + /// + /// Finishes rpc message and sends it to the target. + /// + /// Message writer of the rpc. + /// Id of the receiving client. + /// A representing the asynchronous operation. + ValueTask FinishRpcAsync(IMessageWriter writer, int? targetClientId = null); } } diff --git a/src/Impostor.Api/IDateTimeProvider.cs b/src/Impostor.Api/IDateTimeProvider.cs deleted file mode 100644 index 95b3dee..0000000 --- a/src/Impostor.Api/IDateTimeProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Impostor.Api -{ - public interface IDateTimeProvider - { - DateTimeOffset UtcNow { get; } - } -} diff --git a/src/Impostor.Api/Net/Custom/ICustomMessage.cs b/src/Impostor.Api/Net/Custom/ICustomMessage.cs new file mode 100644 index 0000000..52364c8 --- /dev/null +++ b/src/Impostor.Api/Net/Custom/ICustomMessage.cs @@ -0,0 +1,7 @@ +namespace Impostor.Api.Net.Custom +{ + public interface ICustomMessage + { + byte Id { get; } + } +} diff --git a/src/Impostor.Api/Net/Custom/ICustomMessageManager.cs b/src/Impostor.Api/Net/Custom/ICustomMessageManager.cs new file mode 100644 index 0000000..81cd606 --- /dev/null +++ b/src/Impostor.Api/Net/Custom/ICustomMessageManager.cs @@ -0,0 +1,18 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Impostor.Api.Net.Custom +{ + public interface ICustomMessageManager + where T : ICustomMessage + { + bool TryGet(byte id, [MaybeNullWhen(false)] out T message); + + /// + /// Register a custom message. + /// + /// message. + /// Disposable that unregisters the message. + IDisposable Register(T message); + } +} diff --git a/src/Impostor.Api/Net/Custom/ICustomRootMessage.cs b/src/Impostor.Api/Net/Custom/ICustomRootMessage.cs new file mode 100644 index 0000000..352cc1b --- /dev/null +++ b/src/Impostor.Api/Net/Custom/ICustomRootMessage.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Impostor.Api.Net.Messages; + +namespace Impostor.Api.Net.Custom +{ + public interface ICustomRootMessage : ICustomMessage + { + ValueTask HandleMessageAsync(IClient client, IMessageReader reader, MessageType messageType); + } +} diff --git a/src/Impostor.Api/Net/Custom/ICustomRpc.cs b/src/Impostor.Api/Net/Custom/ICustomRpc.cs new file mode 100644 index 0000000..262b573 --- /dev/null +++ b/src/Impostor.Api/Net/Custom/ICustomRpc.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Impostor.Api.Net.Inner; +using Impostor.Api.Net.Messages; + +namespace Impostor.Api.Net.Custom +{ + public interface ICustomRpc : ICustomMessage + { + ValueTask HandleRpcAsync(IInnerNetObject innerNetObject, IClientPlayer sender, IClientPlayer? target, IMessageReader reader); + } +} diff --git a/src/Impostor.Api/Net/Messages/IMessageWriter.cs b/src/Impostor.Api/Net/Messages/IMessageWriter.cs index fd500ad..346e6e4 100644 --- a/src/Impostor.Api/Net/Messages/IMessageWriter.cs +++ b/src/Impostor.Api/Net/Messages/IMessageWriter.cs @@ -19,6 +19,13 @@ namespace Impostor.Api.Net.Messages public MessageType SendOption { get; } + /// + /// Copies the contents of this writer into a new array. + /// + /// Whether to include message header in the array. + /// An array containing the data in the current writer. + byte[] ToByteArray(bool includeHeader); + /// /// Writes a boolean to the message. /// diff --git a/src/Impostor.Api/Utils/IDateTimeProvider.cs b/src/Impostor.Api/Utils/IDateTimeProvider.cs new file mode 100644 index 0000000..8212b99 --- /dev/null +++ b/src/Impostor.Api/Utils/IDateTimeProvider.cs @@ -0,0 +1,9 @@ +using System; + +namespace Impostor.Api.Utils +{ + public interface IDateTimeProvider + { + DateTimeOffset UtcNow { get; } + } +} diff --git a/src/Impostor.Api/Utils/IServerEnvironment.cs b/src/Impostor.Api/Utils/IServerEnvironment.cs new file mode 100644 index 0000000..006aaeb --- /dev/null +++ b/src/Impostor.Api/Utils/IServerEnvironment.cs @@ -0,0 +1,7 @@ +namespace Impostor.Api.Utils +{ + public interface IServerEnvironment + { + string Version { get; } + } +} diff --git a/src/Impostor.Server/Events/Client/ClientConnectedEvent.cs b/src/Impostor.Server/Events/Client/ClientConnectedEvent.cs new file mode 100644 index 0000000..569fccb --- /dev/null +++ b/src/Impostor.Server/Events/Client/ClientConnectedEvent.cs @@ -0,0 +1,18 @@ +using Impostor.Api.Events.Client; +using Impostor.Api.Net; + +namespace Impostor.Server.Events.Client +{ + public class ClientConnectedEvent : IClientConnectedEvent + { + public ClientConnectedEvent(IHazelConnection connection, IClient client) + { + Connection = connection; + Client = client; + } + + public IHazelConnection Connection { get; } + + public IClient Client { get; } + } +} diff --git a/src/Impostor.Server/Events/Client/ClientConnectionEvent.cs b/src/Impostor.Server/Events/Client/ClientConnectionEvent.cs new file mode 100644 index 0000000..e4bb594 --- /dev/null +++ b/src/Impostor.Server/Events/Client/ClientConnectionEvent.cs @@ -0,0 +1,19 @@ +using Impostor.Api.Events.Client; +using Impostor.Api.Net; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Events.Client +{ + public class ClientConnectionEvent : IClientConnectionEvent + { + public ClientConnectionEvent(IHazelConnection connection, IMessageReader handshakeData) + { + Connection = connection; + HandshakeData = handshakeData; + } + + public IHazelConnection Connection { get; } + + public IMessageReader HandshakeData { get; } + } +} diff --git a/src/Impostor.Server/Events/EventManager.cs b/src/Impostor.Server/Events/EventManager.cs index 4ab2d15..2922631 100644 --- a/src/Impostor.Server/Events/EventManager.cs +++ b/src/Impostor.Server/Events/EventManager.cs @@ -36,7 +36,7 @@ namespace Impostor.Server.Events } var eventListeners = RegisteredEventListener.FromType(listener.GetType()); - var disposes = new IDisposable[eventListeners.Count]; + var disposes = new List(eventListeners.Count); foreach (var eventListener in eventListeners) { @@ -51,7 +51,7 @@ namespace Impostor.Server.Events wrappedEventListener.EventType, _ => new TemporaryEventRegister()); - register.Add(wrappedEventListener); + disposes.Add(register.Add(wrappedEventListener)); } if (eventListeners.Count > 0) diff --git a/src/Impostor.Server/Events/Game/GamePlayerJoiningEvent.cs b/src/Impostor.Server/Events/Game/GamePlayerJoiningEvent.cs new file mode 100644 index 0000000..f24f28b --- /dev/null +++ b/src/Impostor.Server/Events/Game/GamePlayerJoiningEvent.cs @@ -0,0 +1,21 @@ +using Impostor.Api.Events; +using Impostor.Api.Games; +using Impostor.Api.Net; + +namespace Impostor.Server.Events +{ + public class GamePlayerJoiningEvent : IGamePlayerJoiningEvent + { + public GamePlayerJoiningEvent(IGame game, IClientPlayer player) + { + Game = game; + Player = player; + } + + public IGame Game { get; } + + public IClientPlayer Player { get; } + + public GameJoinResult? JoinResult { get; set; } + } +} diff --git a/src/Impostor.Server/Events/MultiDisposable.cs b/src/Impostor.Server/Events/MultiDisposable.cs deleted file mode 100644 index b1e2348..0000000 --- a/src/Impostor.Server/Events/MultiDisposable.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Impostor.Server.Events -{ - /// - /// Disposes multiple . - /// - internal class MultiDisposable : IDisposable - { - private readonly IEnumerable _disposables; - - public MultiDisposable(IEnumerable disposables) - { - _disposables = disposables; - } - - public void Dispose() - { - foreach (var disposable in _disposables) - { - disposable?.Dispose(); - } - } - } -} diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs index dcc501c..5199b6d 100644 --- a/src/Impostor.Server/Net/Client.cs +++ b/src/Impostor.Server/Net/Client.cs @@ -4,6 +4,7 @@ using Impostor.Api; using Impostor.Api.Games; using Impostor.Api.Innersloth; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Messages; using Impostor.Api.Net.Messages.C2S; using Impostor.Api.Net.Messages.S2C; @@ -21,14 +22,16 @@ namespace Impostor.Server.Net private readonly AntiCheatConfig _antiCheatConfig; private readonly ClientManager _clientManager; private readonly GameManager _gameManager; + private readonly ICustomMessageManager _customMessageManager; - public Client(ILogger logger, IOptions antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, int gameVersion, IHazelConnection connection) + public Client(ILogger logger, IOptions antiCheatOptions, ClientManager clientManager, GameManager gameManager, ICustomMessageManager customMessageManager, string name, int gameVersion, IHazelConnection connection) : base(name, gameVersion, connection) { _logger = logger; _antiCheatConfig = antiCheatOptions.Value; _clientManager = clientManager; _gameManager = gameManager; + _customMessageManager = customMessageManager; } public override async ValueTask ReportCheatAsync(CheatContext context, string message) @@ -169,10 +172,10 @@ namespace Impostor.Server.Net var toPlayer = flag == MessageFlags.GameDataTo; - // Handle packet. - using var readerCopy = reader.Copy(); + var position = reader.Position; + var verified = await Player!.Game.HandleGameDataAsync(reader, Player, toPlayer); + reader.Seek(position); - var verified = await Player!.Game.HandleGameDataAsync(readerCopy, Player, toPlayer); if (verified) { // Broadcast packet to all other players. @@ -255,6 +258,12 @@ namespace Impostor.Server.Net } default: + if (_customMessageManager.TryGet(flag, out var customRootMessage)) + { + await customRootMessage.HandleMessageAsync(this, reader, messageType); + break; + } + _logger.LogWarning("Server received unknown flag {0}.", flag); break; } diff --git a/src/Impostor.Server/Net/Custom/CustomMessageManager.cs b/src/Impostor.Server/Net/Custom/CustomMessageManager.cs new file mode 100644 index 0000000..42b3c7b --- /dev/null +++ b/src/Impostor.Server/Net/Custom/CustomMessageManager.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Impostor.Api; +using Impostor.Api.Net.Custom; + +namespace Impostor.Server.Net.Custom +{ + public class CustomMessageManager : ICustomMessageManager + where T : ICustomMessage + { + private readonly ConcurrentDictionary _messages = new ConcurrentDictionary(); + + public bool TryGet(byte id, [MaybeNullWhen(false)] out T message) + { + return _messages.TryGetValue(id, out message); + } + + public IDisposable Register(T message) + { + if (!_messages.TryAdd(message.Id, message)) + { + throw new ImpostorException($"Message with id: {message.Id} was already registered"); + } + + return new UnregisterDisposable(this, message); + } + + private class UnregisterDisposable : IDisposable + { + private readonly CustomMessageManager _manager; + private readonly T _message; + + private bool _disposed; + + public UnregisterDisposable(CustomMessageManager manager, T message) + { + _manager = manager; + _message = message; + } + + public void Dispose() + { + if (_disposed) + { + throw new ObjectDisposedException("Tried to dispose already disposed object"); + } + + _manager._messages.TryRemove(_message.Id, out _); + _disposed = true; + } + } + } +} diff --git a/src/Impostor.Server/Net/Inner/InnerNetObject.cs b/src/Impostor.Server/Net/Inner/InnerNetObject.cs index 4a15104..62339c1 100644 --- a/src/Impostor.Server/Net/Inner/InnerNetObject.cs +++ b/src/Impostor.Server/Net/Inner/InnerNetObject.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Impostor.Api.Games; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Messages; using Impostor.Server.Net.State; @@ -11,8 +12,11 @@ namespace Impostor.Server.Net.Inner { private const int HostInheritId = -2; - protected InnerNetObject(Game game) + private readonly ICustomMessageManager _customMessageManager; + + protected InnerNetObject(ICustomMessageManager customMessageManager, Game game) { + _customMessageManager = customMessageManager; Game = game; } @@ -36,9 +40,22 @@ namespace Impostor.Server.Net.Inner public abstract ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState); - public abstract ValueTask HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader); + public virtual async ValueTask HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + return await HandleCustomRpcAsync(sender, target, call, reader) ?? await UnregisteredCall(call, sender); + } + + protected async ValueTask HandleCustomRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + if (_customMessageManager.TryGet((byte)call, out var customRpc)) + { + return await customRpc.HandleRpcAsync(this, sender, target, reader); + } + + return null; + } - public virtual ValueTask OnSpawnAsync() + internal virtual ValueTask OnSpawnAsync() { return default; } diff --git a/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs b/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs index 0b52905..8d92d50 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Impostor.Api; using Impostor.Api.Events.Managers; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Messages; using Impostor.Api.Net.Messages.Rpcs; @@ -27,7 +28,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components private ushort _lastSequenceId; private bool _spawnSnapAllowed; - public InnerCustomNetworkTransform(Game game, ILogger logger, InnerPlayerControl playerControl, IEventManager eventManager, ObjectPool pool) : base(game) + public InnerCustomNetworkTransform(ICustomMessageManager customMessageManager, Game game, ILogger logger, InnerPlayerControl playerControl, IEventManager eventManager, ObjectPool pool) : base(customMessageManager, game) { _logger = logger; _playerControl = playerControl; @@ -135,7 +136,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components return true; } - return await UnregisteredCall(call, sender); + return await base.HandleRpcAsync(sender, target, call, reader); } internal async ValueTask SetPositionAsync(IClientPlayer sender, Vector2 position, Vector2 velocity) diff --git a/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs b/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs index edd7b1d..053fbf3 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs @@ -1,9 +1,8 @@ using System; using System.Threading.Tasks; using Impostor.Api.Events.Managers; -using Impostor.Api.Innersloth; -using Impostor.Api.Innersloth.Maps; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Messages; using Impostor.Api.Net.Messages.Rpcs; @@ -19,7 +18,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components private readonly InnerPlayerControl _playerControl; private readonly IEventManager _eventManager; - public InnerPlayerPhysics(Game game, ILogger logger, InnerPlayerControl playerControl, IEventManager eventManager) : base(game) + public InnerPlayerPhysics(ICustomMessageManager customMessageManager, Game game, ILogger logger, InnerPlayerControl playerControl, IEventManager eventManager) : base(customMessageManager, game) { _logger = logger; _playerControl = playerControl; @@ -85,7 +84,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components break; default: - return await UnregisteredCall(call, sender); + return await base.HandleRpcAsync(sender, target, call, reader); } return true; diff --git a/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs b/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs index 6934897..6877907 100644 --- a/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs +++ b/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Impostor.Api; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; using Impostor.Api.Net.Messages; @@ -17,7 +18,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components private readonly ILogger _logger; private readonly Dictionary _votes; - public InnerVoteBanSystem(Game game, ILogger logger) : base(game) + public InnerVoteBanSystem(ICustomMessageManager customMessageManager, Game game, ILogger logger) : base(customMessageManager, game) { _logger = logger; _votes = new Dictionary(); @@ -78,7 +79,7 @@ namespace Impostor.Server.Net.Inner.Objects.Components return true; } - return await UnregisteredCall(call, sender); + return await base.HandleRpcAsync(sender, target, call, reader); } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs index 0a29d63..e7980da 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Impostor.Api; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; using Impostor.Api.Net.Messages; @@ -20,7 +21,7 @@ namespace Impostor.Server.Net.Inner.Objects private readonly ILogger _logger; private readonly ConcurrentDictionary _allPlayers; - public InnerGameData(Game game, ILogger logger, IServiceProvider serviceProvider) : base(game) + public InnerGameData(ICustomMessageManager customMessageManager, Game game, ILogger logger, IServiceProvider serviceProvider) : base(customMessageManager, game) { _logger = logger; _allPlayers = new ConcurrentDictionary(); @@ -113,7 +114,7 @@ namespace Impostor.Server.Net.Inner.Objects } default: - return await UnregisteredCall(call, sender); + return await base.HandleRpcAsync(sender, target, call, reader); } return true; diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs b/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs index c897a68..225b438 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using Impostor.Api.Net; -using Impostor.Api.Net.Inner; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner.Objects; using Impostor.Api.Net.Messages; using Impostor.Server.Net.State; @@ -10,7 +10,7 @@ namespace Impostor.Server.Net.Inner.Objects { internal class InnerLobbyBehaviour : InnerNetObject, IInnerLobbyBehaviour { - public InnerLobbyBehaviour(Game game) : base(game) + public InnerLobbyBehaviour(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game) { Components.Add(this); } @@ -24,10 +24,5 @@ namespace Impostor.Server.Net.Inner.Objects { throw new NotImplementedException(); } - - public override ValueTask HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) - { - throw new NotImplementedException(); - } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs index f774cd0..104ceae 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs @@ -6,6 +6,7 @@ using Impostor.Api; using Impostor.Api.Events.Managers; using Impostor.Api.Innersloth; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Messages; using Impostor.Api.Net.Messages.Rpcs; @@ -24,7 +25,7 @@ namespace Impostor.Server.Net.Inner.Objects [AllowNull] private PlayerVoteArea[] _playerStates; - public InnerMeetingHud(Game game, ILogger logger, IEventManager eventManager) : base(game) + public InnerMeetingHud(ICustomMessageManager customMessageManager, Game game, ILogger logger, IEventManager eventManager) : base(customMessageManager, game) { _logger = logger; _eventManager = eventManager; @@ -120,7 +121,7 @@ namespace Impostor.Server.Net.Inner.Objects } default: - return await UnregisteredCall(call, sender); + return await base.HandleRpcAsync(sender, target, call, reader); } return true; diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 488e725..c787577 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -8,10 +8,12 @@ using Impostor.Api.Events.Managers; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Customization; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; using Impostor.Api.Net.Messages; using Impostor.Api.Net.Messages.Rpcs; +using Impostor.Api.Utils; using Impostor.Server.Events.Player; using Impostor.Server.Net.Inner.Objects.Components; using Impostor.Server.Net.State; @@ -28,7 +30,7 @@ namespace Impostor.Server.Net.Inner.Objects private readonly IEventManager _eventManager; private readonly IDateTimeProvider _dateTimeProvider; - public InnerPlayerControl(Game game, ILogger logger, IServiceProvider serviceProvider, IEventManager eventManager, IDateTimeProvider dateTimeProvider) : base(game) + public InnerPlayerControl(ICustomMessageManager customMessageManager, Game game, ILogger logger, IServiceProvider serviceProvider, IEventManager eventManager, IDateTimeProvider dateTimeProvider) : base(customMessageManager, game) { _logger = logger; _eventManager = eventManager; @@ -296,7 +298,7 @@ namespace Impostor.Server.Net.Inner.Objects } default: - return await UnregisteredCall(call, sender); + return await base.HandleRpcAsync(sender, target, call, reader); } return true; diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs index 763782a..1a4ac1c 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using Impostor.Api; using Impostor.Api.Games; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Customization; using Impostor.Api.Net.Messages; +using Impostor.Api.Utils; namespace Impostor.Server.Net.Inner.Objects { diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs index 3ed285c..6ef1fa6 100644 --- a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Numerics; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner.Objects.ShipStatus; using Impostor.Server.Net.Inner.Objects.Systems; using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; @@ -12,7 +13,7 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus { internal class InnerAirshipStatus : InnerShipStatus, IInnerAirshipStatus { - public InnerAirshipStatus(Game game) : base(game) + public InnerAirshipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game) { } diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs index 6f1879d..a75167d 100644 --- a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Numerics; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner.Objects.ShipStatus; using Impostor.Server.Net.Inner.Objects.Systems; using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; @@ -11,7 +12,7 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus { internal class InnerMiraShipStatus : InnerShipStatus, IInnerMiraShipStatus { - public InnerMiraShipStatus(Game game) : base(game) + public InnerMiraShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game) { } diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs index 3274727..dccbeec 100644 --- a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Numerics; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner.Objects.ShipStatus; using Impostor.Server.Net.Inner.Objects.Systems; using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; @@ -11,7 +12,7 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus { internal class InnerPolusShipStatus : InnerShipStatus, IInnerPolusShipStatus { - public InnerPolusShipStatus(Game game) : base(game) + public InnerPolusShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game) { } diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs index 3dd2d54..4d14c63 100644 --- a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs @@ -7,6 +7,7 @@ using Impostor.Api; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Maps; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects.ShipStatus; using Impostor.Api.Net.Messages; @@ -21,7 +22,7 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus { private readonly Dictionary _systems = new Dictionary(); - protected InnerShipStatus(Game game) : base(game) + protected InnerShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game) { Components.Add(this); } @@ -36,7 +37,7 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus public abstract Vector2 MeetingSpawnCenter { get; } - public override ValueTask OnSpawnAsync() + internal override ValueTask OnSpawnAsync() { for (var i = 0; i < Doors.Count; i++) { @@ -105,7 +106,7 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus } default: - return await UnregisteredCall(call, sender); + return await base.HandleRpcAsync(sender, target, call, reader); } return true; diff --git a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs index 6670aad..09523c1 100644 --- a/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs +++ b/src/Impostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Numerics; using Impostor.Api.Innersloth; using Impostor.Api.Innersloth.Maps; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Inner.Objects.ShipStatus; using Impostor.Server.Net.Inner.Objects.Systems; using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; @@ -11,7 +12,7 @@ namespace Impostor.Server.Net.Inner.Objects.ShipStatus { internal class InnerSkeldShipStatus : InnerShipStatus, IInnerSkeldShipStatus { - public InnerSkeldShipStatus(Game game) : base(game) + public InnerSkeldShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game) { } diff --git a/src/Impostor.Server/Net/Manager/ClientManager.cs b/src/Impostor.Server/Net/Manager/ClientManager.cs index 7a10181..903c810 100644 --- a/src/Impostor.Server/Net/Manager/ClientManager.cs +++ b/src/Impostor.Server/Net/Manager/ClientManager.cs @@ -10,6 +10,7 @@ using Impostor.Api.Net.Messages; using Impostor.Api.Net.Messages.S2C; using Impostor.Hazel; using Impostor.Server.Config; +using Impostor.Server.Events.Client; using Impostor.Server.Net.Factories; using Microsoft.Extensions.Logging; @@ -24,13 +25,15 @@ namespace Impostor.Server.Net.Manager }; private readonly ILogger _logger; + private readonly IEventManager _eventManager; private readonly ConcurrentDictionary _clients; private readonly IClientFactory _clientFactory; private int _idLast; - public ClientManager(ILogger logger, IClientFactory clientFactory) + public ClientManager(ILogger logger, IEventManager eventManager, IClientFactory clientFactory) { _logger = logger; + _eventManager = eventManager; _clientFactory = clientFactory; _clients = new ConcurrentDictionary(); } @@ -85,6 +88,8 @@ namespace Impostor.Server.Net.Manager client.Id = id; _logger.LogTrace("Client connected."); _clients.TryAdd(id, client); + + await _eventManager.CallAsync(new ClientConnectedEvent(connection, client)); } public void Remove(IClient client) diff --git a/src/Impostor.Server/Net/Matchmaker.cs b/src/Impostor.Server/Net/Matchmaker.cs index 6af4f55..79d9c8f 100644 --- a/src/Impostor.Server/Net/Matchmaker.cs +++ b/src/Impostor.Server/Net/Matchmaker.cs @@ -6,6 +6,7 @@ using Impostor.Api.Events.Managers; using Impostor.Api.Net.Messages.C2S; using Impostor.Hazel; using Impostor.Hazel.Udp; +using Impostor.Server.Events.Client; using Impostor.Server.Net.Hazel; using Impostor.Server.Net.Manager; using Microsoft.Extensions.Logging; @@ -15,19 +16,19 @@ namespace Impostor.Server.Net { internal class Matchmaker { + private readonly IEventManager _eventManager; private readonly ClientManager _clientManager; private readonly ObjectPool _readerPool; - private readonly ILogger _logger; private readonly ILogger _connectionLogger; private UdpConnectionListener? _connection; public Matchmaker( - ILogger logger, + IEventManager eventManager, ClientManager clientManager, ObjectPool readerPool, ILogger connectionLogger) { - _logger = logger; + _eventManager = eventManager; _clientManager = clientManager; _readerPool = readerPool; _connectionLogger = connectionLogger; @@ -65,6 +66,8 @@ namespace Impostor.Server.Net var connection = new HazelConnection(e.Connection, _connectionLogger); + await _eventManager.CallAsync(new ClientConnectionEvent(connection, e.HandshakeData)); + // Register client await _clientManager.RegisterConnectionAsync(connection, name, clientVersion); } diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index 9a09c1b..ee1111c 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -18,8 +18,6 @@ namespace Impostor.Server.Net.State { internal partial class Game { - private const int FakeClientId = int.MaxValue - 1; - /// /// Used for global object, spawned by the host. /// @@ -105,7 +103,8 @@ namespace Impostor.Server.Net.State { if (!await obj.HandleRpcAsync(sender, target, (RpcCalls)reader.ReadByte(), reader)) { - return false; + parent.RemoveMessage(reader); + continue; } } else @@ -133,13 +132,6 @@ namespace Impostor.Server.Net.State var innerNetObject = (InnerNetObject)ActivatorUtilities.CreateInstance(_serviceProvider, SpawnableObjects[objectId], this); var ownerClientId = reader.ReadPackedInt32(); - // Prevent fake client from being broadcasted. - // TODO: Remove message from stream properly. - if (ownerClientId == FakeClientId) - { - return false; - } - innerNetObject.SpawnFlags = (SpawnFlags)reader.ReadByte(); var components = innerNetObject.GetComponentsInChildren(); diff --git a/src/Impostor.Server/Net/State/Game.Incoming.cs b/src/Impostor.Server/Net/State/Game.Incoming.cs index 6e16208..116465f 100644 --- a/src/Impostor.Server/Net/State/Game.Incoming.cs +++ b/src/Impostor.Server/Net/State/Game.Incoming.cs @@ -199,6 +199,14 @@ namespace Impostor.Server.Net.State return GameJoinResult.CreateSuccess(player); } + var @event = new GamePlayerJoiningEvent(this, player); + await _eventManager.CallAsync(@event); + + if (@event.JoinResult != null && !@event.JoinResult.Value.IsSuccess) + { + return @event.JoinResult.Value; + } + await HandleJoinGameNew(player, isNew); return GameJoinResult.CreateSuccess(player); } diff --git a/src/Impostor.Server/Net/State/Game.Outgoing.cs b/src/Impostor.Server/Net/State/Game.Outgoing.cs index b5b654e..30ebb72 100644 --- a/src/Impostor.Server/Net/State/Game.Outgoing.cs +++ b/src/Impostor.Server/Net/State/Game.Outgoing.cs @@ -38,11 +38,11 @@ namespace Impostor.Server.Net.State } } - internal IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int targetClientId = -1, MessageType type = MessageType.Reliable) + public IMessageWriter StartRpc(uint targetNetId, RpcCalls callId, int? targetClientId = null, MessageType type = MessageType.Reliable) { var writer = MessageWriter.Get(type); - if (targetClientId < 0) + if (targetClientId == null || targetClientId < 0) { writer.StartMessage(MessageFlags.GameData); writer.Write(Code); @@ -51,7 +51,7 @@ namespace Impostor.Server.Net.State { writer.StartMessage(MessageFlags.GameDataTo); writer.Write(Code); - writer.WritePacked(targetClientId); + writer.WritePacked(targetClientId.Value); } writer.StartMessage(GameDataTag.RpcFlag); @@ -61,7 +61,7 @@ namespace Impostor.Server.Net.State return writer; } - internal ValueTask FinishRpcAsync(IMessageWriter writer, int? targetClientId = null) + public ValueTask FinishRpcAsync(IMessageWriter writer, int? targetClientId = null) { writer.EndMessage(); writer.EndMessage(); diff --git a/src/Impostor.Server/Program.cs b/src/Impostor.Server/Program.cs index 846c3ae..46aaf2b 100644 --- a/src/Impostor.Server/Program.cs +++ b/src/Impostor.Server/Program.cs @@ -1,16 +1,18 @@ 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; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Manager; using Impostor.Api.Net.Messages; +using Impostor.Api.Utils; using Impostor.Hazel.Extensions; using Impostor.Server.Config; using Impostor.Server.Events; using Impostor.Server.Net; +using Impostor.Server.Net.Custom; using Impostor.Server.Net.Factories; using Impostor.Server.Net.Manager; using Impostor.Server.Net.Messages; @@ -123,6 +125,7 @@ namespace Impostor.Server .Get() ?? new AuthServerConfig(); services.AddSingleton(); + services.AddSingleton(p => p.GetRequiredService()); services.AddSingleton(); services.Configure(host.Configuration.GetSection(DebugConfig.Section)); @@ -208,6 +211,8 @@ namespace Impostor.Server services.AddEventPools(); services.AddHazel(); + services.AddSingleton, CustomMessageManager>(); + services.AddSingleton, CustomMessageManager>(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Impostor.Server/RealDateTimeProvider.cs b/src/Impostor.Server/RealDateTimeProvider.cs index 506e0c0..c450338 100644 --- a/src/Impostor.Server/RealDateTimeProvider.cs +++ b/src/Impostor.Server/RealDateTimeProvider.cs @@ -1,5 +1,5 @@ using System; -using Impostor.Api; +using Impostor.Api.Utils; namespace Impostor.Server { diff --git a/src/Impostor.Server/Recorder/ClientRecorder.cs b/src/Impostor.Server/Recorder/ClientRecorder.cs index 31af22a..0e01e0c 100644 --- a/src/Impostor.Server/Recorder/ClientRecorder.cs +++ b/src/Impostor.Server/Recorder/ClientRecorder.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Messages; using Impostor.Server.Config; using Impostor.Server.Net; @@ -16,8 +17,8 @@ namespace Impostor.Server.Recorder private bool _createdGame; private bool _recordAfter; - public ClientRecorder(ILogger logger, IOptions antiCheatOptions, ClientManager clientManager, GameManager gameManager, string name, int gameVersion, HazelConnection connection, PacketRecorder recorder) - : base(logger, antiCheatOptions, clientManager, gameManager, name, gameVersion, connection) + public ClientRecorder(ILogger logger, IOptions antiCheatOptions, ClientManager clientManager, ICustomMessageManager customMessageManager, GameManager gameManager, string name, int gameVersion, HazelConnection connection, PacketRecorder recorder) + : base(logger, antiCheatOptions, clientManager, gameManager, customMessageManager, name, gameVersion, connection) { _recorder = recorder; _isFirst = true; diff --git a/src/Impostor.Server/Utils/RealDateTimeProvider.cs b/src/Impostor.Server/Utils/RealDateTimeProvider.cs new file mode 100644 index 0000000..1b7b5f3 --- /dev/null +++ b/src/Impostor.Server/Utils/RealDateTimeProvider.cs @@ -0,0 +1,10 @@ +using System; +using Impostor.Api.Utils; + +namespace Impostor.Server.Utils +{ + public class RealDateTimeProvider : IDateTimeProvider + { + public DateTimeOffset UtcNow => DateTimeOffset.UtcNow; + } +} diff --git a/src/Impostor.Server/Utils/ServerEnvironment.cs b/src/Impostor.Server/Utils/ServerEnvironment.cs index 9c9836a..a3eba15 100644 --- a/src/Impostor.Server/Utils/ServerEnvironment.cs +++ b/src/Impostor.Server/Utils/ServerEnvironment.cs @@ -1,7 +1,11 @@ +using Impostor.Api.Utils; + namespace Impostor.Server.Utils { - public class ServerEnvironment + public class ServerEnvironment : IServerEnvironment { + public string Version { get; } = DotnetUtils.Version; + public bool IsReplay { get; init; } } } diff --git a/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs b/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs index 7bd4beb..4291e15 100644 --- a/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs +++ b/src/Impostor.Tools.ServerReplay/FakeDateTimeProvider.cs @@ -1,5 +1,5 @@ using System; -using Impostor.Api; +using Impostor.Api.Utils; namespace Impostor.Tools.ServerReplay { diff --git a/src/Impostor.Tools.ServerReplay/Program.cs b/src/Impostor.Tools.ServerReplay/Program.cs index 8bfb8e0..6c8c08b 100644 --- a/src/Impostor.Tools.ServerReplay/Program.cs +++ b/src/Impostor.Tools.ServerReplay/Program.cs @@ -4,19 +4,21 @@ 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; using Impostor.Api.Innersloth; using Impostor.Api.Net; +using Impostor.Api.Net.Custom; using Impostor.Api.Net.Messages; using Impostor.Api.Net.Messages.C2S; +using Impostor.Api.Utils; using Impostor.Hazel; using Impostor.Hazel.Extensions; using Impostor.Server; using Impostor.Server.Events; using Impostor.Server.Net; +using Impostor.Server.Net.Custom; using Impostor.Server.Net.Factories; using Impostor.Server.Net.Manager; using Impostor.Server.Net.Redirector; @@ -114,6 +116,8 @@ namespace Impostor.Tools.ServerReplay services.AddEventPools(); services.AddHazel(); + services.AddSingleton, CustomMessageManager>(); + services.AddSingleton, CustomMessageManager>(); return services.BuildServiceProvider(); } diff --git a/src/ProjectRules.ruleset b/src/ProjectRules.ruleset index 5484352..a50fadd 100644 --- a/src/ProjectRules.ruleset +++ b/src/ProjectRules.ruleset @@ -19,4 +19,7 @@ + + +