diff --git a/Harmony/Public/Patching/NativeDetourMethodPatcher.cs b/Harmony/Public/Patching/NativeDetourMethodPatcher.cs index 8f27d60e..7ac04adf 100644 --- a/Harmony/Public/Patching/NativeDetourMethodPatcher.cs +++ b/Harmony/Public/Patching/NativeDetourMethodPatcher.cs @@ -29,21 +29,17 @@ public class NativeDetourMethodPatcher : MethodPatcher private IDisposable _realReplacementPin; private IDisposable _altHandle; - private readonly MethodSignature _staticSignature; - + private readonly MethodSignature _signature; private readonly MethodInfo _altEntryMethod; private readonly IDisposable _altEntryPin; /// public NativeDetourMethodPatcher(MethodBase original) : base(original) { - var staticSignature = new MethodSignature(Original, true); - _staticSignature = staticSignature; - - var altEntryProxyMethod = GenerateAltEntryProxyStub(Original, staticSignature); - _altEntryMethod = altEntryProxyMethod; - _altEntryPin = PlatformTriple.Current.PinMethodIfNeeded(altEntryProxyMethod); - PlatformTriple.Current.Compile(altEntryProxyMethod); + _signature = new MethodSignature(Original); + _altEntryMethod = GenerateAltEntryProxyStub(Original, _signature); + _altEntryPin = PlatformTriple.Current.PinMethodIfNeeded(_altEntryMethod); + PlatformTriple.Current.Compile(_altEntryMethod); } private static MethodInfo GenerateAltEntryProxyStub(MethodBase original, MethodSignature signature) @@ -122,12 +118,12 @@ public override DynamicMethodDefinition CopyOriginal() => private DynamicMethodDefinition GenerateAltEntryProxyCaller(string name) { - var dmd = _staticSignature.CreateDmd(name); + var dmd = _signature.CreateDmd(name); var il = new ILContext(dmd.Definition); var c = new ILCursor(il); - for (var i = 0; i < _staticSignature.ParameterCount; i++) + for (var i = 0; i < _signature.ParameterCount; i++) c.EmitLdarg(i); c.EmitCall(_altEntryMethod); c.EmitRet(); diff --git a/HarmonyTests/Patching/Assets/NativeDetourClasses.cs b/HarmonyTests/Patching/Assets/NativeDetourClasses.cs index 81626682..3788dd59 100644 --- a/HarmonyTests/Patching/Assets/NativeDetourClasses.cs +++ b/HarmonyTests/Patching/Assets/NativeDetourClasses.cs @@ -5,7 +5,54 @@ namespace HarmonyTests.Patching.Assets; -public class ExternalMethod_Patch +public class ExternalInstanceMethod_StringIsInterned_Patch +{ + public static IEnumerable Transpiler(IEnumerable instructions) => + instructions; + + private const string UniqueString = nameof(ExternalInstanceMethod_StringIsInterned_Patch); + + public const string PrefixInput = $"{UniqueString} {nameof(PrefixInput)}"; + public const string PrefixOutput = $"{UniqueString} {nameof(PrefixOutput)}"; + public static void Prefix(ref string __instance, ref string __result, ref bool __runOriginal) + { + if (__instance == PrefixInput) + { + __result = PrefixOutput; + __runOriginal = false; + } + } + + public const string PostfixInput = $"{UniqueString} {nameof(PostfixInput)}"; + public const string PostfixOutput = $"{UniqueString} {nameof(PostfixOutput)}"; + public static void Postfix(ref string __instance, ref string __result) + { + if (__instance == PostfixInput) + { + __result = PostfixOutput; + } + } + + public static readonly Type TranspiledException = typeof(UnauthorizedAccessException); + public static IEnumerable TranspilerThrow(IEnumerable instructions) + { + yield return new CodeInstruction(OpCodes.Newobj, TranspiledException.GetConstructor([])); + yield return new CodeInstruction(OpCodes.Throw); + } + + public const string FinalizerInput = $"{UniqueString} {nameof(FinalizerInput)}"; + public const string FinalizerOutput = $"{UniqueString} {nameof(FinalizerOutput)}"; + public static Exception Finalizer(ref string __instance, ref string __result) + { + if (__instance == FinalizerInput) + { + __result = FinalizerOutput; + } + return null; + } +} + +public class ExternalStaticMethod_MathCos_Patch { public static IEnumerable Transpiler(IEnumerable instructions) => instructions; diff --git a/HarmonyTests/Patching/NativeDetourPatches.cs b/HarmonyTests/Patching/NativeDetourPatches.cs index 10b74966..cdb313f4 100644 --- a/HarmonyTests/Patching/NativeDetourPatches.cs +++ b/HarmonyTests/Patching/NativeDetourPatches.cs @@ -2,14 +2,68 @@ using HarmonyTests.Patching.Assets; using NUnit.Framework; using System; +using System.IO; +using System.Reflection; +using System.Text; namespace HarmonyLibTests.Patching; +using EIP = ExternalInstanceMethod_StringIsInterned_Patch; + [TestFixture] public class NativeDetourPatches : TestLogger { [Test] - public void Test_PatchExternalMethod() + public void Test_PatchInstanceExternalMethod() + { + var target = typeof(string).GetMethod("Intern", BindingFlags.Instance|BindingFlags.NonPublic); + + if(target == null) + Assert.Inconclusive("string.Intern is missing in current runtime"); + +#if !NET35 + if((target.MethodImplementationFlags & MethodImplAttributes.InternalCall) == 0) + Assert.Inconclusive("string.Intern is not an InternalCall (extern) in current runtime "); +#endif + + if(target.GetMethodBody() != null) + Assert.Inconclusive("string.Intern has IL body in current runtime"); + + var str1 = new StringBuilder().Append('o').Append('k').Append('4').Append('1').ToString(); + Assert.IsNull(string.IsInterned(str1)); + var internedStr1 = string.Intern(str1); + Assert.AreEqual(internedStr1, string.IsInterned(str1)); + + var instance = new Harmony("test-patch-external-instance-method"); + Assert.NotNull(instance, "Harmony instance"); + + instance.Patch(target, transpiler: typeof(EIP).Method("Transpiler")); + var str2 = new StringBuilder().Append('o').Append('k').Append('4').Append('2').ToString(); + Assert.IsNull(string.IsInterned(str2)); + var internedStr2 = string.Intern(str2); + Assert.AreEqual(internedStr2, string.IsInterned(str2)); + + instance.Patch(target, prefix: typeof(EIP).Method("Prefix")); + Assert.AreEqual(EIP.PrefixOutput, string.Intern(EIP.PrefixInput)); + + instance.Patch(target, postfix: typeof(EIP).Method("Postfix")); + Assert.AreEqual(EIP.PostfixOutput, string.Intern(EIP.PostfixInput)); + + instance.Patch(target, transpiler: typeof(EIP).Method("TranspilerThrow")); + Assert.Throws(EIP.TranspiledException, () => string.Intern("does not matter")); + + instance.Patch(target, finalizer: typeof(EIP).Method("Finalizer")); + Assert.AreEqual(EIP.FinalizerOutput, string.Intern(EIP.FinalizerInput)); + + instance.UnpatchSelf(); + var str3 = new StringBuilder().Append('o').Append('k').Append('4').Append('3').ToString(); + Assert.IsNull(string.IsInterned(str3)); + Assert.AreEqual(internedStr1, string.IsInterned(str1)); + Assert.AreEqual(internedStr2, string.IsInterned(str2)); + } + + [Test] + public void Test_PatchStaticExternalMethod() { var target = SymbolExtensions.GetMethodInfo(() => Math.Cos(0)); @@ -20,22 +74,22 @@ public void Test_PatchExternalMethod() var cos = Math.Cos; Assert.AreEqual(1d, cos(0d)); - var instance = new Harmony("test-patch-external-method"); + var instance = new Harmony("test-patch-external-static-method"); Assert.NotNull(instance, "Harmony instance"); - instance.Patch(target, transpiler: typeof(ExternalMethod_Patch).Method("Transpiler")); + instance.Patch(target, transpiler: typeof(ExternalStaticMethod_MathCos_Patch).Method("Transpiler")); Assert.AreEqual(1d, cos(0d)); - instance.Patch(target, prefix: typeof(ExternalMethod_Patch).Method("Prefix")); + instance.Patch(target, prefix: typeof(ExternalStaticMethod_MathCos_Patch).Method("Prefix")); Assert.AreEqual(1d, cos(2d)); - instance.Patch(target, postfix: typeof(ExternalMethod_Patch).Method("Postfix")); + instance.Patch(target, postfix: typeof(ExternalStaticMethod_MathCos_Patch).Method("Postfix")); Assert.AreEqual(2d, cos(0d)); - instance.Patch(target, transpiler: typeof(ExternalMethod_Patch).Method("TranspilerThrow")); + instance.Patch(target, transpiler: typeof(ExternalStaticMethod_MathCos_Patch).Method("TranspilerThrow")); Assert.Throws(() => cos(0d)); - instance.Patch(target, finalizer: typeof(ExternalMethod_Patch).Method("Finalizer")); + instance.Patch(target, finalizer: typeof(ExternalStaticMethod_MathCos_Patch).Method("Finalizer")); Assert.AreEqual(-2d, cos(0d)); instance.UnpatchSelf();