HarmonyX icon indicating copy to clipboard operation
HarmonyX copied to clipboard

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

Open 6thmoon opened this issue 2 years ago • 2 comments

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.<Manipulate>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.<Manipulate>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<Refresh>?-1925063936._MonoMod_RuntimeDetour_ILHook+Context::Refresh (MonoMod.RuntimeDetour.ILHook+Context this) (at <75e4ac05845642588cd961442b944b7f>:IL_00EA)
DMD<>?-1925063936.Trampoline<MonoMod.RuntimeDetour.ILHook+Context::Refresh>?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 avatar Nov 11 '22 20:11 6thmoon

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

6thmoon avatar Jun 21 '23 22:06 6thmoon

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/

Zaggy1024 avatar Feb 24 '24 19:02 Zaggy1024