From 17100cdfabfb445a58d8aacf5e5fe5a4c714b1fa Mon Sep 17 00:00:00 2001 From: Denis Pakhorukov Date: Tue, 8 Aug 2023 12:01:55 +0300 Subject: [PATCH] #204 Disable patches for objects with inner synchronized events (#205) --- .../Core/Patch/PatchTargetResolver.cs | 143 ++++++++++++ .../Game/Mechanics/MethodPatchesDisabler.cs | 30 +++ .../Minions/MinionIdentitySpawnPatch.cs | 21 -- .../MinionStartingStatsDeliveryPatch.cs | 21 -- .../Game/Mechanics/Objects/ObjectEvents.cs | 217 ++++++++---------- .../Game/Mechanics/Objects/TargetExtractor.cs | 103 --------- 6 files changed, 269 insertions(+), 266 deletions(-) create mode 100644 MultiplayerMod/Core/Patch/PatchTargetResolver.cs create mode 100644 MultiplayerMod/Game/Mechanics/MethodPatchesDisabler.cs delete mode 100644 MultiplayerMod/Game/Mechanics/Minions/MinionIdentitySpawnPatch.cs delete mode 100644 MultiplayerMod/Game/Mechanics/Minions/MinionStartingStatsDeliveryPatch.cs delete mode 100644 MultiplayerMod/Game/Mechanics/Objects/TargetExtractor.cs diff --git a/MultiplayerMod/Core/Patch/PatchTargetResolver.cs b/MultiplayerMod/Core/Patch/PatchTargetResolver.cs new file mode 100644 index 00000000..f1c0b761 --- /dev/null +++ b/MultiplayerMod/Core/Patch/PatchTargetResolver.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MultiplayerMod.Core.Logging; + +namespace MultiplayerMod.Core.Patch; + +public class PatchTargetResolver { + + private static readonly Logging.Logger log = LoggerFactory.GetLogger(); + + private readonly Dictionary> targets; + private readonly IEnumerable baseTypes; + private readonly Assembly assembly = Assembly.GetAssembly(typeof(global::Game)); + + public PatchTargetResolver(Dictionary> targets, IEnumerable baseTypes) { + this.targets = targets; + this.baseTypes = baseTypes; + } + + public IEnumerable Resolve() { + var classTypes = targets.Keys.Where(type => type.IsClass).ToList(); + var interfaceTypes = targets.Keys.Where(type => type.IsInterface).ToList(); + return assembly.GetTypes() + .Where( + type => type.IsClass && (classTypes.Contains(type) + || interfaceTypes.Any(interfaceType => interfaceType.IsAssignableFrom(type))) + ) + .Where( + type => { + if (!baseTypes.Any()) + return true; + + var assignable = baseTypes.Any(it => it.IsAssignableFrom(type)); + if (!assignable) + log.Warning( + $"{type} can not be assigned to any of " + + $"{string.Join(", ", baseTypes.Select(it => it.Name))}." + ); + return assignable; + } + ) + .SelectMany( + type => { + if (classTypes.Contains(type)) + return targets[type].Select(methodName => GetMethodOrSetter(type, methodName, null)); + + var implementedInterfaces = GetImplementedInterfaces(interfaceTypes, type); + return implementedInterfaces.SelectMany( + implementedInterface => targets[implementedInterface].Select( + methodName => GetMethodOrSetter(type, methodName, implementedInterface) + ) + ); + } + ).ToList(); + } + + private MethodBase GetMethodOrSetter(Type type, string methodName, Type? interfaceType) { + var methodInfo = GetMethod(type, methodName, interfaceType); + if (methodInfo != null) + return methodInfo; + + var property = GetSetter(type, methodName, interfaceType); + if (property != null) + return property; + + var message = $"Method {type}.{methodName} ({interfaceType}) not found"; + log.Error(message); + throw new Exception(message); + } + + private MethodBase? GetMethod(Type type, string methodName, Type? interfaceType) { + var methodInfo = type.GetMethod( + methodName, + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance + ); + if (methodInfo != null) + return methodInfo; + + if (interfaceType == null) + return null; + + // Some overrides names prefixed by interface e.g. Clinic#ISliderControl.SetSliderValue + methodInfo = type.GetMethod( + interfaceType.Name + "." + methodName, + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance + ); + return methodInfo; + } + + private MethodBase? GetSetter(Type type, string propertyName, Type? interfaceType) { + var property = type.GetProperty( + propertyName, + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance + ); + if (property != null) + return property.GetSetMethod(true); + + if (interfaceType == null) + return null; + + // Some overrides names prefixed by interface e.g. Clinic#ISliderControl.SetSliderValue + property = type.GetProperty( + interfaceType.Name + "." + propertyName, + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance + ); + return property?.GetSetMethod(true); + } + + private List GetImplementedInterfaces(IEnumerable interfaceTypes, Type type) => interfaceTypes + .Where(interfaceType => interfaceType.IsAssignableFrom(type)) + .ToList(); + + public class Builder { + + private readonly Dictionary> targets = new(); + private readonly List baseTypes = new(); + + private List GetTargets(Type type) { + if (targets.TryGetValue(type, out var methods)) + return methods; + + methods = new List(); + targets[type] = methods; + return methods; + } + + public Builder AddMethods(Type type, params string[] methods) { + GetTargets(type).AddRange(methods); + return this; + } + + public Builder AddBaseType(Type type) { + baseTypes.Add(type); + return this; + } + + public PatchTargetResolver Build() => new(targets, baseTypes); + + } + +} diff --git a/MultiplayerMod/Game/Mechanics/MethodPatchesDisabler.cs b/MultiplayerMod/Game/Mechanics/MethodPatchesDisabler.cs new file mode 100644 index 00000000..fff3a714 --- /dev/null +++ b/MultiplayerMod/Game/Mechanics/MethodPatchesDisabler.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using MultiplayerMod.Core.Patch; +using MultiplayerMod.Core.Patch.Context; + +namespace MultiplayerMod.Game.Mechanics; + +[HarmonyPatch] +// ReSharper disable once UnusedType.Global +public static class MethodPatchesDisabler { + + private static readonly PatchTargetResolver targets = new PatchTargetResolver.Builder() + .AddMethods(typeof(MinionIdentity), nameof(MinionIdentity.OnSpawn)) + .AddMethods(typeof(MinionStartingStats), nameof(MinionStartingStats.Deliver)) + .Build(); + + // ReSharper disable once UnusedMember.Local + [HarmonyTargetMethods] + private static IEnumerable TargetMethods() => targets.Resolve(); + + // ReSharper disable once UnusedMember.Local + [HarmonyPrefix] + private static void BeforeMethod() => PatchContext.Enter(PatchControl.DisablePatches); + + // ReSharper disable once UnusedMember.Local + [HarmonyPostfix] + private static void AfterMethod() => PatchContext.Leave(); + +} diff --git a/MultiplayerMod/Game/Mechanics/Minions/MinionIdentitySpawnPatch.cs b/MultiplayerMod/Game/Mechanics/Minions/MinionIdentitySpawnPatch.cs deleted file mode 100644 index f6a5e4c0..00000000 --- a/MultiplayerMod/Game/Mechanics/Minions/MinionIdentitySpawnPatch.cs +++ /dev/null @@ -1,21 +0,0 @@ -using HarmonyLib; -using MultiplayerMod.Core.Patch; -using MultiplayerMod.Core.Patch.Context; - -namespace MultiplayerMod.Game.Mechanics.Minions; - -[HarmonyPatch(typeof(MinionIdentity))] -// ReSharper disable once UnusedType.Global -public static class MinionIdentitySpawnPatch { - - // ReSharper disable once UnusedMember.Local - [HarmonyPrefix] - [HarmonyPatch(nameof(MinionIdentity.OnSpawn))] - private static void BeforeSpawn() => PatchContext.Enter(PatchControl.DisablePatches); - - // ReSharper disable once UnusedMember.Local - [HarmonyPostfix] - [HarmonyPatch(nameof(MinionIdentity.OnSpawn))] - private static void AfterSpawn() => PatchContext.Leave(); - -} diff --git a/MultiplayerMod/Game/Mechanics/Minions/MinionStartingStatsDeliveryPatch.cs b/MultiplayerMod/Game/Mechanics/Minions/MinionStartingStatsDeliveryPatch.cs deleted file mode 100644 index b2ad1c21..00000000 --- a/MultiplayerMod/Game/Mechanics/Minions/MinionStartingStatsDeliveryPatch.cs +++ /dev/null @@ -1,21 +0,0 @@ -using HarmonyLib; -using MultiplayerMod.Core.Patch; -using MultiplayerMod.Core.Patch.Context; - -namespace MultiplayerMod.Game.Mechanics.Minions; - -[HarmonyPatch(typeof(MinionStartingStats))] -// ReSharper disable once UnusedType.Global -public static class MinionStartingStatsDeliveryPatch { - - // ReSharper disable once UnusedMember.Local - [HarmonyPrefix] - [HarmonyPatch(nameof(MinionStartingStats.Deliver))] - private static void BeforeDelivery() => PatchContext.Enter(PatchControl.DisablePatches); - - // ReSharper disable once UnusedMember.Local - [HarmonyPostfix] - [HarmonyPatch(nameof(MinionStartingStats.Deliver))] - private static void AfterDelivery() => PatchContext.Leave(); - -} diff --git a/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs b/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs index 7f05e92b..6177715b 100644 --- a/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs +++ b/MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs @@ -4,6 +4,7 @@ using System.Reflection; using HarmonyLib; using MultiplayerMod.Core.Patch; +using MultiplayerMod.Core.Patch.Context; using MultiplayerMod.Multiplayer.Objects; using UnityEngine; @@ -15,146 +16,119 @@ public static class ObjectEvents { public static event Action? ComponentMethodCalled; public static event Action? StateMachineMethodCalled; - private static readonly Dictionary methodsForPatch = new() { - { - typeof(Filterable), - new[] { nameof(Filterable.SelectedTag) } - }, { + private static readonly PatchTargetResolver targets = new PatchTargetResolver.Builder() + .AddMethods(typeof(Filterable), nameof(Filterable.SelectedTag)) + .AddMethods( typeof(TreeFilterable), - new[] { nameof(TreeFilterable.AddTagToFilter), nameof(TreeFilterable.RemoveTagFromFilter) } - }, - { typeof(Storage), new[] { nameof(Storage.SetOnlyFetchMarkedItems) } }, { - typeof(Door), - new[] { nameof(Door.QueueStateChange), nameof(Door.OrderUnseal) } - }, { + nameof(TreeFilterable.AddTagToFilter), + nameof(TreeFilterable.RemoveTagFromFilter) + ) + .AddMethods(typeof(Storage), nameof(Storage.SetOnlyFetchMarkedItems)) + .AddMethods(typeof(Door), nameof(Door.QueueStateChange), nameof(Door.OrderUnseal)) + .AddMethods( typeof(ComplexFabricator), - new[] { - nameof(ComplexFabricator.IncrementRecipeQueueCount), - nameof(ComplexFabricator.DecrementRecipeQueueCount), - nameof(ComplexFabricator.SetRecipeQueueCount) - } - }, { - typeof(PassengerRocketModule), - new[] { nameof(PassengerRocketModule.RequestCrewBoard) } - }, { - typeof(RocketControlStation), - new[] { nameof(RocketControlStation.RestrictWhenGrounded) } - }, { - typeof(ICheckboxControl), - new[] { nameof(ICheckboxControl.SetCheckboxValue) } - }, { - typeof(SuitLocker), - new[] { nameof(SuitLocker.ConfigNoSuit), nameof(SuitLocker.ConfigRequestSuit) } - }, { + nameof(ComplexFabricator.IncrementRecipeQueueCount), + nameof(ComplexFabricator.DecrementRecipeQueueCount), + nameof(ComplexFabricator.SetRecipeQueueCount) + ) + .AddMethods(typeof(PassengerRocketModule), nameof(PassengerRocketModule.RequestCrewBoard)) + .AddMethods(typeof(RocketControlStation), nameof(RocketControlStation.RestrictWhenGrounded)) + .AddMethods(typeof(ICheckboxControl), nameof(ICheckboxControl.SetCheckboxValue)) + .AddMethods(typeof(SuitLocker), nameof(SuitLocker.ConfigNoSuit), nameof(SuitLocker.ConfigRequestSuit)) + .AddMethods( typeof(IThresholdSwitch), - new[] { nameof(IThresholdSwitch.Threshold), nameof(IThresholdSwitch.ActivateAboveThreshold) } - }, { - typeof(ISliderControl), - new[] { nameof(ISingleSliderControl.SetSliderValue) } - }, { - typeof(Valve), - new[] { nameof(Valve.ChangeFlow) } - }, { + nameof(IThresholdSwitch.Threshold), + nameof(IThresholdSwitch.ActivateAboveThreshold) + ) + .AddMethods(typeof(ISliderControl), nameof(ISingleSliderControl.SetSliderValue)) + .AddMethods(typeof(Valve), nameof(Valve.ChangeFlow)) + .AddMethods( typeof(SingleEntityReceptacle), - new[] { - nameof(SingleEntityReceptacle.OrderRemoveOccupant), - nameof(SingleEntityReceptacle.CancelActiveRequest), - nameof(SingleEntityReceptacle.CreateOrder), - nameof(SingleEntityReceptacle.SetPreview) - } - }, { - typeof(LimitValve), - new[] { nameof(LimitValve.Limit), nameof(LimitValve.ResetAmount) } - }, { + nameof(SingleEntityReceptacle.OrderRemoveOccupant), + nameof(SingleEntityReceptacle.CancelActiveRequest), + nameof(SingleEntityReceptacle.CreateOrder), + nameof(SingleEntityReceptacle.SetPreview) + ) + .AddMethods(typeof(LimitValve), nameof(LimitValve.Limit), nameof(LimitValve.ResetAmount)) + .AddMethods( typeof(ILogicRibbonBitSelector), - new[] { nameof(ILogicRibbonBitSelector.SetBitSelection), nameof(ILogicRibbonBitSelector.UpdateVisuals) } - }, { - typeof(CreatureLure), - new[] { nameof(CreatureLure.ChangeBaitSetting) } - }, { - typeof(MonumentPart), - new[] { nameof(MonumentPart.SetState) } - }, { - typeof(INToggleSideScreenControl), - new[] { nameof(INToggleSideScreenControl.QueueSelectedOption) } - }, { - typeof(Artable), - new[] { nameof(Artable.SetUserChosenTargetState), nameof(Artable.SetDefault) } - }, { - typeof(Automatable), - new[] { nameof(Automatable.SetAutomationOnly) } - }, { + nameof(ILogicRibbonBitSelector.SetBitSelection), + nameof(ILogicRibbonBitSelector.UpdateVisuals) + ) + .AddMethods(typeof(CreatureLure), nameof(CreatureLure.ChangeBaitSetting)) + .AddMethods(typeof(MonumentPart), nameof(MonumentPart.SetState)) + .AddMethods(typeof(INToggleSideScreenControl), nameof(INToggleSideScreenControl.QueueSelectedOption)) + .AddMethods(typeof(Artable), nameof(Artable.SetUserChosenTargetState), nameof(Artable.SetDefault)) + .AddMethods(typeof(Automatable), nameof(Automatable.SetAutomationOnly)) + .AddMethods( typeof(IDispenser), - new[] { - nameof(IDispenser.OnCancelDispense), - nameof(IDispenser.OnOrderDispense), - nameof(IDispenser.SelectItem) - } - }, { - typeof(FlatTagFilterable), - new[] { nameof(FlatTagFilterable.ToggleTag) } - }, { - typeof(GeneShuffler), - new[] { nameof(GeneShuffler.RequestRecharge) } - }, { + nameof(IDispenser.OnCancelDispense), + nameof(IDispenser.OnOrderDispense), + nameof(IDispenser.SelectItem) + ) + .AddMethods(typeof(FlatTagFilterable), nameof(FlatTagFilterable.ToggleTag)) + .AddMethods(typeof(GeneShuffler), nameof(GeneShuffler.RequestRecharge)) + .AddMethods( typeof(GeneticAnalysisStation.StatesInstance), - new[] { nameof(GeneticAnalysisStation.StatesInstance.SetSeedForbidden) } - }, { - typeof(IHighEnergyParticleDirection), - new[] { nameof(IHighEnergyParticleDirection.Direction) } - }, { + nameof(GeneticAnalysisStation.StatesInstance.SetSeedForbidden) + ) + .AddMethods(typeof(IHighEnergyParticleDirection), nameof(IHighEnergyParticleDirection.Direction)) + .AddMethods( typeof(CraftModuleInterface), - new[] { nameof(CraftModuleInterface.CancelLaunch), nameof(CraftModuleInterface.TriggerLaunch) } - }, { + nameof(CraftModuleInterface.CancelLaunch), + nameof(CraftModuleInterface.TriggerLaunch) + ) + .AddMethods( typeof(IActivationRangeTarget), - new[] { nameof(IActivationRangeTarget.ActivateValue), nameof(IActivationRangeTarget.DeactivateValue) } - }, { - typeof(ISidescreenButtonControl), - new[] { nameof(ISidescreenButtonControl.OnSidescreenButtonPressed) } - }, { - typeof(IUserControlledCapacity), - new[] { nameof(IUserControlledCapacity.UserMaxCapacity) } - }, - { typeof(Assignable), new[] { nameof(Assignable.Assign), nameof(Assignable.Unassign) } }, { + nameof(IActivationRangeTarget.ActivateValue), + nameof(IActivationRangeTarget.DeactivateValue) + ) + .AddMethods(typeof(ISidescreenButtonControl), nameof(ISidescreenButtonControl.OnSidescreenButtonPressed)) + .AddMethods(typeof(IUserControlledCapacity), nameof(IUserControlledCapacity.UserMaxCapacity)) + .AddMethods(typeof(Assignable), nameof(Assignable.Assign), nameof(Assignable.Unassign)).AddMethods( typeof(AccessControl), - new[] { - nameof(AccessControl.SetPermission), - nameof(AccessControl.ClearPermission), - nameof(AccessControl.DefaultPermission) - } - }, - { typeof(LogicBroadcastReceiver), new[] { nameof(LogicBroadcastReceiver.SetChannel) } }, - { typeof(LaunchConditionManager), new[] { nameof(LaunchConditionManager.Launch) } }, - { typeof(GeoTuner.Instance), new[] { nameof(GeoTuner.Instance.AssignFutureGeyser) } }, - { typeof(IConfigurableConsumer), new[] { nameof(IConfigurableConsumer.SetSelectedOption) } }, - { typeof(LogicTimerSensor), new[] { nameof(LogicTimerSensor.ResetTimer) } }, { + nameof(AccessControl.SetPermission), + nameof(AccessControl.ClearPermission), + nameof(AccessControl.DefaultPermission) + ) + .AddMethods(typeof(LogicBroadcastReceiver), nameof(LogicBroadcastReceiver.SetChannel)) + .AddMethods(typeof(LaunchConditionManager), nameof(LaunchConditionManager.Launch)) + .AddMethods(typeof(GeoTuner.Instance), nameof(GeoTuner.Instance.AssignFutureGeyser)) + .AddMethods(typeof(IConfigurableConsumer), nameof(IConfigurableConsumer.SetSelectedOption)) + .AddMethods(typeof(LogicTimerSensor), nameof(LogicTimerSensor.ResetTimer)) + .AddMethods( typeof(IEmptyableCargo), - new[] { - nameof(IEmptyableCargo.AutoDeploy), - nameof(IEmptyableCargo.EmptyCargo), - nameof(IEmptyableCargo.ChosenDuplicant) - } - }, { + nameof(IEmptyableCargo.AutoDeploy), + nameof(IEmptyableCargo.EmptyCargo), + nameof(IEmptyableCargo.ChosenDuplicant) + ) + .AddMethods( typeof(IPlayerControlledToggle), - new[] { nameof(IPlayerControlledToggle.ToggleRequested), nameof(IPlayerControlledToggle.ToggledByPlayer) } - } + nameof(IPlayerControlledToggle.ToggleRequested), + nameof(IPlayerControlledToggle.ToggledByPlayer) + ) // TODO decide how to proper patch KMonoBehaviour#Trigger - // { + // .AddMethods( // typeof(ReorderableBuilding), - // new[] { - // nameof(ReorderableBuilding.SwapWithAbove), - // nameof(ReorderableBuilding.SwapWithBelow), - // nameof(ReorderableBuilding.Trigger) - // } - // } - }; + // nameof(ReorderableBuilding.SwapWithAbove), + // nameof(ReorderableBuilding.SwapWithBelow), + // nameof(ReorderableBuilding.Trigger) + // ) + .AddBaseType(typeof(KMonoBehaviour)) + .AddBaseType(typeof(StateMachine.Instance)) + .Build(); + + // ReSharper disable once UnusedMember.Local + private static IEnumerable TargetMethods() => targets.Resolve(); + [HarmonyPrefix] // ReSharper disable once UnusedMember.Local - private static IEnumerable TargetMethods() => TargetExtractor.GetTargetMethods(methodsForPatch); + private static void ObjectEventsPrefix() => PatchContext.Enter(PatchControl.DisablePatches); [HarmonyPostfix] // ReSharper disable once UnusedMember.Local - private static void ObjectEventsPostfix(object __instance, MethodBase __originalMethod, object[] __args) => + private static void ObjectEventsPostfix(object __instance, MethodBase __originalMethod, object[] __args) { + PatchContext.Leave(); PatchControl.RunIfEnabled( () => { var args = __args.Select( @@ -191,5 +165,6 @@ private static void ObjectEventsPostfix(object __instance, MethodBase __original } } ); + } } diff --git a/MultiplayerMod/Game/Mechanics/Objects/TargetExtractor.cs b/MultiplayerMod/Game/Mechanics/Objects/TargetExtractor.cs deleted file mode 100644 index f62ecd5b..00000000 --- a/MultiplayerMod/Game/Mechanics/Objects/TargetExtractor.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using MultiplayerMod.Core.Logging; - -namespace MultiplayerMod.Game.Mechanics.Objects; - -static class TargetExtractor { - - private static readonly Core.Logging.Logger log = LoggerFactory.GetLogger(typeof(TargetExtractor)); - - public static IEnumerable GetTargetMethods(Dictionary methodsForPatch) { - var classTypes = methodsForPatch.Keys.Where(type => type.IsClass).ToList(); - var interfaceTypes = methodsForPatch.Keys.Where(type => type.IsInterface).ToList(); - var targetMethods = Assembly.GetAssembly(typeof(ISliderControl)) - .GetTypes() - .Where( - type => type.IsClass && (classTypes.Contains(type) - || interfaceTypes.Any(interfaceType => interfaceType.IsAssignableFrom(type))) - ) - .Where( - type => { - var isAssignableFrom = typeof(KMonoBehaviour).IsAssignableFrom(type) || - typeof(StateMachine.Instance).IsAssignableFrom(type); - if (!isAssignableFrom) { - log.Error($"{type} can not be assigned to KMonoBehaviour or StateMachine.Instance."); - } - return isAssignableFrom; - } - ) - .SelectMany( - type => { - if (classTypes.Contains(type)) - return methodsForPatch[type].Select(methodName => GetMethodOrSetter(type, methodName, null)); - - var implementedInterfaces = GetImplementedInterfaces(interfaceTypes, type); - return implementedInterfaces.SelectMany( - implementedInterface => methodsForPatch[implementedInterface].Select( - methodName => GetMethodOrSetter(type, methodName, implementedInterface) - ) - ); - } - ).ToList(); - return targetMethods; - } - - private static MethodBase GetMethodOrSetter(Type type, string methodName, Type? interfaceType) { - var methodInfo = GetMethod(type, methodName, interfaceType); - if (methodInfo != null) - return methodInfo; - - var property = GetSetter(type, methodName, interfaceType); - if (property != null) - return property; - - var message = $"Method {type}.{methodName} ({interfaceType}) not found"; - log.Error(message); - throw new Exception(message); - } - - private static MethodBase? GetMethod(Type type, string methodName, Type? interfaceType) { - var methodInfo = type.GetMethod( - methodName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance - ); - if (methodInfo != null) - return methodInfo; - - if (interfaceType == null) return null; - - // Some overrides names prefixed by interface e.g. Clinic#ISliderControl.SetSliderValue - methodInfo = type.GetMethod( - interfaceType.Name + "." + methodName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance - ); - return methodInfo; - } - - private static MethodBase? GetSetter(Type type, string propertyName, Type? interfaceType) { - var property = type.GetProperty( - propertyName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance - ); - if (property != null) - return property.GetSetMethod(true); - - if (interfaceType == null) - return null; - - // Some overrides names prefixed by interface e.g. Clinic#ISliderControl.SetSliderValue - property = type.GetProperty( - interfaceType.Name + "." + propertyName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance - ); - return property?.GetSetMethod(true); - } - - private static List GetImplementedInterfaces(List interfaceTypes, Type type) { - return interfaceTypes.Where(interfaceType => interfaceType.IsAssignableFrom(type)).ToList(); - } - -}