Search code examples
c#.netgodotharmony

C# Library (Harmony) works as expected within Godot editor, but does not work in Godot build


I'm trying to use Harmony 2.3.3 with Godot 4.2.1, and it works as expected within the editor, but not within an exported build.

I'm attempting to patch a prefix to a method, and within the editor the method is patched successfully, but in the exported build, the method fails to be patched and the following exception is thrown:

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 HarmonyTester.Test() in G:\Godot Projects\HarmonyExportIssue\HarmonyTesting\HarmonyTester.cs:line 24

It seems to be some kind of dependency issue, as discussed in this issue on the Harmony Github, but I have no idea where to go from here or how to fix this problem.

Minimal reproducible example (Godot 4.2.1-mono)

Built version of example


Solution

  • After researching some more, this appears to be a known issue with Godot and external .dll files. https://github.com/godotengine/godot/issues/75160

    It's possible to work around this by using reflection as described in this answer: https://github.com/godotengine/godot/issues/75160#issuecomment-2071203840

    public partial class ModLoader : Node
    {
        public override void _Ready()
        {
            string modPath = ProjectSettings.GlobalizePath("user://TestMod.dll");
    
            var alc = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly());
            Assembly assembly = alc.LoadFromAssemblyPath(modPath);
    
            Type t = assembly.GetType("TestMod.TestModInit");
    
            t.GetMethod("Init")?.Invoke(null, null);
        }
    }
    

    Do note that at runtime you will need to get the dll file using

    OS.GetExecutablePath().GetBaseDir().PathJoin(file: "dllname.dll")
    

    instead