BepInEx / HarmonyX

Harmony built on top of MonoMod.RuntimeDetours with additional features
MIT License
331 stars 42 forks source link

`HarmonyILManipulator` on method w/ `HarmonyTranspiler` results in `InvalidCastException` #54

Open 6thmoon opened 1 year ago

6thmoon commented 1 year ago

It appears that ILPatternMatchingExt expects ILLabel as branch operand but instead receives Instruction after running the Transpiler. Here is the stack trace displayed in the BepInEx console:

Click to expand... ``` [Error : HarmonyX] Failed to patch void Test.Plugin::Example(): System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidCastException: Specified cast is not valid. at MonoMod.Cil.ILPatternMatchingExt.MatchBr (Mono.Cecil.Cil.Instruction instr, MonoMod.Cil.ILLabel& value) [0x0002b] in <6733e342b5b549bba815373898724469>:IL_002B at Test.Plugin+<>c.b__3_0 (Mono.Cecil.Cil.Instruction instruction) [0x00000] in <16905bc0082a4b6088e041b87217eb81>:IL_0000 at MonoMod.Cil.ILCursor.TryGotoNext (MonoMod.Cil.MoveType moveType, System.Func`2[Mono.Cecil.Cil.Instruction,System.Boolean][] predicates) [0x00034] in <6733e342b5b549bba815373898724469>:IL_0034 at MonoMod.Cil.ILCursor.GotoNext (MonoMod.Cil.MoveType moveType, System.Func`2[Mono.Cecil.Cil.Instruction,System.Boolean][] predicates) [0x00000] in <6733e342b5b549bba815373898724469>:IL_0000 at MonoMod.Cil.ILCursor.GotoNext (System.Func`2[Mono.Cecil.Cil.Instruction,System.Boolean][] predicates) [0x00000] in <6733e342b5b549bba815373898724469>:IL_0000 at Test.Plugin.Manipulate (MonoMod.Cil.ILContext context) [0x00006] in <16905bc0082a4b6088e041b87217eb81>:IL_0006 at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&) at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <44afb4564e9347cf99a1865351ea8f4a>:IL_0032 --- End of inner exception stack trace --- at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0004b] in <44afb4564e9347cf99a1865351ea8f4a>:IL_004B at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <44afb4564e9347cf99a1865351ea8f4a>:IL_0000 at HarmonyLib.Public.Patching.HarmonyManipulator.ApplyManipulators (MonoMod.Cil.ILContext ctx, System.Reflection.MethodBase original, System.Collections.Generic.List`1[T] ilManipulators, HarmonyLib.Internal.Util.ILEmitter+Label retLabel) [0x000ea] in <474744d65d8e460fa08cd5fd82b5d65f>:IL_00EA at HarmonyLib.Public.Patching.HarmonyManipulator.WriteImpl () [0x0030d] in <474744d65d8e460fa08cd5fd82b5d65f>:IL_030D [Error : Unity Log] InvalidCastException: Specified cast is not valid. Stack trace: MonoMod.Cil.ILPatternMatchingExt.MatchBr (Mono.Cecil.Cil.Instruction instr, MonoMod.Cil.ILLabel& value) (at <6733e342b5b549bba815373898724469>:IL_002B) Test.Plugin+<>c.b__3_0 (Mono.Cecil.Cil.Instruction instruction) (at <16905bc0082a4b6088e041b87217eb81>:IL_0000) MonoMod.Cil.ILCursor.TryGotoNext (MonoMod.Cil.MoveType moveType, System.Func`2[Mono.Cecil.Cil.Instruction,System.Boolean][] predicates) (at <6733e342b5b549bba815373898724469>:IL_0034) MonoMod.Cil.ILCursor.GotoNext (MonoMod.Cil.MoveType moveType, System.Func`2[Mono.Cecil.Cil.Instruction,System.Boolean][] predicates) (at <6733e342b5b549bba815373898724469>:IL_0000) MonoMod.Cil.ILCursor.GotoNext (System.Func`2[Mono.Cecil.Cil.Instruction,System.Boolean][] predicates) (at <6733e342b5b549bba815373898724469>:IL_0000) Test.Plugin.Manipulate (MonoMod.Cil.ILContext context) (at <16905bc0082a4b6088e041b87217eb81>:IL_0006) System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <44afb4564e9347cf99a1865351ea8f4a>:IL_0032) Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation. System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at <44afb4564e9347cf99a1865351ea8f4a>:IL_004B) System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at <44afb4564e9347cf99a1865351ea8f4a>:IL_0000) HarmonyLib.Public.Patching.HarmonyManipulator.ApplyManipulators (MonoMod.Cil.ILContext ctx, System.Reflection.MethodBase original, System.Collections.Generic.List`1[T] ilManipulators, HarmonyLib.Internal.Util.ILEmitter+Label retLabel) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_00EA) HarmonyLib.Public.Patching.HarmonyManipulator.WriteImpl () (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_030D) Rethrow as HarmonyException: IL Compile Error (unknown location) HarmonyLib.Public.Patching.HarmonyManipulator.WriteImpl () (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0378) HarmonyLib.Public.Patching.HarmonyManipulator.Process (MonoMod.Cil.ILContext ilContext, System.Reflection.MethodBase originalMethod) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0042) HarmonyLib.Public.Patching.HarmonyManipulator.Manipulate (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo, MonoMod.Cil.ILContext ctx) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0006) HarmonyLib.Public.Patching.HarmonyManipulator.Manipulate (System.Reflection.MethodBase original, MonoMod.Cil.ILContext ctx) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0007) HarmonyLib.Public.Patching.ManagedMethodPatcher.Manipulator (MonoMod.Cil.ILContext ctx) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0012) MonoMod.Cil.ILContext.Invoke (MonoMod.Cil.ILContext+Manipulator manip) (at <6733e342b5b549bba815373898724469>:IL_0087) MonoMod.RuntimeDetour.ILHook+Context.InvokeManipulator (Mono.Cecil.MethodDefinition def, MonoMod.Cil.ILContext+Manipulator cb) (at <4e2760c7517c4ea79c633d67e84b319f>:IL_0012) DMD?-1925063936._MonoMod_RuntimeDetour_ILHook+Context::Refresh (MonoMod.RuntimeDetour.ILHook+Context this) (at <75e4ac05845642588cd961442b944b7f>:IL_00EA) DMD<>?-1925063936.Trampoline?1807096832 (System.Object ) (at <1242ad070f5f425c8b0fba06324360e4>:IL_0020) HarmonyLib.Internal.RuntimeFixes.StackTraceFixes.OnILChainRefresh (System.Object self) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0000) MonoMod.RuntimeDetour.ILHook.Apply () (at <4e2760c7517c4ea79c633d67e84b319f>:IL_0059) HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0047) Rethrow as HarmonyException: IL Compile Error (unknown location) HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_005F) HarmonyLib.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0033) Rethrow as HarmonyException: IL Compile Error (unknown location) HarmonyLib.PatchClassProcessor.ReportException (System.Exception exception, System.Reflection.MethodBase original) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0045) HarmonyLib.PatchClassProcessor.Patch () (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0095) HarmonyLib.Harmony.PatchAll (System.Type type) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_0008) HarmonyLib.Harmony.CreateAndPatchAll (System.Type type, System.String harmonyInstanceId) (at <474744d65d8e460fa08cd5fd82b5d65f>:IL_001E) Test.Plugin.Awake () (at <16905bc0082a4b6088e041b87217eb81>:IL_0000) UnityEngine.GameObject:AddComponent(Type) BepInEx.Bootstrap.Chainloader:Start() FlashWindow:.cctor() ```

BepInEx package used was the latest version - 5.4.21, running on Windows 10. I tried a few different combinations of .NET framework and Unity game engine with the same results. The following code illustrates a simple way to reproduce this error:

using HarmonyLib;
using MonoMod.Cil;
using System;
using System.Collections.Generic;

namespace Test
{
    [BepInEx.BepInPlugin("local.test.plugin", "TestPlugin", "0.0.0")]
    class Plugin : BepInEx.BaseUnityPlugin
    {
        void Awake() => Harmony.CreateAndPatchAll(typeof(Plugin));
        void Example()              // Generate a branch instruction.
             => Console.WriteLine(DateTime.Now.Second % 2 == 0 ? "even" : "odd");

        [HarmonyTranspiler, HarmonyPatch(typeof(Plugin), nameof(Example))]
        static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
             => instructions;       // In theory, this does nothing. However...

        [HarmonyILManipulator, HarmonyPatch(typeof(Plugin), nameof(Example))]
        static void Manipulate(ILContext context)
             // `InvalidCastException` thrown when matching branch instruction.
             => new ILCursor(context).GotoNext(instruction => instruction.MatchBr(out _));
    }
}

Of course, this is a rather contrived example. The real situation where this may arise is when two separate plugins are attempting to patch the same method in game - one using ILManipulator, while the other leverages a Transpiler. Interestingly enough, the issue did not occur upon replacing the HarmonyILManipulator with an identical MonoMod IL hook.

6thmoon commented 1 year ago

Any comment? It doesn't come up often, but there have been a few users affected by this.

Zaggy1024 commented 4 months ago

I just encountered this issue when running two Lethal Company mods together:

https://thunderstore.io/c/lethal-company/p/Zaggy1024/TwoRadarMaps/ https://thunderstore.io/c/lethal-company/p/SylviBlossom/TerminalConflictFix/