Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support non-static signatures in NativeDetourMethodPatcher, fixes #122. #123

Merged
merged 2 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions Harmony/Public/Patching/NativeDetourMethodPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <inheritdoc/>
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)
Expand Down Expand Up @@ -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);
ManlyMarco marked this conversation as resolved.
Show resolved Hide resolved
c.EmitCall(_altEntryMethod);
c.EmitRet();
Expand Down
49 changes: 48 additions & 1 deletion HarmonyTests/Patching/Assets/NativeDetourClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,54 @@

namespace HarmonyTests.Patching.Assets;

public class ExternalMethod_Patch
public class ExternalInstanceMethod_StringIsInterned_Patch
{
public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> 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<CodeInstruction> TranspilerThrow(IEnumerable<CodeInstruction> 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<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) =>
instructions;
Expand Down
68 changes: 61 additions & 7 deletions HarmonyTests/Patching/NativeDetourPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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<UnauthorizedAccessException>(() => 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();
Expand Down