From e2296ceb26aaf54a7bee1b523a76dd7a4b280b5c Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Wed, 1 Mar 2023 02:57:26 +0100 Subject: [PATCH 1/2] Adds support for targeting an async method's internal state machine's MoveNext with Attributes Co-authored-by: Michael Ripley --- Harmony/Internal/PatchTools.cs | 12 ++++++++++-- Harmony/Public/Attributes.cs | 6 +++++- Harmony/Tools/AccessTools.cs | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Harmony/Internal/PatchTools.cs b/Harmony/Internal/PatchTools.cs index 88494d9b..32028975 100644 --- a/Harmony/Internal/PatchTools.cs +++ b/Harmony/Internal/PatchTools.cs @@ -84,8 +84,16 @@ internal static MethodBase GetOriginalMethod(this HarmonyMethod attr) case MethodType.Enumerator: if (attr.methodName is null) return null; - var method = AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes); - return AccessTools.EnumeratorMoveNext(method); + var enumMethod = AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes); + return AccessTools.EnumeratorMoveNext(enumMethod); + +#if NET40_OR_GREATER + case MethodType.Async: + if (attr.methodName is null) + return null; + var asyncMethod = AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes); + return AccessTools.AsyncMoveNext(asyncMethod); +#endif } } catch (AmbiguousMatchException ex) diff --git a/Harmony/Public/Attributes.cs b/Harmony/Public/Attributes.cs index 4911f80f..0cf9cadf 100644 --- a/Harmony/Public/Attributes.cs +++ b/Harmony/Public/Attributes.cs @@ -18,7 +18,11 @@ public enum MethodType /// This is a static constructor StaticConstructor, /// This targets the MoveNext method of the enumerator result - Enumerator + Enumerator, +#if NET40_OR_GREATER + /// This targets the MoveNext method of the async state machine + Async +#endif } /// Specifies the type of argument diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs index 4d4ba658..017bae7e 100644 --- a/Harmony/Tools/AccessTools.cs +++ b/Harmony/Tools/AccessTools.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Reflection.Emit; using System.Runtime.Serialization; +using System.Runtime.CompilerServices; using System.Threading; namespace HarmonyLib @@ -498,6 +499,37 @@ public static MethodInfo EnumeratorMoveNext(MethodBase method) return Method(type, nameof(IEnumerator.MoveNext)); } +#if NET40_OR_GREATER + /// Gets the method of an async method's state machine + /// Async method that creates the state machine internally + /// The internal method of the async state machine or null if no valid async method is detected + public static MethodInfo AsyncMoveNext(MethodBase method) + { + if (method is null) + { + FileLog.Debug("AccessTools.AsyncMoveNext: method is null"); + return null; + } + + var asyncAttribute = method.GetCustomAttribute(); + if (asyncAttribute == null) + { + FileLog.Debug($"AccessTools.AsyncMoveNext: Could not find AsyncStateMachine for {method.FullDescription()}"); + return null; + } + + var asyncStateMachineType = asyncAttribute.StateMachineType; + var asyncMethodBody = DeclaredMethod(asyncStateMachineType, "MoveNext"); + if (asyncMethodBody == null) + { + FileLog.Debug($"AccessTools.AsyncMoveNext: Could not find async method body for {method.FullDescription()}"); + return null; + } + + return asyncMethodBody; + } +#endif + /// Gets the names of all method that are declared in a type /// The declaring class/type /// A list of method names From b329cbc30f66c88d8a4265f1daafb6fe567dd983 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Wed, 22 Mar 2023 01:18:59 +0100 Subject: [PATCH 2/2] Adapts style and requests --- Harmony/Internal/PatchTools.cs | 2 +- Harmony/Public/Attributes.cs | 6 +++--- Harmony/Tools/AccessTools.cs | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Harmony/Internal/PatchTools.cs b/Harmony/Internal/PatchTools.cs index 32028975..1a2df22d 100644 --- a/Harmony/Internal/PatchTools.cs +++ b/Harmony/Internal/PatchTools.cs @@ -87,7 +87,7 @@ internal static MethodBase GetOriginalMethod(this HarmonyMethod attr) var enumMethod = AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes); return AccessTools.EnumeratorMoveNext(enumMethod); -#if NET40_OR_GREATER +#if NET45_OR_GREATER case MethodType.Async: if (attr.methodName is null) return null; diff --git a/Harmony/Public/Attributes.cs b/Harmony/Public/Attributes.cs index 0cf9cadf..edd077ac 100644 --- a/Harmony/Public/Attributes.cs +++ b/Harmony/Public/Attributes.cs @@ -17,10 +17,10 @@ public enum MethodType Constructor, /// This is a static constructor StaticConstructor, - /// This targets the MoveNext method of the enumerator result + /// This targets the MoveNext method of the enumerator result, that actually contains the method's implementation Enumerator, -#if NET40_OR_GREATER - /// This targets the MoveNext method of the async state machine +#if NET45_OR_GREATER + /// This targets the MoveNext method of the async state machine, that actually contains the method's implementation Async #endif } diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs index 017bae7e..e22b9e89 100644 --- a/Harmony/Tools/AccessTools.cs +++ b/Harmony/Tools/AccessTools.cs @@ -467,9 +467,9 @@ public static MethodInfo Method(string typeColonName, Type[] parameters = null, return Method(info.type, info.name, parameters, generics); } - /// Gets the method of an enumerator method + /// Gets the method of an enumerator method /// Enumerator method that creates the enumerator - /// The internal method of the enumerator or null if no valid enumerator is detected + /// The internal method of the enumerator or null if no valid enumerator is detected public static MethodInfo EnumeratorMoveNext(MethodBase method) { if (method is null) @@ -499,10 +499,10 @@ public static MethodInfo EnumeratorMoveNext(MethodBase method) return Method(type, nameof(IEnumerator.MoveNext)); } -#if NET40_OR_GREATER - /// Gets the method of an async method's state machine +#if NET45_OR_GREATER + /// Gets the method of an async method's state machine /// Async method that creates the state machine internally - /// The internal method of the async state machine or null if no valid async method is detected + /// The internal method of the async state machine or null if no valid async method is detected public static MethodInfo AsyncMoveNext(MethodBase method) { if (method is null) @@ -512,15 +512,15 @@ public static MethodInfo AsyncMoveNext(MethodBase method) } var asyncAttribute = method.GetCustomAttribute(); - if (asyncAttribute == null) + if (asyncAttribute is null) { FileLog.Debug($"AccessTools.AsyncMoveNext: Could not find AsyncStateMachine for {method.FullDescription()}"); return null; } var asyncStateMachineType = asyncAttribute.StateMachineType; - var asyncMethodBody = DeclaredMethod(asyncStateMachineType, "MoveNext"); - if (asyncMethodBody == null) + var asyncMethodBody = DeclaredMethod(asyncStateMachineType, nameof(IAsyncStateMachine.MoveNext)); + if (asyncMethodBody is null) { FileLog.Debug($"AccessTools.AsyncMoveNext: Could not find async method body for {method.FullDescription()}"); return null;