From 4a393627020a83664b154aba61229db92f51cda5 Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Sat, 19 Aug 2017 04:56:41 -0700 Subject: [PATCH] Stronger runtime checks in MultiplayerManager and Reflection MultiplayerManager now uses Keen's wrapper wrapper (wrapper?) of SteamSDK Removed rogue file in the csproj --- Torch/Managers/MultiplayerManager.cs | 240 ++++++++++----------- Torch/Reflection.cs | 312 +++++++++++++++++---------- Torch/Torch.csproj | 1 - 3 files changed, 322 insertions(+), 231 deletions(-) diff --git a/Torch/Managers/MultiplayerManager.cs b/Torch/Managers/MultiplayerManager.cs index bdd5d44..99c5417 100644 --- a/Torch/Managers/MultiplayerManager.cs +++ b/Torch/Managers/MultiplayerManager.cs @@ -16,6 +16,7 @@ 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; @@ -31,6 +32,7 @@ using VRage.Game.ModAPI; using VRage.GameServices; using VRage.Library.Collections; using VRage.Network; +using VRage.Steam; using VRage.Utils; namespace Torch.Managers @@ -61,7 +63,7 @@ namespace Torch.Managers internal MultiplayerManager(ITorchBase torch) : base(torch) { - + } /// @@ -166,11 +168,22 @@ namespace Torch.Managers ValidateOnlinePlayersList(); //TODO: Move these with the methods? - RemoveHandlers(); - SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse; - SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus; - _members = (List)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static); - _waitingForGroup = (HashSet)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static); + if (!RemoveHandlers()) + { + Log.Error("Steam auth failed to initialize"); + return; + } + MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse; + MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse; + _members = MyMultiplayer.Static.GetPrivateField>("m_members", true); + if (_members == null) + throw new InvalidOperationException("Unable to get m_members from MyMultiplayer.Static. Is this a dedicated server?"); + _waitingForGroup = MyMultiplayer.Static.GetPrivateField>("m_waitingForGroup", true); + if (_waitingForGroup == null) + throw new InvalidOperationException("Unable to get m_waitingForGroup from MyMultiplayer.Static. Is this a dedicated server?"); + _kickedClients = MyMultiplayer.Static.GetPrivateField>("m_kickedClients", true); + if (_kickedClients == null) + throw new InvalidOperationException("Unable to get m_kickedClients from MyMultiplayer.Static. Is this a dedicated server?"); Log.Info("Steam auth initialized"); } @@ -194,6 +207,7 @@ namespace Torch.Managers //This lets us have a server set to private (admins only) or friends (friends of all listed admins) private List _members; private HashSet _waitingForGroup; + private Dictionary _kickedClients; //private HashSet _waitingForFriends; private Dictionary _gameOwnerIds = new Dictionary(); //private IMultiplayer _multiplayerImplementation; @@ -201,149 +215,116 @@ namespace Torch.Managers /// /// Removes Keen's hooks into some Steam events so we have full control over client authentication /// - private static void RemoveHandlers() + private static bool RemoveHandlers() { - var eventField = typeof(GameServer).GetField("" + nameof(SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse), BindingFlags.NonPublic | BindingFlags.Instance); - if (eventField?.GetValue(SteamServerAPI.Instance.GameServer) is MulticastDelegate eventDel) + MethodInfo methodValidateAuthTicket = typeof(MyDedicatedServerBase).GetMethod("GameServer_ValidateAuthTicketResponse", + BindingFlags.NonPublic | BindingFlags.Instance); + if (methodValidateAuthTicket == null) { - foreach (var handle in eventDel.GetInvocationList()) - { - if (handle.Method.Name == "GameServer_ValidateAuthTicketResponse") - { - SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse -= - handle as ValidateAuthTicketResponse; - Log.Debug("Removed GameServer_ValidateAuthTicketResponse"); - } - } + Log.Error("Unable to find the GameServer_ValidateAuthTicketResponse method to unhook"); + return false; } - else - Log.Warn("Unable to unhook GameServer_ValidateAuthTicketResponse from the ValidateAuthTicketResponse event"); - - eventField = typeof(GameServer).GetField("" + nameof(SteamServerAPI.Instance.GameServer.UserGroupStatus), BindingFlags.NonPublic | BindingFlags.Instance); - eventDel = eventField?.GetValue(SteamServerAPI.Instance.GameServer) as MulticastDelegate; - if (eventDel != null) + var eventValidateAuthTicket = Reflection.GetEvents(MyGameService.GameServer, nameof(MyGameService.GameServer.ValidateAuthTicketResponse)) + .FirstOrDefault(x => x.Method == methodValidateAuthTicket) as Action; + if (eventValidateAuthTicket == null) { - foreach (var handle in eventDel.GetInvocationList()) - { - if (handle.Method.Name == "GameServer_UserGroupStatus") - { - SteamServerAPI.Instance.GameServer.UserGroupStatus -= handle as UserGroupStatus; - Log.Debug("Removed GameServer_UserGroupStatus"); - } - } - } else - Log.Warn("Unable to unhook GameServer_UserGroupStatus from the UserGroupStatusResponse event"); + 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.GetEvents(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, AuthSessionResponseEnum response, ulong ownerSteamID) + private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner) { - Log.Info($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}"); - - if (steamID != ownerSteamID) + Log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}"); + if (IsClientBanned(steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner)) { - Log.Info($"User {steamID} is using a game owned by {ownerSteamID}. Tracking..."); - _gameOwnerIds[steamID] = ownerSteamID; - - if (MySandboxGame.ConfigDedicated.Banned.Contains(ownerSteamID)) - { - Log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}..."); - UserRejected(steamID, JoinResult.BannedByAdmins); - BanPlayer(steamID); - } + this.UserRejected(steamID, JoinResult.BannedByAdmins); + RaiseClientKicked(steamID); } - - if (response == AuthSessionResponseEnum.OK) + else if (IsClientKicked(steamOwner)) { - if (MySession.Static.MaxPlayers > 0 && _members.Count - 1 >= MySession.Static.MaxPlayers) - { - UserRejected(steamID, JoinResult.ServerFull); - } - else if (MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) /*|| MySandboxGame.ConfigDedicated.Administrators.Contains(MyDedicatedServerBase.ConvertSteamIDFrom64(steamID))*/) - { - UserAccepted(steamID); - } - else if (MySandboxGame.ConfigDedicated.GroupID == 0) - { - switch (MySession.Static.OnlineMode) - { - case MyOnlineModeEnum.PUBLIC: - UserAccepted(steamID); - break; - case MyOnlineModeEnum.PRIVATE: - UserRejected(steamID, JoinResult.NotInGroup); - break; - case MyOnlineModeEnum.FRIENDS: - //TODO: actually verify friendship - UserRejected(steamID, JoinResult.NotInGroup); - break; - } - } - else if (SteamServerAPI.Instance.GetAccountType(MySandboxGame.ConfigDedicated.GroupID) != AccountType.Clan) - { - UserRejected(steamID, JoinResult.GroupIdInvalid); - } - else if (SteamServerAPI.Instance.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID)) - { - // Returns false when there's no connection to Steam - _waitingForGroup.Add(steamID); - } - else - { - UserRejected(steamID, JoinResult.SteamServersOffline); - } + UserRejected(steamID, JoinResult.KickedRecently); + RaiseClientKicked(steamID); } - else + if (response != JoinResult.OK) { - JoinResult joinResult = JoinResult.TicketInvalid; - switch (response) - { - case AuthSessionResponseEnum.AuthTicketCanceled: - joinResult = JoinResult.TicketCanceled; - break; - case AuthSessionResponseEnum.AuthTicketInvalidAlreadyUsed: - joinResult = JoinResult.TicketAlreadyUsed; - break; - case AuthSessionResponseEnum.LoggedInElseWhere: - joinResult = JoinResult.LoggedInElseWhere; - break; - case AuthSessionResponseEnum.NoLicenseOrExpired: - joinResult = JoinResult.NoLicenseOrExpired; - break; - case AuthSessionResponseEnum.UserNotConnectedToSteam: - joinResult = JoinResult.UserNotConnected; - break; - case AuthSessionResponseEnum.VACBanned: - joinResult = JoinResult.VACBanned; - break; - case AuthSessionResponseEnum.VACCheckTimedOut: - joinResult = JoinResult.VACCheckTimedOut; - break; - } - - UserRejected(steamID, joinResult); + UserRejected(steamID, response); + return; } + if (MyMultiplayer.Static.MemberLimit > 0 && this._members.Count - 1 >= MyMultiplayer.Static.MemberLimit) + { + UserRejected(steamID, JoinResult.ServerFull); + return; + } + if (MySandboxGame.ConfigDedicated.GroupID == 0uL || + MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) || + MySandboxGame.ConfigDedicated.Administrators.Contains((string)Reflection.InvokeStaticMethod(typeof(MyDedicatedServerBase), "ConvertSteamIDFrom64", steamID))) + { + this.UserAccepted(steamID); + return; + } + if ((MyGameServiceAccountType)Reflection.InvokeStaticMethod(typeof(MyGameService), "GetServerAccountType", MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan) + { + this.UserRejected(steamID, JoinResult.GroupIdInvalid); + return; + } + if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID)) + { + this._waitingForGroup.Add(steamID); + return; + } + this.UserRejected(steamID, JoinResult.SteamServersOffline); } - private void UserGroupStatus(ulong userId, ulong groupId, bool member, bool officer) + private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer) { if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Remove(userId)) { if (member || officer) - { UserAccepted(userId); - } else - { UserRejected(userId, JoinResult.NotInGroup); - } } } private void UserAccepted(ulong steamId) { - typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId}); - var vm = new PlayerViewModel(steamId) {State = ConnectionState.Connected}; + Reflection.InvokePrivateMethod(MyMultiplayer.Static, "UserAccepted", steamId); + + var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected }; Log.Info($"Player {vm.Name} joined ({vm.SteamId})"); Players.Add(steamId, vm); PlayerJoined?.Invoke(vm); @@ -351,7 +332,22 @@ namespace Torch.Managers private void UserRejected(ulong steamId, JoinResult reason) { - typeof(MyDedicatedServerBase).GetMethod("UserRejected", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId, reason}); + Reflection.InvokePrivateMethod(MyMultiplayer.Static, "UserRejected", steamId, reason); + } + + private bool IsClientBanned(ulong steamId) + { + return (bool)Reflection.InvokePrivateMethod(MyMultiplayer.Static, "IsClientBanned", steamId); + } + + private bool IsClientKicked(ulong steamId) + { + return (bool)Reflection.InvokePrivateMethod(MyMultiplayer.Static, "IsClientKicked", steamId); + } + + private void RaiseClientKicked(ulong steamId) + { + Reflection.InvokePrivateMethod(MyMultiplayer.Static, "RaiseClientKicked", steamId); } } } diff --git a/Torch/Reflection.cs b/Torch/Reflection.cs index 4f8b62d..9c523b8 100644 --- a/Torch/Reflection.cs +++ b/Torch/Reflection.cs @@ -5,127 +5,223 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; using NLog; +using SteamSDK; namespace Torch { - public static class Reflection - { - private static readonly Logger Log = LogManager.GetLogger("Reflection"); + public static class Reflection + { + private static readonly Logger Log = LogManager.GetLogger("Reflection"); - public static bool HasMethod(Type type, string methodName, Type[] argTypes = null) - { - try - { - if (string.IsNullOrEmpty(methodName)) - return false; + public static bool HasMethod(Type type, string methodName, Type[] argTypes = null) + { + try + { + if (string.IsNullOrEmpty(methodName)) + return false; - if (argTypes == null) - { - var methodInfo = type.GetMethod(methodName); - if (methodInfo == null) - methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); - if (methodInfo == null && type.BaseType != null) - methodInfo = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); - if (methodInfo == null) - { - Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "'" ); - return false; - } - } - else - { - MethodInfo method = type.GetMethod(methodName, argTypes); - if (method == null) - method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null); - if (method == null && type.BaseType != null) - method = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null); - if (method == null) - { - Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "'" ); - return false; - } - } + if (argTypes == null) + { + var methodInfo = type.GetMethod(methodName); + if (methodInfo == null) + methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (methodInfo == null && type.BaseType != null) + methodInfo = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (methodInfo == null) + { + Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "'"); + return false; + } + } + else + { + MethodInfo method = type.GetMethod(methodName, argTypes); + if (method == null) + method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null); + if (method == null && type.BaseType != null) + method = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null); + if (method == null) + { + Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "'"); + return false; + } + } - return true; - } - catch (AmbiguousMatchException) - { - return true; - } - catch (Exception ex) - { - Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "': " + ex.Message ); - Log.Error( ex ); - return false; - } - } + return true; + } + catch (AmbiguousMatchException) + { + return true; + } + catch (Exception ex) + { + Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "': " + ex.Message); + Log.Error(ex); + return false; + } + } - public static bool HasField(Type type, string fieldName) - { - try - { - if (string.IsNullOrEmpty(fieldName)) - return false; - var field = type.GetField(fieldName); - if (field == null) - field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); - if (field == null) - field = type.BaseType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); - if (field == null) - { - Log.Error("Failed to find field '{0}' in type '{1}'", fieldName, type.FullName); - return false; - } - return true; - } - catch (Exception ex) - { + public static bool HasField(Type type, string fieldName) + { + try + { + if (string.IsNullOrEmpty(fieldName)) + return false; + var field = type.GetField(fieldName); + if (field == null) + field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (field == null) + field = type.BaseType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (field == null) + { + Log.Error("Failed to find field '{0}' in type '{1}'", fieldName, type.FullName); + return false; + } + return true; + } + catch (Exception ex) + { Log.Error(ex, "Failed to find field '{0}' in type '{1}'", fieldName, type.FullName); - return false; - } - } + return false; + } + } - public static bool HasProperty(Type type, string propertyName) - { - try - { - if (string.IsNullOrEmpty(propertyName)) - return false; - var prop = type.GetProperty(propertyName); - if (prop == null) - prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); - if (prop == null) - prop = type.BaseType?.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); - if (prop == null) - { - Log.Error("Failed to find property '{0}' in type '{1}'", propertyName, type.FullName); - return false; - } - return true; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to find property '{0}' in type '{1}'", propertyName, type.FullName); - return false; - } - } + public static bool HasProperty(Type type, string propertyName) + { + try + { + if (string.IsNullOrEmpty(propertyName)) + return false; + var prop = type.GetProperty(propertyName); + if (prop == null) + prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (prop == null) + prop = type.BaseType?.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (prop == null) + { + Log.Error("Failed to find property '{0}' in type '{1}'", propertyName, type.FullName); + return false; + } + return true; + } + catch (Exception ex) + { + Log.Error(ex, "Failed to find property '{0}' in type '{1}'", propertyName, type.FullName); + return false; + } + } - public static object InvokeStaticMethod(Type type, string methodName, params object[] args) - { - var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (method == null) - { + /// + /// Invokes the static method of the given type, with the given arguments. + /// + /// Type the method is contained in + /// Method name + /// Arguments to the method + /// return value of the invoked method, or null if it failed + public static object InvokeStaticMethod(Type type, string methodName, params object[] args) + { + var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (method == null) + { Log.Error($"Method {methodName} not found in static class {type.FullName}"); return null; } - return method.Invoke(null, args); - } + return method.Invoke(null, args); + } - public static T GetPrivateField(this object obj, string fieldName) - { - var type = obj.GetType(); - return (T)type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); - } - } + /// + /// Invokes the private method with the given arguments on the instance. Includes base types of instance. + /// + /// + /// + /// + /// the return value of the method, or null if it failed + public static object InvokePrivateMethod(object instance, string methodName, params object[] args) + { + Type type = instance.GetType(); + while (type != null) + { + MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (method != null) + return method.Invoke(instance, args); + type = type.BaseType; + } + + Log.Error($"Method {methodName} not found in type {instance.GetType().FullName} or its parents"); + return null; + } + + /// + /// Gets the value of a private field in an instance. + /// + /// The type of the private field + /// The instance + /// Field name + /// Should the base types be examined + /// + public static T GetPrivateField(this object obj, string fieldName, bool recurse = false) + { + var type = obj.GetType(); + while (type != null) + { + FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + if (field != null) + return (T)field + .GetValue(obj); + + if (!recurse) + break; + type = type.BaseType; + } + Log.Error($"Field {fieldName} not found in type {obj.GetType().FullName}" + (recurse ? " or its parents" : "")); + return default(T); + } + + /// + /// Gets the list of all delegates registered in the named static event + /// + /// The type (or child type) that contains the event + /// Name of the event + /// All delegates registered with the event + public static IEnumerable GetStaticEvent(Type type, string eventName) + { + return GetEventsInternal(null, eventName, type); + } + + /// + /// Gets the list of all delegates registered in the named event + /// + /// Instance to retrieve the event list for + /// Name of the event + /// All delegates registered with the event + public static IEnumerable GetInstanceEvent(object instance, string eventName) + { + return GetEventsInternal(instance, eventName); + } + + private static readonly string[] _backingFieldForEvent = { "{0}", "{0}" }; + private static IEnumerable GetEventsInternal(object instance, string eventName, Type baseType = null) + { + BindingFlags bindingFlags = BindingFlags.NonPublic | + (instance == null ? BindingFlags.Static : BindingFlags.Instance); + + FieldInfo eventField = null; + baseType = baseType ?? instance?.GetType(); + 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), bindingFlags); + type = type.BaseType; + } + if (eventField?.GetValue(instance) is MulticastDelegate eventDel) + { + foreach (Delegate handle in eventDel.GetInvocationList()) + yield return handle; + } + else + Log.Error($"{eventName} doesn't have a backing store in {baseType} or its parents."); + } + } } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index ab66bc4..a00c97e 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -181,7 +181,6 @@ -