pardeike / Harmony

A library for patching, replacing and decorating .NET and Mono methods during runtime
https://www.patreon.com/pardeike
MIT License
5.16k stars 486 forks source link

InvalidProgramException in Stride/Godot #552

Closed Awarets closed 11 months ago

Awarets commented 11 months ago

Describe the bug When attempting to perform a basic patch in the Stride engine or the Godot engine, the same exception is returned: "InvalidProgramException: Common Language Runtime detected an invalid program."

To Reproduce Use the following code with Harmony 2.2.2 in Stride 4.1. (In Godot 4.1.1, the code is essentially the same, but using Godot's Autoloader feature instead of ModuleInitializer, and using GD.Print() instead of Debug.WriteLine)

internal class HarmonyRequirementsTesting
{
    [Stride.Core.ModuleInitializer]
    internal static void Initialize()
    {
        HarmonyLib.Harmony.DEBUG = true;

        var harmony = new HarmonyLib.Harmony(id: "com.rumbler.patch");

        MethodToTryPatching(intParameterTest: 20);

        var original = typeof(HarmonyRequirementsTesting).GetMethod(name: nameof(MethodToTryPatching), bindingAttr: System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
        var prefix = new HarmonyLib.HarmonyMethod(method: ((Action<int>)((intParameterTest) => Debug.WriteLine("patched method. parameter: " + intParameterTest))).Method);

        harmony.Patch(
            original: original,
            prefix: prefix);

        MethodToTryPatching(intParameterTest: 52652464);        
    }

    private static void MethodToTryPatching(int intParameterTest)
    {

    }
}

Harmony debug log:

### Harmony id=com.rumbler.patch, version=2.2.2.0, location=C:\Stride Projects\RumblerS\Bin\Windows\Debug\win-x64\0Harmony.dll, env/clr=6.0.21, platform=Win32NT, ptrsize:runtime/env=8/Bits64, Windows
### Started from System.Void RumblerS.<>c::<Initialize>b__0_0(System.Object sender, System.EventArgs e), location C:\Stride Projects\RumblerS\Bin\Windows\Debug\win-x64\RumblerS.dll
### At 2023-09-29 02.22.17
### Patch: static System.Void RumblerS.HarmonyRequirementsTesting::MethodToTryPatching(System.Int32 intParameterTest)
### Replacement: static System.Void RumblerS.HarmonyRequirementsTesting::RumblerS.HarmonyRequirementsTesting.MethodToTryPatching_Patch1(System.Int32 intParameterTest)
IL_0000: ldarg      0
IL_0004: call       System.Void RumblerS.<>c::<Initialize>b__0_1(System.Int32 intParameterTest)
IL_0009: // start original
IL_0009: nop
IL_000A: // end original
IL_000A: ret
DONE

Exception StackTrace:

System.Reflection.TargetInvocationException
  HResult=0x80131604
  Message=Exception has been thrown by the target of an invocation.
  Source=System.Private.CoreLib
  StackTrace:
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at MonoMod.RuntimeDetour.Platforms.DetourRuntimeNETPlatform.GetMethodHandle(MethodBase method)
   at MonoMod.RuntimeDetour.Platforms.DetourRuntimeILPlatform.GetIdentifiable(MethodBase method)
   at MonoMod.RuntimeDetour.Platforms.DetourRuntimeILPlatform.Pin(MethodBase method)
   at MonoMod.RuntimeDetour.DetourHelper.Pin[T](T method)
   at HarmonyLib.MethodPatcher.CreateReplacement(Dictionary`2& finalInstructions)
   at HarmonyLib.PatchFunctions.UpdateWrapper(MethodBase original, PatchInfo patchInfo)
   at HarmonyLib.PatchProcessor.Patch()
   at RumblerS.HarmonyRequirementsTesting.<>c.<Initialize>b__0_0(Object sender, EventArgs e) in C:\Stride Projects\RumblerS\RumblerS\HarmonyRequirementsTesting.cs:line 25
   at Stride.Games.GameBase.InitializeBeforeRun()

  This exception was originally thrown at this call stack:

Inner Exception 1:
InvalidProgramException: Common Language Runtime detected an invalid program.

Expected behavior For "patched method. parameter: 52652464" to be printed to the console.

Runtime environment:

pardeike commented 11 months ago

I don't think patch methods can be closures. Does the same error happen if the patch method is a static method and you feed its MethodInfo into HarmonyMethod() ?

Awarets commented 11 months ago

Ah I see, I think I misremembered using closures in a previous project, when I had actually used a transpiler.

I performed the tests again and got a strange result though. It works perfectly in Stride 4.1, the patched result is returned as expected. But in Godot 4.1.1, no exception is thrown, but the method doesn't appear to be being patched.

internal static class HarmonyRequirementsTesting
{
    private static bool testPatchFlag = false;

    internal static void Initialize()
    {
        HarmonyLib.Harmony.DEBUG = true;

        var harmony = new HarmonyLib.Harmony(id: "com.rumbler.patch");

        MethodToTryPatching(intParameterTest: 20);

        var original = typeof(HarmonyRequirementsTesting).GetMethod(name: nameof(MethodToTryPatching), bindingAttr: System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
        var prefix = new HarmonyLib.HarmonyMethod(method: typeof(HarmonyRequirementsTesting).GetMethod(name: nameof(PrefixPatchMethod), bindingAttr: System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic));

        harmony.Patch(
            original: original,
            prefix: prefix);

        MethodToTryPatching(intParameterTest: 52652464);

        GD.Print(nameof(testPatchFlag) + ": " + testPatchFlag);
    }

    private static void MethodToTryPatching(int intParameterTest)
    {

    }

    private static void PrefixPatchMethod(int intParameterTest)
    {
        testPatchFlag = true;
        GD.Print("patched method. parameter: " + intParameterTest);
    }
}

The expected result in the console is:

patched method. parameter: 52652464
testPatchFlag: True

but instead the result is:

testPatchFlag: False

Here's the harmony debug log:

### Harmony id=com.rumbler.patch, version=2.2.2.0, location=, env/clr=7.0.10, platform=Win32NT, ptrsize:runtime/env=8/Bits64, Windows
### Started from static System.Void RumblerG.HarmonyRequirementsTesting::Initialize(), location 
### At 2023-09-30 04.59.44
### Patch: static System.Void RumblerG.HarmonyRequirementsTesting::MethodToTryPatching(System.Int32 intParameterTest)
### Replacement: static System.Void RumblerG.HarmonyRequirementsTesting::RumblerG.HarmonyRequirementsTesting.MethodToTryPatching_Patch1(System.Int32 intParameterTest)
IL_0000: ldarg      0
IL_0004: call       static System.Void RumblerG.HarmonyRequirementsTesting::PrefixPatchMethod(System.Int32 intParameterTest)
IL_0009: // start original
IL_0009: nop
IL_000A: // end original
IL_000A: ret
DONE
pardeike commented 11 months ago

That's probably because of the runtime settings allow for more aggressive inlining. Have you tried the new beta of Harmony 2.3?

Awarets commented 11 months ago

Using the 2.3 beta fixed the issue! Thanks for clearing that up.