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

Error in exported Godot Engine build when attempting to patch method #562

Closed Awarets closed 8 months ago

Awarets commented 8 months ago

Describe the bug An exception (detailed below) is thrown when attempting to perform a patch in an exported Godot Build.

To Reproduce Use the following code with Harmony 2.3-prerelease.4 in Godot 4.2.1:

using System;
using System.Collections;
using System.Collections.Generic;
using Godot;
using HarmonyLib;

public partial class Main : Node
{
    public sealed override void _Ready()
    {
        HarmonyLib.Harmony.DEBUG = true;

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

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

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

    private static void MethodToTryPatching()
    {

    }

    private static void PrefixPatchMethod()
    {

    }
}

Harmony debug log:

### Harmony id=com.rumbler.patch, version=2.3.0.0, location=C:\HarmonyExportIssueBuild\data_HarmonyExportIssue_windows_x86_64\0Harmony.dll, env/clr=6.0.21, platform=Win32NT
### Started from virtual System.Void Main::_Ready(), location C:\HarmonyExportIssueBuild\data_HarmonyExportIssue_windows_x86_64\HarmonyExportIssue.dll
### At 2023-12-30 12.35.44
### Patch: static System.Void Main::MethodToTryPatching()
### Replacement: static System.Void Main::Main.MethodToTryPatching_Patch1()

Exception StackTrace:

ERROR: System.ArgumentException: GenericArguments[0], 'MonoMod.Utils.Cil.CecilILGenerator', on 'MonoMod.Utils.Cil.ILGeneratorProxy[TTarget]' violates the constraint of type 'TTarget'.
 ---> System.TypeLoadException: GenericArguments[0], 'MonoMod.Utils.Cil.CecilILGenerator', on 'MonoMod.Utils.Cil.ILGeneratorProxy[TTarget]' violates the constraint of type parameter 'TTarget'.
   at System.RuntimeTypeHandle.Instantiate(RuntimeType inst)
   at System.RuntimeType.MakeGenericType(Type[] instantiation)
   --- End of inner exception stack trace ---
   at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
   at System.RuntimeType.MakeGenericType(Type[] instantiation)
   at MonoMod.Utils.Cil.ILGeneratorShim.GetProxy()
   at MonoMod.Utils.DynamicMethodDefinition.GetILGenerator()
   at HarmonyLib.MethodPatcher..ctor(MethodBase original, MethodBase source, List`1 prefixes, List`1 postfixes, List`1 transpilers, List`1 finalizers, Boolean debug)
   at HarmonyLib.PatchFunctions.UpdateWrapper(MethodBase original, PatchInfo patchInfo)
   at HarmonyLib.PatchProcessor.Patch()
   at HarmonyLib.Harmony.Patch(MethodBase original, HarmonyMethod prefix, HarmonyMethod postfix, HarmonyMethod transpiler, HarmonyMethod finalizer)
   at Main._Ready() in C:\Godot Projects\HarmonyExportIssue\Main.cs:line 18
   at Godot.Node.InvokeGodotClassMethod(godot_string_name& method, NativeVariantPtrArgs args, godot_variant& ret)
   at Main.InvokeGodotClassMethod(godot_string_name& method, NativeVariantPtrArgs args, godot_variant& ret) in C:\Godot Projects\HarmonyExportIssue\Godot.SourceGenerators\Godot.SourceGenerators.ScriptMethodsGenerator\Main_ScriptMethods.generated.cs:line 58
   at Godot.Bridge.CSharpInstanceBridge.Call(IntPtr godotObjectGCHandle, godot_string_name* method, godot_variant** args, Int32 argCount, godot_variant_call_error* refCallError, godot_variant* ret)
   at: void Godot.NativeInterop.ExceptionUtils.LogException(System.Exception) (:0)

Runtime environment (please complete the following information):

Additional context The exception only appears to be thrown in an exported build. No exception is thrown within the editor, and everything behaves as expected.

pardeike commented 8 months ago

Could it be that the empty methods are optimised away or inlined when you build a release build (I assume that’s what Godot exported build means). Can you try to add some code and maybe some try/catch code that is known to prevent inlining?

pardeike commented 8 months ago

A more likely reason is a mismatch of dependency version. Harmony needs and uses some dependencies and I guess one of them does not match the one that is actually loaded and present at runtime.

Awarets commented 8 months ago

The problem still occurs even with non-empty methods. (Harmony log and exception stacktrace are identical)

I think I may have identified the mismatched dependency though. Harmony in .NET 6 is dependent on System.Text.Json 5.0.2, but that version of System.Text.Json doesn't appear to be compatible with .NET 6. The version of System.Text.Json in the release build is 6.0.2123.36311 instead.

pardeike commented 8 months ago

fixed in next prerelease

Awarets commented 8 months ago

Issue is unfortunately still occurring even in the newest prerelease.

The only other lead I have is that Mono.Cecil doesn't seem to support .NET 6.0 (or any base .NET version), however that leaves me confused as to how patching is able to even function within the godot editor itself.