From d9ef60d4e85666660da403cf2b69c7855e7ef98d Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Tue, 22 Aug 2017 06:07:33 -0700 Subject: [PATCH 01/19] Client manager components - ReflectedManager now supports MemberInfo and event replacement - New chat manager interfaces for both client and server - New multiplayer manager interfaces for both client and server --- Torch.API/ITorchBase.cs | 10 +- Torch.API/Managers/IChatManagerClient.cs | 74 ++++ Torch.API/Managers/IChatManagerServer.cs | 45 +++ ...rManager.cs => IMultiplayerManagerBase.cs} | 31 +- .../Managers/IMultiplayerManagerClient.cs | 12 + .../Managers/IMultiplayerManagerServer.cs | 21 ++ Torch.API/Managers/INetworkManager.cs | 7 + Torch.API/Torch.API.csproj | 6 +- .../TorchClientReflectionTest.cs | 26 ++ .../Manager/MultiplayerManagerClient.cs | 32 ++ .../Manager/MultiplayerManagerLobby.cs | 38 ++ Torch.Client/Torch.Client.csproj | 3 + Torch.Client/TorchClient.cs | 12 + Torch.Client/TorchMainMenuScreen.cs | 10 + .../TorchServerReflectionTest.cs | 15 + .../Managers/MultiplayerManagerDedicated.cs | 166 +++++++++ Torch.Server/Torch.Server.csproj | 1 + Torch.Server/TorchServer.cs | 14 +- Torch.Server/Views/ChatControl.xaml.cs | 27 +- Torch.Server/Views/PlayerListControl.xaml.cs | 12 +- Torch.Tests/ReflectionSystemTest.cs | 119 +++++- Torch.Tests/ReflectionTestManager.cs | 44 ++- Torch.Tests/TorchReflectionTest.cs | 26 ++ Torch/Commands/CommandContext.cs | 3 +- Torch/Commands/CommandManager.cs | 18 +- Torch/Commands/TorchCommands.cs | 12 +- Torch/Managers/ChatManager.cs | 104 ------ .../Managers/ChatManager/ChatManagerClient.cs | 144 +++++++ .../Managers/ChatManager/ChatManagerServer.cs | 193 ++++++++++ Torch/Managers/MultiplayerManager.cs | 338 ----------------- Torch/Managers/MultiplayerManagerBase.cs | 123 ++++++ .../Managers/NetworkManager/NetworkManager.cs | 24 +- Torch/Torch.csproj | 5 +- Torch/TorchBase.cs | 43 +-- Torch/Utils/ReflectedManager.cs | 350 ++++++++++++++++-- 35 files changed, 1539 insertions(+), 569 deletions(-) create mode 100644 Torch.API/Managers/IChatManagerClient.cs create mode 100644 Torch.API/Managers/IChatManagerServer.cs rename Torch.API/Managers/{IMultiplayerManager.cs => IMultiplayerManagerBase.cs} (65%) create mode 100644 Torch.API/Managers/IMultiplayerManagerClient.cs create mode 100644 Torch.API/Managers/IMultiplayerManagerServer.cs create mode 100644 Torch.Client/Manager/MultiplayerManagerClient.cs create mode 100644 Torch.Client/Manager/MultiplayerManagerLobby.cs create mode 100644 Torch.Server/Managers/MultiplayerManagerDedicated.cs delete mode 100644 Torch/Managers/ChatManager.cs create mode 100644 Torch/Managers/ChatManager/ChatManagerClient.cs create mode 100644 Torch/Managers/ChatManager/ChatManagerServer.cs delete mode 100644 Torch/Managers/MultiplayerManager.cs create mode 100644 Torch/Managers/MultiplayerManagerBase.cs diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs index b7e15d0..6c851ab 100644 --- a/Torch.API/ITorchBase.cs +++ b/Torch.API/ITorchBase.cs @@ -44,10 +44,6 @@ namespace Torch.API /// ITorchConfig Config { get; } - /// - [Obsolete] - IMultiplayerManager Multiplayer { get; } - /// [Obsolete] IPluginManager Plugins { get; } @@ -55,6 +51,12 @@ namespace Torch.API /// IDependencyManager Managers { get; } + [Obsolete("Prefer using Managers.GetManager for global managers")] + T GetManager() where T : class, IManager; + + [Obsolete("Prefer using Managers.AddManager for global managers")] + bool AddManager(T manager) where T : class, IManager; + /// /// The binary version of the current instance. /// diff --git a/Torch.API/Managers/IChatManagerClient.cs b/Torch.API/Managers/IChatManagerClient.cs new file mode 100644 index 0000000..0c8ca18 --- /dev/null +++ b/Torch.API/Managers/IChatManagerClient.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VRage.Network; + +namespace Torch.API.Managers +{ + /// + /// Represents a scripted or user chat message. + /// + public struct TorchChatMessage + { + /// + /// The author's steam ID, if available. Else, null. + /// + public ulong? AuthorSteamId; + /// + /// The author's name, if available. Else, null. + /// + public string Author; + /// + /// The message contents. + /// + public string Message; + /// + /// The font, or null if default. + /// + public string Font; + } + + /// + /// Callback used to indicate that a messaage has been recieved. + /// + /// + /// If true, this event has been consumed and should be ignored + public delegate void DelMessageRecieved(TorchChatMessage msg, ref bool consumed); + + /// + /// Callback used to indicate the user is attempting to send a message locally. + /// + /// Message the user is attempting to send + /// If true, this event has been consumed and should be ignored + public delegate void DelMessageSending(string msg, ref bool consumed); + + public interface IChatManagerClient : IManager + { + /// + /// Event that is raised when a message addressed to us is recieved. + /// + event DelMessageRecieved MessageRecieved; + + /// + /// Event that is raised when we are attempting to send a message. + /// + event DelMessageSending MessageSending; + + /// + /// Triggers the event, + /// typically raised by the user entering text into the chat window. + /// + /// The message to send + void SendMessageAsSelf(string message); + + /// + /// Displays a message on the UI given an author name and a message. + /// + /// Author name + /// Message content + /// font to use + void DisplayMessageOnSelf(string author, string message, string font = "Blue" ); + } +} diff --git a/Torch.API/Managers/IChatManagerServer.cs b/Torch.API/Managers/IChatManagerServer.cs new file mode 100644 index 0000000..3a402c8 --- /dev/null +++ b/Torch.API/Managers/IChatManagerServer.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VRage.Network; + +namespace Torch.API.Managers +{ + + /// + /// Callback used to indicate the server has recieved a message to process. + /// + /// Steam ID of the user sending a message + /// Message the user is attempting to send + /// If true, this event has been consumed and should be ignored + public delegate void DelMessageProcessing(TorchChatMessage msg, ref bool consumed); + + public interface IChatManagerServer : IChatManagerClient + { + /// + /// Event triggered when the server has recieved a message and should process it. + /// + event DelMessageProcessing MessageProcessing; + + + /// + /// Sends a message with the given author and message to the given player, or all players by default. + /// + /// Author's steam ID + /// The message to send + /// Player to send the message to, or everyone by default + void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0); + + + /// + /// Sends a scripted message with the given author and message to the given player, or all players by default. + /// + /// Author name + /// The message to send + /// Font to use + /// Player to send the message to, or everyone by default + void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0); + } +} diff --git a/Torch.API/Managers/IMultiplayerManager.cs b/Torch.API/Managers/IMultiplayerManagerBase.cs similarity index 65% rename from Torch.API/Managers/IMultiplayerManager.cs rename to Torch.API/Managers/IMultiplayerManagerBase.cs index 509360d..8b552e7 100644 --- a/Torch.API/Managers/IMultiplayerManager.cs +++ b/Torch.API/Managers/IMultiplayerManagerBase.cs @@ -16,7 +16,7 @@ namespace Torch.API.Managers /// /// API for multiplayer related functions. /// - public interface IMultiplayerManager : IManager + public interface IMultiplayerManagerBase : IManager { /// /// Fired when a player joins. @@ -27,27 +27,7 @@ namespace Torch.API.Managers /// Fired when a player disconnects. /// event Action PlayerLeft; - - /// - /// Fired when a chat message is received. - /// - event MessageReceivedDel MessageReceived; - - /// - /// Send a chat message to all or one specific player. - /// - void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue); - - /// - /// Kicks the player from the game. - /// - void KickPlayer(ulong steamId); - - /// - /// Bans or unbans a player from the game. - /// - void BanPlayer(ulong steamId, bool banned = true); - + /// /// Gets a player by their Steam64 ID or returns null if the player isn't found. /// @@ -57,5 +37,12 @@ namespace Torch.API.Managers /// Gets a player by their display name or returns null if the player isn't found. /// IMyPlayer GetPlayerByName(string name); + + /// + /// Gets the steam username of a member's steam ID + /// + /// steam ID + /// steam username + string GetSteamUsername(ulong steamId); } } \ No newline at end of file diff --git a/Torch.API/Managers/IMultiplayerManagerClient.cs b/Torch.API/Managers/IMultiplayerManagerClient.cs new file mode 100644 index 0000000..f4e6a32 --- /dev/null +++ b/Torch.API/Managers/IMultiplayerManagerClient.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.API.Managers +{ + public interface IMultiplayerManagerClient : IMultiplayerManagerBase + { + } +} diff --git a/Torch.API/Managers/IMultiplayerManagerServer.cs b/Torch.API/Managers/IMultiplayerManagerServer.cs new file mode 100644 index 0000000..36dddf8 --- /dev/null +++ b/Torch.API/Managers/IMultiplayerManagerServer.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.API.Managers +{ + public interface IMultiplayerManagerServer : IMultiplayerManagerBase + { + /// + /// Kicks the player from the game. + /// + void KickPlayer(ulong steamId); + + /// + /// Bans or unbans a player from the game. + /// + void BanPlayer(ulong steamId, bool banned = true); + } +} diff --git a/Torch.API/Managers/INetworkManager.cs b/Torch.API/Managers/INetworkManager.cs index 548ec10..acbea3b 100644 --- a/Torch.API/Managers/INetworkManager.cs +++ b/Torch.API/Managers/INetworkManager.cs @@ -18,6 +18,12 @@ namespace Torch.API.Managers /// Register a network handler. /// void RegisterNetworkHandler(INetworkHandler handler); + + /// + /// Unregister a network handler. + /// + /// true if the handler was unregistered, false if it wasn't registered to begin with + bool UnregisterNetworkHandler(INetworkHandler handler); } /// @@ -33,6 +39,7 @@ namespace Torch.API.Managers /// /// Processes a network message. /// + /// true if the message should be discarded bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet); } } diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj index 99b4836..6ffcc86 100644 --- a/Torch.API/Torch.API.csproj +++ b/Torch.API/Torch.API.csproj @@ -164,11 +164,15 @@ + + - + + + diff --git a/Torch.Client.Tests/TorchClientReflectionTest.cs b/Torch.Client.Tests/TorchClientReflectionTest.cs index 756b144..12a1948 100644 --- a/Torch.Client.Tests/TorchClientReflectionTest.cs +++ b/Torch.Client.Tests/TorchClientReflectionTest.cs @@ -29,6 +29,10 @@ namespace Torch.Client.Tests public static IEnumerable Invokers => Manager().Invokers; + public static IEnumerable MemberInfo => Manager().MemberInfo; + + public static IEnumerable Events => Manager().Events; + #region Binding [Theory] [MemberData(nameof(Getters))] @@ -62,6 +66,28 @@ namespace Torch.Client.Tests if (field.Field.IsStatic) Assert.NotNull(field.Field.GetValue(null)); } + + [Theory] + [MemberData(nameof(MemberInfo))] + public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field) + { + if (field.Field == null) + return; + Assert.True(ReflectedManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } + + [Theory] + [MemberData(nameof(Events))] + public void TestBindingEvents(ReflectionTestManager.FieldRef field) + { + if (field.Field == null) + return; + Assert.True(ReflectedManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } #endregion } } diff --git a/Torch.Client/Manager/MultiplayerManagerClient.cs b/Torch.Client/Manager/MultiplayerManagerClient.cs new file mode 100644 index 0000000..3cfc519 --- /dev/null +++ b/Torch.Client/Manager/MultiplayerManagerClient.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Sandbox.Engine.Multiplayer; +using Torch.API; +using Torch.API.Managers; +using Torch.Managers; + +namespace Torch.Client.Manager +{ + public class MultiplayerManagerClient : MultiplayerManagerBase, IMultiplayerManagerClient + { + /// + public MultiplayerManagerClient(ITorchBase torch) : base(torch) { } + + /// + public override void Attach() + { + base.Attach(); + MyMultiplayer.Static.ClientJoined += RaiseClientJoined; + } + + /// + public override void Detach() + { + MyMultiplayer.Static.ClientJoined -= RaiseClientJoined; + base.Detach(); + } + } +} diff --git a/Torch.Client/Manager/MultiplayerManagerLobby.cs b/Torch.Client/Manager/MultiplayerManagerLobby.cs new file mode 100644 index 0000000..5ffd7e9 --- /dev/null +++ b/Torch.Client/Manager/MultiplayerManagerLobby.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Sandbox.Engine.Multiplayer; +using Torch.API; +using Torch.API.Managers; +using Torch.Managers; + +namespace Torch.Client.Manager +{ + public class MultiplayerManagerLobby : MultiplayerManagerBase, IMultiplayerManagerServer + { + /// + public MultiplayerManagerLobby(ITorchBase torch) : base(torch) { } + + /// + public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId)); + + /// + public void BanPlayer(ulong steamId, bool banned = true) => Torch.Invoke(() => MyMultiplayer.Static.BanClient(steamId, banned)); + + /// + public override void Attach() + { + base.Attach(); + MyMultiplayer.Static.ClientJoined += RaiseClientJoined; + } + + /// + public override void Detach() + { + MyMultiplayer.Static.ClientJoined -= RaiseClientJoined; + base.Detach(); + } + } +} diff --git a/Torch.Client/Torch.Client.csproj b/Torch.Client/Torch.Client.csproj index abe6a5f..9ed8a34 100644 --- a/Torch.Client/Torch.Client.csproj +++ b/Torch.Client/Torch.Client.csproj @@ -121,6 +121,8 @@ Properties\AssemblyVersion.cs + + @@ -167,6 +169,7 @@ + diff --git a/Torch.Client/TorchClient.cs b/Torch.Client/TorchClient.cs index 384ad3b..839c40a 100644 --- a/Torch.Client/TorchClient.cs +++ b/Torch.Client/TorchClient.cs @@ -4,12 +4,17 @@ using System.IO; using System.Reflection; using System.Windows; using Sandbox; +using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Networking; using Sandbox.Engine.Platform; using Sandbox.Game; using SpaceEngineers.Game; using VRage.Steam; using Torch.API; +using Torch.API.Managers; +using Torch.API.Session; +using Torch.Client.Manager; +using Torch.Session; using VRage; using VRage.FileSystem; using VRage.GameServices; @@ -27,6 +32,13 @@ namespace Torch.Client public TorchClient() { Config = new TorchClientConfig(); + var sessionManager = Managers.GetManager(); + sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerLobby + ? new MultiplayerManagerLobby(this) + : null); + sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerClientBase + ? new MultiplayerManagerClient(this) + : null); } public override void Init() diff --git a/Torch.Client/TorchMainMenuScreen.cs b/Torch.Client/TorchMainMenuScreen.cs index c0078e4..0bd9e5c 100644 --- a/Torch.Client/TorchMainMenuScreen.cs +++ b/Torch.Client/TorchMainMenuScreen.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Sandbox.Game.Gui; using Sandbox.Graphics; using Sandbox.Graphics.GUI; using Sandbox.Gui; @@ -16,6 +17,15 @@ namespace Torch.Client { public class TorchMainMenuScreen : MyGuiScreenMainMenu { + public TorchMainMenuScreen() + : this(false) + { + } + + public TorchMainMenuScreen(bool pauseGame) + : base(pauseGame) + { + } /// public override void RecreateControls(bool constructor) { diff --git a/Torch.Server.Tests/TorchServerReflectionTest.cs b/Torch.Server.Tests/TorchServerReflectionTest.cs index e4ec2de..8897c8d 100644 --- a/Torch.Server.Tests/TorchServerReflectionTest.cs +++ b/Torch.Server.Tests/TorchServerReflectionTest.cs @@ -28,6 +28,10 @@ namespace Torch.Server.Tests public static IEnumerable Invokers => Manager().Invokers; + public static IEnumerable MemberInfo => Manager().MemberInfo; + + public static IEnumerable Events => Manager().Events; + #region Binding [Theory] [MemberData(nameof(Getters))] @@ -61,6 +65,17 @@ namespace Torch.Server.Tests if (field.Field.IsStatic) Assert.NotNull(field.Field.GetValue(null)); } + + [Theory] + [MemberData(nameof(Events))] + public void TestBindingEvents(ReflectionTestManager.FieldRef field) + { + if (field.Field == null) + return; + Assert.True(ReflectedManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } #endregion } } diff --git a/Torch.Server/Managers/MultiplayerManagerDedicated.cs b/Torch.Server/Managers/MultiplayerManagerDedicated.cs new file mode 100644 index 0000000..85399c0 --- /dev/null +++ b/Torch.Server/Managers/MultiplayerManagerDedicated.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using NLog; +using NLog.Fluent; +using Sandbox; +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Torch.API; +using Torch.API.Managers; +using Torch.Managers; +using Torch.Utils; +using Torch.ViewModels; +using VRage.GameServices; +using VRage.Network; + +namespace Torch.Server.Managers +{ + public class MultiplayerManagerDedicated : MultiplayerManagerBase, IMultiplayerManagerServer + { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + +#pragma warning disable 649 + [ReflectedGetter(Name = "m_members")] + private static Func> _members; + [ReflectedGetter(Name = "m_waitingForGroup")] + private static Func> _waitingForGroup; +#pragma warning restore 649 + + private Dictionary _gameOwnerIds = new Dictionary(); + + /// + public MultiplayerManagerDedicated(ITorchBase torch) : base(torch) { } + + /// + public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId)); + + /// + public void BanPlayer(ulong steamId, bool banned = true) + { + Torch.Invoke(() => + { + MyMultiplayer.Static.BanClient(steamId, banned); + if (_gameOwnerIds.ContainsKey(steamId)) + MyMultiplayer.Static.BanClient(_gameOwnerIds[steamId], banned); + }); + } + + /// + public override void Attach() + { + base.Attach(); + _gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke(); + _gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke(); + _gameServerValidateAuthTicketReplacer.Replace(new Action(ValidateAuthTicketResponse), MyGameService.GameServer); + _gameServerUserGroupStatusReplacer.Replace(new Action(UserGroupStatusResponse), MyGameService.GameServer); + _log.Info("Inserted steam authentication intercept"); + } + + /// + public override void Detach() + { + if (_gameServerValidateAuthTicketReplacer != null && _gameServerValidateAuthTicketReplacer.Replaced) + _gameServerValidateAuthTicketReplacer.Restore(MyGameService.GameServer); + if (_gameServerUserGroupStatusReplacer != null && _gameServerUserGroupStatusReplacer.Replaced) + _gameServerUserGroupStatusReplacer.Restore(MyGameService.GameServer); + _log.Info("Removed steam authentication intercept"); + base.Detach(); + } + + +#pragma warning disable 649 + [ReflectedEventReplace(typeof(IMyGameServer), nameof(IMyGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")] + private static Func _gameServerValidateAuthTicketFactory; + [ReflectedEventReplace(typeof(IMyGameServer), nameof(IMyGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")] + private static Func _gameServerUserGroupStatusFactory; + private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer; + private ReflectedEventReplacer _gameServerUserGroupStatusReplacer; +#pragma warning restore 649 + + #region CustomAuth +#pragma warning disable 649 + [ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")] + private static Func _convertSteamIDFrom64; + + [ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")] + private static Func _getServerAccountType; + + [ReflectedMethod(Name = "UserAccepted")] + private static Action _userAcceptedImpl; + + [ReflectedMethod(Name = "UserRejected")] + private static Action _userRejected; + [ReflectedMethod(Name = "IsClientBanned")] + private static Func _isClientBanned; + [ReflectedMethod(Name = "IsClientKicked")] + private static Func _isClientKicked; + [ReflectedMethod(Name = "RaiseClientKicked")] + private static Action _raiseClientKicked; +#pragma warning restore 649 + + //Largely copied from SE + private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner) + { + _log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}"); + if (_isClientBanned.Invoke(MyMultiplayer.Static, steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner)) + { + _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins); + _raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID); + } + else if (_isClientKicked.Invoke(MyMultiplayer.Static, steamOwner)) + { + _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently); + _raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID); + } + if (response != JoinResult.OK) + { + _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response); + return; + } + if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit) + { + _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull); + return; + } + if (MySandboxGame.ConfigDedicated.GroupID == 0uL || + MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) || + MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(steamID))) + { + this.UserAccepted(steamID); + return; + } + if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan) + { + _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid); + return; + } + if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID)) + { + _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID); + return; + } + _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline); + } + + private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer) + { + if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId)) + { + if (member || officer) + UserAccepted(userId); + else + _userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup); + } + } + private void UserAccepted(ulong steamId) + { + _userAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId); + base.RaiseClientJoined(steamId); + } + #endregion + } +} diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj index fbfab75..5d9bee4 100644 --- a/Torch.Server/Torch.Server.csproj +++ b/Torch.Server/Torch.Server.csproj @@ -191,6 +191,7 @@ Properties\AssemblyVersion.cs + diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 987aad9..b5dfef4 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -17,6 +17,8 @@ using Sandbox.Game.Multiplayer; using Sandbox.ModAPI; using SteamSDK; using Torch.API; +using Torch.API.Managers; +using Torch.API.Session; using Torch.Managers; using Torch.Server.Managers; using Torch.Utils; @@ -63,6 +65,9 @@ namespace Torch.Server AddManager(DedicatedInstance); Config = config ?? new TorchConfig(); MyFakes.ENABLE_INFINARIO = false; + + var sessionManager = Managers.GetManager(); + sessionManager.AddFactory((x) => new MultiplayerManagerDedicated(this)); } /// @@ -253,19 +258,20 @@ namespace Torch.Server { case SaveGameStatus.Success: Log.Info("Save completed."); - Multiplayer.SendMessage("Saved game.", playerId: callerId); + // TODO +// Multiplayer.SendMessage("Saved game.", playerId: callerId); break; case SaveGameStatus.SaveInProgress: Log.Error("Save failed, a save is already in progress."); - Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red); +// Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red); break; case SaveGameStatus.GameNotReady: Log.Error("Save failed, game was not ready."); - Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red); +// Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red); break; case SaveGameStatus.TimedOut: Log.Error("Save failed, save timed out."); - Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red); +// Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red); break; default: break; diff --git a/Torch.Server/Views/ChatControl.xaml.cs b/Torch.Server/Views/ChatControl.xaml.cs index 03bab57..2b8e9de 100644 --- a/Torch.Server/Views/ChatControl.xaml.cs +++ b/Torch.Server/Views/ChatControl.xaml.cs @@ -20,7 +20,9 @@ using Sandbox.Engine.Multiplayer; using Sandbox.Game.World; using SteamSDK; using Torch.API; +using Torch.API.Managers; using Torch.Managers; +using Torch.Server.Managers; namespace Torch.Server { @@ -30,7 +32,6 @@ namespace Torch.Server public partial class ChatControl : UserControl { private TorchBase _server; - private MultiplayerManager _multiplayer; public ChatControl() { @@ -40,11 +41,15 @@ namespace Torch.Server public void BindServer(ITorchServer server) { _server = (TorchBase)server; - _multiplayer = (MultiplayerManager)server.Multiplayer; ChatItems.Items.Clear(); - DataContext = _multiplayer; - if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc) - ncc.CollectionChanged += ChatHistory_CollectionChanged; + server.SessionLoaded += () => + { + var multiplayer = server.CurrentSession.Managers.GetManager(); + DataContext = multiplayer; + // TODO + // if (multiplayer.ChatHistory is INotifyCollectionChanged ncc) + // ncc.CollectionChanged += ChatHistory_CollectionChanged; + }; } private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -78,10 +83,10 @@ namespace Torch.Server if (string.IsNullOrEmpty(text)) return; - var commands = _server.Commands; - if (commands.IsCommand(text)) + var commands = _server.CurrentSession?.Managers.GetManager(); + if (commands != null && commands.IsCommand(text)) { - _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text)); + // TODO _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text)); _server.Invoke(() => { var response = commands.HandleCommandFromServer(text); @@ -90,15 +95,15 @@ namespace Torch.Server } else { - _server.Multiplayer.SendMessage(text); + _server.CurrentSession?.Managers.GetManager().SendMessageAsSelf(text); } Message.Text = ""; } private void OnMessageEntered_Callback(string response) { - if (!string.IsNullOrEmpty(response)) - _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response)); + //if (!string.IsNullOrEmpty(response)) + // TODO _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response)); } } } diff --git a/Torch.Server/Views/PlayerListControl.xaml.cs b/Torch.Server/Views/PlayerListControl.xaml.cs index a54a306..70fbbc3 100644 --- a/Torch.Server/Views/PlayerListControl.xaml.cs +++ b/Torch.Server/Views/PlayerListControl.xaml.cs @@ -20,7 +20,9 @@ using Sandbox.Game.World; using Sandbox.ModAPI; using SteamSDK; using Torch.API; +using Torch.API.Managers; using Torch.Managers; +using Torch.Server.Managers; using Torch.ViewModels; using VRage.Game.ModAPI; @@ -41,19 +43,23 @@ namespace Torch.Server public void BindServer(ITorchServer server) { _server = server; - DataContext = (MultiplayerManager)_server.Multiplayer; + server.SessionLoaded += () => + { + var multiplayer = server.CurrentSession?.Managers.GetManager(); + DataContext = multiplayer; + }; } private void KickButton_Click(object sender, RoutedEventArgs e) { var player = (KeyValuePair)PlayerList.SelectedItem; - _server.Multiplayer.KickPlayer(player.Key); + _server.CurrentSession?.Managers.GetManager()?.KickPlayer(player.Key); } private void BanButton_Click(object sender, RoutedEventArgs e) { var player = (KeyValuePair) PlayerList.SelectedItem; - _server.Multiplayer.BanPlayer(player.Key); + _server.CurrentSession?.Managers.GetManager()?.BanPlayer(player.Key); } } } diff --git a/Torch.Tests/ReflectionSystemTest.cs b/Torch.Tests/ReflectionSystemTest.cs index 0e6fc92..4161fe7 100644 --- a/Torch.Tests/ReflectionSystemTest.cs +++ b/Torch.Tests/ReflectionSystemTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using Torch.Utils; using Xunit; @@ -11,7 +12,7 @@ namespace Torch.Tests { TestUtils.Init(); } - + private static ReflectionTestManager _manager = new ReflectionTestManager().Init(typeof(ReflectionTestBinding)); public static IEnumerable Getters => _manager.Getters; @@ -19,6 +20,10 @@ namespace Torch.Tests public static IEnumerable Invokers => _manager.Invokers; + public static IEnumerable MemberInfo => _manager.MemberInfo; + + public static IEnumerable Events => _manager.Events; + #region Binding [Theory] [MemberData(nameof(Getters))] @@ -52,6 +57,28 @@ namespace Torch.Tests if (field.Field.IsStatic) Assert.NotNull(field.Field.GetValue(null)); } + + [Theory] + [MemberData(nameof(MemberInfo))] + public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field) + { + if (field.Field == null) + return; + Assert.True(ReflectedManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } + + [Theory] + [MemberData(nameof(Events))] + public void TestBindingEvents(ReflectionTestManager.FieldRef field) + { + if (field.Field == null) + return; + Assert.True(ReflectedManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } #endregion #region Results @@ -79,10 +106,51 @@ namespace Torch.Tests { return k >= 0; } + + public event Action Event1; + + public ReflectionTestTarget() + { + Event1 += Callback1; + } + + public bool Callback1Flag = false; + public void Callback1() + { + Callback1Flag = true; + } + public bool Callback2Flag = false; + public void Callback2() + { + Callback2Flag = true; + } + + public void RaiseEvent() + { + Event1?.Invoke(); + } } private class ReflectionTestBinding { + #region Instance + #region MemberInfo + [ReflectedFieldInfo(typeof(ReflectionTestTarget), "TestField")] + public static FieldInfo TestFieldInfo; + + [ReflectedPropertyInfo(typeof(ReflectionTestTarget), "TestProperty")] + public static PropertyInfo TestPropertyInfo; + + [ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall")] + public static MethodInfo TestMethodInfoGeneral; + + [ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall", Parameters = new[] { typeof(int) })] + public static MethodInfo TestMethodInfoExplicitArgs; + + [ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall", ReturnType = typeof(bool))] + public static MethodInfo TestMethodInfoExplicitReturn; + #endregion + [ReflectedGetter(Name = "TestField")] public static Func TestFieldGetter; [ReflectedSetter(Name = "TestField")] @@ -96,7 +164,27 @@ namespace Torch.Tests [ReflectedMethod] public static Func TestCall; + [ReflectedEventReplace(typeof(ReflectionTestTarget), "Event1", typeof(ReflectionTestTarget), "Callback1")] + public static Func TestEventReplacer; + #endregion + #region Static + #region MemberInfo + [ReflectedFieldInfo(typeof(ReflectionTestTarget), "TestFieldStatic")] + public static FieldInfo TestStaticFieldInfo; + + [ReflectedPropertyInfo(typeof(ReflectionTestTarget), "TestPropertyStatic")] + public static PropertyInfo TestStaticPropertyInfo; + + [ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic")] + public static MethodInfo TestStaticMethodInfoGeneral; + + [ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic", Parameters = new[] { typeof(int) })] + public static MethodInfo TestStaticMethodInfoExplicitArgs; + + [ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic", ReturnType = typeof(bool))] + public static MethodInfo TestStaticMethodInfoExplicitReturn; + #endregion [ReflectedGetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))] public static Func TestStaticFieldGetter; [ReflectedSetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))] @@ -109,6 +197,7 @@ namespace Torch.Tests [ReflectedStaticMethod(Type = typeof(ReflectionTestTarget))] public static Func TestCallStatic; + #endregion } #endregion @@ -215,7 +304,33 @@ namespace Torch.Tests Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1)); Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1)); } + + [Fact] + public void TestInstanceEventReplace() + { + var target = new ReflectionTestTarget(); + target.Callback1Flag = false; + target.RaiseEvent(); + Assert.True(target.Callback1Flag, "Control test failed"); + + target.Callback1Flag = false; + target.Callback2Flag = false; + ReflectedEventReplacer binder = ReflectionTestBinding.TestEventReplacer.Invoke(); + Assert.True(binder.Test(target), "Binder was unable to find the requested method"); + + binder.Replace(new Action(() => target.Callback2()), target); + target.RaiseEvent(); + Assert.True(target.Callback2Flag, "Substitute callback wasn't called"); + Assert.False(target.Callback1Flag, "Original callback wasn't removed"); + + target.Callback1Flag = false; + target.Callback2Flag = false; + binder.Restore(target); + target.RaiseEvent(); + Assert.False(target.Callback2Flag, "Substitute callback wasn't removed"); + Assert.True(target.Callback1Flag, "Original callback wasn't restored"); + } #endregion #endregion - } + } } diff --git a/Torch.Tests/ReflectionTestManager.cs b/Torch.Tests/ReflectionTestManager.cs index 425024e..87c3757 100644 --- a/Torch.Tests/ReflectionTestManager.cs +++ b/Torch.Tests/ReflectionTestManager.cs @@ -28,12 +28,16 @@ namespace Torch.Tests private readonly HashSet _getters = new HashSet(); private readonly HashSet _setters = new HashSet(); private readonly HashSet _invokers = new HashSet(); + private readonly HashSet _memberInfo = new HashSet(); + private readonly HashSet _events = new HashSet(); public ReflectionTestManager() { _getters.Add(new object[] { new FieldRef(null) }); _setters.Add(new object[] { new FieldRef(null) }); _invokers.Add(new object[] { new FieldRef(null) }); + _memberInfo.Add(new object[] {new FieldRef(null)}); + _events.Add(new object[] {new FieldRef(null)}); } public ReflectionTestManager Init(Assembly asm) @@ -50,12 +54,36 @@ namespace Torch.Tests BindingFlags.Public | BindingFlags.NonPublic)) { - if (field.GetCustomAttribute() != null) - _invokers.Add(new object[] { new FieldRef(field) }); - if (field.GetCustomAttribute() != null) - _getters.Add(new object[] { new FieldRef(field) }); - if (field.GetCustomAttribute() != null) - _setters.Add(new object[] { new FieldRef(field) }); + var args = new object[] { new FieldRef(field) }; + foreach (ReflectedMemberAttribute attr in field.GetCustomAttributes()) + { + if (!field.IsStatic) + throw new ArgumentException("Field must be static to be reflected"); + switch (attr) + { + case ReflectedMethodAttribute rma: + _invokers.Add(args); + break; + case ReflectedGetterAttribute rga: + _getters.Add(args); + break; + case ReflectedSetterAttribute rsa: + _setters.Add(args); + break; + case ReflectedFieldInfoAttribute rfia: + case ReflectedPropertyInfoAttribute rpia: + case ReflectedMethodInfoAttribute rmia: + _memberInfo.Add(args); + break; + } + } + var reflectedEventReplacer = field.GetCustomAttribute(); + if (reflectedEventReplacer != null) + { + if (!field.IsStatic) + throw new ArgumentException("Field must be static to be reflected"); + _events.Add(args); + } } return this; } @@ -66,6 +94,10 @@ namespace Torch.Tests public IEnumerable Invokers => _invokers; + public IEnumerable MemberInfo => _memberInfo; + + public IEnumerable Events => _events; + #endregion } diff --git a/Torch.Tests/TorchReflectionTest.cs b/Torch.Tests/TorchReflectionTest.cs index bb495d8..26014b5 100644 --- a/Torch.Tests/TorchReflectionTest.cs +++ b/Torch.Tests/TorchReflectionTest.cs @@ -27,6 +27,10 @@ namespace Torch.Tests public static IEnumerable Invokers => Manager().Invokers; + public static IEnumerable MemberInfo => Manager().MemberInfo; + + public static IEnumerable Events => Manager().Events; + #region Binding [Theory] [MemberData(nameof(Getters))] @@ -60,6 +64,28 @@ namespace Torch.Tests if (field.Field.IsStatic) Assert.NotNull(field.Field.GetValue(null)); } + + [Theory] + [MemberData(nameof(MemberInfo))] + public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field) + { + if (field.Field == null) + return; + Assert.True(ReflectedManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } + + [Theory] + [MemberData(nameof(Events))] + public void TestBindingEvents(ReflectionTestManager.FieldRef field) + { + if (field.Field == null) + return; + Assert.True(ReflectedManager.Process(field.Field)); + if (field.Field.IsStatic) + Assert.NotNull(field.Field.GetValue(null)); + } #endregion } } diff --git a/Torch/Commands/CommandContext.cs b/Torch/Commands/CommandContext.cs index dd54918..30700a7 100644 --- a/Torch/Commands/CommandContext.cs +++ b/Torch/Commands/CommandContext.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Text.RegularExpressions; using Torch.API; +using Torch.API.Managers; using Torch.API.Plugins; using VRage.Game; using VRage.Game.ModAPI; @@ -47,7 +48,7 @@ namespace Torch.Commands Response = message; if (Player != null) - Torch.Multiplayer.SendMessage(message, sender, Player.IdentityId, font); + Torch.CurrentSession.Managers.GetManager()?.SendMessageAsOther(sender, message, font, Player.SteamUserId); } } } \ No newline at end of file diff --git a/Torch/Commands/CommandManager.cs b/Torch/Commands/CommandManager.cs index 20b2101..e1083cc 100644 --- a/Torch/Commands/CommandManager.cs +++ b/Torch/Commands/CommandManager.cs @@ -9,6 +9,7 @@ using Torch.API; using Torch.API.Managers; using Torch.API.Plugins; using Torch.Managers; +using VRage.Game; using VRage.Game.ModAPI; using VRage.Network; @@ -21,7 +22,7 @@ namespace Torch.Commands public CommandTree Commands { get; set; } = new CommandTree(); private Logger _log = LogManager.GetLogger(nameof(CommandManager)); [Dependency] - private ChatManager _chatManager; + private IChatManagerServer _chatManager; public CommandManager(ITorchBase torch, char prefix = '!') : base(torch) { @@ -31,7 +32,7 @@ namespace Torch.Commands public override void Attach() { RegisterCommandModule(typeof(TorchCommands)); - _chatManager.MessageRecieved += HandleCommand; + _chatManager.MessageProcessing += HandleCommand; } public bool HasPermission(ulong steamId, Command command) @@ -93,20 +94,21 @@ namespace Torch.Commands return context.Response; } - public void HandleCommand(ChatMsg msg, ref bool sendToOthers) + public void HandleCommand(TorchChatMessage msg, ref bool consumed) { - HandleCommand(msg.Text, msg.Author, ref sendToOthers); + if (msg.AuthorSteamId.HasValue) + HandleCommand(msg.Message, msg.AuthorSteamId.Value, ref consumed); } - public void HandleCommand(string message, ulong steamId, ref bool sendToOthers, bool serverConsole = false) + public void HandleCommand(string message, ulong steamId, ref bool consumed, bool serverConsole = false) { if (message.Length < 1 || message[0] != Prefix) return; - sendToOthers = false; + consumed = true; - var player = Torch.Multiplayer.GetPlayerBySteamId(steamId); + var player = Torch.GetManager().GetPlayerBySteamId(steamId); if (player == null) { _log.Error($"Command {message} invoked by nonexistant player"); @@ -123,7 +125,7 @@ namespace Torch.Commands if (!HasPermission(steamId, command)) { _log.Info($"{player.DisplayName} tried to use command {cmdPath} without permission"); - Torch.Multiplayer.SendMessage($"You need to be a {command.MinimumPromoteLevel} or higher to use that command.", playerId: player.IdentityId); + _chatManager.SendMessageAsOther("Server", $"You need to be a {command.MinimumPromoteLevel} or higher to use that command.", MyFontEnum.Red, steamId); return; } diff --git a/Torch/Commands/TorchCommands.cs b/Torch/Commands/TorchCommands.cs index 0f604f0..5e7dbb6 100644 --- a/Torch/Commands/TorchCommands.cs +++ b/Torch/Commands/TorchCommands.cs @@ -21,7 +21,7 @@ namespace Torch.Commands [Permission(MyPromoteLevel.None)] public void Help() { - var commandManager = ((TorchBase)Context.Torch).Commands; + var commandManager = ((TorchBase)Context.Torch).CurrentSession.Managers.GetManager(); commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node); if (node != null) @@ -128,13 +128,15 @@ namespace Torch.Commands { if (i >= 60 && i % 60 == 0) { - Context.Torch.Multiplayer.SendMessage($"Restarting server in {i / 60} minute{Pluralize(i / 60)}."); - yield return null; + // TODO +// Context.Torch.Multiplayer.SendMessage($"Restarting server in {i / 60} minute{Pluralize(i / 60)}."); +// yield return null; } else if (i > 0) { - if (i < 11) - Context.Torch.Multiplayer.SendMessage($"Restarting server in {i} second{Pluralize(i)}."); + // TODO +// if (i < 11) +// Context.Torch.Multiplayer.SendMessage($"Restarting server in {i} second{Pluralize(i)}."); yield return null; } else diff --git a/Torch/Managers/ChatManager.cs b/Torch/Managers/ChatManager.cs deleted file mode 100644 index 2d9216a..0000000 --- a/Torch/Managers/ChatManager.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NLog; -using Sandbox.Engine.Multiplayer; -using Torch.API; -using Torch.API.Managers; -using VRage; -using VRage.Library.Collections; -using VRage.Network; -using VRage.Serialization; -using VRage.Utils; - -namespace Torch.Managers -{ - [Manager] - public class ChatManager : Manager - { - private static Logger _log = LogManager.GetLogger(nameof(ChatManager)); - - public delegate void MessageRecievedDel(ChatMsg msg, ref bool sendToOthers); - - public event MessageRecievedDel MessageRecieved; - - internal void RaiseMessageRecieved(ChatMsg msg, ref bool sendToOthers) => - MessageRecieved?.Invoke(msg, ref sendToOthers); - - [Dependency] - private INetworkManager _networkManager; - - public ChatManager(ITorchBase torchInstance) : base(torchInstance) - { - - } - - public override void Attach() - { - try - { - _networkManager.RegisterNetworkHandler(new ChatIntercept(this)); - } - catch - { - _log.Error("Failed to initialize network intercept, command hiding will not work! Falling back to another method."); - MyMultiplayer.Static.ChatMessageReceived += Static_ChatMessageReceived; - } - } - - private void Static_ChatMessageReceived(ulong arg1, string arg2) - { - var msg = new ChatMsg {Author = arg1, Text = arg2}; - var sendToOthers = true; - - RaiseMessageRecieved(msg, ref sendToOthers); - } - - internal class ChatIntercept : NetworkHandlerBase, INetworkHandler - { - private ChatManager _chatManager; - private bool? _unitTestResult; - - public ChatIntercept(ChatManager chatManager) - { - _chatManager = chatManager; - } - - public override bool CanHandle(CallSite site) - { - if (site.MethodInfo.Name != "OnChatMessageRecieved") - return false; - - if (_unitTestResult.HasValue) - return _unitTestResult.Value; - - var parameters = site.MethodInfo.GetParameters(); - if (parameters.Length != 1) - { - _unitTestResult = false; - return false; - } - - if (parameters[0].ParameterType != typeof(ChatMsg)) - _unitTestResult = false; - - _unitTestResult = true; - - return _unitTestResult.Value; - } - - public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet) - { - var msg = new ChatMsg(); - Serialize(site.MethodInfo, stream, ref msg); - - bool sendToOthers = true; - _chatManager.RaiseMessageRecieved(msg, ref sendToOthers); - - return !sendToOthers; - } - } - } -} diff --git a/Torch/Managers/ChatManager/ChatManagerClient.cs b/Torch/Managers/ChatManager/ChatManagerClient.cs new file mode 100644 index 0000000..796b8de --- /dev/null +++ b/Torch/Managers/ChatManager/ChatManagerClient.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NLog; +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Sandbox.Game.Entities.Character; +using Sandbox.Game.Gui; +using Sandbox.Game.World; +using Torch.API; +using Torch.API.Managers; +using Torch.Utils; +using VRage.Game; +using Game = Sandbox.Engine.Platform.Game; + +namespace Torch.Managers.ChatManager +{ + public class ChatManagerClient : Manager, IChatManagerClient + { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + /// + public ChatManagerClient(ITorchBase torchInstance) : base(torchInstance) { } + + /// + // TODO doesn't work in Offline worlds. Method injection or network injection. + public event DelMessageRecieved MessageRecieved; + + /// + // TODO doesn't work at all. Method injection or network injection. + public event DelMessageSending MessageSending; + + /// + public void SendMessageAsSelf(string message) + { + if (MyMultiplayer.Static != null) + { + if (Game.IsDedicated) + { + var scripted = new ScriptedChatMsg() + { + Author = "Server", + Font = MyFontEnum.Red, + Text = message, + Target = 0 + }; + MyMultiplayerBase.SendScriptedChatMessage(ref scripted); + } + else + MyMultiplayer.Static.SendChatMessage(message); + } + else if (MyHud.Chat != null) + MyHud.Chat.ShowMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", message); + } + + /// + public void DisplayMessageOnSelf(string author, string message, string font) + { + MyHud.Chat.ShowMessage(author, message, font); + MySession.Static.GlobalChatHistory.GlobalChatHistory.Chat.Enqueue(new MyGlobalChatItem() + { + Author = author, + AuthorFont = font, + Text = message + }); + } + + /// + public override void Attach() + { + base.Attach(); + if (MyMultiplayer.Static == null) + _log.Warn("Currently ChatManagerClient doesn't support handling on an offline client"); + else + { + _chatMessageRecievedReplacer = _chatMessageReceivedFactory.Invoke(); + _scriptedChatMessageRecievedReplacer = _scriptedChatMessageReceivedFactory.Invoke(); + _chatMessageRecievedReplacer.Replace(new Action(Multiplayer_ChatMessageReceived), + MyMultiplayer.Static); + _scriptedChatMessageRecievedReplacer.Replace( + new Action(Multiplayer_ScriptedChatMessageReceived), MyMultiplayer.Static); + } + } + + /// + public override void Detach() + { + if (_chatMessageRecievedReplacer != null && _chatMessageRecievedReplacer.Replaced) + _chatMessageRecievedReplacer.Restore(MyHud.Chat); + if (_scriptedChatMessageRecievedReplacer != null && _scriptedChatMessageRecievedReplacer.Replaced) + _scriptedChatMessageRecievedReplacer.Restore(MyHud.Chat); + base.Detach(); + } + + private void Multiplayer_ChatMessageReceived(ulong steamUserId, string message) + { + var torchMsg = new TorchChatMessage() + { + AuthorSteamId = steamUserId, + Author = Torch.CurrentSession.Managers.GetManager() + ?.GetSteamUsername(steamUserId), + Font = (steamUserId == MyGameService.UserId) ? "DarkBlue" : "Blue", + Message = message + }; + var consumed = false; + MessageRecieved?.Invoke(torchMsg, ref consumed); + if (!consumed) + _hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message); + } + + private void Multiplayer_ScriptedChatMessageReceived(string author, string message, string font) + { + var torchMsg = new TorchChatMessage() + { + AuthorSteamId = null, + Author = author, + Font = font, + Message = message + }; + var consumed = false; + MessageRecieved?.Invoke(torchMsg, ref consumed); + if (!consumed) + _hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font); + } + + private const string _hudChatMessageReceivedName = "Multiplayer_ChatMessageReceived"; + private const string _hudChatScriptedMessageReceivedName = "multiplayer_ScriptedChatMessageReceived"; + + [ReflectedMethod(Name = _hudChatMessageReceivedName)] + private static Action _hudChatMessageReceived; + [ReflectedMethod(Name = _hudChatScriptedMessageReceivedName)] + private static Action _hudChatScriptedMessageReceived; + + [ReflectedEventReplace(typeof(MyMultiplayerBase), nameof(MyMultiplayerBase.ChatMessageReceived), typeof(MyHudChat), _hudChatMessageReceivedName)] + private static Func _chatMessageReceivedFactory; + [ReflectedEventReplace(typeof(MyMultiplayerBase), nameof(MyMultiplayerBase.ScriptedChatMessageReceived), typeof(MyHudChat), _hudChatScriptedMessageReceivedName)] + private static Func _scriptedChatMessageReceivedFactory; + + private ReflectedEventReplacer _chatMessageRecievedReplacer; + private ReflectedEventReplacer _scriptedChatMessageRecievedReplacer; + } +} diff --git a/Torch/Managers/ChatManager/ChatManagerServer.cs b/Torch/Managers/ChatManager/ChatManagerServer.cs new file mode 100644 index 0000000..8f1a507 --- /dev/null +++ b/Torch/Managers/ChatManager/ChatManagerServer.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using NLog; +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Sandbox.Game.Gui; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.World; +using Torch.API; +using Torch.API.Managers; +using Torch.Utils; +using VRage; +using VRage.Library.Collections; +using VRage.Network; + +namespace Torch.Managers.ChatManager +{ + public class ChatManagerServer : ChatManagerClient, IChatManagerServer + { + [Dependency(Optional = true)] + private INetworkManager _networkManager; + + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + private readonly ChatIntercept _chatIntercept; + + /// + public ChatManagerServer(ITorchBase torchInstance) : base(torchInstance) + { + _chatIntercept = new ChatIntercept(this); + } + + /// + public event DelMessageProcessing MessageProcessing; + + /// + public void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0) + { + if (MyMultiplayer.Static == null) + { + if (targetSteamId == MyGameService.UserId || targetSteamId == 0) + MyHud.Chat?.ShowMessage(authorId == MyGameService.UserId ? + (MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player") : $"user_{authorId}", message); + return; + } + if (MyMultiplayer.Static is MyDedicatedServerBase dedicated) + { + var msg = new ChatMsg() { Author = authorId, Text = message }; + _dedicatedServerBaseSendChatMessage.Invoke(ref msg); + _dedicatedServerBaseOnChatMessage.Invoke(dedicated, new object[] { msg }); + + } + } + + +#pragma warning disable 649 + private delegate void DelMultiplayerBaseSendChatMessage(ref ChatMsg arg); + [ReflectedStaticMethod(Name = "SendChatMessage", Type = typeof(MyMultiplayerBase))] + private static DelMultiplayerBaseSendChatMessage _dedicatedServerBaseSendChatMessage; + + // [ReflectedMethod] doesn't play well with instance methods and refs. + [ReflectedMethodInfo(typeof(MyDedicatedServerBase), "OnChatMessage")] + private static MethodInfo _dedicatedServerBaseOnChatMessage; +#pragma warning restore 649 + + /// + public void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0) + { + if (MyMultiplayer.Static == null) + { + if (targetSteamId == MyGameService.UserId || targetSteamId == 0) + MyHud.Chat?.ShowMessage(author, message, font); + return; + } + var scripted = new ScriptedChatMsg() + { + Author = author, + Text = message, + Font = font, + Target = (long)targetSteamId + }; + MyMultiplayerBase.SendScriptedChatMessage(ref scripted); + } + + + /// + public override void Attach() + { + base.Attach(); + if (_networkManager != null) + try + { + _networkManager.RegisterNetworkHandler(_chatIntercept); + _log.Debug("Initialized network intercept for chat messages"); + return; + } + catch + { + // Discard exception and use second method + } + + if (MyMultiplayer.Static != null) + { + MyMultiplayer.Static.ChatMessageReceived += MpStaticChatMessageReceived; + _log.Warn("Failed to initialize network intercept, we can't discard chat messages! Falling back to another method."); + } + else + _log.Error("Failed to initialize network intercept, we can't discard chat messages!"); + } + + private void MpStaticChatMessageReceived(ulong a, string b) + { + var tmp = false; + RaiseMessageRecieved(new ChatMsg() + { + Author = a, + Text = b + }, ref tmp); + } + + /// + public override void Detach() + { + if (MyMultiplayer.Static != null) + MyMultiplayer.Static.ChatMessageReceived -= MpStaticChatMessageReceived; + _networkManager?.UnregisterNetworkHandler(_chatIntercept); + base.Detach(); + } + + internal void RaiseMessageRecieved(ChatMsg message, ref bool consumed) + { + var torchMsg = new TorchChatMessage() + { + AuthorSteamId = message.Author, + Author = MyMultiplayer.Static?.GetMemberName(message.Author) ?? $"user_{message.Author}", + Message = message.Text + }; + MessageProcessing?.Invoke(torchMsg, ref consumed); + } + + internal class ChatIntercept : NetworkHandlerBase, INetworkHandler + { + private readonly ChatManagerServer _chatManager; + private bool? _unitTestResult; + + public ChatIntercept(ChatManagerServer chatManager) + { + _chatManager = chatManager; + } + + /// + public override bool CanHandle(CallSite site) + { + if (site.MethodInfo.Name != "OnChatMessageRecieved") + return false; + + if (_unitTestResult.HasValue) + return _unitTestResult.Value; + + ParameterInfo[] parameters = site.MethodInfo.GetParameters(); + if (parameters.Length != 1) + { + _unitTestResult = false; + return false; + } + + if (parameters[0].ParameterType != typeof(ChatMsg)) + _unitTestResult = false; + + _unitTestResult = true; + + return _unitTestResult.Value; + } + + /// + public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet) + { + var msg = new ChatMsg(); + Serialize(site.MethodInfo, stream, ref msg); + + var consumed = false; + _chatManager.RaiseMessageRecieved(msg, ref consumed); + + return consumed; + } + } + } +} diff --git a/Torch/Managers/MultiplayerManager.cs b/Torch/Managers/MultiplayerManager.cs deleted file mode 100644 index ac1f09f..0000000 --- a/Torch/Managers/MultiplayerManager.cs +++ /dev/null @@ -1,338 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Data; -using System.Windows.Threading; -using NLog; -using Torch; -using Sandbox; -using Sandbox.Engine.Multiplayer; -using Sandbox.Engine.Networking; -using Sandbox.Game.Entities.Character; -using Sandbox.Game.Multiplayer; -using Sandbox.Game.World; -using Sandbox.ModAPI; -using SteamSDK; -using Torch.API; -using Torch.API.Managers; -using Torch.Collections; -using Torch.Commands; -using Torch.Utils; -using Torch.ViewModels; -using VRage.Game; -using VRage.Game.ModAPI; -using VRage.GameServices; -using VRage.Library.Collections; -using VRage.Network; -using VRage.Steam; -using VRage.Utils; - -namespace Torch.Managers -{ - /// - public class MultiplayerManager : Manager, IMultiplayerManager - { - /// - public event Action PlayerJoined; - /// - public event Action PlayerLeft; - /// - public event MessageReceivedDel MessageReceived; - - public IList ChatHistory { get; } = new ObservableList(); - public ObservableDictionary Players { get; } = new ObservableDictionary(); - public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer; - private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager)); - private static readonly Logger ChatLog = LogManager.GetLogger("Chat"); - - [ReflectedGetter(Name = "m_players")] - private static Func> _onlinePlayers; - - [Dependency] - private ChatManager _chatManager; - [Dependency] - private CommandManager _commandManager; - [Dependency] - private NetworkManager _networkManager; - - internal MultiplayerManager(ITorchBase torch) : base(torch) - { - - } - - /// - public override void Attach() - { - Torch.SessionLoaded += OnSessionLoaded; - _chatManager.MessageRecieved += Instance_MessageRecieved; - } - - private void Instance_MessageRecieved(ChatMsg msg, ref bool sendToOthers) - { - var message = ChatMessage.FromChatMsg(msg); - ChatHistory.Add(message); - ChatLog.Info($"{message.Name}: {message.Message}"); - MessageReceived?.Invoke(message, ref sendToOthers); - } - - /// - public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId)); - - /// - public void BanPlayer(ulong steamId, bool banned = true) - { - Torch.Invoke(() => - { - MyMultiplayer.Static.BanClient(steamId, banned); - if (_gameOwnerIds.ContainsKey(steamId)) - MyMultiplayer.Static.BanClient(_gameOwnerIds[steamId], banned); - }); - } - - /// - public IMyPlayer GetPlayerByName(string name) - { - return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value; - } - - /// - public IMyPlayer GetPlayerBySteamId(ulong steamId) - { - _onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p); - return p; - } - - public ulong GetSteamId(long identityId) - { - foreach (var kv in _onlinePlayers.Invoke(MySession.Static.Players)) - { - if (kv.Value.Identity.IdentityId == identityId) - return kv.Key.SteamId; - } - - return 0; - } - - /// - public string GetSteamUsername(ulong steamId) - { - return MyMultiplayer.Static.GetMemberName(steamId); - } - - /// - public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red) - { - if (string.IsNullOrEmpty(message)) - return; - - ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, message)); - if (_commandManager.IsCommand(message)) - { - var response = _commandManager.HandleCommandFromServer(message); - ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, response)); - } - else - { - var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message }; - MyMultiplayerBase.SendScriptedChatMessage(ref msg); - var character = MySession.Static.Players.TryGetIdentity(playerId)?.Character; - var steamId = GetSteamId(playerId); - if (character == null) - return; - - var addToGlobalHistoryMethod = typeof(MyCharacter).GetMethod("OnGlobalMessageSuccess", BindingFlags.Instance | BindingFlags.NonPublic); - _networkManager.RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message); - } - } - - private void OnSessionLoaded() - { - Log.Info("Initializing Steam auth"); - MyMultiplayer.Static.ClientKicked += OnClientKicked; - MyMultiplayer.Static.ClientLeft += OnClientLeft; - - //TODO: Move these with the methods? - if (!RemoveHandlers()) - { - Log.Error("Steam auth failed to initialize"); - return; - } - MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse; - MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse; - Log.Info("Steam auth initialized"); - } - - private void OnClientKicked(ulong steamId) - { - OnClientLeft(steamId, MyChatMemberStateChangeEnum.Kicked); - } - - private void OnClientLeft(ulong steamId, MyChatMemberStateChangeEnum stateChange) - { - Players.TryGetValue(steamId, out PlayerViewModel vm); - if (vm == null) - vm = new PlayerViewModel(steamId); - Log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}."); - PlayerLeft?.Invoke(vm); - Players.Remove(steamId); - } - - //TODO: Split the following into a new file? - //These methods override some Keen code to allow us full control over client authentication. - //This lets us have a server set to private (admins only) or friends (friends of all listed admins) - [ReflectedGetter(Name = "m_members")] - private static Func> _members; - [ReflectedGetter(Name = "m_waitingForGroup")] - private static Func> _waitingForGroup; - [ReflectedGetter(Name = "m_kickedClients")] - private static Func> _kickedClients; - //private HashSet _waitingForFriends; - private Dictionary _gameOwnerIds = new Dictionary(); - //private IMultiplayer _multiplayerImplementation; - - /// - /// Removes Keen's hooks into some Steam events so we have full control over client authentication - /// - private static bool RemoveHandlers() - { - MethodInfo methodValidateAuthTicket = typeof(MyDedicatedServerBase).GetMethod("GameServer_ValidateAuthTicketResponse", - BindingFlags.NonPublic | BindingFlags.Instance); - if (methodValidateAuthTicket == null) - { - Log.Error("Unable to find the GameServer_ValidateAuthTicketResponse method to unhook"); - return false; - } - var eventValidateAuthTicket = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.ValidateAuthTicketResponse)) - .FirstOrDefault(x => x.Method == methodValidateAuthTicket) as Action; - if (eventValidateAuthTicket == null) - { - Log.Error( - "Unable to unhook the GameServer_ValidateAuthTicketResponse method from GameServer.ValidateAuthTicketResponse"); - Log.Debug(" Want to unhook {0}", methodValidateAuthTicket); - Log.Debug(" Registered handlers: "); - foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer, - nameof(MyGameService.GameServer.ValidateAuthTicketResponse))) - Log.Debug(" - " + method.Method); - return false; - } - - MethodInfo methodUserGroupStatus = typeof(MyDedicatedServerBase).GetMethod("GameServer_UserGroupStatus", - BindingFlags.NonPublic | BindingFlags.Instance); - if (methodUserGroupStatus == null) - { - Log.Error("Unable to find the GameServer_UserGroupStatus method to unhook"); - return false; - } - var eventUserGroupStatus = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse)) - .FirstOrDefault(x => x.Method == methodUserGroupStatus) - as Action; - if (eventUserGroupStatus == null) - { - Log.Error("Unable to unhook the GameServer_UserGroupStatus method from GameServer.UserGroupStatus"); - Log.Debug(" Want to unhook {0}", methodUserGroupStatus); - Log.Debug(" Registered handlers: "); - foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse))) - Log.Debug(" - " + method.Method); - return false; - } - - MyGameService.GameServer.ValidateAuthTicketResponse -= - eventValidateAuthTicket; - MyGameService.GameServer.UserGroupStatusResponse -= - eventUserGroupStatus; - return true; - } - - //Largely copied from SE - private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner) - { - Log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}"); - if (IsClientBanned.Invoke(MyMultiplayer.Static, steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner)) - { - UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins); - RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID); - } - else if (IsClientKicked.Invoke(MyMultiplayer.Static, steamOwner)) - { - UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently); - RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID); - } - if (response != JoinResult.OK) - { - UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response); - return; - } - if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit) - { - UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull); - return; - } - if (MySandboxGame.ConfigDedicated.GroupID == 0uL || - MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) || - MySandboxGame.ConfigDedicated.Administrators.Contains(ConvertSteamIDFrom64(steamID))) - { - this.UserAccepted(steamID); - return; - } - if (GetServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan) - { - UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid); - return; - } - if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID)) - { - _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID); - return; - } - UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline); - } - - private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer) - { - if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId)) - { - if (member || officer) - UserAccepted(userId); - else - UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup); - } - } - - private void UserAccepted(ulong steamId) - { - UserAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId); - - var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected }; - Log.Info($"Player {vm.Name} joined ({vm.SteamId})"); - Players.Add(steamId, vm); - PlayerJoined?.Invoke(vm); - } - - [ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase))] - private static Func ConvertSteamIDFrom64; - - [ReflectedStaticMethod(Type = typeof(MyGameService))] - private static Func GetServerAccountType; - - [ReflectedMethod(Name = "UserAccepted")] - private static Action UserAcceptedImpl; - - [ReflectedMethod] - private static Action UserRejected; - [ReflectedMethod] - private static Func IsClientBanned; - [ReflectedMethod] - private static Func IsClientKicked; - [ReflectedMethod] - private static Action RaiseClientKicked; - } -} diff --git a/Torch/Managers/MultiplayerManagerBase.cs b/Torch/Managers/MultiplayerManagerBase.cs new file mode 100644 index 0000000..eaad039 --- /dev/null +++ b/Torch/Managers/MultiplayerManagerBase.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; +using System.Windows.Threading; +using NLog; +using Torch; +using Sandbox; +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Sandbox.Game.Entities.Character; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.World; +using Sandbox.ModAPI; +using SteamSDK; +using Torch.API; +using Torch.API.Managers; +using Torch.Collections; +using Torch.Commands; +using Torch.Utils; +using Torch.ViewModels; +using VRage.Game; +using VRage.Game.ModAPI; +using VRage.GameServices; +using VRage.Library.Collections; +using VRage.Network; +using VRage.Steam; +using VRage.Utils; + +namespace Torch.Managers +{ + /// + public abstract class MultiplayerManagerBase : Manager, IMultiplayerManagerBase + { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + + /// + public event Action PlayerJoined; + /// + public event Action PlayerLeft; + + public ObservableDictionary Players { get; } = new ObservableDictionary(); + +#pragma warning disable 649 + [ReflectedGetter(Name = "m_players")] + private static Func> _onlinePlayers; +#pragma warning restore 649 + + protected MultiplayerManagerBase(ITorchBase torch) : base(torch) + { + + } + + /// + public override void Attach() + { + MyMultiplayer.Static.ClientLeft += OnClientLeft; + } + + /// + public override void Detach() + { + MyMultiplayer.Static.ClientLeft -= OnClientLeft; + } + + /// + public IMyPlayer GetPlayerByName(string name) + { + return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value; + } + + /// + public IMyPlayer GetPlayerBySteamId(ulong steamId) + { + _onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p); + return p; + } + + public ulong GetSteamId(long identityId) + { + foreach (KeyValuePair kv in _onlinePlayers.Invoke(MySession.Static.Players)) + { + if (kv.Value.Identity.IdentityId == identityId) + return kv.Key.SteamId; + } + + return 0; + } + + /// + public string GetSteamUsername(ulong steamId) + { + return MyMultiplayer.Static.GetMemberName(steamId); + } + + private void OnClientLeft(ulong steamId, MyChatMemberStateChangeEnum stateChange) + { + Players.TryGetValue(steamId, out PlayerViewModel vm); + if (vm == null) + vm = new PlayerViewModel(steamId); + _log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}."); + PlayerLeft?.Invoke(vm); + Players.Remove(steamId); + } + + protected void RaiseClientJoined(ulong steamId) + { + var vm = new PlayerViewModel(steamId){State=ConnectionState.Connected}; + _log.Info($"Plat {vm.Name} joined ({vm.SteamId}"); + Players.Add(steamId, vm); + PlayerJoined?.Invoke(vm); + } + } +} diff --git a/Torch/Managers/NetworkManager/NetworkManager.cs b/Torch/Managers/NetworkManager/NetworkManager.cs index 91dff87..743f319 100644 --- a/Torch/Managers/NetworkManager/NetworkManager.cs +++ b/Torch/Managers/NetworkManager/NetworkManager.cs @@ -20,9 +20,9 @@ namespace Torch.Managers { private static Logger _log = LogManager.GetLogger(nameof(NetworkManager)); - private const string MyTransportLayerField = "TransportLayer"; - private const string TransportHandlersField = "m_handlers"; - private HashSet _networkHandlers = new HashSet(); + private const string _myTransportLayerField = "TransportLayer"; + private const string _transportHandlersField = "m_handlers"; + private readonly HashSet _networkHandlers = new HashSet(); private bool _init; [ReflectedGetter(Name = "m_typeTable")] @@ -40,14 +40,14 @@ namespace Torch.Managers try { var syncLayerType = typeof(MySyncLayer); - var transportLayerField = syncLayerType.GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance); + var transportLayerField = syncLayerType.GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance); if (transportLayerField == null) throw new TypeLoadException("Could not find internal type for TransportLayer"); var transportLayerType = transportLayerField.FieldType; - if (!Reflection.HasField(transportLayerType, TransportHandlersField)) + if (!Reflection.HasField(transportLayerType, _transportHandlersField)) throw new TypeLoadException("Could not find Handlers field"); return true; @@ -79,9 +79,9 @@ namespace Torch.Managers throw new InvalidOperationException("Reflection unit test failed."); //don't bother with nullchecks here, it was all handled in ReflectionUnitTest - var transportType = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType; - var transportInstance = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.Static.SyncLayer); - var handlers = (IDictionary)transportType.GetField(TransportHandlersField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(transportInstance); + var transportType = typeof(MySyncLayer).GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType; + var transportInstance = typeof(MySyncLayer).GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.Static.SyncLayer); + var handlers = (IDictionary)transportType.GetField(_transportHandlersField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(transportInstance); var handlerTypeField = handlers.GetType().GenericTypeArguments[0].GetField("messageId"); //Should be MyTransportLayer.HandlerId object id = null; foreach (var key in handlers.Keys) @@ -205,6 +205,8 @@ namespace Torch.Managers } } + + /// public void RegisterNetworkHandler(INetworkHandler handler) { var handlerType = handler.GetType().FullName; @@ -225,6 +227,12 @@ namespace Torch.Managers _networkHandlers.Add(handler); } + /// + public bool UnregisterNetworkHandler(INetworkHandler handler) + { + return _networkHandlers.Remove(handler); + } + public void RegisterNetworkHandlers(params INetworkHandler[] handlers) { foreach (var handler in handlers) diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index 77002b8..4b7be00 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -155,6 +155,8 @@ + + @@ -172,13 +174,12 @@ - - + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 3a40a23..cc88c84 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using NLog; using Sandbox; +using Sandbox.Engine.Multiplayer; using Sandbox.Game; using Sandbox.Game.Multiplayer; using Sandbox.Game.Screens.Helpers; @@ -22,10 +23,12 @@ using Torch.API.ModAPI; using Torch.API.Session; using Torch.Commands; using Torch.Managers; +using Torch.Managers.ChatManager; using Torch.Utils; using Torch.Session; using VRage.Collections; using VRage.FileSystem; +using VRage.Game; using VRage.Game.ObjectBuilder; using VRage.ObjectBuilders; using VRage.Plugins; @@ -67,18 +70,6 @@ namespace Torch /// [Obsolete("Use GetManager() or the [Dependency] attribute.")] public IPluginManager Plugins { get; protected set; } - /// - [Obsolete("Use GetManager() or the [Dependency] attribute.")] - public IMultiplayerManager Multiplayer { get; protected set; } - /// - [Obsolete("Use GetManager() or the [Dependency] attribute.")] - public EntityManager Entities { get; protected set; } - /// - [Obsolete("Use GetManager() or the [Dependency] attribute.")] - public INetworkManager Network { get; protected set; } - /// - [Obsolete("Use GetManager() or the [Dependency] attribute.")] - public CommandManager Commands { get; protected set; } /// public ITorchSession CurrentSession => Managers?.GetManager()?.CurrentSession; @@ -120,31 +111,28 @@ namespace Torch Managers = new DependencyManager(); Plugins = new PluginManager(this); - Multiplayer = new MultiplayerManager(this); - Entities = new EntityManager(this); - Network = new NetworkManager(this); - Commands = new CommandManager(this); - Managers.AddManager(new TorchSessionManager(this)); + var sessionManager = new TorchSessionManager(this); + sessionManager.AddFactory((x) => MyMultiplayer.Static?.SyncLayer != null ? new NetworkManager(this) : null); + sessionManager.AddFactory((x) => Sync.IsServer ? new ChatManagerServer(this) : new ChatManagerClient(this)); + sessionManager.AddFactory((x) => Sync.IsServer ? new CommandManager(this) : null); + sessionManager.AddFactory((x) => new EntityManager(this)); + + Managers.AddManager(sessionManager); Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new UpdateManager(this)); - Managers.AddManager(Network); - Managers.AddManager(Commands); Managers.AddManager(Plugins); - Managers.AddManager(Multiplayer); - Managers.AddManager(Entities); - Managers.AddManager(new ChatManager(this)); TorchAPI.Instance = this; } - /// + [Obsolete("Prefer using Managers.GetManager for global managers")] public T GetManager() where T : class, IManager { return Managers.GetManager(); } - /// + [Obsolete("Prefer using Managers.AddManager for global managers")] public bool AddManager(T manager) where T : class, IManager { return Managers.AddManager(manager); @@ -254,10 +242,13 @@ namespace Torch Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null"); GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", ".")); - try { Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}"; } + try + { + Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}"; + } catch { - ///Running as service + // Running as service } #if DEBUG diff --git a/Torch/Utils/ReflectedManager.cs b/Torch/Utils/ReflectedManager.cs index ce27f93..2f52507 100644 --- a/Torch/Utils/ReflectedManager.cs +++ b/Torch/Utils/ReflectedManager.cs @@ -25,6 +25,71 @@ namespace Torch.Utils public Type Type { get; set; } = null; } + #region MemberInfoAttributes + /// + /// Indicates that this field should contain the instance for the given field. + /// + [AttributeUsage(AttributeTargets.Field)] + public class ReflectedFieldInfoAttribute : ReflectedMemberAttribute + { + /// + /// Creates a reflected field info attribute using the given type and name. + /// + /// Type that contains the member + /// Name of the member + public ReflectedFieldInfoAttribute(Type type, string name) + { + Type = type; + Name = name; + } + } + + /// + /// Indicates that this field should contain the instance for the given method. + /// + [AttributeUsage(AttributeTargets.Field)] + public class ReflectedMethodInfoAttribute : ReflectedMemberAttribute + { + /// + /// Creates a reflected method info attribute using the given type and name. + /// + /// Type that contains the member + /// Name of the member + public ReflectedMethodInfoAttribute(Type type, string name) + { + Type = type; + Name = name; + } + /// + /// Expected parameters of this method, or null if any parameters are accepted. + /// + public Type[] Parameters { get; set; } = null; + /// + /// Expected return type of this method, or null if any return type is accepted. + /// + public Type ReturnType { get; set; } = null; + } + + /// + /// Indicates that this field should contain the instance for the given property. + /// + [AttributeUsage(AttributeTargets.Field)] + public class ReflectedPropertyInfoAttribute : ReflectedMemberAttribute + { + /// + /// Creates a reflected property info attribute using the given type and name. + /// + /// Type that contains the member + /// Name of the member + public ReflectedPropertyInfoAttribute(Type type, string name) + { + Type = type; + Name = name; + } + } + #endregion + + #region FieldPropGetSet /// /// Indicates that this field should contain a delegate capable of retrieving the value of a field. /// @@ -72,7 +137,9 @@ namespace Torch.Utils public class ReflectedSetterAttribute : ReflectedMemberAttribute { } + #endregion + #region Invoker /// /// Indicates that this field should contain a delegate capable of invoking an instance method. /// @@ -116,6 +183,184 @@ namespace Torch.Utils public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute { } + #endregion + + #region EventReplacer + /// + /// Instance of statefully replacing and restoring the callbacks of an event. + /// + public class ReflectedEventReplacer + { + private const BindingFlags BindFlagAll = BindingFlags.Static | + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic; + + private object _instance; + private Func> _backingStoreReader; + private Action _callbackAdder; + private Action _callbackRemover; + private readonly ReflectedEventReplaceAttribute _attributes; + private readonly HashSet _registeredCallbacks = new HashSet(); + private readonly MethodInfo _targetMethodInfo; + + internal ReflectedEventReplacer(ReflectedEventReplaceAttribute attr) + { + _attributes = attr; + FieldInfo backingStore = GetEventBackingField(attr.EventName, attr.EventDeclaringType); + if (backingStore == null) + throw new ArgumentException($"Unable to find backing field for event {attr.EventDeclaringType.FullName}#{attr.EventName}"); + EventInfo evtInfo = ReflectedManager.GetFieldPropRecursive(attr.EventDeclaringType, attr.EventName, BindFlagAll, (a, b, c) => a.GetEvent(b, c)); + if (evtInfo == null) + throw new ArgumentException($"Unable to find event info for event {attr.EventDeclaringType.FullName}#{attr.EventName}"); + _backingStoreReader = () => GetEventsInternal(_instance, backingStore); + _callbackAdder = (x) => evtInfo.AddEventHandler(_instance, x); + _callbackRemover = (x) => evtInfo.RemoveEventHandler(_instance, x); + if (attr.TargetParameters == null) + { + _targetMethodInfo = attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll); + if (_targetMethodInfo == null) + throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName} to replace"); + } + else + { + _targetMethodInfo = + attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll, null, attr.TargetParameters, null); + if (_targetMethodInfo == null) + throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName}){string.Join(", ", attr.TargetParameters.Select(x => x.FullName))}) to replace"); + } + } + + /// + /// Test that this replacement can be performed. + /// + /// The instance to operate on, or null if static + /// true if possible, false if unsuccessful + public bool Test(object instance) + { + _instance = instance; + _registeredCallbacks.Clear(); + foreach (Delegate callback in _backingStoreReader.Invoke()) + if (callback.Method == _targetMethodInfo) + _registeredCallbacks.Add(callback); + + return _registeredCallbacks.Count > 0; + } + + private Delegate _newCallback; + + /// + /// Removes the target callback defined in the attribute and replaces it with the provided callback. + /// + /// The new event callback + /// The instance to operate on, or null if static + public void Replace(Delegate newCallback, object instance) + { + _instance = instance; + if (_newCallback != null) + throw new Exception("Reflected event replacer is in invalid state: Replace when already replaced"); + _newCallback = newCallback; + Test(instance); + if (_registeredCallbacks.Count == 0) + throw new Exception("Reflected event replacer is in invalid state: Nothing to replace"); + foreach (Delegate callback in _registeredCallbacks) + _callbackRemover.Invoke(callback); + _callbackAdder.Invoke(_newCallback); + } + + /// + /// Checks if the callback is currently replaced + /// + public bool Replaced => _newCallback != null; + + /// + /// Removes the callback added by and puts the original callback back. + /// + /// The instance to operate on, or null if static + public void Restore(object instance) + { + _instance = instance; + if (_newCallback == null) + throw new Exception("Reflected event replacer is in invalid state: Restore when not replaced"); + _callbackRemover.Invoke(_newCallback); + foreach (Delegate callback in _registeredCallbacks) + _callbackAdder.Invoke(callback); + _newCallback = null; + } + + + private static readonly string[] _backingFieldForEvent = { "{0}", "{0}" }; + + private static FieldInfo GetEventBackingField(string eventName, Type baseType) + { + FieldInfo eventField = null; + Type type = baseType; + while (type != null && eventField == null) + { + for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++) + eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), BindFlagAll); + type = type.BaseType; + } + return eventField; + } + + private static IEnumerable GetEventsInternal(object instance, FieldInfo eventField) + { + if (eventField.GetValue(instance) is MulticastDelegate eventDel) + { + foreach (Delegate handle in eventDel.GetInvocationList()) + yield return handle; + } + } + } + + /// + /// Attribute used to indicate that the the given field, of type ]]>, should be filled with + /// a function used to create a new event replacer. + /// + [AttributeUsage(AttributeTargets.Field)] + public class ReflectedEventReplaceAttribute : Attribute + { + /// + /// Type that the event is declared in + /// + public Type EventDeclaringType { get; set; } + /// + /// Name of the event + /// + public string EventName { get; set; } + + /// + /// Type that the method to replace is declared in + /// + public Type TargetDeclaringType { get; set; } + /// + /// Name of the method to replace + /// + public string TargetName { get; set; } + /// + /// Optional parameters of the method to replace. Null to ignore. + /// + public Type[] TargetParameters { get; set; } = null; + + /// + /// Creates a reflected event replacer attribute to, for the event defined as eventName in eventDeclaringType, + /// replace the method defined as targetName in targetDeclaringType with a custom callback. + /// + /// Type the event is declared in + /// Name of the event + /// Type the method to remove is declared in + /// Name of the method to remove + public ReflectedEventReplaceAttribute(Type eventDeclaringType, string eventName, Type targetDeclaringType, + string targetName) + { + EventDeclaringType = eventDeclaringType; + EventName = eventName; + TargetDeclaringType = targetDeclaringType; + TargetName = targetName; + } + } + #endregion /// /// Automatically calls for every assembly already loaded, and every assembly that is loaded in the future. @@ -125,7 +370,7 @@ namespace Torch.Utils private static readonly string[] _namespaceBlacklist = new[] { "System", "VRage", "Sandbox", "SpaceEngineers" }; - + /// /// Registers the assembly load event and loads every already existing assembly. /// @@ -185,37 +430,94 @@ namespace Torch.Utils /// If the field failed to process public static bool Process(FieldInfo field) { - var attr = field.GetCustomAttribute(); - if (attr != null) + foreach (ReflectedMemberAttribute attr in field.GetCustomAttributes()) { if (!field.IsStatic) throw new ArgumentException("Field must be static to be reflected"); - ProcessReflectedMethod(field, attr); - return true; - } - var attr2 = field.GetCustomAttribute(); - if (attr2 != null) - { - if (!field.IsStatic) - throw new ArgumentException("Field must be static to be reflected"); - ProcessReflectedField(field, attr2); - return true; - } - var attr3 = field.GetCustomAttribute(); - if (attr3 != null) - { - if (!field.IsStatic) + switch (attr) { - throw new ArgumentException("Field must be static to be reflected"); + case ReflectedMethodAttribute rma: + ProcessReflectedMethod(field, rma); + return true; + case ReflectedGetterAttribute rga: + ProcessReflectedField(field, rga); + return true; + case ReflectedSetterAttribute rsa: + ProcessReflectedField(field, rsa); + return true; + case ReflectedFieldInfoAttribute rfia: + ProcessReflectedMemberInfo(field, rfia); + return true; + case ReflectedPropertyInfoAttribute rpia: + ProcessReflectedMemberInfo(field, rpia); + return true; + case ReflectedMethodInfoAttribute rmia: + ProcessReflectedMemberInfo(field, rmia); + return true; } - - ProcessReflectedField(field, attr3); - return true; } + var reflectedEventReplacer = field.GetCustomAttribute(); + if (reflectedEventReplacer != null) + { + if (!field.IsStatic) + throw new ArgumentException("Field must be static to be reflected"); + field.SetValue(null, new Func(() => new ReflectedEventReplacer(reflectedEventReplacer))); + return true; + } return false; } + private static void ProcessReflectedMemberInfo(FieldInfo field, ReflectedMemberAttribute attr) + { + MemberInfo info = null; + if (attr.Type == null) + throw new ArgumentException("Reflected member info attributes require Type to be defined"); + if (attr.Name == null) + throw new ArgumentException("Reflected member info attributes require Name to be defined"); + switch (attr) + { + case ReflectedFieldInfoAttribute rfia: + info = GetFieldPropRecursive(rfia.Type, rfia.Name, + BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, + (a, b, c) => a.GetField(b)); + if (info == null) + throw new ArgumentException($"Unable to find field {rfia.Type.FullName}#{rfia.Name}"); + break; + case ReflectedPropertyInfoAttribute rpia: + info = GetFieldPropRecursive(rpia.Type, rpia.Name, + BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, + (a, b, c) => a.GetProperty(b)); + if (info == null) + throw new ArgumentException($"Unable to find property {rpia.Type.FullName}#{rpia.Name}"); + break; + case ReflectedMethodInfoAttribute rmia: + if (rmia.Parameters != null) + { + info = rmia.Type.GetMethod(rmia.Name, + BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, CallingConventions.Any, rmia.Parameters, null); + if (info == null) + throw new ArgumentException( + $"Unable to find method {rmia.Type.FullName}#{rmia.Name}({string.Join(", ", rmia.Parameters.Select(x => x.FullName))})"); + } + else + { + info = rmia.Type.GetMethod(rmia.Name, + BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (info == null) + throw new ArgumentException( + $"Unable to find method {rmia.Type.FullName}#{rmia.Name}"); + } + if (rmia.ReturnType != null && !rmia.ReturnType.IsAssignableFrom(((MethodInfo)info).ReturnType)) + throw new ArgumentException($"Method {rmia.Type.FullName}#{rmia.Name} has return type {((MethodInfo)info).ReturnType.FullName}, expected {rmia.ReturnType.FullName}"); + break; + } + if (info == null) + throw new ArgumentException($"Unable to find member info for {attr.GetType().Name}[{attr.Type.FullName}#{attr.Name}"); + field.SetValue(null, info); + } + private static void ProcessReflectedMethod(FieldInfo field, ReflectedMethodAttribute attr) { MethodInfo delegateMethod = field.FieldType.GetMethod("Invoke"); @@ -249,14 +551,14 @@ namespace Torch.Utils field.SetValue(null, Delegate.CreateDelegate(field.FieldType, methodInstance)); else { - ParameterExpression[] paramExp = parameters.Select(x => Expression.Parameter(x.ParameterType)).ToArray(); + ParameterExpression[] paramExp = parameters.Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray(); field.SetValue(null, Expression.Lambda(Expression.Call(paramExp[0], methodInstance, paramExp.Skip(1)), paramExp) .Compile()); } } - private static T GetFieldPropRecursive(Type baseType, string name, BindingFlags flags, Func getter) where T : class + internal static T GetFieldPropRecursive(Type baseType, string name, BindingFlags flags, Func getter) where T : class { while (baseType != null) { From 2c7b52237897ccaa9c3cfbc4001631fd450223a6 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Tue, 22 Aug 2017 08:03:27 -0700 Subject: [PATCH 02/19] Proper delegate naming --- Torch.API/Managers/IChatManagerClient.cs | 12 ++++++------ Torch.API/Managers/IChatManagerServer.cs | 8 ++++---- Torch/Managers/ChatManager/ChatManagerClient.cs | 4 ++-- Torch/Managers/ChatManager/ChatManagerServer.cs | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Torch.API/Managers/IChatManagerClient.cs b/Torch.API/Managers/IChatManagerClient.cs index 0c8ca18..a3a1f64 100644 --- a/Torch.API/Managers/IChatManagerClient.cs +++ b/Torch.API/Managers/IChatManagerClient.cs @@ -35,26 +35,26 @@ namespace Torch.API.Managers /// /// /// If true, this event has been consumed and should be ignored - public delegate void DelMessageRecieved(TorchChatMessage msg, ref bool consumed); + public delegate void MessageRecievedDel(TorchChatMessage msg, ref bool consumed); /// /// Callback used to indicate the user is attempting to send a message locally. /// /// Message the user is attempting to send /// If true, this event has been consumed and should be ignored - public delegate void DelMessageSending(string msg, ref bool consumed); + public delegate void MessageSendingDel(string msg, ref bool consumed); public interface IChatManagerClient : IManager { /// - /// Event that is raised when a message addressed to us is recieved. + /// Event that is raised when a message addressed to us is recieved. /// - event DelMessageRecieved MessageRecieved; + event MessageRecievedDel MessageRecieved; /// - /// Event that is raised when we are attempting to send a message. + /// Event that is raised when we are attempting to send a message. /// - event DelMessageSending MessageSending; + event MessageSendingDel MessageSending; /// /// Triggers the event, diff --git a/Torch.API/Managers/IChatManagerServer.cs b/Torch.API/Managers/IChatManagerServer.cs index 3a402c8..52bd75f 100644 --- a/Torch.API/Managers/IChatManagerServer.cs +++ b/Torch.API/Managers/IChatManagerServer.cs @@ -9,19 +9,19 @@ namespace Torch.API.Managers { /// - /// Callback used to indicate the server has recieved a message to process. + /// Callback used to indicate the server has recieved a message to process and forward on to others. /// /// Steam ID of the user sending a message /// Message the user is attempting to send /// If true, this event has been consumed and should be ignored - public delegate void DelMessageProcessing(TorchChatMessage msg, ref bool consumed); + public delegate void MessageProcessingDel(TorchChatMessage msg, ref bool consumed); public interface IChatManagerServer : IChatManagerClient { /// - /// Event triggered when the server has recieved a message and should process it. + /// Event triggered when the server has recieved a message and should process it. /// - event DelMessageProcessing MessageProcessing; + event MessageProcessingDel MessageProcessing; /// diff --git a/Torch/Managers/ChatManager/ChatManagerClient.cs b/Torch/Managers/ChatManager/ChatManagerClient.cs index 796b8de..3aacbdb 100644 --- a/Torch/Managers/ChatManager/ChatManagerClient.cs +++ b/Torch/Managers/ChatManager/ChatManagerClient.cs @@ -26,11 +26,11 @@ namespace Torch.Managers.ChatManager /// // TODO doesn't work in Offline worlds. Method injection or network injection. - public event DelMessageRecieved MessageRecieved; + public event MessageRecievedDel MessageRecieved; /// // TODO doesn't work at all. Method injection or network injection. - public event DelMessageSending MessageSending; + public event MessageSendingDel MessageSending; /// public void SendMessageAsSelf(string message) diff --git a/Torch/Managers/ChatManager/ChatManagerServer.cs b/Torch/Managers/ChatManager/ChatManagerServer.cs index 8f1a507..4a93c45 100644 --- a/Torch/Managers/ChatManager/ChatManagerServer.cs +++ b/Torch/Managers/ChatManager/ChatManagerServer.cs @@ -36,7 +36,7 @@ namespace Torch.Managers.ChatManager } /// - public event DelMessageProcessing MessageProcessing; + public event MessageProcessingDel MessageProcessing; /// public void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0) @@ -59,9 +59,9 @@ namespace Torch.Managers.ChatManager #pragma warning disable 649 - private delegate void DelMultiplayerBaseSendChatMessage(ref ChatMsg arg); + private delegate void MultiplayerBaseSendChatMessageDel(ref ChatMsg arg); [ReflectedStaticMethod(Name = "SendChatMessage", Type = typeof(MyMultiplayerBase))] - private static DelMultiplayerBaseSendChatMessage _dedicatedServerBaseSendChatMessage; + private static MultiplayerBaseSendChatMessageDel _dedicatedServerBaseSendChatMessage; // [ReflectedMethod] doesn't play well with instance methods and refs. [ReflectedMethodInfo(typeof(MyDedicatedServerBase), "OnChatMessage")] From 4b2fee761497dcf03e39bcbdf860b6abc9b3177d Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Tue, 22 Aug 2017 20:33:53 -0700 Subject: [PATCH 03/19] Offline mode fallbacks for the chat manager. --- .../Managers/ChatManager/ChatManagerClient.cs | 52 +++++++++++++++++-- .../Managers/ChatManager/ChatManagerServer.cs | 12 ++++- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/Torch/Managers/ChatManager/ChatManagerClient.cs b/Torch/Managers/ChatManager/ChatManagerClient.cs index 3aacbdb..529154a 100644 --- a/Torch/Managers/ChatManager/ChatManagerClient.cs +++ b/Torch/Managers/ChatManager/ChatManagerClient.cs @@ -8,7 +8,9 @@ using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Networking; using Sandbox.Game.Entities.Character; using Sandbox.Game.Gui; +using Sandbox.Game.Multiplayer; using Sandbox.Game.World; +using Sandbox.ModAPI; using Torch.API; using Torch.API.Managers; using Torch.Utils; @@ -25,11 +27,9 @@ namespace Torch.Managers.ChatManager public ChatManagerClient(ITorchBase torchInstance) : base(torchInstance) { } /// - // TODO doesn't work in Offline worlds. Method injection or network injection. public event MessageRecievedDel MessageRecieved; /// - // TODO doesn't work at all. Method injection or network injection. public event MessageSendingDel MessageSending; /// @@ -71,9 +71,8 @@ namespace Torch.Managers.ChatManager public override void Attach() { base.Attach(); - if (MyMultiplayer.Static == null) - _log.Warn("Currently ChatManagerClient doesn't support handling on an offline client"); - else + MyAPIUtilities.Static.MessageEntered += OnMessageEntered; + if (MyMultiplayer.Static != null) { _chatMessageRecievedReplacer = _chatMessageReceivedFactory.Invoke(); _scriptedChatMessageRecievedReplacer = _scriptedChatMessageReceivedFactory.Invoke(); @@ -82,18 +81,61 @@ namespace Torch.Managers.ChatManager _scriptedChatMessageRecievedReplacer.Replace( new Action(Multiplayer_ScriptedChatMessageReceived), MyMultiplayer.Static); } + else + { + MyAPIUtilities.Static.MessageEntered += OfflineMessageReciever; + } } /// public override void Detach() { + MyAPIUtilities.Static.MessageEntered -= OnMessageEntered; if (_chatMessageRecievedReplacer != null && _chatMessageRecievedReplacer.Replaced) _chatMessageRecievedReplacer.Restore(MyHud.Chat); if (_scriptedChatMessageRecievedReplacer != null && _scriptedChatMessageRecievedReplacer.Replaced) _scriptedChatMessageRecievedReplacer.Restore(MyHud.Chat); + MyAPIUtilities.Static.MessageEntered -= OfflineMessageReciever; base.Detach(); } + /// + /// Callback used to process offline messages. + /// + /// + /// true if the message was consumed + protected virtual bool OfflineMessageProcessor(TorchChatMessage msg) + { + return false; + } + + private void OfflineMessageReciever(string messageText, ref bool sendToOthers) + { + if (!sendToOthers) + return; + var torchMsg = new TorchChatMessage() + { + AuthorSteamId = Sync.MyId, + Author = MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", + Message = messageText + }; + var consumed = false; + MessageRecieved?.Invoke(torchMsg, ref consumed); + if (!consumed) + consumed = OfflineMessageProcessor(torchMsg); + sendToOthers = !consumed; + } + + private void OnMessageEntered(string messageText, ref bool sendToOthers) + { + if (!sendToOthers) + return; + var consumed = false; + MessageSending?.Invoke(messageText, ref consumed); + sendToOthers = !consumed; + } + + private void Multiplayer_ChatMessageReceived(ulong steamUserId, string message) { var torchMsg = new TorchChatMessage() diff --git a/Torch/Managers/ChatManager/ChatManagerServer.cs b/Torch/Managers/ChatManager/ChatManagerServer.cs index 4a93c45..82b9cc9 100644 --- a/Torch/Managers/ChatManager/ChatManagerServer.cs +++ b/Torch/Managers/ChatManager/ChatManagerServer.cs @@ -109,8 +109,16 @@ namespace Torch.Managers.ChatManager MyMultiplayer.Static.ChatMessageReceived += MpStaticChatMessageReceived; _log.Warn("Failed to initialize network intercept, we can't discard chat messages! Falling back to another method."); } - else - _log.Error("Failed to initialize network intercept, we can't discard chat messages!"); + } + + /// + protected override bool OfflineMessageProcessor(TorchChatMessage msg) + { + if (MyMultiplayer.Static != null) + return false; + var consumed = false; + MessageProcessing?.Invoke(msg, ref consumed); + return consumed; } private void MpStaticChatMessageReceived(ulong a, string b) From 140000df55e64952e75e46ae8becf29df9a32f0b Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Thu, 24 Aug 2017 20:24:07 -0700 Subject: [PATCH 04/19] Test-time reflected event checker Server UI components work with new system Events for loading and unloading TorchSessions Commands report information again. Catch, log, and rethrow errors that occur in Torch-handled SE events --- Torch.API/Session/ITorchSessionManager.cs | 16 +++++ .../TorchClientReflectionTest.cs | 5 +- .../TorchServerReflectionTest.cs | 5 +- .../Managers/MultiplayerManagerDedicated.cs | 5 +- Torch.Server/Torch.Server.csproj | 3 + Torch.Server/Views/ChatControl.xaml.cs | 65 ++++++++++++++++--- Torch.Server/Views/PlayerListControl.xaml.cs | 20 ++++-- Torch.Tests/ReflectionSystemTest.cs | 2 +- Torch.Tests/TorchReflectionTest.cs | 5 +- Torch/Commands/Command.cs | 2 +- Torch/Commands/CommandManager.cs | 2 +- Torch/Commands/TorchCommands.cs | 28 +++++--- .../Managers/ChatManager/ChatManagerClient.cs | 20 +++--- .../Managers/ChatManager/ChatManagerServer.cs | 13 ++-- Torch/Managers/MultiplayerManagerBase.cs | 2 +- .../Managers/NetworkManager/NetworkManager.cs | 16 ++--- Torch/Managers/PluginManager.cs | 34 ++++++++-- Torch/Session/TorchSessionManager.cs | 20 ++++-- Torch/TorchBase.cs | 40 ++++++++++-- 19 files changed, 230 insertions(+), 73 deletions(-) diff --git a/Torch.API/Session/ITorchSessionManager.cs b/Torch.API/Session/ITorchSessionManager.cs index 08fef9e..8e5a5ed 100644 --- a/Torch.API/Session/ITorchSessionManager.cs +++ b/Torch.API/Session/ITorchSessionManager.cs @@ -17,11 +17,27 @@ namespace Torch.API.Session /// The manager that will live in the session, or null if none. public delegate IManager SessionManagerFactoryDel(ITorchSession session); + /// + /// Fired when the given session has been completely loaded or is unloading. + /// + /// The session + public delegate void TorchSessionLoadDel(ITorchSession session); + /// /// Manages the creation and destruction of instances for each created by Space Engineers. /// public interface ITorchSessionManager : IManager { + /// + /// Fired when a has finished loading. + /// + event TorchSessionLoadDel SessionLoaded; + + /// + /// Fired when a has begun unloading. + /// + event TorchSessionLoadDel SessionUnloading; + /// /// The currently running session /// diff --git a/Torch.Client.Tests/TorchClientReflectionTest.cs b/Torch.Client.Tests/TorchClientReflectionTest.cs index 12a1948..80cb47f 100644 --- a/Torch.Client.Tests/TorchClientReflectionTest.cs +++ b/Torch.Client.Tests/TorchClientReflectionTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Torch.Client; using Torch.Tests; using Torch.Utils; @@ -86,7 +87,7 @@ namespace Torch.Client.Tests return; Assert.True(ReflectedManager.Process(field.Field)); if (field.Field.IsStatic) - Assert.NotNull(field.Field.GetValue(null)); + ((Func)field.Field.GetValue(null)).Invoke(); } #endregion } diff --git a/Torch.Server.Tests/TorchServerReflectionTest.cs b/Torch.Server.Tests/TorchServerReflectionTest.cs index 8897c8d..b9c7904 100644 --- a/Torch.Server.Tests/TorchServerReflectionTest.cs +++ b/Torch.Server.Tests/TorchServerReflectionTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Torch.Tests; using Torch.Utils; using Xunit; @@ -74,7 +75,7 @@ namespace Torch.Server.Tests return; Assert.True(ReflectedManager.Process(field.Field)); if (field.Field.IsStatic) - Assert.NotNull(field.Field.GetValue(null)); + ((Func)field.Field.GetValue(null)).Invoke(); } #endregion } diff --git a/Torch.Server/Managers/MultiplayerManagerDedicated.cs b/Torch.Server/Managers/MultiplayerManagerDedicated.cs index 85399c0..3a9b331 100644 --- a/Torch.Server/Managers/MultiplayerManagerDedicated.cs +++ b/Torch.Server/Managers/MultiplayerManagerDedicated.cs @@ -16,6 +16,7 @@ using Torch.Utils; using Torch.ViewModels; using VRage.GameServices; using VRage.Network; +using VRage.Steam; namespace Torch.Server.Managers { @@ -73,9 +74,9 @@ namespace Torch.Server.Managers #pragma warning disable 649 - [ReflectedEventReplace(typeof(IMyGameServer), nameof(IMyGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")] + [ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")] private static Func _gameServerValidateAuthTicketFactory; - [ReflectedEventReplace(typeof(IMyGameServer), nameof(IMyGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")] + [ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")] private static Func _gameServerUserGroupStatusFactory; private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer; private ReflectedEventReplacer _gameServerUserGroupStatusReplacer; diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj index 5d9bee4..ff2f432 100644 --- a/Torch.Server/Torch.Server.csproj +++ b/Torch.Server/Torch.Server.csproj @@ -182,6 +182,9 @@ ..\GameBinaries\VRage.Scripting.dll False + + ..\GameBinaries\VRage.Steam.dll + diff --git a/Torch.Server/Views/ChatControl.xaml.cs b/Torch.Server/Views/ChatControl.xaml.cs index 2b8e9de..1c3152e 100644 --- a/Torch.Server/Views/ChatControl.xaml.cs +++ b/Torch.Server/Views/ChatControl.xaml.cs @@ -21,6 +21,7 @@ using Sandbox.Game.World; using SteamSDK; using Torch.API; using Torch.API.Managers; +using Torch.API.Session; using Torch.Managers; using Torch.Server.Managers; @@ -42,14 +43,29 @@ namespace Torch.Server { _server = (TorchBase)server; ChatItems.Items.Clear(); - server.SessionLoaded += () => + + var sessionManager = server.Managers.GetManager(); + sessionManager.SessionLoaded += BindSession; + sessionManager.SessionUnloading += UnbindSession; + } + + private void BindSession(ITorchSession session) + { + Dispatcher.Invoke(() => { - var multiplayer = server.CurrentSession.Managers.GetManager(); - DataContext = multiplayer; - // TODO - // if (multiplayer.ChatHistory is INotifyCollectionChanged ncc) - // ncc.CollectionChanged += ChatHistory_CollectionChanged; - }; + var chatMgr = _server?.CurrentSession?.Managers.GetManager(); + if (chatMgr != null) + DataContext = new ChatManagerProxy(chatMgr); + }); + } + + private void UnbindSession(ITorchSession session) + { + Dispatcher.Invoke(() => + { + (DataContext as ChatManagerProxy)?.Dispose(); + DataContext = null; + }); } private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -86,7 +102,7 @@ namespace Torch.Server var commands = _server.CurrentSession?.Managers.GetManager(); if (commands != null && commands.IsCommand(text)) { - // TODO _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text)); + (DataContext as ChatManagerProxy)?.AddMessage(new TorchChatMessage() { Author = "Server", Message = text }); _server.Invoke(() => { var response = commands.HandleCommandFromServer(text); @@ -102,8 +118,37 @@ namespace Torch.Server private void OnMessageEntered_Callback(string response) { - //if (!string.IsNullOrEmpty(response)) - // TODO _multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response)); + if (!string.IsNullOrEmpty(response)) + (DataContext as ChatManagerProxy)?.AddMessage(new TorchChatMessage() { Author = "Server", Message = response }); + } + + private class ChatManagerProxy : IDisposable + { + private readonly IChatManagerClient _chatMgr; + + public ChatManagerProxy(IChatManagerClient chatMgr) + { + this._chatMgr = chatMgr; + this._chatMgr.MessageRecieved += ChatMgr_MessageRecieved; ; + } + + public IList ChatHistory { get; } = new ObservableList(); + + /// + public void Dispose() + { + _chatMgr.MessageRecieved -= ChatMgr_MessageRecieved; + } + + private void ChatMgr_MessageRecieved(TorchChatMessage msg, ref bool consumed) + { + AddMessage(msg); + } + + internal void AddMessage(TorchChatMessage msg) + { + ChatHistory.Add(new ChatMessage(DateTime.Now, msg.AuthorSteamId ?? 0, msg.Author, msg.Message)); + } } } } diff --git a/Torch.Server/Views/PlayerListControl.xaml.cs b/Torch.Server/Views/PlayerListControl.xaml.cs index 70fbbc3..b76b7c7 100644 --- a/Torch.Server/Views/PlayerListControl.xaml.cs +++ b/Torch.Server/Views/PlayerListControl.xaml.cs @@ -21,6 +21,7 @@ using Sandbox.ModAPI; using SteamSDK; using Torch.API; using Torch.API.Managers; +using Torch.API.Session; using Torch.Managers; using Torch.Server.Managers; using Torch.ViewModels; @@ -43,11 +44,20 @@ namespace Torch.Server public void BindServer(ITorchServer server) { _server = server; - server.SessionLoaded += () => - { - var multiplayer = server.CurrentSession?.Managers.GetManager(); - DataContext = multiplayer; - }; + + var sessionManager = server.Managers.GetManager(); + sessionManager.SessionLoaded += BindSession; + sessionManager.SessionUnloading += UnbindSession; + } + + private void BindSession(ITorchSession session) + { + Dispatcher.Invoke(() => DataContext = _server?.CurrentSession?.Managers.GetManager()); + } + + private void UnbindSession(ITorchSession session) + { + Dispatcher.Invoke(() => DataContext = null); } private void KickButton_Click(object sender, RoutedEventArgs e) diff --git a/Torch.Tests/ReflectionSystemTest.cs b/Torch.Tests/ReflectionSystemTest.cs index 4161fe7..c586a19 100644 --- a/Torch.Tests/ReflectionSystemTest.cs +++ b/Torch.Tests/ReflectionSystemTest.cs @@ -77,7 +77,7 @@ namespace Torch.Tests return; Assert.True(ReflectedManager.Process(field.Field)); if (field.Field.IsStatic) - Assert.NotNull(field.Field.GetValue(null)); + ((Func)field.Field.GetValue(null)).Invoke(); } #endregion diff --git a/Torch.Tests/TorchReflectionTest.cs b/Torch.Tests/TorchReflectionTest.cs index 26014b5..775d100 100644 --- a/Torch.Tests/TorchReflectionTest.cs +++ b/Torch.Tests/TorchReflectionTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Torch.Utils; using Xunit; @@ -84,7 +85,7 @@ namespace Torch.Tests return; Assert.True(ReflectedManager.Process(field.Field)); if (field.Field.IsStatic) - Assert.NotNull(field.Field.GetValue(null)); + ((Func)field.Field.GetValue(null)).Invoke(); } #endregion } diff --git a/Torch/Commands/Command.cs b/Torch/Commands/Command.cs index 7aedcab..cbd08e2 100644 --- a/Torch/Commands/Command.cs +++ b/Torch/Commands/Command.cs @@ -117,7 +117,7 @@ namespace Torch.Commands catch (Exception e) { context.Respond(e.Message, "Error", MyFontEnum.Red); - Log.Error($"Command '{SyntaxHelp}' from '{Plugin.Name ?? "Torch"}' threw an exception. Args: {string.Join(", ", context.Args)}"); + Log.Error($"Command '{SyntaxHelp}' from '{Plugin?.Name ?? "Torch"}' threw an exception. Args: {string.Join(", ", context.Args)}"); Log.Error(e); return true; } diff --git a/Torch/Commands/CommandManager.cs b/Torch/Commands/CommandManager.cs index e1083cc..ff2d9a0 100644 --- a/Torch/Commands/CommandManager.cs +++ b/Torch/Commands/CommandManager.cs @@ -108,7 +108,7 @@ namespace Torch.Commands consumed = true; - var player = Torch.GetManager().GetPlayerBySteamId(steamId); + var player = Torch.CurrentSession.Managers.GetManager().GetPlayerBySteamId(steamId); if (player == null) { _log.Error($"Command {message} invoked by nonexistant player"); diff --git a/Torch/Commands/TorchCommands.cs b/Torch/Commands/TorchCommands.cs index 5e7dbb6..1430587 100644 --- a/Torch/Commands/TorchCommands.cs +++ b/Torch/Commands/TorchCommands.cs @@ -21,7 +21,12 @@ namespace Torch.Commands [Permission(MyPromoteLevel.None)] public void Help() { - var commandManager = ((TorchBase)Context.Torch).CurrentSession.Managers.GetManager(); + var commandManager = Context.Torch.CurrentSession?.Managers.GetManager(); + if (commandManager == null) + { + Context.Respond("Must have an attached session to list commands"); + return; + } commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node); if (node != null) @@ -51,7 +56,12 @@ namespace Torch.Commands [Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")] public void LongHelp() { - var commandManager = Context.Torch.Managers.GetManager(); + var commandManager = Context.Torch.CurrentSession?.Managers.GetManager(); + if (commandManager == null) + { + Context.Respond("Must have an attached session to list commands"); + return; + } commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node); if (node != null) @@ -96,7 +106,7 @@ namespace Torch.Commands [Permission(MyPromoteLevel.None)] public void Plugins() { - var plugins = Context.Torch.Plugins.Select(p => p.Name); + var plugins = Context.Torch.Managers.GetManager()?.Plugins.Select(p => p.Name) ?? Enumerable.Empty(); Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}"); } @@ -128,15 +138,13 @@ namespace Torch.Commands { if (i >= 60 && i % 60 == 0) { - // TODO -// Context.Torch.Multiplayer.SendMessage($"Restarting server in {i / 60} minute{Pluralize(i / 60)}."); -// yield return null; + Context.Torch.CurrentSession.Managers.GetManager().SendMessageAsSelf($"Restarting server in {i / 60} minute{Pluralize(i / 60)}."); + yield return null; } else if (i > 0) { - // TODO -// if (i < 11) -// Context.Torch.Multiplayer.SendMessage($"Restarting server in {i} second{Pluralize(i)}."); + if (i < 11) + Context.Torch.CurrentSession.Managers.GetManager().SendMessageAsSelf($"Restarting server in {i} second{Pluralize(i)}."); yield return null; } else @@ -155,7 +163,7 @@ namespace Torch.Commands { return num == 1 ? "" : "s"; } - + /// /// Initializes a save of the game. /// Caller id defaults to 0 in the case of triggering the chat command from server. diff --git a/Torch/Managers/ChatManager/ChatManagerClient.cs b/Torch/Managers/ChatManager/ChatManagerClient.cs index 529154a..f18a494 100644 --- a/Torch/Managers/ChatManager/ChatManagerClient.cs +++ b/Torch/Managers/ChatManager/ChatManagerClient.cs @@ -15,7 +15,6 @@ using Torch.API; using Torch.API.Managers; using Torch.Utils; using VRage.Game; -using Game = Sandbox.Engine.Platform.Game; namespace Torch.Managers.ChatManager { @@ -37,7 +36,7 @@ namespace Torch.Managers.ChatManager { if (MyMultiplayer.Static != null) { - if (Game.IsDedicated) + if (Sandbox.Engine.Platform.Game.IsDedicated) { var scripted = new ScriptedChatMsg() { @@ -51,14 +50,15 @@ namespace Torch.Managers.ChatManager else MyMultiplayer.Static.SendChatMessage(message); } - else if (MyHud.Chat != null) + else if (HasHud) MyHud.Chat.ShowMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", message); } /// public void DisplayMessageOnSelf(string author, string message, string font) { - MyHud.Chat.ShowMessage(author, message, font); + if (HasHud) + MyHud.Chat?.ShowMessage(author, message, font); MySession.Static.GlobalChatHistory.GlobalChatHistory.Chat.Enqueue(new MyGlobalChatItem() { Author = author, @@ -91,9 +91,9 @@ namespace Torch.Managers.ChatManager public override void Detach() { MyAPIUtilities.Static.MessageEntered -= OnMessageEntered; - if (_chatMessageRecievedReplacer != null && _chatMessageRecievedReplacer.Replaced) + if (_chatMessageRecievedReplacer != null && _chatMessageRecievedReplacer.Replaced && HasHud) _chatMessageRecievedReplacer.Restore(MyHud.Chat); - if (_scriptedChatMessageRecievedReplacer != null && _scriptedChatMessageRecievedReplacer.Replaced) + if (_scriptedChatMessageRecievedReplacer != null && _scriptedChatMessageRecievedReplacer.Replaced && HasHud) _scriptedChatMessageRecievedReplacer.Restore(MyHud.Chat); MyAPIUtilities.Static.MessageEntered -= OfflineMessageReciever; base.Detach(); @@ -148,11 +148,11 @@ namespace Torch.Managers.ChatManager }; var consumed = false; MessageRecieved?.Invoke(torchMsg, ref consumed); - if (!consumed) + if (!consumed && HasHud) _hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message); } - private void Multiplayer_ScriptedChatMessageReceived(string author, string message, string font) + private void Multiplayer_ScriptedChatMessageReceived(string message, string author, string font) { var torchMsg = new TorchChatMessage() { @@ -163,12 +163,14 @@ namespace Torch.Managers.ChatManager }; var consumed = false; MessageRecieved?.Invoke(torchMsg, ref consumed); - if (!consumed) + if (!consumed && HasHud) _hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font); } private const string _hudChatMessageReceivedName = "Multiplayer_ChatMessageReceived"; private const string _hudChatScriptedMessageReceivedName = "multiplayer_ScriptedChatMessageReceived"; + + protected static bool HasHud => !Sandbox.Engine.Platform.Game.IsDedicated; [ReflectedMethod(Name = _hudChatMessageReceivedName)] private static Action _hudChatMessageReceived; diff --git a/Torch/Managers/ChatManager/ChatManagerServer.cs b/Torch/Managers/ChatManager/ChatManagerServer.cs index 82b9cc9..f71900c 100644 --- a/Torch/Managers/ChatManager/ChatManagerServer.cs +++ b/Torch/Managers/ChatManager/ChatManagerServer.cs @@ -43,7 +43,7 @@ namespace Torch.Managers.ChatManager { if (MyMultiplayer.Static == null) { - if (targetSteamId == MyGameService.UserId || targetSteamId == 0) + if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud) MyHud.Chat?.ShowMessage(authorId == MyGameService.UserId ? (MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player") : $"user_{authorId}", message); return; @@ -73,7 +73,7 @@ namespace Torch.Managers.ChatManager { if (MyMultiplayer.Static == null) { - if (targetSteamId == MyGameService.UserId || targetSteamId == 0) + if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud) MyHud.Chat?.ShowMessage(author, message, font); return; } @@ -82,7 +82,7 @@ namespace Torch.Managers.ChatManager Author = author, Text = message, Font = font, - Target = (long)targetSteamId + Target = Sync.Players.TryGetIdentityId(targetSteamId) }; MyMultiplayerBase.SendScriptedChatMessage(ref scripted); } @@ -107,7 +107,12 @@ namespace Torch.Managers.ChatManager if (MyMultiplayer.Static != null) { MyMultiplayer.Static.ChatMessageReceived += MpStaticChatMessageReceived; - _log.Warn("Failed to initialize network intercept, we can't discard chat messages! Falling back to another method."); + _log.Warn( + "Failed to initialize network intercept, we can't discard chat messages! Falling back to another method."); + } + else + { + _log.Debug("Using offline message processor"); } } diff --git a/Torch/Managers/MultiplayerManagerBase.cs b/Torch/Managers/MultiplayerManagerBase.cs index eaad039..d6fcd8a 100644 --- a/Torch/Managers/MultiplayerManagerBase.cs +++ b/Torch/Managers/MultiplayerManagerBase.cs @@ -115,7 +115,7 @@ namespace Torch.Managers protected void RaiseClientJoined(ulong steamId) { var vm = new PlayerViewModel(steamId){State=ConnectionState.Connected}; - _log.Info($"Plat {vm.Name} joined ({vm.SteamId}"); + _log.Info($"Player {vm.Name} joined ({vm.SteamId}"); Players.Add(steamId, vm); PlayerJoined?.Invoke(vm); } diff --git a/Torch/Managers/NetworkManager/NetworkManager.cs b/Torch/Managers/NetworkManager/NetworkManager.cs index 743f319..948ef80 100644 --- a/Torch/Managers/NetworkManager/NetworkManager.cs +++ b/Torch/Managers/NetworkManager/NetworkManager.cs @@ -60,15 +60,9 @@ namespace Torch.Managers throw; } } - /// - /// Loads the network intercept system - /// + + /// public override void Attach() - { - Torch.SessionLoaded += OnSessionLoaded; - } - - private void OnSessionLoaded() { if (_init) return; @@ -105,6 +99,12 @@ namespace Torch.Managers _log.Debug("Initialized network intercept"); } + /// + public override void Detach() + { + // TODO reverse what was done in Attach + } + #region Network Intercept /// diff --git a/Torch/Managers/PluginManager.cs b/Torch/Managers/PluginManager.cs index 3f0108d..0b136ab 100644 --- a/Torch/Managers/PluginManager.cs +++ b/Torch/Managers/PluginManager.cs @@ -10,6 +10,7 @@ using NLog; using Torch.API; using Torch.API.Managers; using Torch.API.Plugins; +using Torch.API.Session; using Torch.Commands; using VRage.Collections; @@ -22,8 +23,8 @@ namespace Torch.Managers public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"); [Dependency] private UpdateManager _updateManager; - [Dependency] - private CommandManager _commandManager; + [Dependency(Optional = true)] + private ITorchSessionManager _sessionManager; /// public IList Plugins { get; } = new ObservableList(); @@ -41,6 +42,11 @@ namespace Torch.Managers /// public void UpdatePlugins() { + if (_sessionManager != null) + { + _sessionManager.SessionLoaded += AttachCommandsToSession; + _sessionManager.SessionUnloading += DetachCommandsFromSession; + } foreach (var plugin in Plugins) plugin.Update(); } @@ -50,12 +56,32 @@ namespace Torch.Managers /// public override void Detach() { + if (_sessionManager != null) + { + _sessionManager.SessionLoaded -= AttachCommandsToSession; + _sessionManager.SessionUnloading -= DetachCommandsFromSession; + } foreach (var plugin in Plugins) plugin.Dispose(); Plugins.Clear(); } + private void AttachCommandsToSession(ITorchSession session) + { + var cmdMgr = session.Managers.GetManager(); + foreach (ITorchPlugin plugin in Plugins) + cmdMgr?.RegisterPluginCommands(plugin); + } + + private void DetachCommandsFromSession(ITorchSession session) + { + var cmdMgr = session.Managers.GetManager(); + foreach (ITorchPlugin plugin in Plugins) { + // cmdMgr?.UnregisterPluginCommands(plugin); + } + } + private void DownloadPlugins() { var folders = Directory.GetDirectories(PluginDir); @@ -80,7 +106,7 @@ namespace Torch.Managers foreach (var repository in toDownload) { - var manifest = new PluginManifest {Repository = repository, Version = "0.0"}; + var manifest = new PluginManifest { Repository = repository, Version = "0.0" }; taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest)); } @@ -118,8 +144,6 @@ namespace Torch.Managers _log.Info($"Loading plugin {plugin.Name} ({plugin.Version})"); plugin.StoragePath = Torch.Config.InstancePath; Plugins.Add(plugin); - - _commandManager.RegisterPluginCommands(plugin); } catch (Exception e) { diff --git a/Torch/Session/TorchSessionManager.cs b/Torch/Session/TorchSessionManager.cs index 3e4b1a6..933f61f 100644 --- a/Torch/Session/TorchSessionManager.cs +++ b/Torch/Session/TorchSessionManager.cs @@ -21,6 +21,12 @@ namespace Torch.Session private static readonly Logger _log = LogManager.GetCurrentClassLogger(); private TorchSession _currentSession; + /// + public event TorchSessionLoadDel SessionLoaded; + + /// + public event TorchSessionLoadDel SessionUnloading; + /// public ITorchSession CurrentSession => _currentSession; @@ -46,7 +52,7 @@ namespace Torch.Session return _factories.Remove(factory); } - private void SessionLoaded() + private void LoadSession() { if (_currentSession != null) { @@ -63,12 +69,14 @@ namespace Torch.Session CurrentSession.Managers.AddManager(manager); } (CurrentSession as TorchSession)?.Attach(); + SessionLoaded?.Invoke(_currentSession); } - private void SessionUnloaded() + private void UnloadSession() { if (_currentSession == null) return; + SessionUnloading?.Invoke(_currentSession); _log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}"); _currentSession.Detach(); _currentSession = null; @@ -77,8 +85,8 @@ namespace Torch.Session /// public override void Attach() { - MySession.AfterLoading += SessionLoaded; - MySession.OnUnloaded += SessionUnloaded; + MySession.AfterLoading += LoadSession; + MySession.OnUnloaded += UnloadSession; } /// @@ -86,8 +94,8 @@ namespace Torch.Session { _currentSession?.Detach(); _currentSession = null; - MySession.AfterLoading -= SessionLoaded; - MySession.OnUnloaded -= SessionUnloaded; + MySession.AfterLoading -= LoadSession; + MySession.OnUnloaded -= UnloadSession; } } } diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index cc88c84..f167e26 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -273,25 +273,57 @@ namespace Torch private void OnSessionLoading() { Log.Debug("Session loading"); - SessionLoading?.Invoke(); + try + { + SessionLoading?.Invoke(); + } + catch (Exception e) + { + Log.Error(e); + throw; + } } private void OnSessionLoaded() { Log.Debug("Session loaded"); - SessionLoaded?.Invoke(); + try + { + SessionLoaded?.Invoke(); + } + catch (Exception e) + { + Log.Error(e); + throw; + } } private void OnSessionUnloading() { Log.Debug("Session unloading"); - SessionUnloading?.Invoke(); + try + { + SessionUnloading?.Invoke(); + } + catch (Exception e) + { + Log.Error(e); + throw; + } } private void OnSessionUnloaded() { Log.Debug("Session unloaded"); - SessionUnloaded?.Invoke(); + try + { + SessionUnloaded?.Invoke(); + } + catch (Exception e) + { + Log.Error(e); + throw; + } } /// From aa784c121ba15e0b75939d00367e121b47486cf2 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Sun, 10 Sep 2017 15:17:50 -0700 Subject: [PATCH 05/19] Null protection in multiplayer manager detach --- Torch/Managers/MultiplayerManagerBase.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Torch/Managers/MultiplayerManagerBase.cs b/Torch/Managers/MultiplayerManagerBase.cs index d6fcd8a..4825ac2 100644 --- a/Torch/Managers/MultiplayerManagerBase.cs +++ b/Torch/Managers/MultiplayerManagerBase.cs @@ -47,7 +47,7 @@ namespace Torch.Managers public event Action PlayerJoined; /// public event Action PlayerLeft; - + public ObservableDictionary Players { get; } = new ObservableDictionary(); #pragma warning disable 649 @@ -69,7 +69,8 @@ namespace Torch.Managers /// public override void Detach() { - MyMultiplayer.Static.ClientLeft -= OnClientLeft; + if (MyMultiplayer.Static != null) + MyMultiplayer.Static.ClientLeft -= OnClientLeft; } /// @@ -114,7 +115,7 @@ namespace Torch.Managers protected void RaiseClientJoined(ulong steamId) { - var vm = new PlayerViewModel(steamId){State=ConnectionState.Connected}; + var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected }; _log.Info($"Player {vm.Name} joined ({vm.SteamId}"); Players.Add(steamId, vm); PlayerJoined?.Invoke(vm); From 9d8988a2ecccada51c45fecd1ef9836649d45f2d Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Sun, 10 Sep 2017 17:40:43 -0700 Subject: [PATCH 06/19] MyLog logs to the Torch logging system Fixed issue with labels not being transferred correctly when transpiling. --- NLog.config | 2 + Torch/Managers/KeenLogManager.cs | 157 ++++++++++++++++++ .../Managers/PatchManager/DecoratedMethod.cs | 53 +++--- .../PatchManager/MSIL/MsilInstruction.cs | 20 ++- .../PatchManager/Transpile/MethodContext.cs | 12 +- .../Transpile/MethodTranspiler.cs | 10 +- Torch/Torch.csproj | 1 + Torch/TorchBase.cs | 3 + 8 files changed, 217 insertions(+), 41 deletions(-) create mode 100644 Torch/Managers/KeenLogManager.cs diff --git a/NLog.config b/NLog.config index 984f095..270fe34 100644 --- a/NLog.config +++ b/NLog.config @@ -6,10 +6,12 @@ + + \ No newline at end of file diff --git a/Torch/Managers/KeenLogManager.cs b/Torch/Managers/KeenLogManager.cs new file mode 100644 index 0000000..aefeb92 --- /dev/null +++ b/Torch/Managers/KeenLogManager.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NLog; +using Torch.API; +using Torch.Managers.PatchManager; +using Torch.Utils; +using VRage.Utils; + +namespace Torch.Managers +{ + public class KeenLogManager : Manager + { + private static readonly Logger _log = LogManager.GetLogger("Keen"); + +#pragma warning disable 649 + [Dependency] + private PatchManager.PatchManager _patchManager; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Log), Parameters = new[] { typeof(MyLogSeverity), typeof(StringBuilder) })] + private static MethodInfo _logStringBuilder; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Log), Parameters = new[] { typeof(MyLogSeverity), typeof(string), typeof(object[]) })] + private static MethodInfo _logFormatted; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(string) })] + private static MethodInfo _logWriteLine; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.AppendToClosedLog), Parameters = new[] { typeof(string) })] + private static MethodInfo _logAppendToClosedLog; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(string), typeof(LoggingOptions) })] + private static MethodInfo _logWriteLineOptions; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(Exception) })] + private static MethodInfo _logWriteLineException; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.AppendToClosedLog), Parameters = new[] { typeof(Exception) })] + private static MethodInfo _logAppendToClosedLogException; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLineAndConsole), Parameters = new[] { typeof(string) })] + private static MethodInfo _logWriteLineAndConsole; +#pragma warning restore 649 + + private PatchContext _context; + + public KeenLogManager(ITorchBase torchInstance) : base(torchInstance) + { + } + + public override void Attach() + { + _context = _patchManager.AcquireContext(); + + _context.GetPattern(_logStringBuilder).Prefixes.Add(Method(nameof(PrefixLogStringBuilder))); + _context.GetPattern(_logFormatted).Prefixes.Add(Method(nameof(PrefixLogFormatted))); + + _context.GetPattern(_logWriteLine).Prefixes.Add(Method(nameof(PrefixWriteLine))); + _context.GetPattern(_logAppendToClosedLog).Prefixes.Add(Method(nameof(PrefixAppendToClosedLog))); + _context.GetPattern(_logWriteLineAndConsole).Prefixes.Add(Method(nameof(PrefixWriteLine))); + + _context.GetPattern(_logWriteLineException).Prefixes.Add(Method(nameof(PrefixWriteLineException))); + _context.GetPattern(_logAppendToClosedLogException).Prefixes.Add(Method(nameof(PrefixAppendToClosedLogException))); + + _context.GetPattern(_logWriteLineOptions).Prefixes.Add(Method(nameof(PrefixWriteLineOptions))); + + _patchManager.Commit(); + } + + public override void Detach() + { + _patchManager.FreeContext(_context); + } + + private static MethodInfo Method(string name) + { + return typeof(KeenLogManager).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + } + + [ReflectedMethod(Name = "GetThreadId")] + private static Func _getThreadId; + + [ReflectedMethod(Name = "GetIdentByThread")] + private static Func _getIndentByThread; + + private static readonly ThreadLocal _tmpStringBuilder = new ThreadLocal(() => new StringBuilder(32)); + + private static StringBuilder PrepareLog(MyLog log) + { + return _tmpStringBuilder.Value.Clear().Append(' ', _getIndentByThread(log, _getThreadId(log)) * 3); + } + + private static bool PrefixWriteLine(MyLog __instance, string msg) + { + _log.Info(PrepareLog(__instance).Append(msg)); + return false; + } + private static bool PrefixAppendToClosedLog(MyLog __instance, string text) + { + _log.Info(PrepareLog(__instance).Append(text)); + return false; + } + private static bool PrefixWriteLineOptions(MyLog __instance, string message, LoggingOptions option) + { + if (__instance.LogFlag(option)) + _log.Info(PrepareLog(__instance).Append(message)); + return false; + } + + private static bool PrefixAppendToClosedLogException(Exception e) + { + _log.Info(e); + return false; + } + + private static bool PrefixWriteLineException(Exception ex) + { + _log.Info(ex); + return false; + } + + private static bool PrefixLogFormatted(MyLog __instance, MyLogSeverity severity, string format, object[] args) + { + _log.Log(LogLevelFor(severity), PrepareLog(__instance).AppendFormat(format, args)); + return false; + } + + private static bool PrefixLogStringBuilder(MyLog __instance, MyLogSeverity severity, StringBuilder builder) + { + _log.Log(LogLevelFor(severity), PrepareLog(__instance).Append(builder)); + return false; + } + + private static LogLevel LogLevelFor(MyLogSeverity severity) + { + switch (severity) + { + case MyLogSeverity.Debug: + return LogLevel.Debug; + case MyLogSeverity.Info: + return LogLevel.Info; + case MyLogSeverity.Warning: + return LogLevel.Warn; + case MyLogSeverity.Error: + return LogLevel.Error; + case MyLogSeverity.Critical: + return LogLevel.Fatal; + default: + return LogLevel.Info; + } + } + } +} diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index 025b61a..5892de8 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -34,18 +34,21 @@ namespace Torch.Managers.PatchManager if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0) return; + _log.Debug($"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); var patch = ComposePatchedMethod(); _revertAddress = AssemblyMemory.GetMethodBodyStart(_method); var newAddress = AssemblyMemory.GetMethodBodyStart(patch); _revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress); _pinnedPatch = GCHandle.Alloc(patch); + _log.Debug($"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); } internal void Revert() { if (_pinnedPatch.HasValue) { + _log.Debug($"Revert {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); AssemblyMemory.WriteMemory(_revertAddress, _revertData); _revertData = null; _pinnedPatch.Value.Free(); @@ -113,29 +116,30 @@ namespace Torch.Managers.PatchManager var specialVariables = new Dictionary(); - Label? labelAfterOriginalContent = Suffixes.Count > 0 ? target.DefineLabel() : (Label?)null; - Label? labelAfterOriginalReturn = Prefixes.Any(x => x.ReturnType == typeof(bool)) ? target.DefineLabel() : (Label?)null; + Label labelAfterOriginalContent = target.DefineLabel(); + Label labelAfterOriginalReturn = target.DefineLabel(); - var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void); - var resultVariable = returnType != typeof(void) && (labelAfterOriginalReturn.HasValue || // If we jump past main content we need local to store return val - Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER)) - ? target.DeclareLocal(returnType) - : null; + Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void); + LocalBuilder resultVariable = null; + if (returnType != typeof(void)) + { + if (Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER)) + resultVariable = target.DeclareLocal(returnType); + else if (Prefixes.Any(x => x.ReturnType == typeof(bool))) + resultVariable = target.DeclareLocal(returnType); + } resultVariable?.SetToDefault(target); if (resultVariable != null) specialVariables.Add(RESULT_PARAMETER, resultVariable); target.EmitComment("Prefixes Begin"); - foreach (var prefix in Prefixes) + foreach (MethodInfo prefix in Prefixes) { EmitMonkeyCall(target, prefix, specialVariables); if (prefix.ReturnType == typeof(bool)) - { - Debug.Assert(labelAfterOriginalReturn.HasValue); - target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn.Value); - } + target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn); else if (prefix.ReturnType != typeof(void)) throw new Exception( $"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}"); @@ -145,30 +149,23 @@ namespace Torch.Managers.PatchManager target.EmitComment("Original Begin"); MethodTranspiler.Transpile(_method, Transpilers, target, labelAfterOriginalContent); target.EmitComment("Original End"); - if (labelAfterOriginalContent.HasValue) - { - target.MarkLabel(labelAfterOriginalContent.Value); - if (resultVariable != null) - target.Emit(OpCodes.Stloc, resultVariable); - } - if (labelAfterOriginalReturn.HasValue) - target.MarkLabel(labelAfterOriginalReturn.Value); + + target.MarkLabel(labelAfterOriginalContent); + if (resultVariable != null) + target.Emit(OpCodes.Stloc, resultVariable); + target.MarkLabel(labelAfterOriginalReturn); target.EmitComment("Suffixes Begin"); - foreach (var suffix in Suffixes) + foreach (MethodInfo suffix in Suffixes) { EmitMonkeyCall(target, suffix, specialVariables); if (suffix.ReturnType != typeof(void)) throw new Exception($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}"); } target.EmitComment("Suffixes End"); - - if (labelAfterOriginalContent.HasValue || labelAfterOriginalReturn.HasValue) - { - if (resultVariable != null) - target.Emit(OpCodes.Ldloc, resultVariable); - target.Emit(OpCodes.Ret); - } + if (resultVariable != null) + target.Emit(OpCodes.Ldloc, resultVariable); + target.Emit(OpCodes.Ret); } private void EmitMonkeyCall(LoggingIlGenerator target, MethodInfo patch, diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index 9ac0359..259cff0 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Reflection.Emit; using System.Text; using Torch.Managers.PatchManager.Transpile; +using Label = System.Windows.Controls.Label; namespace Torch.Managers.PatchManager.MSIL { @@ -69,7 +70,7 @@ namespace Torch.Managers.PatchManager.MSIL break; case OperandType.ShortInlineI: Operand = OpCode == OpCodes.Ldc_I4_S - ? (MsilOperand) new MsilOperandInline.MsilOperandInt8(this) + ? (MsilOperand)new MsilOperandInline.MsilOperandInt8(this) : new MsilOperandInline.MsilOperandUInt8(this); break; case OperandType.ShortInlineR: @@ -120,10 +121,23 @@ namespace Torch.Managers.PatchManager.MSIL /// This instruction public MsilInstruction InlineValue(T o) { - ((MsilOperandInline) Operand).Value = o; + ((MsilOperandInline)Operand).Value = o; return this; } + /// + /// Makes a copy of the instruction with a new opcode. + /// + /// The new opcode + /// The copy + public MsilInstruction CopyWith(OpCode code) + { + var result = new MsilInstruction(code) { Operand = this.Operand }; + foreach (MsilLabel x in Labels) + result.Labels.Add(x); + return result; + } + /// /// Sets the inline branch target for this instruction. /// @@ -131,7 +145,7 @@ namespace Torch.Managers.PatchManager.MSIL /// This instruction public MsilInstruction InlineTarget(MsilLabel label) { - ((MsilOperandBrTarget) Operand).Target = label; + ((MsilOperandBrTarget)Operand).Target = label; return this; } diff --git a/Torch/Managers/PatchManager/Transpile/MethodContext.cs b/Torch/Managers/PatchManager/Transpile/MethodContext.cs index 8c5be0d..36682d6 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodContext.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodContext.cs @@ -34,8 +34,8 @@ namespace Torch.Managers.PatchManager.Transpile public MethodContext(MethodBase method) { Method = method; - _msilBytes = Method.GetMethodBody().GetILAsByteArray(); - TokenResolver = new NormalTokenResolver(method); + _msilBytes = Method.GetMethodBody().GetILAsByteArray(); + TokenResolver = new NormalTokenResolver(method); } public void Read() @@ -56,7 +56,7 @@ namespace Torch.Managers.PatchManager.Transpile var instructionValue = (short)memory.ReadByte(); if (Prefixes.Contains(instructionValue)) { - instructionValue = (short) ((instructionValue << 8) | memory.ReadByte()); + instructionValue = (short)((instructionValue << 8) | memory.ReadByte()); count++; } if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode)) @@ -65,7 +65,7 @@ namespace Torch.Managers.PatchManager.Transpile throw new Exception($"Opcode said it was {opcode.Size} but we read {count}"); var instruction = new MsilInstruction(opcode) { - Offset = (int) memory.Position + Offset = (int)memory.Position }; _instructions.Add(instruction); instruction.Operand?.Read(this, reader); @@ -106,9 +106,9 @@ namespace Torch.Managers.PatchManager.Transpile var opcode = (OpCode)field.GetValue(null); if (opcode.OpCodeType != OpCodeType.Nternal) OpCodeLookup.Add(opcode.Value, opcode); - if ((ushort) opcode.Value > 0xFF) + if ((ushort)opcode.Value > 0xFF) { - Prefixes.Add((short) ((ushort) opcode.Value >> 8)); + Prefixes.Add((short)((ushort)opcode.Value >> 8)); } } } diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index d9a37eb..3defb4a 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -28,18 +28,19 @@ namespace Torch.Managers.PatchManager.Transpile private static IEnumerable FixBranchAndReturn(IEnumerable insn, Label? retTarget) { - foreach (var i in insn) + foreach (MsilInstruction i in insn) { if (retTarget.HasValue && i.OpCode == OpCodes.Ret) { - var j = new MsilInstruction(OpCodes.Br); - ((MsilOperandBrTarget)j.Operand).Target = new MsilLabel(retTarget.Value); + MsilInstruction j = new MsilInstruction(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value)); + foreach (MsilLabel l in i.Labels) + j.Labels.Add(l); yield return j; continue; } if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode)) { - yield return new MsilInstruction(replaceOpcode) { Operand = i.Operand }; + yield return i.CopyWith(replaceOpcode); continue; } yield return i; @@ -62,6 +63,7 @@ namespace Torch.Managers.PatchManager.Transpile _opcodeReplaceRule.Add(opcode, other.Value); } } + _opcodeReplaceRule[OpCodes.Leave_S] = OpCodes.Leave; } } } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index aed1db0..db958da 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -159,6 +159,7 @@ + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index f167e26..00cccaa 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -24,6 +24,7 @@ using Torch.API.Session; using Torch.Commands; using Torch.Managers; using Torch.Managers.ChatManager; +using Torch.Managers.PatchManager; using Torch.Utils; using Torch.Session; using VRage.Collections; @@ -119,6 +120,8 @@ namespace Torch sessionManager.AddFactory((x) => new EntityManager(this)); Managers.AddManager(sessionManager); + Managers.AddManager(new PatchManager(this)); + Managers.AddManager(new KeenLogManager(this)); Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new UpdateManager(this)); Managers.AddManager(Plugins); From b42d43c0e15023aae9cd7fd0ac6d1dd143ef5540 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 05:17:21 -0700 Subject: [PATCH 07/19] Redirect Keen console to Info, all else to trace. --- Torch/Managers/KeenLogManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Torch/Managers/KeenLogManager.cs b/Torch/Managers/KeenLogManager.cs index aefeb92..591c63b 100644 --- a/Torch/Managers/KeenLogManager.cs +++ b/Torch/Managers/KeenLogManager.cs @@ -61,7 +61,7 @@ namespace Torch.Managers _context.GetPattern(_logWriteLine).Prefixes.Add(Method(nameof(PrefixWriteLine))); _context.GetPattern(_logAppendToClosedLog).Prefixes.Add(Method(nameof(PrefixAppendToClosedLog))); - _context.GetPattern(_logWriteLineAndConsole).Prefixes.Add(Method(nameof(PrefixWriteLine))); + _context.GetPattern(_logWriteLineAndConsole).Prefixes.Add(Method(nameof(PrefixWriteLineConsole))); _context.GetPattern(_logWriteLineException).Prefixes.Add(Method(nameof(PrefixWriteLineException))); _context.GetPattern(_logAppendToClosedLogException).Prefixes.Add(Method(nameof(PrefixAppendToClosedLogException))); @@ -95,10 +95,17 @@ namespace Torch.Managers } private static bool PrefixWriteLine(MyLog __instance, string msg) + { + _log.Trace(PrepareLog(__instance).Append(msg)); + return false; + } + + private static bool PrefixWriteLineConsole(MyLog __instance, string msg) { _log.Info(PrepareLog(__instance).Append(msg)); return false; } + private static bool PrefixAppendToClosedLog(MyLog __instance, string text) { _log.Info(PrepareLog(__instance).Append(text)); From 0073e101dde5ccb5d945a490d004f508eba436d3 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 05:18:07 -0700 Subject: [PATCH 08/19] Fixed issues with operand replacing, and branch instructions. --- .../PatchManager/MSIL/MsilInstruction.cs | 44 +++++++---- .../Managers/PatchManager/MSIL/MsilOperand.cs | 2 + .../PatchManager/MSIL/MsilOperandBrTarget.cs | 15 +++- .../PatchManager/MSIL/MsilOperandInline.cs | 27 +++++-- .../PatchManager/MSIL/MsilOperandSwitch.cs | 17 ++++- .../Transpile/LoggingILGenerator.cs | 2 +- .../PatchManager/Transpile/MethodContext.cs | 76 ++++++++++++++++--- .../Transpile/MethodTranspiler.cs | 19 +++-- 8 files changed, 155 insertions(+), 47 deletions(-) diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index 259cff0..e6ae140 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Reflection.Emit; using System.Text; using Torch.Managers.PatchManager.Transpile; +using Torch.Utils; using Label = System.Windows.Controls.Label; namespace Torch.Managers.PatchManager.MSIL @@ -13,8 +14,6 @@ namespace Torch.Managers.PatchManager.MSIL /// public class MsilInstruction { - private MsilOperand _operandBacking; - /// /// Creates a new instruction with the given opcode. /// @@ -97,16 +96,7 @@ namespace Torch.Managers.PatchManager.MSIL /// /// The operand for this instruction, or null. /// - public MsilOperand Operand - { - get => _operandBacking; - set - { - if (_operandBacking != null && value.GetType() != _operandBacking.GetType()) - throw new ArgumentException($"Operand for {OpCode.Name} must be {_operandBacking.GetType().Name}"); - _operandBacking = value; - } - } + public MsilOperand Operand { get; } /// /// Labels pointing to this instruction. @@ -128,11 +118,12 @@ namespace Torch.Managers.PatchManager.MSIL /// /// Makes a copy of the instruction with a new opcode. /// - /// The new opcode + /// The new opcode /// The copy - public MsilInstruction CopyWith(OpCode code) + public MsilInstruction CopyWith(OpCode newOpcode) { - var result = new MsilInstruction(code) { Operand = this.Operand }; + var result = new MsilInstruction(newOpcode); + Operand?.CopyTo(result.Operand); foreach (MsilLabel x in Labels) result.Labels.Add(x); return result; @@ -172,5 +163,28 @@ namespace Torch.Managers.PatchManager.MSIL sb.Append(OpCode.Name).Append("\t").Append(Operand); return sb.ToString(); } + + + +#pragma warning disable 169 + [ReflectedMethod(Name = "StackChange")] + private static Func _stackChange; +#pragma warning restore 169 + + public int StackChange() + { + int num = _stackChange.Invoke(OpCode); + if ((OpCode == OpCodes.Call || OpCode == OpCodes.Callvirt || OpCode == OpCodes.Newobj) && + Operand is MsilOperandInline inline) + { + MethodBase op = inline.Value; + if (op is MethodInfo mi && mi.ReturnType != typeof(void)) + num++; + num -= op.GetParameters().Length; + if (!op.IsStatic && OpCode != OpCodes.Newobj) + num--; + } + return num; + } } } \ No newline at end of file diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperand.cs b/Torch/Managers/PatchManager/MSIL/MsilOperand.cs index d9dbf2f..0df66c4 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperand.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperand.cs @@ -18,6 +18,8 @@ namespace Torch.Managers.PatchManager.MSIL /// public MsilInstruction Instruction { get; } + internal abstract void CopyTo(MsilOperand operand); + internal abstract void Read(MethodContext context, BinaryReader reader); internal abstract void Emit(LoggingIlGenerator generator); diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs index 01e0913..ea93ce4 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Reflection.Emit; using Torch.Managers.PatchManager.Transpile; @@ -22,8 +23,8 @@ namespace Torch.Managers.PatchManager.MSIL { int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget ? reader.ReadInt32() - : reader.ReadByte(); - Target = context.LabelAt((int) reader.BaseStream.Position + val); + : reader.ReadSByte(); + Target = context.LabelAt((int)reader.BaseStream.Position + val); } internal override void Emit(LoggingIlGenerator generator) @@ -31,6 +32,14 @@ namespace Torch.Managers.PatchManager.MSIL generator.Emit(Instruction.OpCode, Target.LabelFor(generator)); } + internal override void CopyTo(MsilOperand operand) + { + var lt = operand as MsilOperandBrTarget; + if (lt == null) + throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand)); + lt.Target = Target; + } + /// public override string ToString() { diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs index e327054..b247a54 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection; using System.Reflection.Emit; using Torch.Managers.PatchManager.Transpile; @@ -22,6 +23,15 @@ namespace Torch.Managers.PatchManager.MSIL /// public T Value { get; set; } + internal override void CopyTo(MsilOperand operand) + { + var lt = operand as MsilOperandInline; + if (lt == null) + throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand)); + lt.Value = Value; + ; + } + /// public override string ToString() { @@ -66,7 +76,7 @@ namespace Torch.Managers.PatchManager.MSIL internal override void Read(MethodContext context, BinaryReader reader) { Value = - (sbyte) reader.ReadByte(); + (sbyte)reader.ReadByte(); } internal override void Emit(LoggingIlGenerator generator) @@ -209,16 +219,19 @@ namespace Torch.Managers.PatchManager.MSIL internal override void Read(MethodContext context, BinaryReader reader) { - Value = - context.Method.GetParameters()[ - Instruction.OpCode.OperandType == OperandType.ShortInlineVar - ? reader.ReadByte() - : reader.ReadUInt16()]; + int paramID = + Instruction.OpCode.OperandType == OperandType.ShortInlineVar + ? reader.ReadByte() + : reader.ReadUInt16(); + if (paramID == 0 && !context.Method.IsStatic) + throw new ArgumentException("Haven't figured out how to ldarg with the \"this\" argument"); + Value = context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)]; } internal override void Emit(LoggingIlGenerator generator) { - generator.Emit(Instruction.OpCode, Value.Position); + var methodInfo = Value.Member as MethodBase; + generator.Emit(Instruction.OpCode, Value.Position + (methodInfo != null && methodInfo.IsStatic ? 0 : 1)); } } diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs index 66e3b2d..2f28297 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using System.Reflection.Emit; using Torch.Managers.PatchManager.Transpile; @@ -19,6 +20,20 @@ namespace Torch.Managers.PatchManager.MSIL /// public MsilLabel[] Labels { get; set; } + + internal override void CopyTo(MsilOperand operand) + { + var lt = operand as MsilOperandSwitch; + if (lt == null) + throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand)); + if (Labels == null) + lt.Labels = null; + else + { + lt.Labels = new MsilLabel[Labels.Length]; + Array.Copy(Labels, lt.Labels, Labels.Length); + } + } internal override void Read(MethodContext context, BinaryReader reader) { int length = reader.ReadInt32(); diff --git a/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs b/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs index e18eb30..1835ac2 100644 --- a/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs +++ b/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs @@ -52,7 +52,7 @@ namespace Torch.Managers.PatchManager.Transpile /// public void Emit(OpCode op, LocalBuilder arg) { - _log?.Trace($"Emit\t{op,_opcodePadding} L:{arg.LocalIndex} {arg.LocalType}"); + _log?.Trace($"Emit\t{op,_opcodePadding} Local:{arg.LocalIndex}/{arg.LocalType}"); Backing.Emit(op, arg); } diff --git a/Torch/Managers/PatchManager/Transpile/MethodContext.cs b/Torch/Managers/PatchManager/Transpile/MethodContext.cs index 36682d6..1e69606 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodContext.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodContext.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Reflection.Emit; using NLog; using Torch.Managers.PatchManager.MSIL; +using Torch.Utils; namespace Torch.Managers.PatchManager.Transpile { @@ -52,20 +53,19 @@ namespace Torch.Managers.PatchManager.Transpile using (var reader = new BinaryReader(memory)) while (memory.Length > memory.Position) { - var count = 1; + var opcodeOffset = (int) memory.Position; var instructionValue = (short)memory.ReadByte(); if (Prefixes.Contains(instructionValue)) { instructionValue = (short)((instructionValue << 8) | memory.ReadByte()); - count++; } if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode)) throw new Exception($"Unknown opcode {instructionValue:X}"); - if (opcode.Size != count) - throw new Exception($"Opcode said it was {opcode.Size} but we read {count}"); + if (opcode.Size != memory.Position - opcodeOffset) + throw new Exception($"Opcode said it was {opcode.Size} but we read {memory.Position - opcodeOffset}"); var instruction = new MsilInstruction(opcode) { - Offset = (int)memory.Position + Offset = opcodeOffset }; _instructions.Add(instruction); instruction.Operand?.Read(this, reader); @@ -76,22 +76,74 @@ namespace Torch.Managers.PatchManager.Transpile { foreach (var label in Labels) { - int min = 0, max = _instructions.Count - 1; - while (min <= max) + int min = 0, max = _instructions.Count; + while (min != max) { - var mid = min + ((max - min) / 2); - if (label.Key < _instructions[mid].Offset) - max = mid - 1; - else + int mid = (min + max) / 2; + if (_instructions[mid].Offset < label.Key) min = mid + 1; + else + max = mid; } +#if DEBUG + if (min >= _instructions.Count || min < 0) + { + _log.Trace( + $"Want offset {label.Key} for {label.Value}, instruction offsets at\n {string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4} {x}"))}"); + } + MsilInstruction prevInsn = min > 0 ? _instructions[min - 1] : null; + if ((prevInsn == null || prevInsn.Offset >= label.Key) || + _instructions[min].Offset < label.Key) + _log.Error($"Label {label.Value} wanted {label.Key} but instruction is at {_instructions[min].Offset}. Previous instruction is at {prevInsn?.Offset ?? -1}"); +#endif _instructions[min]?.Labels?.Add(label.Value); } } + + [Conditional("DEBUG")] + public void CheckIntegrity() + { + var entryStackCount = new Dictionary>(); + var currentStackSize = 0; + foreach (MsilInstruction insn in _instructions) + { + // I don't want to deal with this, so I won't + if (insn.OpCode == OpCodes.Br || insn.OpCode == OpCodes.Br_S || insn.OpCode == OpCodes.Jmp || + insn.OpCode == OpCodes.Leave || insn.OpCode == OpCodes.Leave_S) + break; + foreach (MsilLabel label in insn.Labels) + if (entryStackCount.TryGetValue(label, out Dictionary dict)) + dict.Add(insn, currentStackSize); + else + (entryStackCount[label] = new Dictionary()).Add(insn, currentStackSize); + + currentStackSize += insn.StackChange(); + + if (insn.Operand is MsilOperandBrTarget br) + if (entryStackCount.TryGetValue(br.Target, out Dictionary dict)) + dict.Add(insn, currentStackSize); + else + (entryStackCount[br.Target] = new Dictionary()).Add(insn, currentStackSize); + } + foreach (KeyValuePair> label in entryStackCount) + { + if (label.Value.Values.Aggregate(new HashSet(), (a, b) => + { + a.Add(b); + return a; + }).Count > 1) + { + _log.Warn($"Label {label.Key} has multiple entry stack counts"); + foreach (KeyValuePair kv in label.Value) + _log.Warn($"{kv.Key.Offset:X4} {kv.Key} => {kv.Value}"); + } + } + } + public string ToHumanMsil() { - return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x}")); + return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange()} {x}")); } private static readonly Dictionary OpCodeLookup; diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index 3defb4a..25ea7dc 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -9,16 +9,17 @@ namespace Torch.Managers.PatchManager.Transpile { internal class MethodTranspiler { - private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + public static readonly Logger _log = LogManager.GetCurrentClassLogger(); internal static void Transpile(MethodBase baseMethod, IEnumerable transpilers, LoggingIlGenerator output, Label? retLabel) { var context = new MethodContext(baseMethod); context.Read(); + context.CheckIntegrity(); _log.Trace("Input Method:"); _log.Trace(context.ToHumanMsil); - var methodContent = (IEnumerable) context.Instructions; + var methodContent = (IEnumerable)context.Instructions; foreach (var transpiler in transpilers) methodContent = (IEnumerable)transpiler.Invoke(null, new object[] { methodContent }); methodContent = FixBranchAndReturn(methodContent, retLabel); @@ -35,15 +36,17 @@ namespace Torch.Managers.PatchManager.Transpile MsilInstruction j = new MsilInstruction(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value)); foreach (MsilLabel l in i.Labels) j.Labels.Add(l); + _log.Trace($"Replacing {i} with {j}"); yield return j; - continue; } - if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode)) + else if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode)) { - yield return i.CopyWith(replaceOpcode); - continue; + var result = i.CopyWith(replaceOpcode); + _log.Trace($"Replacing {i} with {result}"); + yield return result; } - yield return i; + else + yield return i; } } @@ -57,7 +60,7 @@ namespace Torch.Managers.PatchManager.Transpile if (opcode.OperandType == OperandType.ShortInlineBrTarget && opcode.Name.EndsWith(".s", StringComparison.OrdinalIgnoreCase)) { - var other = (OpCode?) typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2), + var other = (OpCode?)typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2), BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (other.HasValue && other.Value.OperandType == OperandType.InlineBrTarget) _opcodeReplaceRule.Add(opcode, other.Value); From e57f885d3b7a51258346a19e825cef8b66f41698 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 05:19:02 -0700 Subject: [PATCH 09/19] Log errors in ReflectedManager --- Torch/Utils/ReflectedManager.cs | 34 ++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/Torch/Utils/ReflectedManager.cs b/Torch/Utils/ReflectedManager.cs index 39dae0b..5976912 100644 --- a/Torch/Utils/ReflectedManager.cs +++ b/Torch/Utils/ReflectedManager.cs @@ -7,6 +7,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Threading.Tasks; +using NLog; using Sandbox.Engine.Multiplayer; using Torch.API; @@ -389,8 +390,9 @@ namespace Torch.Utils /// public class ReflectedManager { + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); private static readonly string[] _namespaceBlacklist = new[] { - "System", "VRage", "Sandbox", "SpaceEngineers" + "System", "VRage", "Sandbox", "SpaceEngineers", "Microsoft" }; /// @@ -426,11 +428,28 @@ namespace Torch.Utils { if (_processedTypes.Add(t)) { + if (string.IsNullOrWhiteSpace(t.Namespace)) + return; foreach (string ns in _namespaceBlacklist) if (t.FullName.StartsWith(ns)) return; - foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) - Process(field); + foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Instance | + BindingFlags.Public | BindingFlags.NonPublic)) + { + try + { +#if DEBUG + if (Process(field)) + _log?.Trace($"Field {field.DeclaringType?.FullName}#{field.Name} = {field.GetValue(null) ?? "null"}"); +#else + Process(field); +#endif + } + catch (Exception e) + { + _log?.Error(e.InnerException ?? e, $"Unable to fill {field.DeclaringType?.FullName}#{field.Name}. {(e.InnerException ?? e).Message}"); + } + } } } @@ -484,7 +503,8 @@ namespace Torch.Utils { if (!field.IsStatic) throw new ArgumentException("Field must be static to be reflected"); - field.SetValue(null, new Func(() => new ReflectedEventReplacer(reflectedEventReplacer))); + field.SetValue(null, + new Func(() => new ReflectedEventReplacer(reflectedEventReplacer))); return true; } return false; @@ -502,14 +522,14 @@ namespace Torch.Utils case ReflectedFieldInfoAttribute rfia: info = GetFieldPropRecursive(rfia.Type, rfia.Name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, - (a, b, c) => a.GetField(b)); + (type, name, bindingFlags) => type.GetField(name, bindingFlags)); if (info == null) throw new ArgumentException($"Unable to find field {rfia.Type.FullName}#{rfia.Name}"); break; case ReflectedPropertyInfoAttribute rpia: info = GetFieldPropRecursive(rpia.Type, rpia.Name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, - (a, b, c) => a.GetProperty(b)); + (type, name, bindingFlags) => type.GetProperty(name, bindingFlags)); if (info == null) throw new ArgumentException($"Unable to find property {rpia.Type.FullName}#{rpia.Name}"); break; @@ -674,7 +694,7 @@ namespace Torch.Utils Expression instanceExpr = null; if (!isStatic) { - instanceExpr = trueType == paramExp[0].Type ? (Expression) paramExp[0] : Expression.Convert(paramExp[0], trueType); + instanceExpr = trueType == paramExp[0].Type ? (Expression)paramExp[0] : Expression.Convert(paramExp[0], trueType); } MemberExpression fieldExp = sourceField != null From 57acb274c616f448b2f46dc31d7f08ae0513df68 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 18:28:53 -0700 Subject: [PATCH 10/19] Don't crash when modifying constructor Tweak log level of assembly resolver. Events relating to game initialization and shutdown. Plugin manager loads plugins right before the dependency manager is attached. --- Torch.API/ITorchBase.cs | 12 ++- Torch.API/Torch.API.csproj | 1 + Torch.API/TorchGameState.cs | 47 ++++++++++ Torch.Server/TorchServer.cs | 1 - .../PatchManager/MSIL/ITokenResolver.cs | 2 +- Torch/Managers/PluginManager.cs | 2 +- Torch/TorchBase.cs | 93 ++++++++++++++++++- Torch/Utils/TorchAssemblyResolver.cs | 2 +- 8 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 Torch.API/TorchGameState.cs diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs index 6c851ab..c319818 100644 --- a/Torch.API/ITorchBase.cs +++ b/Torch.API/ITorchBase.cs @@ -103,6 +103,16 @@ namespace Torch.API /// Initialize the Torch instance. /// void Init(); + + /// + /// The current state of the game this instance of torch is controlling. + /// + TorchGameState GameState { get; } + + /// + /// Event raised when changes. + /// + event TorchGameStateChangedDel GameStateChanged; } /// @@ -121,6 +131,6 @@ namespace Torch.API /// public interface ITorchClient : ITorchBase { - + } } diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj index d771abd..bbf2e3b 100644 --- a/Torch.API/Torch.API.csproj +++ b/Torch.API/Torch.API.csproj @@ -186,6 +186,7 @@ + diff --git a/Torch.API/TorchGameState.cs b/Torch.API/TorchGameState.cs new file mode 100644 index 0000000..5c81393 --- /dev/null +++ b/Torch.API/TorchGameState.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Sandbox; + +namespace Torch.API +{ + /// + /// Represents the state of a + /// + public enum TorchGameState + { + /// + /// The game is currently being created. + /// + Creating, + /// + /// The game has been created and is ready to begin loading. + /// + Created, + /// + /// The game is currently loading. + /// + Loading, + /// + /// The game is fully loaded and ready to start sessions + /// + Loaded, + /// + /// The game is beginning the unload sequence + /// + Unloading, + /// + /// The game has been shutdown and is no longer active + /// + Unloaded + } + + /// + /// Callback raised when a game's state changes + /// + /// The game who had a state change + /// The game's new state + public delegate void TorchGameStateChangedDel(MySandboxGame game, TorchGameState newState); +} diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index b5dfef4..afd6bfe 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -95,7 +95,6 @@ namespace Torch.Server MyGlobalTypeMetadata.Static.Init(); GetManager().LoadInstance(Config.InstancePath); - Plugins.LoadPlugins(); } private void InvokeBeforeRun() diff --git a/Torch/Managers/PatchManager/MSIL/ITokenResolver.cs b/Torch/Managers/PatchManager/MSIL/ITokenResolver.cs index ffaac0a..5b85c93 100644 --- a/Torch/Managers/PatchManager/MSIL/ITokenResolver.cs +++ b/Torch/Managers/PatchManager/MSIL/ITokenResolver.cs @@ -24,7 +24,7 @@ namespace Torch.Managers.PatchManager.MSIL { _module = method.Module; _genericTypeArgs = method.DeclaringType?.GenericTypeArguments ?? new Type[0]; - _genericMethArgs = method.GetGenericArguments(); + _genericMethArgs = (method is MethodInfo ? method.GetGenericArguments() : new Type[0]); } public MemberInfo ResolveMember(int token) diff --git a/Torch/Managers/PluginManager.cs b/Torch/Managers/PluginManager.cs index c6828e2..92112f2 100644 --- a/Torch/Managers/PluginManager.cs +++ b/Torch/Managers/PluginManager.cs @@ -148,7 +148,7 @@ namespace Torch.Managers var dlls = Directory.GetFiles(PluginDir, "*.dll", SearchOption.AllDirectories); foreach (var dllPath in dlls) { - _log.Debug($"Loading plugin {dllPath}"); + _log.Info($"Loading plugin {dllPath}"); var asm = Assembly.UnsafeLoadFrom(dllPath); foreach (var type in asm.GetExportedTypes()) diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 00cccaa..7bf0b18 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -120,12 +120,13 @@ namespace Torch sessionManager.AddFactory((x) => new EntityManager(this)); Managers.AddManager(sessionManager); - Managers.AddManager(new PatchManager(this)); - Managers.AddManager(new KeenLogManager(this)); + var patcher = new PatchManager(this); + GameStateInjector.Inject(patcher.AcquireContext()); + Managers.AddManager(patcher); + // Managers.AddManager(new KeenLogManager(this)); Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new UpdateManager(this)); Managers.AddManager(Plugins); - TorchAPI.Instance = this; } @@ -269,6 +270,7 @@ namespace Torch MySession.OnUnloading += OnSessionUnloading; MySession.OnUnloaded += OnSessionUnloaded; RegisterVRagePlugin(); + Managers.GetManager().LoadPlugins(); Managers.Attach(); _init = true; } @@ -383,5 +385,90 @@ namespace Torch { GetManager().UpdatePlugins(); } + + + private TorchGameState _gameState = TorchGameState.Unloaded; + + /// + public TorchGameState GameState + { + get => _gameState; + private set + { + _gameState = value; + GameStateChanged?.Invoke(MySandboxGame.Static, _gameState); + Log.Info($"Game state changed {_gameState}"); + } + } + + /// + public event TorchGameStateChangedDel GameStateChanged; + + #region GameStateInjecting + private static class GameStateInjector + { +#pragma warning disable 649 + [ReflectedMethodInfo(typeof(MySandboxGame), nameof(MySandboxGame.Dispose))] + private static MethodInfo _sandboxGameDispose; + [ReflectedMethodInfo(typeof(MySandboxGame), "Initialize")] + private static MethodInfo _sandboxGameInit; +#pragma warning restore 649 + + internal static void Inject(PatchContext target) + { + ConstructorInfo ctor = typeof(MySandboxGame).GetConstructor(new[] {typeof(string[])}); + if (ctor == null) + throw new ArgumentException("Can't find constructor MySandboxGame(string[])"); + target.GetPattern(ctor).Prefixes.Add(MethodRef(nameof(PrefixConstructor))); + target.GetPattern(ctor).Suffixes.Add(MethodRef(nameof(SuffixConstructor))); + target.GetPattern(_sandboxGameInit).Prefixes.Add(MethodRef(nameof(PrefixInit))); + target.GetPattern(_sandboxGameInit).Suffixes.Add(MethodRef(nameof(SuffixInit))); + target.GetPattern(_sandboxGameDispose).Prefixes.Add(MethodRef(nameof(PrefixDispose))); + target.GetPattern(_sandboxGameDispose).Suffixes.Add(MethodRef(nameof(SuffixDispose))); + } + + private static MethodInfo MethodRef(string name) + { + return typeof(GameStateInjector).GetMethod(name, + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + } + + private static void PrefixConstructor() + { + if (Instance is TorchBase tb) + tb.GameState = TorchGameState.Creating; + } + + private static void SuffixConstructor() + { + if (Instance is TorchBase tb) + tb.GameState = TorchGameState.Created; + } + + private static void PrefixInit() + { + if (Instance is TorchBase tb) + tb.GameState = TorchGameState.Loading; + } + + private static void SuffixInit() + { + if (Instance is TorchBase tb) + tb.GameState = TorchGameState.Loaded; + } + + private static void PrefixDispose() + { + if (Instance is TorchBase tb) + tb.GameState = TorchGameState.Unloading; + } + + private static void SuffixDispose() + { + if (Instance is TorchBase tb) + tb.GameState = TorchGameState.Unloaded; + } + } + #endregion } } diff --git a/Torch/Utils/TorchAssemblyResolver.cs b/Torch/Utils/TorchAssemblyResolver.cs index ca1f6bb..7b558bd 100644 --- a/Torch/Utils/TorchAssemblyResolver.cs +++ b/Torch/Utils/TorchAssemblyResolver.cs @@ -67,7 +67,7 @@ namespace Torch.Utils string assemblyPath = Path.Combine(path, assemblyName + ".dll"); if (!File.Exists(assemblyPath)) continue; - _log.Debug("Loading {0} from {1}", assemblyName, SimplifyPath(assemblyPath)); + _log.Trace("Loading {0} from {1}", assemblyName, SimplifyPath(assemblyPath)); LogManager.Flush(); Assembly asm = Assembly.LoadFrom(assemblyPath); _assemblies.Add(assemblyName, asm); From 0810e76474d855eeec71f01d9f4174ad0722788c Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 18:55:09 -0700 Subject: [PATCH 11/19] Once the game is created we can patch it with impunity. --- Torch/TorchBase.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index 7bf0b18..cb0f9d1 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -127,6 +127,13 @@ namespace Torch Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new UpdateManager(this)); Managers.AddManager(Plugins); + GameStateChanged += (game, state) => + { + if (state != TorchGameState.Created) + return; + // At this point flush the patches; it's safe. + patcher.Commit(); + }; TorchAPI.Instance = this; } @@ -397,7 +404,6 @@ namespace Torch { _gameState = value; GameStateChanged?.Invoke(MySandboxGame.Static, _gameState); - Log.Info($"Game state changed {_gameState}"); } } From b1145c8926e749cfecd323f60d11021c120d26af Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 19:50:07 -0700 Subject: [PATCH 12/19] Utility method to invert common load and store instructions --- .../PatchManager/MSIL/MsilInstruction.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index e6ae140..b4f6281 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs @@ -171,6 +171,70 @@ namespace Torch.Managers.PatchManager.MSIL private static Func _stackChange; #pragma warning restore 169 + + /// + /// Gets an instruction that represents the inverse of this load or store instruction. + /// + /// + /// + /// new MsilInstruction(OpCodes.Ldloc_0).StoreLoadInverse().OpCode == OpCodes.Stloc_0 + /// + /// + /// Inverse + public MsilInstruction StoreLoadInverse() + { + if (OpCode == OpCodes.Ldloc) + return new MsilInstruction(OpCodes.Stloc).InlineValue( + ((MsilOperandInline)Operand).Value); + if (OpCode == OpCodes.Ldloc_S) + return new MsilInstruction(OpCodes.Stloc_S).InlineValue( + ((MsilOperandInline)Operand).Value); + if (OpCode == OpCodes.Ldloc_0) + return new MsilInstruction(OpCodes.Stloc_0); + if (OpCode == OpCodes.Ldloc_1) + return new MsilInstruction(OpCodes.Stloc_1); + if (OpCode == OpCodes.Ldloc_2) + return new MsilInstruction(OpCodes.Stloc_2); + if (OpCode == OpCodes.Ldloc_3) + return new MsilInstruction(OpCodes.Stloc_3); + + if (OpCode == OpCodes.Stloc) + return new MsilInstruction(OpCodes.Ldloc).InlineValue( + ((MsilOperandInline)Operand).Value); + if (OpCode == OpCodes.Stloc_S) + return new MsilInstruction(OpCodes.Ldloc_S).InlineValue( + ((MsilOperandInline)Operand).Value); + if (OpCode == OpCodes.Stloc_0) + return new MsilInstruction(OpCodes.Ldloc_0); + if (OpCode == OpCodes.Stloc_1) + return new MsilInstruction(OpCodes.Ldloc_1); + if (OpCode == OpCodes.Stloc_2) + return new MsilInstruction(OpCodes.Ldloc_2); + if (OpCode == OpCodes.Stloc_3) + return new MsilInstruction(OpCodes.Ldloc_3); + + if (OpCode == OpCodes.Ldarg) + return new MsilInstruction(OpCodes.Starg).InlineValue( + ((MsilOperandInline)Operand).Value); + if (OpCode == OpCodes.Ldarg_S) + return new MsilInstruction(OpCodes.Starg_S).InlineValue( + ((MsilOperandInline)Operand).Value); + // TODO Ldarg_0 etc + + if (OpCode == OpCodes.Starg) + return new MsilInstruction(OpCodes.Ldarg).InlineValue( + ((MsilOperandInline)Operand).Value); + if (OpCode == OpCodes.Starg_S) + return new MsilInstruction(OpCodes.Ldarg_S).InlineValue( + ((MsilOperandInline)Operand).Value); + + throw new ArgumentException($"Can't invert the instruction {this}"); + } + + /// + /// Estimates the stack delta for this instruction. + /// + /// Stack delta public int StackChange() { int num = _stackChange.Invoke(OpCode); From 373c476d2d43e5595987d641e4e45ccc16a2e887 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 20:26:33 -0700 Subject: [PATCH 13/19] Better guessing on the generic operand type --- NLog.config | 2 +- .../PatchManager/MSIL/MsilInstruction.cs | 22 +++++++++++++++++++ .../PatchManager/Transpile/MethodContext.cs | 2 +- .../Transpile/MethodTranspiler.cs | 4 ++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/NLog.config b/NLog.config index 270fe34..3ac0285 100644 --- a/NLog.config +++ b/NLog.config @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index b4f6281..4614098 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using System.Text; @@ -103,6 +105,9 @@ namespace Torch.Managers.PatchManager.MSIL /// public HashSet Labels { get; } = new HashSet(); + + private static readonly ConcurrentDictionary _setterInfoForInlines = new ConcurrentDictionary(); + /// /// Sets the inline value for this instruction. /// @@ -111,6 +116,23 @@ namespace Torch.Managers.PatchManager.MSIL /// This instruction public MsilInstruction InlineValue(T o) { + Type type = typeof(T); + while (type != null) + { + if (!_setterInfoForInlines.TryGetValue(type, out PropertyInfo target)) + { + Type genType = typeof(MsilOperandInline<>).MakeGenericType(type); + target = genType.GetProperty(nameof(MsilOperandInline.Value)); + _setterInfoForInlines[type] = target; + } + Debug.Assert(target?.DeclaringType != null); + if (target.DeclaringType.IsInstanceOfType(Operand)) + { + target.SetValue(Operand, o); + return this; + } + type = type.BaseType; + } ((MsilOperandInline)Operand).Value = o; return this; } diff --git a/Torch/Managers/PatchManager/Transpile/MethodContext.cs b/Torch/Managers/PatchManager/Transpile/MethodContext.cs index 1e69606..7d316f7 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodContext.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodContext.cs @@ -143,7 +143,7 @@ namespace Torch.Managers.PatchManager.Transpile public string ToHumanMsil() { - return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange()} {x}")); + return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange():+0;-#} {x}")); } private static readonly Dictionary OpCodeLookup; diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index 25ea7dc..33faedb 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -16,8 +16,8 @@ namespace Torch.Managers.PatchManager.Transpile var context = new MethodContext(baseMethod); context.Read(); context.CheckIntegrity(); - _log.Trace("Input Method:"); - _log.Trace(context.ToHumanMsil); +// _log.Trace("Input Method:"); +// _log.Trace(context.ToHumanMsil); var methodContent = (IEnumerable)context.Instructions; foreach (var transpiler in transpilers) From a61b646295e24fda48499c9995b7640718d4208e Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 22:32:37 -0700 Subject: [PATCH 14/19] ReflectedMethodInfo allows non-public type names. MsilInstructionExtensions to make life easier --- NLog.config | 2 +- .../Managers/PatchManager/DecoratedMethod.cs | 3 +- .../PatchManager/MSIL/MsilArgument.cs | 49 +++++ .../PatchManager/MSIL/MsilInstruction.cs | 62 +----- .../MSIL/MsilInstructionExtensions.cs | 202 ++++++++++++++++++ Torch/Managers/PatchManager/MSIL/MsilLocal.cs | 56 +++++ .../PatchManager/MSIL/MsilOperandInline.cs | 19 +- .../Transpile/MethodTranspiler.cs | 25 ++- Torch/Torch.csproj | 3 + Torch/Utils/ReflectedManager.cs | 10 + 10 files changed, 353 insertions(+), 78 deletions(-) create mode 100644 Torch/Managers/PatchManager/MSIL/MsilArgument.cs create mode 100644 Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs create mode 100644 Torch/Managers/PatchManager/MSIL/MsilLocal.cs diff --git a/NLog.config b/NLog.config index 3ac0285..270fe34 100644 --- a/NLog.config +++ b/NLog.config @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index 5892de8..8ca6a47 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -7,6 +7,7 @@ using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using NLog; +using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.Transpile; using Torch.Utils; @@ -147,7 +148,7 @@ namespace Torch.Managers.PatchManager target.EmitComment("Prefixes End"); target.EmitComment("Original Begin"); - MethodTranspiler.Transpile(_method, Transpilers, target, labelAfterOriginalContent); + MethodTranspiler.Transpile(_method, (type) => new MsilLocal(target.DeclareLocal(type)), Transpilers, target, labelAfterOriginalContent); target.EmitComment("Original End"); target.MarkLabel(labelAfterOriginalContent); diff --git a/Torch/Managers/PatchManager/MSIL/MsilArgument.cs b/Torch/Managers/PatchManager/MSIL/MsilArgument.cs new file mode 100644 index 0000000..2e10b14 --- /dev/null +++ b/Torch/Managers/PatchManager/MSIL/MsilArgument.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Managers.PatchManager.MSIL +{ + /// + /// Represents metadata about a method's parameter + /// + public class MsilArgument + { + /// + /// The positon of this argument. Note, if the method is static, index 0 is the instance. + /// + public int Position { get; } + + /// + /// The type of this parameter, or null if unknown. + /// + public Type Type { get; } + + /// + /// The name of this parameter, or null if unknown. + /// + public string Name { get; } + + internal MsilArgument(ParameterInfo local) + { + Position = (((MethodBase)local.Member).IsStatic ? 0 : 1) + local.Position; + Type = local.ParameterType; + Name = local.Name; + } + + /// + /// Creates an empty argument reference with the given position. + /// + /// The argument's position + public MsilArgument(int position) + { + Position = position; + Type = null; + Name = null; + } + } +} diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index 4614098..54ad06e 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs @@ -67,7 +67,7 @@ namespace Torch.Managers.PatchManager.MSIL if (OpCode.Name.IndexOf("loc", StringComparison.OrdinalIgnoreCase) != -1) Operand = new MsilOperandInline.MsilOperandLocal(this); else - Operand = new MsilOperandInline.MsilOperandParameter(this); + Operand = new MsilOperandInline.MsilOperandArgument(this); break; case OperandType.ShortInlineI: Operand = OpCode == OpCodes.Ldc_I4_S @@ -193,66 +193,6 @@ namespace Torch.Managers.PatchManager.MSIL private static Func _stackChange; #pragma warning restore 169 - - /// - /// Gets an instruction that represents the inverse of this load or store instruction. - /// - /// - /// - /// new MsilInstruction(OpCodes.Ldloc_0).StoreLoadInverse().OpCode == OpCodes.Stloc_0 - /// - /// - /// Inverse - public MsilInstruction StoreLoadInverse() - { - if (OpCode == OpCodes.Ldloc) - return new MsilInstruction(OpCodes.Stloc).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Ldloc_S) - return new MsilInstruction(OpCodes.Stloc_S).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Ldloc_0) - return new MsilInstruction(OpCodes.Stloc_0); - if (OpCode == OpCodes.Ldloc_1) - return new MsilInstruction(OpCodes.Stloc_1); - if (OpCode == OpCodes.Ldloc_2) - return new MsilInstruction(OpCodes.Stloc_2); - if (OpCode == OpCodes.Ldloc_3) - return new MsilInstruction(OpCodes.Stloc_3); - - if (OpCode == OpCodes.Stloc) - return new MsilInstruction(OpCodes.Ldloc).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Stloc_S) - return new MsilInstruction(OpCodes.Ldloc_S).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Stloc_0) - return new MsilInstruction(OpCodes.Ldloc_0); - if (OpCode == OpCodes.Stloc_1) - return new MsilInstruction(OpCodes.Ldloc_1); - if (OpCode == OpCodes.Stloc_2) - return new MsilInstruction(OpCodes.Ldloc_2); - if (OpCode == OpCodes.Stloc_3) - return new MsilInstruction(OpCodes.Ldloc_3); - - if (OpCode == OpCodes.Ldarg) - return new MsilInstruction(OpCodes.Starg).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Ldarg_S) - return new MsilInstruction(OpCodes.Starg_S).InlineValue( - ((MsilOperandInline)Operand).Value); - // TODO Ldarg_0 etc - - if (OpCode == OpCodes.Starg) - return new MsilInstruction(OpCodes.Ldarg).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Starg_S) - return new MsilInstruction(OpCodes.Ldarg_S).InlineValue( - ((MsilOperandInline)Operand).Value); - - throw new ArgumentException($"Can't invert the instruction {this}"); - } - /// /// Estimates the stack delta for this instruction. /// diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs b/Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs new file mode 100644 index 0000000..4629f7f --- /dev/null +++ b/Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Managers.PatchManager.MSIL +{ + /// + /// Various methods to make composing MSIL easier + /// + public static class MsilInstructionExtensions + { + #region Local Utils + /// + /// Is this instruction a local load-by-value instruction. + /// + public static bool IsLocalLoad(this MsilInstruction me) + { + return me.OpCode == OpCodes.Ldloc || me.OpCode == OpCodes.Ldloc_S || me.OpCode == OpCodes.Ldloc_0 || + me.OpCode == OpCodes.Ldloc_1 || me.OpCode == OpCodes.Ldloc_2 || me.OpCode == OpCodes.Ldloc_3; + } + + /// + /// Is this instruction a local load-by-reference instruction. + /// + public static bool IsLocalLoadByRef(this MsilInstruction me) + { + return me.OpCode == OpCodes.Ldloca || me.OpCode == OpCodes.Ldloca_S; + } + + /// + /// Is this instruction a local store instruction. + /// + public static bool IsLocalStore(this MsilInstruction me) + { + return me.OpCode == OpCodes.Stloc || me.OpCode == OpCodes.Stloc_S || me.OpCode == OpCodes.Stloc_0 || + me.OpCode == OpCodes.Stloc_1 || me.OpCode == OpCodes.Stloc_2 || me.OpCode == OpCodes.Stloc_3; + } + + /// + /// For a local referencing opcode, get the local it is referencing. + /// + public static MsilLocal GetReferencedLocal(this MsilInstruction me) + { + if (me.Operand is MsilOperandInline.MsilOperandLocal mol) + return mol.Value; + if (me.OpCode == OpCodes.Stloc_0 || me.OpCode == OpCodes.Ldloc_0) + return new MsilLocal(0); + if (me.OpCode == OpCodes.Stloc_1 || me.OpCode == OpCodes.Ldloc_1) + return new MsilLocal(1); + if (me.OpCode == OpCodes.Stloc_2 || me.OpCode == OpCodes.Ldloc_2) + return new MsilLocal(2); + if (me.OpCode == OpCodes.Stloc_3 || me.OpCode == OpCodes.Ldloc_3) + return new MsilLocal(3); + throw new ArgumentException($"Can't get referenced local in instruction {me}"); + } + /// + /// Gets an instruction representing a load-by-value from the given local. + /// + /// Local to load + /// Loading instruction + public static MsilInstruction AsValueLoad(this MsilLocal local) + { + switch (local.Index) + { + case 0: + return new MsilInstruction(OpCodes.Ldloc_0); + case 1: + return new MsilInstruction(OpCodes.Ldloc_1); + case 2: + return new MsilInstruction(OpCodes.Ldloc_2); + case 3: + return new MsilInstruction(OpCodes.Ldloc_3); + default: + return new MsilInstruction(local.Index < 0xFF ? OpCodes.Ldloc_S : OpCodes.Ldloc).InlineValue(local); + } + } + + /// + /// Gets an instruction representing a store-by-value to the given local. + /// + /// Local to write to + /// Loading instruction + public static MsilInstruction AsValueStore(this MsilLocal local) + { + switch (local.Index) + { + case 0: + return new MsilInstruction(OpCodes.Stloc_0); + case 1: + return new MsilInstruction(OpCodes.Stloc_1); + case 2: + return new MsilInstruction(OpCodes.Stloc_2); + case 3: + return new MsilInstruction(OpCodes.Stloc_3); + default: + return new MsilInstruction(local.Index < 0xFF ? OpCodes.Stloc_S : OpCodes.Stloc).InlineValue(local); + } + } + + /// + /// Gets an instruction representing a load-by-reference from the given local. + /// + /// Local to load + /// Loading instruction + public static MsilInstruction AsReferenceLoad(this MsilLocal local) + { + return new MsilInstruction(local.Index < 0xFF ? OpCodes.Ldloca_S : OpCodes.Ldloca).InlineValue(local); + } + #endregion + + #region Argument Utils + /// + /// Is this instruction an argument load-by-value instruction. + /// + public static bool IsArgumentLoad(this MsilInstruction me) + { + return me.OpCode == OpCodes.Ldarg || me.OpCode == OpCodes.Ldarg_S || me.OpCode == OpCodes.Ldarg_0 || + me.OpCode == OpCodes.Ldarg_1 || me.OpCode == OpCodes.Ldarg_2 || me.OpCode == OpCodes.Ldarg_3; + } + + /// + /// Is this instruction an argument load-by-reference instruction. + /// + public static bool IsArgumentLoadByRef(this MsilInstruction me) + { + return me.OpCode == OpCodes.Ldarga || me.OpCode == OpCodes.Ldarga_S; + } + + /// + /// Is this instruction an argument store instruction. + /// + public static bool IsArgumentStore(this MsilInstruction me) + { + return me.OpCode == OpCodes.Starg || me.OpCode == OpCodes.Starg_S; + } + + /// + /// For an argument referencing opcode, get the index of the local it is referencing. + /// + public static MsilArgument GetReferencedArgument(this MsilInstruction me) + { + if (me.Operand is MsilOperandInline.MsilOperandArgument mol) + return mol.Value; + if (me.OpCode == OpCodes.Ldarg_0) + return new MsilArgument(0); + if (me.OpCode == OpCodes.Ldarg_1) + return new MsilArgument(1); + if (me.OpCode == OpCodes.Ldarg_2) + return new MsilArgument(2); + if (me.OpCode == OpCodes.Ldarg_3) + return new MsilArgument(3); + throw new ArgumentException($"Can't get referenced argument in instruction {me}"); + } + + /// + /// Gets an instruction representing a load-by-value from the given argument. + /// + /// argument to load + /// Load instruction + public static MsilInstruction AsValueLoad(this MsilArgument argument) + { + switch (argument.Position) + { + case 0: + return new MsilInstruction(OpCodes.Ldarg_0); + case 1: + return new MsilInstruction(OpCodes.Ldarg_1); + case 2: + return new MsilInstruction(OpCodes.Ldarg_2); + case 3: + return new MsilInstruction(OpCodes.Ldarg_3); + default: + return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Ldarg_S : OpCodes.Ldarg).InlineValue(argument); + } + } + + /// + /// Gets an instruction representing a store-by-value to the given argument. + /// + /// argument to write to + /// Store instruction + public static MsilInstruction AsValueStore(this MsilArgument argument) + { + return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Starg_S : OpCodes.Starg).InlineValue(argument); + } + + /// + /// Gets an instruction representing a load-by-reference from the given argument. + /// + /// argument to load + /// Reference load instruction + public static MsilInstruction AsReferenceLoad(this MsilArgument argument) + { + return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Ldarga_S : OpCodes.Ldarga).InlineValue(argument); + } + #endregion + } +} diff --git a/Torch/Managers/PatchManager/MSIL/MsilLocal.cs b/Torch/Managers/PatchManager/MSIL/MsilLocal.cs new file mode 100644 index 0000000..8268aee --- /dev/null +++ b/Torch/Managers/PatchManager/MSIL/MsilLocal.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Managers.PatchManager.MSIL +{ + /// + /// Represents metadata about a method's local + /// + public class MsilLocal + { + /// + /// The index of this local. + /// + public int Index { get; } + + /// + /// The type of this local, or null if unknown. + /// + public Type Type { get; } + + /// + /// The name of this local, or null if unknown. + /// + public string Name { get; } + + internal MsilLocal(LocalBuilder local) + { + Index = local.LocalIndex; + Type = local.LocalType; + Name = null; + } + + internal MsilLocal(LocalVariableInfo local) + { + Index = local.LocalIndex; + Type = local.LocalType; + Name = null; + } + + /// + /// Creates an empty local reference with the given index. + /// + /// The local's index + public MsilLocal(int index) + { + Index = index; + Type = null; + Name = null; + } + } +} diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs index b247a54..2442fd9 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs @@ -209,11 +209,11 @@ namespace Torch.Managers.PatchManager.MSIL } /// - /// Inline parameter reference + /// Inline argument reference /// - public class MsilOperandParameter : MsilOperandInline + public class MsilOperandArgument : MsilOperandInline { - internal MsilOperandParameter(MsilInstruction instruction) : base(instruction) + internal MsilOperandArgument(MsilInstruction instruction) : base(instruction) { } @@ -225,20 +225,19 @@ namespace Torch.Managers.PatchManager.MSIL : reader.ReadUInt16(); if (paramID == 0 && !context.Method.IsStatic) throw new ArgumentException("Haven't figured out how to ldarg with the \"this\" argument"); - Value = context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)]; + Value = new MsilArgument(context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)]); } internal override void Emit(LoggingIlGenerator generator) { - var methodInfo = Value.Member as MethodBase; - generator.Emit(Instruction.OpCode, Value.Position + (methodInfo != null && methodInfo.IsStatic ? 0 : 1)); + generator.Emit(Instruction.OpCode, Value.Position); } } /// /// Inline local variable reference /// - public class MsilOperandLocal : MsilOperandInline + public class MsilOperandLocal : MsilOperandInline { internal MsilOperandLocal(MsilInstruction instruction) : base(instruction) { @@ -247,15 +246,15 @@ namespace Torch.Managers.PatchManager.MSIL internal override void Read(MethodContext context, BinaryReader reader) { Value = - context.Method.GetMethodBody().LocalVariables[ + new MsilLocal(context.Method.GetMethodBody().LocalVariables[ Instruction.OpCode.OperandType == OperandType.ShortInlineVar ? reader.ReadByte() - : reader.ReadUInt16()]; + : reader.ReadUInt16()]); } internal override void Emit(LoggingIlGenerator generator) { - generator.Emit(Instruction.OpCode, Value.LocalIndex); + generator.Emit(Instruction.OpCode, Value.Index); } } diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index 33faedb..e2db491 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -11,17 +11,32 @@ namespace Torch.Managers.PatchManager.Transpile { public static readonly Logger _log = LogManager.GetCurrentClassLogger(); - internal static void Transpile(MethodBase baseMethod, IEnumerable transpilers, LoggingIlGenerator output, Label? retLabel) + internal static void Transpile(MethodBase baseMethod, Func localCreator, IEnumerable transpilers, LoggingIlGenerator output, Label? retLabel) { var context = new MethodContext(baseMethod); context.Read(); context.CheckIntegrity(); -// _log.Trace("Input Method:"); -// _log.Trace(context.ToHumanMsil); + // _log.Trace("Input Method:"); + // _log.Trace(context.ToHumanMsil); var methodContent = (IEnumerable)context.Instructions; - foreach (var transpiler in transpilers) - methodContent = (IEnumerable)transpiler.Invoke(null, new object[] { methodContent }); + foreach (MethodInfo transpiler in transpilers) + { + var paramList = new List(); + foreach (var parameter in transpiler.GetParameters()) + { + if (parameter.Name.Equals("__methodBody")) + paramList.Add(baseMethod.GetMethodBody()); + else if (parameter.Name.Equals("__localCreator")) + paramList.Add(localCreator); + else if (parameter.ParameterType == typeof(IEnumerable)) + paramList.Add(methodContent); + else + throw new ArgumentException( + $"Bad transpiler parameter type {parameter.ParameterType.FullName} {parameter.Name}"); + } + methodContent = (IEnumerable)transpiler.Invoke(null, paramList.ToArray()); + } methodContent = FixBranchAndReturn(methodContent, retLabel); foreach (var k in methodContent) k.Emit(output); diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index db958da..5a33e03 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -165,7 +165,10 @@ + + + diff --git a/Torch/Utils/ReflectedManager.cs b/Torch/Utils/ReflectedManager.cs index 5976912..9687cf1 100644 --- a/Torch/Utils/ReflectedManager.cs +++ b/Torch/Utils/ReflectedManager.cs @@ -74,6 +74,16 @@ namespace Torch.Utils /// Expected parameters of this method, or null if any parameters are accepted. /// public Type[] Parameters { get; set; } = null; + + /// + /// Assembly qualified names of + /// + public string[] ParameterNames + { + get => Parameters.Select(x => x.AssemblyQualifiedName).ToArray(); + set => Parameters = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray(); + } + /// /// Expected return type of this method, or null if any return type is accepted. /// From 5eceb21ec73928e092fee6e48fea01c22307dde8 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Tue, 12 Sep 2017 01:44:13 -0700 Subject: [PATCH 15/19] Comments --- Torch/Managers/PatchManager/MSIL/MsilArgument.cs | 6 ++++++ Torch/Managers/PatchManager/MSIL/MsilLocal.cs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Torch/Managers/PatchManager/MSIL/MsilArgument.cs b/Torch/Managers/PatchManager/MSIL/MsilArgument.cs index 2e10b14..1246546 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilArgument.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilArgument.cs @@ -45,5 +45,11 @@ namespace Torch.Managers.PatchManager.MSIL Type = null; Name = null; } + + /// + public override string ToString() + { + return $"arg{Position:X4}({Type?.Name ?? "unknown"})"; + } } } diff --git a/Torch/Managers/PatchManager/MSIL/MsilLocal.cs b/Torch/Managers/PatchManager/MSIL/MsilLocal.cs index 8268aee..1db7eb1 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilLocal.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilLocal.cs @@ -52,5 +52,11 @@ namespace Torch.Managers.PatchManager.MSIL Type = null; Name = null; } + + /// + public override string ToString() + { + return $"lcl{Index:X4}({Type?.Name ?? "unknown"})"; + } } } From 96f813a17b6a415f7ee127501c5446f3b7140d5f Mon Sep 17 00:00:00 2001 From: Tomas Blaho Date: Wed, 20 Sep 2017 12:34:09 +0200 Subject: [PATCH 16/19] IMultiplayerManager: added list of banned steam ID's --- Torch.API/Managers/IMultiplayerManager.cs | 5 +++++ Torch/Managers/MultiplayerManager.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/Torch.API/Managers/IMultiplayerManager.cs b/Torch.API/Managers/IMultiplayerManager.cs index 509360d..2726cd2 100644 --- a/Torch.API/Managers/IMultiplayerManager.cs +++ b/Torch.API/Managers/IMultiplayerManager.cs @@ -33,6 +33,11 @@ namespace Torch.API.Managers /// event MessageReceivedDel MessageReceived; + /// + /// List of banned SteamID's + /// + List BannedPlayers { get; } + /// /// Send a chat message to all or one specific player. /// diff --git a/Torch/Managers/MultiplayerManager.cs b/Torch/Managers/MultiplayerManager.cs index ac1f09f..d4ee660 100644 --- a/Torch/Managers/MultiplayerManager.cs +++ b/Torch/Managers/MultiplayerManager.cs @@ -50,6 +50,7 @@ namespace Torch.Managers public IList ChatHistory { get; } = new ObservableList(); public ObservableDictionary Players { get; } = new ObservableDictionary(); + public List BannedPlayers => MySandboxGame.ConfigDedicated.Banned; public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer; private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager)); private static readonly Logger ChatLog = LogManager.GetLogger("Chat"); From d8e2072493c61a7d36dd4cecda83cab9e1a6d308 Mon Sep 17 00:00:00 2001 From: Tomas Blaho Date: Wed, 20 Sep 2017 13:03:06 +0200 Subject: [PATCH 17/19] documentation added --- Torch.API/Managers/IMultiplayerManager.cs | 2 +- Torch/Managers/MultiplayerManager.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Torch.API/Managers/IMultiplayerManager.cs b/Torch.API/Managers/IMultiplayerManager.cs index 2726cd2..4dd5f0f 100644 --- a/Torch.API/Managers/IMultiplayerManager.cs +++ b/Torch.API/Managers/IMultiplayerManager.cs @@ -34,7 +34,7 @@ namespace Torch.API.Managers event MessageReceivedDel MessageReceived; /// - /// List of banned SteamID's + /// List of the banned SteamID's /// List BannedPlayers { get; } diff --git a/Torch/Managers/MultiplayerManager.cs b/Torch/Managers/MultiplayerManager.cs index d4ee660..e187eb3 100644 --- a/Torch/Managers/MultiplayerManager.cs +++ b/Torch/Managers/MultiplayerManager.cs @@ -50,7 +50,12 @@ namespace Torch.Managers public IList ChatHistory { get; } = new ObservableList(); public ObservableDictionary Players { get; } = new ObservableDictionary(); + + /// + /// List of the banned SteamID's + /// public List BannedPlayers => MySandboxGame.ConfigDedicated.Banned; + public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer; private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager)); private static readonly Logger ChatLog = LogManager.GetLogger("Chat"); From eb7f7f42449eb5e310718ab7adcdbdc0acb3405d Mon Sep 17 00:00:00 2001 From: Tomas Blaho Date: Wed, 20 Sep 2017 17:05:23 +0200 Subject: [PATCH 18/19] MultiplayerManagerLobby doesn't implement IMultiplayerManagerServer --- Torch.Client/Manager/MultiplayerManagerLobby.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Torch.Client/Manager/MultiplayerManagerLobby.cs b/Torch.Client/Manager/MultiplayerManagerLobby.cs index 5ffd7e9..1013398 100644 --- a/Torch.Client/Manager/MultiplayerManagerLobby.cs +++ b/Torch.Client/Manager/MultiplayerManagerLobby.cs @@ -10,7 +10,7 @@ using Torch.Managers; namespace Torch.Client.Manager { - public class MultiplayerManagerLobby : MultiplayerManagerBase, IMultiplayerManagerServer + public class MultiplayerManagerLobby : MultiplayerManagerBase { /// public MultiplayerManagerLobby(ITorchBase torch) : base(torch) { } From 9c505c4f5de7ec1d064ce6137f29d16eefa6c4a2 Mon Sep 17 00:00:00 2001 From: Tomas Blaho Date: Wed, 20 Sep 2017 17:16:15 +0200 Subject: [PATCH 19/19] banned player list made readonly, lobby fakes support --- Torch.API/Managers/IMultiplayerManagerServer.cs | 2 +- Torch.Client/Manager/MultiplayerManagerLobby.cs | 8 +++++++- Torch.Server/Managers/MultiplayerManagerDedicated.cs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Torch.API/Managers/IMultiplayerManagerServer.cs b/Torch.API/Managers/IMultiplayerManagerServer.cs index 82c7f42..31dadcd 100644 --- a/Torch.API/Managers/IMultiplayerManagerServer.cs +++ b/Torch.API/Managers/IMultiplayerManagerServer.cs @@ -24,7 +24,7 @@ namespace Torch.API.Managers /// /// List of the banned SteamID's /// - List BannedPlayers { get; } + IReadOnlyList BannedPlayers { get; } /// /// Checks if the player with the given SteamID is banned. diff --git a/Torch.Client/Manager/MultiplayerManagerLobby.cs b/Torch.Client/Manager/MultiplayerManagerLobby.cs index 1013398..1ef3e3f 100644 --- a/Torch.Client/Manager/MultiplayerManagerLobby.cs +++ b/Torch.Client/Manager/MultiplayerManagerLobby.cs @@ -10,8 +10,11 @@ using Torch.Managers; namespace Torch.Client.Manager { - public class MultiplayerManagerLobby : MultiplayerManagerBase + public class MultiplayerManagerLobby : MultiplayerManagerBase, IMultiplayerManagerServer { + /// + public IReadOnlyList BannedPlayers => new List(); + /// public MultiplayerManagerLobby(ITorchBase torch) : base(torch) { } @@ -21,6 +24,9 @@ namespace Torch.Client.Manager /// public void BanPlayer(ulong steamId, bool banned = true) => Torch.Invoke(() => MyMultiplayer.Static.BanClient(steamId, banned)); + /// + public bool IsBanned(ulong steamId) => false; + /// public override void Attach() { diff --git a/Torch.Server/Managers/MultiplayerManagerDedicated.cs b/Torch.Server/Managers/MultiplayerManagerDedicated.cs index 3f0b1b8..d8427fc 100644 --- a/Torch.Server/Managers/MultiplayerManagerDedicated.cs +++ b/Torch.Server/Managers/MultiplayerManagerDedicated.cs @@ -32,7 +32,7 @@ namespace Torch.Server.Managers #pragma warning restore 649 /// - public List BannedPlayers => MySandboxGame.ConfigDedicated.Banned; + public IReadOnlyList BannedPlayers => MySandboxGame.ConfigDedicated.Banned; private Dictionary _gameOwnerIds = new Dictionary();