Giter Club home page Giter Club logo

harmonyx's Introduction

HarmonyX Logo

NuGet


A library for patching, replacing and decorating .NET and Mono methods during runtime. Now powered by MonoMod.RuntimeDetour!


About

HarmonyX is a fork of Harmony 2 that specializes on support for games and game modding frameworks.

HarmonyX is being developed primarily for use in game frameworks alongside MonoMod. The main target usage of HarmonyX is BepInEx and Unity.

Important aspects of HarmonyX include:

  • Unity support first: builds for .NET Framework 3.5 and .NET Standard 2.0
  • Patching feature parity with Harmony while reducing code duplication using MonoMod.RuntimeDetour
  • Fast release schedule: get periodic stable releases off NuGet or GitHub, or download the latest CI build with nightly.link
  • Full interop with MonoMod.RuntimeDetour: patches made with either can coexist
  • Easily extendable patching: built-in support for native method patching and possibility to extend to other patch targets (e.g. IL2CPP)
  • Fixes, changes and optimizations aimed at Unity modding

HarmonyX is powered by MonoMod and its runtime patching tools.

Documentation

Check the documentation out at HarmonyX wiki.

harmonyx's People

Contributors

0x0ade avatar amperl avatar asquared31415 avatar banane9 avatar bbepis avatar bugproof avatar cybershadow avatar denjur avatar erisapps avatar failedshack avatar ghorsington avatar hcoona avatar kian738 avatar kittyfisto avatar kohanis avatar lbmaian avatar manlymarco avatar mehni avatar naounderscore avatar nwestfall avatar pardeike avatar pardeike-bot avatar pathoschild avatar pengweiqhca avatar rrazgriz avatar rube200 avatar simplywiri avatar tarbisu avatar wasabii avatar windows10ce avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

harmonyx's Issues

Harmony.CreateAndPatchAll - ensure no duplicate guids

https://github.com/BepInEx/HarmonyX/blob/master/Harmony/Public/Harmony.cs#L367

There exists a world where Guid.NewGuid() returns the same thing twice for different instances, and it creates a weird bug someone wastes time chasing.

How about adding a check for existing instances and generate a new guid if it already exists?

Alternatively it could use assembly-qualified type name instead. As a bonus it would be much more descriptive when debugging (it would need a dupe check as well).

HarmonyX cannot patch other Assemblies loaded in another AppDomains

hi,

I don't know if it is a feature request or a bug but here it is my scenario which is not working.

Describe the bug
I have 2 c# executable files. First one, lets say A is the target, Second one B, is the injector.
The purpose is to inject the dll and start harmony to patch DateTime.Now used in A.

I used another library and the injection works perfectly, even the starting code for harmony is working but the problem is that i cannot get the Assemblies of Executable A because they are in another AppDomain so i cannot patch them.

The question is: Is it possible for Harmony to patch all assemblies in other AppDpomains?

Wrong decode Bge_Un_S, decoding as Bge_Un ...

2023-03-26_222846
2023-03-26_222902

Use this patch function, original Harmony - ok, HarmonyX - failed ...
After debugger - HarmonyX decode Bge_Un_S as Bge_Un ...

		static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{			
			return new CodeMatcher(instructions)
				.MatchEndForward(
					new CodeMatch(i => i.opcode == OpCodes.Ldsfld  && ((FieldInfo)i.operand).Name == "worldSurface"),
//					new CodeMatch(OpCodes.Bge_Un_S),
					new CodeMatch(OpCodes.Bge_Un),
					new CodeMatch(OpCodes.Ret))
				.ThrowIfInvalid("MinecartDiggerHelper.TryDigging(): Error! Can't find pattern!")
				.SetAndAdvance(OpCodes.Nop, null)
				.InstructionEnumeration();
		}

New patch type: IL Manipulators

With the help of MonoMod it is possible to manipulate the generated patched method in much more powerful ways than simple transpilers. Most importantly, usage of Mono.Cecil and helper MonoMod API is possible (for example usage of ILCursor as a powerful alternative to CodeMatcher).

As such, a new patcher type is proposed: IL Manipulators.

General features (not set in stone, can be changed before implementation):

  • Specified with HarmonyILManipulator attribute
  • Takes MonoMod's ILContext as an (optional) parameter and MethodBase of original method as another (optional) parameter
  • Runs after all transpilers, prefixes, postfixes and finalizers are applied.
    • This is a technical requirement to ensure that normal patch generation process can be run properly

Writing an empty Transpiler changes the behavior of the patched method

There is currently a bug in Harmony that makes a method behave differently while being patched by a transpiler. This issue was initially encountered by @Aeroluna.

It doesn't have to be empty, but I didn't know how to better express this issue. Feel free to edit the title.

Reproduce steps

  1. Install Beat Saber and BSIPA.

  2. Make a mod that contains this transpiler returning unchanged instructions.

    [HarmonyPatch(typeof(BeatmapDataLoader), "GetBeatmapDataFromBeatmapSaveData")]
    internal class BeatmapDataLoaderGetBeatmapDataFromBeatmapSaveDataPatch
    {
        private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
        {
            return instructions;
        }
    }
  3. Add a postfix that logs something when the offending method in the transpiled method runs.

    [HarmonyPatch(typeof(DefaultEnvironmentEventsFactory), nameof(DefaultEnvironmentEventsFactory.InsertDefaultEnvironmentEvents))]
    internal class DefaultEnvironmentEventsFactoryInsertDefaultEnvironmentEventsPatch
    {
        private static void Postfix()
        {
            Plugin.Log.Notice("InsertDefaultEnvironmentEvents");
        }
    }
  4. Put the mod into Beat Saber\Plugins.

  5. Run the game and start a level.

Alternatively, use the attached plugin: BSPlugin1.zip

Expected behavior

The DefaultEnvironmentEventsFactory.InsertDefaultEnvironmentEvents method shouldn't run and the postfix shouldn't log anything.

Actual behavior

The DefaultEnvironmentEventsFactory.InsertDefaultEnvironmentEvents method runs and the postfix is logging.

Environment

  • OS: Windows 11
  • .NET environment: Unity 2019.4.28 Mono
  • HarmonyX version: v2.7.0 to latest (v2.10.1 as of this writing)

Additional information

I made MonoMod dumps for the unpatched and patched method.

Here's a diff of the patched method, after applying the transpiler:

image

A workaround has been found, which consists to add a Nop instruction after the Leave instruction. By using the same workaround on this test case, the resulting method is exactly the same as the original, according to the dumps.

Feature request - HarmonyOptional attribute

Add a patch attribute that marks the patch as optional - if the method is not found or patching fails, the patch process is not aborted and there's a warning given, not an error.
It could have a parameter to specify if exceptions should be ignored or not. If target method is not found, it will always skip the patch (and show a short warning that an optional patch was skipped).

Use case is supporting multiple game versions where name or parameters of a method got changed.

Port reverse patchers

Harmony 2 has finally finalized the reverse patcher API and it can be thus ported over

var aa = new Harmony("123"); error

var aa = new Harmony("123"); error   Type must derive from Delegate.

 	System.Private.CoreLib.dll!System.Delegate.CreateDelegateNoSecurityCheck(System.Type type, object target, System.RuntimeMethodHandle method)	未知
 	System.Private.CoreLib.dll!System.Reflection.Emit.DynamicMethod.CreateDelegate(System.Type delegateType)	未知
 	MonoMod.RuntimeDetour.dll!MonoMod.RuntimeDetour.Platforms.DetourRuntimeNETPlatform.GetMethodHandle(System.Reflection.MethodBase method)	未知
 	MonoMod.RuntimeDetour.dll!MonoMod.RuntimeDetour.Platforms.DetourRuntimeILPlatform.GetIdentifiable(System.Reflection.MethodBase method)	未知
 	MonoMod.RuntimeDetour.dll!MonoMod.RuntimeDetour.Platforms.DetourRuntimeILPlatform.Pin(System.Reflection.MethodBase method)	未知
 	MonoMod.RuntimeDetour.dll!MonoMod.RuntimeDetour.Platforms.DetourRuntimeILPlatform.DetourRuntimeILPlatform()	未知
 	MonoMod.RuntimeDetour.dll!MonoMod.RuntimeDetour.Platforms.DetourRuntimeNETCorePlatform.Create()	未知
 	MonoMod.RuntimeDetour.dll!MonoMod.RuntimeDetour.DetourHelper.Runtime.get()	未知
 	MonoMod.RuntimeDetour.dll!MonoMod.RuntimeDetour.Detour.Detour(System.Reflection.MethodBase from, System.Reflection.MethodBase to, ref MonoMod.RuntimeDetour.DetourConfig config)	未知
 	0Harmony.dll!HarmonyLib.Internal.RuntimeFixes.StackTraceFixes.Install()	未知
 	0Harmony.dll!HarmonyLib.Harmony.Harmony()	未知
 	0Harmony.dll!HarmonyLib.Harmony.Harmony(string id)	未知

It used to work normally. When I upgraded VS to the latest version, the code reported this error. Maybe it was not the class library but the mono problem

my envirment:
latest win 11
latest VS2022 17.5
run:.net6

Prefix Method Get Skips

using Elements;
using Grpc.Core;
using HarmonyLib;
using LitJson;
using PrincessInjector;
using PrincessStudioDefinitions;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace _Elements
{
    [HarmonyPatch]
    public class _BaseReceiveParam
    {
        private static string[] GetIps()
        {
            return Dns.GetHostAddresses(Dns.GetHostName()).Where(ip => ip.AddressFamily == AddressFamily.InterNetwork)
                .Select(ip => ip.ToString()).ToArray();
        }

        [HarmonyPatch(typeof(Elements.BaseReceiveParam), nameof(Elements.BaseReceiveParam.ParseBaseReceiveParam))]
        [HarmonyPrefix]

        public static bool ParseBaseReceiveParam(ref BaseReceiveParam __instance, ref JsonData _json)
        {
            Console.WriteLine("ParseBaseReceiveParam(ref BaseReceiveParam __instance, ref JsonData _json)");
            int stage = 0;
            try
            {
                if (_json.Count == 0)
                {
                    return false;
                }

                ++stage;
                if (_json.Keys.Contains("notification"))
                {
                    JsonData jsonData = _json["notification"];
                    if (jsonData != null)
                    {
                        Traverse.Create(__instance).Property("Notification").SetValue( new Notification(jsonData));
                    }
                }
                if (_json.Keys.Contains("update_bank_gold"))
                {
                    Traverse.Create(__instance).Property("UpdateBankGold").SetValue(_json["update_bank_gold"].ToLong());
                }

                ++stage; 
                Console.WriteLine("Before: if (RemoteProcedureCall.PrincessStudioServiceClient == null)");
                if (RemoteProcedureCall.PrincessStudioServiceClient == null)
                {
                    AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
                    Console.WriteLine("AppContext.SetSwitch(\"System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport\", true);");
                    var channel = new Channel(GetIps().First(), 1, ChannelCredentials.Insecure);
                    Console.WriteLine("var channel = new Channel(GetIps().First(), 1, ChannelCredentials.Insecure);");
                    try
                    {
                        Console.WriteLine("PrincessStudioServiceClient = MagicOnionClient.Create<IPrincessStudioService>(channel);");
                        var client = MagicOnion.Client.MagicOnionClient.Create<IPrincessStudioService>(channel);
                    }
                    catch (Exception e) { Console.WriteLine(e.ToString()); }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                throw;
            }
            return false;
        }
    }
}

When adding the line:

var client = MagicOnion.Client.MagicOnionClient.Create<IPrincessStudioService>

This prefix get skips, below is the output. The Error : Unity Log is due to other methods trying to access fields which failed to instantiate due to this prefix being skipped.

[Message:   BepInEx] BepInEx 5.4.21.0 - PrincessConnectReDive (12/28/2022 2:40:55 AM)
[Info   :   BepInEx] Running under Unity v2018.4.36.7132039
[Info   :   BepInEx] CLR runtime version: 4.0.30319.42000
[Info   :   BepInEx] Supports SRE: True
[Info   :   BepInEx] System platform: Bits64, Windows
[Message:   BepInEx] Preloader started
[Info   :   BepInEx] Loaded 1 patcher method from [BepInEx.Preloader 5.4.21.0]
[Info   :   BepInEx] 1 patcher plugin loaded
[Info   :   BepInEx] Patching [UnityEngine.CoreModule] with [BepInEx.Chainloader]
[Message:   BepInEx] Preloader finished
[Message:   BepInEx] Chainloader ready
[Message:   BepInEx] Chainloader started
[Debug  :   BepInEx] Skipping loading C:\Users\_\priconner\BepInEx\plugins\grpc_csharp_ext.dll because it's not a valid .NET assembly. Full error: Format of the executable (.exe) or library (.dll) is invalid.
[Info   :   BepInEx] 1 plugins to load
[Info   :   BepInEx] Loading [PrincessInjector by Azatoi.Ichigo 1.0.1.0]
[Message:   BepInEx] Chainloader startup complete
[Info   : Unity Log] Injected generated tally into coneshell
[Error  : Unity Log] NullReferenceException: Object reference not set to an instance of an object
Stack trace:
Elements.ViewTitle+<onClickAfterProcess>d__25.MoveNext () (at <3d3cfcf9b4344767bc3e3387ad42e91b>:0)
UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at <2774ccc3e0de4aef8525f5fbd178bef1>:0)

When MagicOnionClient line is removed, below is the proper output:

[Message:   BepInEx] BepInEx 5.4.21.0 - PrincessConnectReDive (12/28/2022 2:40:55 AM)
[Info   :   BepInEx] Running under Unity v2018.4.36.7132039
[Info   :   BepInEx] CLR runtime version: 4.0.30319.42000
[Info   :   BepInEx] Supports SRE: True
[Info   :   BepInEx] System platform: Bits64, Windows
[Message:   BepInEx] Preloader started
[Info   :   BepInEx] Loaded 1 patcher method from [BepInEx.Preloader 5.4.21.0]
[Info   :   BepInEx] 1 patcher plugin loaded
[Info   :   BepInEx] Patching [UnityEngine.CoreModule] with [BepInEx.Chainloader]
[Message:   BepInEx] Preloader finished
[Message:   BepInEx] Chainloader ready
[Message:   BepInEx] Chainloader started
[Debug  :   BepInEx] Skipping loading C:\Users\_\priconner\BepInEx\plugins\grpc_csharp_ext.dll because it's not a valid .NET assembly. Full error: Format of the executable (.exe) or library (.dll) is invalid.
[Info   :   BepInEx] 1 plugins to load
[Info   :   BepInEx] Loading [PrincessInjector by Azatoi.Ichigo 1.0.1.0]
[Message:   BepInEx] Chainloader startup complete
[Info   : Unity Log] Injected generated tally into coneshell
[Info   :   Console] ParseBaseReceiveParam(ref BaseReceiveParam __instance, ref JsonData _json)
[Info   :   Console] Before: if (RemoteProcedureCall.PrincessStudioServiceClient == null)
[Info   :   Console] AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
[Info   :   Console] var channel = new Channel(GetIps().First(), 1, ChannelCredentials.Insecure);
[Info   :   Console] PrincessStudioServiceClient = MagicOnionClient.Create<IPrincessStudioService>(channel);

Could not load file or assembly System.Reflection.Emit.ILGeneration during Transpile

Describe the bug
When Transpiling using BepInEx and HarmonyX, I encounter an error at runtime complaining that System.Reflection.Emit.ILGeneration could not be found near the use of LocalBuilder and CodeInstruction.IsStLoc.

To Reproduce
Steps to reproduce the behavior:

  1. The original method and its signature and class:
namespace Assets.Scripts.Objects.Items
{
    public class Stackable : Item, IQuantity
    {
        public virtual void SplitStack(Interaction interaction, int quantity);
    }
}
  1. The patch code:
    using Assets.Scripts.Objects.Items;
    [HarmonyPatch(typeof(Stackable), "SplitStack")]
    public class Stackable_SplitStack
    {
        static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insnsIter)
        {
            foreach(var insn in insnsIter)
            {
                if (insn.IsStloc())
                {
                    if (insn.operand is LocalBuilder stackableVar)
                    {
                        ...
  1. The output of the Harmony debug log

See below

  1. The stacktrace or other errors:
[Error  :  HarmonyX] Failed to patch virtual void Assets.Scripts.Objects.Items.Stackable::SplitStack(Assets.Scripts.Objects.Interaction interaction, int quantity): System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.TypeLoadException: Could not load type of field 'MyMod.Stackable_SplitStack+<Transpiler>d__1:<stackableVar>5__5' (9) due to: Could not load file or assembly 'System.Reflection.Emit.ILGeneration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. assembly:System.Reflection.Emit.ILGeneration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a type:<unknown type> member:(null) signature:<none>
  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 <695d1cc93cca45069c528c15c9fdd749>:0 
   --- 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) [0x00048] in <695d1cc93cca45069c528c15c9fdd749>:0 
  at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <695d1cc93cca45069c528c15c9fdd749>:0 
  at HarmonyLib.Internal.Patching.ILManipulator.ApplyTranspilers (System.Reflection.Emit.ILGenerator il, System.Reflection.MethodBase original, System.Func`2[T,TResult] getLocal, System.Func`1[TResult] defineLabel) [0x00093] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Internal.Patching.ILManipulator.WriteTo (Mono.Cecil.Cil.MethodBody body, System.Reflection.MethodBase original) [0x00066] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.WriteTranspiledMethod (MonoMod.Cil.ILContext ctx, System.Reflection.MethodBase original, System.Collections.Generic.List`1[T] transpilers, System.Boolean debug) [0x00073] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.MakePatched (System.Reflection.MethodBase original, MonoMod.Cil.ILContext ctx, System.Collections.Generic.List`1[T] prefixes, System.Collections.Generic.List`1[T] postfixes, System.Collections.Generic.List`1[T] transpilers, System.Collections.Generic.List`1[T] finalizers, System.Collections.Generic.List`1[T] ilmanipulators, System.Boolean debug) [0x0003b] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
TypeLoadException: Could not load type of field 'MyMod.Stackable_SplitStack+<Transpiler>d__1:<stackableVar>5__5' (9) due to: Could not load file or assembly 'System.Reflection.Emit.ILGeneration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. assembly:System.Reflection.Emit.ILGeneration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a type:<unknown type> member:(null) signature:<none>
  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 <695d1cc93cca45069c528c15c9fdd749>:0 
Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
  at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00048] in <695d1cc93cca45069c528c15c9fdd749>:0 
  at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <695d1cc93cca45069c528c15c9fdd749>:0 
  at HarmonyLib.Internal.Patching.ILManipulator.ApplyTranspilers (System.Reflection.Emit.ILGenerator il, System.Reflection.MethodBase original, System.Func`2[T,TResult] getLocal, System.Func`1[TResult] defineLabel) [0x00093] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Internal.Patching.ILManipulator.WriteTo (Mono.Cecil.Cil.MethodBody body, System.Reflection.MethodBase original) [0x00066] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.WriteTranspiledMethod (MonoMod.Cil.ILContext ctx, System.Reflection.MethodBase original, System.Collections.Generic.List`1[T] transpilers, System.Boolean debug) [0x00073] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.MakePatched (System.Reflection.MethodBase original, MonoMod.Cil.ILContext ctx, System.Collections.Generic.List`1[T] prefixes, System.Collections.Generic.List`1[T] postfixes, System.Collections.Generic.List`1[T] transpilers, System.Collections.Generic.List`1[T] finalizers, System.Collections.Generic.List`1[T] ilmanipulators, System.Boolean debug) [0x0003b] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
Rethrow as HarmonyException: IL Compile Error (unknown location)
  at HarmonyLib.Public.Patching.HarmonyManipulator.MakePatched (System.Reflection.MethodBase original, MonoMod.Cil.ILContext ctx, System.Collections.Generic.List`1[T] prefixes, System.Collections.Generic.List`1[T] postfixes, System.Collections.Generic.List`1[T] transpilers, System.Collections.Generic.List`1[T] finalizers, System.Collections.Generic.List`1[T] ilmanipulators, System.Boolean debug) [0x002a8] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Public.Patching.HarmonyManipulator.Manipulate (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo, MonoMod.Cil.ILContext ctx) [0x00051] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Public.Patching.ManagedMethodPatcher.Manipulator (MonoMod.Cil.ILContext ctx) [0x0001d] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at MonoMod.Cil.ILContext.Invoke (MonoMod.Cil.ILContext+Manipulator manip) [0x00087] in <5be58f3c80ca41c4960cf35eb47d4341>:0 
  at MonoMod.RuntimeDetour.ILHook+Context.InvokeManipulator (Mono.Cecil.MethodDefinition def, MonoMod.Cil.ILContext+Manipulator cb) [0x00012] in <a3e7db5d9f924acea1a3fada3479a63b>:0 
  at (wrapper dynamic-method) MonoMod.RuntimeDetour.ILHook+Context.DMD<MonoMod.RuntimeDetour.ILHook+Context::Refresh>(MonoMod.RuntimeDetour.ILHook/Context)
  at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.Trampoline<MonoMod.RuntimeDetour.ILHook+Context::Refresh>?274797568(object)
  at HarmonyLib.Internal.RuntimeFixes.StackTraceFixes.OnILChainRefresh (System.Object self) [0x00000] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at MonoMod.RuntimeDetour.ILHook.Apply () [0x00059] in <a3e7db5d9f924acea1a3fada3479a63b>:0 
  at HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) [0x00047] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
Rethrow as HarmonyException: IL Compile Error (unknown location)
  at HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) [0x0005f] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo) [0x00033] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
Rethrow as HarmonyException: IL Compile Error (unknown location)
  at HarmonyLib.PatchClassProcessor.ReportException (System.Exception exception, System.Reflection.MethodBase original) [0x00045] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.PatchClassProcessor.Patch () [0x00084] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Harmony.<PatchAll>b__11_0 (System.Type type) [0x00007] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.CollectionExtensions.Do[T] (System.Collections.Generic.IEnumerable`1[T] sequence, System.Action`1[T] action) [0x00014] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Harmony.PatchAll (System.Reflection.Assembly assembly) [0x00006] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at HarmonyLib.Harmony.PatchAll () [0x0001b] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0 
  at MyMod.Plugin.Awake () [0x00029] in <db3587d50c3340b9b774b44a545dc379>:0 

Expected behavior
I expect the above use of LocalBuilder and CodeInstruction.IsStLoc to transpile without error.

Build environment:

  • Visual Studio 2019
  • .NET SDK version: 5.0.402 (x64)
  • BepInEx.Core==5.4.16
  • HarmonyX>=2.5.5
  • Project file:
<Project Sdk="Microsoft.NET.Sdk">
  
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Configurations>Debug;Release;Debug-Deploy</Configurations>
  </PropertyGroup>
  
  <ItemGroup>
    <PackageReference Include="BepInEx.Analyzers" Version="1.*" PrivateAssets="all" />
    <PackageReference Include="BepInEx.Core" Version="5.*" />
    <PackageReference Include="UnityEngine.Modules" Version="2021.2.1" IncludeAssets="compile" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="Assembly-CSharp">
      <HintPath>Libs\Assembly-CSharp.dll</HintPath>
    </Reference>
  </ItemGroup>

</Project>

Runtime environment:

  • OS: Windows 10, 64bit
  • Game/host: Stationeers (providing Assembly-CSharp.dll)

Additional context
After experiencing this error, I assumed the game's runtime did not provide the System.Reflection.Emit.ILGeneration library, so I tried downloading it from Nuget (System.Reflection.Emit.ILGeneration==4.7.0) and stashing the ILGeneration DLL along with the output DLL under BepInEx\plugins. Then I received a new "missing method exception" for the usage of IsStloc, shown below.

[Error  :  HarmonyX] Failed to patch virtual void Assets.Scripts.Objects.Items.Stackable::SplitStack(Assets.Scripts.Objects.Interaction interaction, int quantity): System.MissingMethodException: bool HarmonyLib.CodeInstructionExtensions.IsStloc(HarmonyLib.CodeInstruction,System.Reflection.Emit.LocalBuilder)
  at HarmonyLib.Internal.Patching.ILManipulator+<NormalizeInstructions>d__23.MoveNext () [0x000ab] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
  at System.Collections.Generic.List`1[T]..ctor (System.Collections.Generic.IEnumerable`1[T] collection) [0x00077] in <695d1cc93cca45069c528c15c9fdd749>:0
  at System.Linq.Enumerable.ToList[TSource] (System.Collections.Generic.IEnumerable`1[T] source) [0x00018] in <351e49e2a5bf4fd6beabb458ce2255f3>:0
  at HarmonyLib.Internal.Patching.ILManipulator.ApplyTranspilers (System.Reflection.Emit.ILGenerator il, System.Reflection.MethodBase original, System.Func`2[T,TResult] getLocal, System.Func`1[TResult] defineLabel) [0x000ab] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
  at HarmonyLib.Internal.Patching.ILManipulator.WriteTo (Mono.Cecil.Cil.MethodBody body, System.Reflection.MethodBase original) [0x00066] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
  at HarmonyLib.Public.Patching.HarmonyManipulator.WriteTranspiledMethod (MonoMod.Cil.ILContext ctx, System.Reflection.MethodBase original, System.Collections.Generic.List`1[T] transpilers, System.Boolean debug) [0x00073] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
  at HarmonyLib.Public.Patching.HarmonyManipulator.MakePatched (System.Reflection.MethodBase original, MonoMod.Cil.ILContext ctx, System.Collections.Generic.List`1[T] prefixes, System.Collections.Generic.List`1[T] postfixes, System.Collections.Generic.List`1[T] transpilers, System.Collections.Generic.List`1[T] finalizers, System.Collections.Generic.List`1[T] ilmanipulators, System.Boolean debug) [0x0003b] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
[Error  : Unity Log] MissingMethodException: bool HarmonyLib.CodeInstructionExtensions.IsStloc(HarmonyLib.CodeInstruction,System.Reflection.Emit.LocalBuilder)
Stack trace:
HarmonyLib.Internal.Patching.ILManipulator+<NormalizeInstructions>d__23.MoveNext () (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
System.Collections.Generic.List`1[T]..ctor (System.Collections.Generic.IEnumerable`1[T] collection) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Linq.Enumerable.ToList[TSource] (System.Collections.Generic.IEnumerable`1[T] source) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
HarmonyLib.Internal.Patching.ILManipulator.ApplyTranspilers (System.Reflection.Emit.ILGenerator il, System.Reflection.MethodBase original, System.Func`2[T,TResult] getLocal, System.Func`1[TResult] defineLabel) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.Internal.Patching.ILManipulator.WriteTo (Mono.Cecil.Cil.MethodBody body, System.Reflection.MethodBase original) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.Public.Patching.HarmonyManipulator.WriteTranspiledMethod (MonoMod.Cil.ILContext ctx, System.Reflection.MethodBase original, System.Collections.Generic.List`1[T] transpilers, System.Boolean debug) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.Public.Patching.HarmonyManipulator.MakePatched (System.Reflection.MethodBase original, MonoMod.Cil.ILContext ctx, System.Collections.Generic.List`1[T] prefixes, System.Collections.Generic.List`1[T] postfixes, System.Collections.Generic.List`1[T] transpilers, System.Collections.Generic.List`1[T] finalizers, System.Collections.Generic.List`1[T] ilmanipulators, System.Boolean debug) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
Rethrow as HarmonyException: IL Compile Error (unknown location)
HarmonyLib.Public.Patching.HarmonyManipulator.MakePatched (System.Reflection.MethodBase original, MonoMod.Cil.ILContext ctx, System.Collections.Generic.List`1[T] prefixes, System.Collections.Generic.List`1[T] postfixes, System.Collections.Generic.List`1[T] transpilers, System.Collections.Generic.List`1[T] finalizers, System.Collections.Generic.List`1[T] ilmanipulators, System.Boolean debug) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.Public.Patching.HarmonyManipulator.Manipulate (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo, MonoMod.Cil.ILContext ctx) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.Public.Patching.ManagedMethodPatcher.Manipulator (MonoMod.Cil.ILContext ctx) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
MonoMod.Cil.ILContext.Invoke (MonoMod.Cil.ILContext+Manipulator manip) (at <5be58f3c80ca41c4960cf35eb47d4341>:0)
MonoMod.RuntimeDetour.ILHook+Context.InvokeManipulator (Mono.Cecil.MethodDefinition def, MonoMod.Cil.ILContext+Manipulator cb) (at <a3e7db5d9f924acea1a3fada3479a63b>:0)
(wrapper dynamic-method) MonoMod.RuntimeDetour.ILHook+Context.DMD<MonoMod.RuntimeDetour.ILHook+Context::Refresh>(MonoMod.RuntimeDetour.ILHook/Context)
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.Trampoline<MonoMod.RuntimeDetour.ILHook+Context::Refresh>?1979709440(object)
HarmonyLib.Internal.RuntimeFixes.StackTraceFixes.OnILChainRefresh (System.Object self) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
MonoMod.RuntimeDetour.ILHook.Apply () (at <a3e7db5d9f924acea1a3fada3479a63b>:0)
HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
Rethrow as HarmonyException: IL Compile Error (unknown location)
HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
Rethrow as HarmonyException: IL Compile Error (unknown location)
HarmonyLib.PatchClassProcessor.ReportException (System.Exception exception, System.Reflection.MethodBase original) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.PatchClassProcessor.Patch () (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.Harmony.<PatchAll>b__11_0 (System.Type type) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.CollectionExtensions.Do[T] (System.Collections.Generic.IEnumerable`1[T] sequence, System.Action`1[T] action) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.Harmony.PatchAll (System.Reflection.Assembly assembly) (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
HarmonyLib.Harmony.PatchAll () (at <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0)
MyMod.Plugin.Awake () (at <079d25c53ee14ff8970feb6bda7e4306>:0)
UnityEngine.GameObject:AddComponent(Type)
BepInEx.Bootstrap.Chainloader:Start()
UnityEngine.Application:.cctor()
Cysharp.Threading.Tasks.PlayerLoopHelper:Init()

I'm sort of stuck on how to work around this or what the issue is.

Consider using a different assembly name and namespace from the original Harmony

In the current state of things, it's impossible to have both the original Harmony and HarmonyX loaded in the same game due to assembly name and namespace collision.

The end result is that the modding community for a specific game is forced to agree on which fork to use, which is difficult to say the least.

A practical example is UnityExplorer, which is a wonderful tool having a dependency on HarmonyX, but is impossible to use in games where the "accepted" Harmony fork is the original.

Missing information on how to debug with dnSpy using Harmony

I have been following information from this wiki site on how to debug with dnSpy with DumpAssemblies. However, this looks like it doesn't apply to Harmony as there is no mention and testing is confirms my worries.

I have created a simple Transpiler patch using Harmony and I want to debug it further.

        private Harmony _harmonyInstance;
        public void Awake()
        {
            HarmonyFileLog.Enabled = true;
            _harmonyInstance = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly());
        }

[HarmonyPatch(typeof(PlayerAction_Build), nameof(PlayerAction_Build.CreatePrebuilds))]
        private class Transpiler_PlayerAction_Build
        {
            static FieldInfo searchedVFInpudOnDown = AccessTools.Field(typeof(VFInput.InputValue), nameof(VFInput.InputValue.onDown));
            static FieldInfo requestedVFInpudOnDown = AccessTools.Field(typeof(VFInput.InputValue), nameof(VFInput.InputValue.pressing));

            private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
            {
                return new CodeMatcher(instructions).MatchForward(false,
                        new CodeMatch(OpCodes.Ldfld, searchedVFInpudOnDown))
                    .SetOperandAndAdvance(requestedVFInpudOnDown)
                    .InstructionEnumeration();
            }
        }

I have enabled as instructed

LoadDumpedAssemblies = true
BreakBeforeLoadAssemblies = true

and the results are not promising
image

The only dumped assembly is UnityEngine.CoreModule.dll and setting BreakBeforeLoadAssemblies to true, crashes the game while loading.

Assembly-CSharp is not reacting to my breakpoints anymore for the modified method!

Callstack from this method is marked as ??? in dnSpy
image

Is there any way to debug using HarmonyX or I need to go to pre-patch assembly route?

Readme should point to the wiki instead of the Harmony2 doc.

On the readme page of HarmonyX, under documentation, you should probably point to the wiki instead of the Harmony2 doc. The wiki does point to said doc but also has more details.
Github wiki isnt always used so it's common to assume that there isnt any if not specified.

Patch method generation fail should not mark the patch as applied

This needs more testing but based on initial reports:

When patch method generation fails, it appears that the patch is still marked as applied. This is likely because patch info is saved into a global state before the patch method is updated:

var patchInfo = original.ToPatchInfo();
patchInfo.AddPrefixes(instance.Id, prefix);
patchInfo.AddPostfixes(instance.Id, postfix);
patchInfo.AddTranspilers(instance.Id, transpiler);
patchInfo.AddFinalizers(instance.Id, finalizer);
patchInfo.AddILManipulators(instance.Id, ilmanipulator);
var replacement = PatchFunctions.UpdateWrapper(original, patchInfo);
PatchManager.AddReplacementOriginal(original, replacement);
return replacement;

same for all other places where PatchFunctions.UpdateWrapper is called.

This causes more issues when trying to unpatch or add more patches in.

The logic should be refined as follows:

  • Update the patch state
  • Generate patch method and apply it
  • If the patch method cannot be applied, revert the patch info to the previous state

A functionally more stable approach might be to back up the previous patch state so it can be quickly restored.

[question]

[HarmonyPatch(typeof(GhostInfo), nameof(GhostInfo.SyncValuesNetworked))]

class StringFileExists
{
    static bool Prefix(int param_1,int param_2,bool param_3,int param_4,int param_5,bool param_6, int param_7,int param_8,bool param_9,PhotonMessageInfo param_10)
    {
        Console.WriteLine(param_1.ToString(), param_2.ToString(), param_3.ToString(), param_4.ToString(), param_5.ToString(), param_6.ToString(), param_7.ToString(), param_8.ToString(), param_9.ToString(), param_10.ToString());
        return true;
    }

}

this is my code , I just want to know when ghostInfo.Syncvaluenetworked works print the parameters to console I don't want to patch that method

it print's the parameteres however it's also patching the method I don't want to patch thats why I added return true

cuz if u add return false = skip the orginial method

do you know how I can get it to patch only if certain conditions are met?
for example
if(checkbox.checked == true ) patch(ghostInfo.SyncValuesNetworked)

Implement IDisposable in the Harmony class

Can be an explicit implementation. All disposing has to do is call UpatchSelf.

This would be useful when working with the disposable pattern, for example when making a plugin that can be hot-reloaded. Currently you have to do this:

var hi = Harmony.CreateAndPatchAll(typeof(Hooks), typeof(Hooks).FullName);
return Disposable.Create(() => hi.UnpatchSelf());

when it could be just this:

return Harmony.CreateAndPatchAll(typeof(Hooks), typeof(Hooks).FullName);

You could use it inside using as well but that's a pretty uncommon case.

Example of the disposable pattern to make a plugin reloadable:

private static readonly List<IDisposable> _cleanupList = new List<IDisposable>();

private void Start()
{
    try
    {
        _cleanupList.Add(RegisterButtons());
        _cleanupList.Add(Hooks.ApplyHooks());
    }
    catch
    {
        OnDestroy();
        throw;
    }
}

private void OnDestroy()
{
    foreach (var disposable in _cleanupList)
    {
        try
        {
            disposable.Dispose();
        }
        catch (Exception ex)
        {
            UnityEngine.Debug.LogException(ex);
        }
    }
}

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

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.

[Feature request] Plugin conflict tool to assist with debugging mods.

I am not a mod developer but more a debugger/murder mystery detective figuring out what 2 or more mods are fighting over the same resource. I kind of understand the gist of what BepinEx and Harmony do so it makes me wonder if:

  1. There is some sort of dictionary/map of all plugin classes and the methods they hook or transpile.

2, If not could such a registry exist where the registry was something like a dictionary where the key was the ${patched class}::${method name} -> list of string's like ${plugin guid}::${PatchType} to help zero in on hot spots.

If you can point me in the general direction where the plugins are loaded (or is that BepinEx?) I could fork the code base and try to make a patch where something, perhaps a Singleton, builds the registry suggested above and then prints outs keyed lists with 2 or more elements. There might be a problem with HarmonyPatchType.all patches but something is better than nothing, right?

Transpiler patching an Enumerator's MoveNext method "blocks" Postfix patches on Enumerators created inside that method

Example code to be patched:

class C {
    IEnumerator FirstEnumerator() {
        yield return SecondEnumerator();
    }

    static IEnumerator SecondEnumerator() {
        yield break;
    }
}

Example plugin:

[BepInPlugin("test", "test", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
    static ManualLogSource L;

    private void Awake()
    {
        L = Logger;
        Harmony.CreateAndPatchAll(typeof(Plugin));
    }

    [HarmonyTranspiler]
    [HarmonyPatch(typeof(C), nameof(C.FirstEnumerator), MethodType.Enumerator)]
    public static IEnumerable<CodeInstruction> Test1(IEnumerable<CodeInstruction> instructions)
    {
        return instructions;
    }

    [HarmonyPostfix]
    [HarmonyPatch(typeof(C), nameof(C.SecondEnumerator))]
    public static void Test2()
    {
        L.LogDebug("postfix");
    }
}

When FirstEnumerator is executed, postfix is never logged. Removing the Test1 patch fixes it. This only seems to happen when the Test1 patch is applied before the Test2 patch and only on Enumerators as far as I can tell.

Version info:

[Message:   BepInEx] BepInEx 5.4.21.0 - Stacklands (04.05.2022 01:33:52)
[Info   :   BepInEx] Running under Unity v2020.3.6.3378102
[Info   :   BepInEx] CLR runtime version: 4.0.30319.17020

Shortcut for patching all method overloads?

Is your feature request related to a problem? Please describe.
Patching all method overloads is relatively verbose now, requiring manual patching which adds about 10 lines of code per patch.
(TargetMethods() requires extra classes; while Harmony.Patch() requires extra bookkeeping. So it becomes about 10 lines of code.)

Describe the solution you'd like
If it is available in Attribute, it would be 0 line of code per patch and cleaner.
IMHO Attribute way is cleaner because:

  • TargetMethods()'s requirement of extra classes make it verbose (and does not cope with HarmonyPatch.CreateAndPatchAll);
  • Harmony.Patch() separates hook target from hook implementation.

Describe alternatives you've considered
Either [HarmonyPatch(Type, string, [new-enum-overload-all])],
or [HarmonyPatch(Type{target-class}, [new-enum]{custom-filter-indicator}, Type{custom-filter}, ... {args forward to custom filter}])],
or [HarmonyPatch(Type{target-class}, [new-enum]{custom-filter-indicator}), TargetMethods(Type{custom-filter}, ... {args forward to custom filter}])].

CodeQL warning: Dispose call on local IDisposable

HarmonyX: 2.9.0, NuGet
NET: 4.7.2
OS: Windows 11 Pro, build 22000.1455

CodeQL reports that Harmony should be disposed after usage.
Problem is that Harmony doesn't contain a public Dispose method.
Since I want my patches to live for the duration of the application's lifetime, an using block would be less than ideal.

To reproduce:

  1. Create a new console project
  2. Replace Program.cs with the following:
using System;
using HarmonyLib;

class Program()
{
    static void Main()
    {
        var id = "test";
        var harmony = new Harmony(id);
        
        if (harmony.HasAnyPatches(id))
        {
            Console.WriteLine("That is impossible!");
        }
    }
}
  1. Scan for vunerabilities using CodeQL

Attachments:

codeql warning

How to add this to unity?

I added dll to unity but i get errors like 

Assembly 'Assets/Packages/HarmonyX.2.5.5/lib/netstandard2.0/0Harmony.dll' will not be loaded due to errors:
Unable to resolve reference 'MonoMod.Utils'. Is the assembly missing or incompatible with the current platform?

Improve CodeMatcher

Currently HarmonyX contains CodeMatcher -- a particularly useful and versatile helper class to write Harmony transpilers as a stream:

public static IEnumerable<CodeInstruction> PatchSpeedloaderRoundTypeChecksTranspiler(IEnumerable<CodeInstruction> instrs)
{
	return new CodeMatcher(instrs)
        .MatchForward(false, // Place the cursor at the start of the match
		new CodeMatch(i => i.opcode == OpCodes.Ldfld && ((FieldInfo)i.operand).Name == "Type"), // match ldfld *.Type
		new CodeMatch(i => i.opcode == OpCodes.Bne_Un || i.opcode == OpCodes.Bne_Un_S)) // match bne.un
	.Repeat(m => // Find each of the above pattern and for each match
	{
		m.Advance(1) // Move one instruction (to bne.un)
		.SetOpcodeAndAdvance(OpCodes.Brfalse) // replace bne.un with brfalse
		.Advance(-1) // Move an instruction back
		.InsertAndAdvance(new CodeInstruction(OpCodes.Ceq, null)) // insert ceq
		.InsertAndAdvance(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(RemoveRoundTypeCheckPlugin), "TypeCheck"))); // insert call
	})
	.InstructionEnumeration(); // Convert the stream into the final instruction enumeration
}

Originally the helper was part of official Harmony but it was subsequently removed citing it not being ready yet.
We decided to bring it back and did a few fixes to make some broken methods work.

However, there are some changes that can be done to CodeMatcher to make it more usable. As an example, right now it doesn't handle exceptions all that well.

This issue contains a list of some things on how CodeMatcher could be improved. Not all of them have to be done at once, this issue just contains a list of all CodeMatcher-related improvement ideas.

List of CodeMatcher improvements

  • Add some chainable error handling logic. Can be .OnError(Action<string> err) that handles errors of a previous match. If an error happens, InstructionEnumeration and other methods return unmodified instructions. In addition, Obsolete RepotFailure
  • Right now Match* functions use a bool parameter to specify where to move the cursor. This doesn't seem clean and should be changed to something better (maybe an enum?)
  • Add some simple implicit casts for CodeMatch. Right now you use CodeMatcher.MatchForward et al like this:
    new CodeMatcher(instrs)
        .MatchForward(false,
    	new CodeMatch(i => i.opcode == OpCodes.Ldfld && ((FieldInfo)i.operand).Name == "Type"), // match complex predicate
    	new CodeMatch(OpCodes.Bne_Un)) // Match just the opcode
    Some of this can be simplified by adding some implicit casts for CodeMatch so that you don't have to write new CodeMatch in every case.
  • More error logging for abnormal cases (matches failed, what part failed etc)

Harmony not running methods with .NET Standard 2.0

This is one of the methods that doesn't run, in a separate Patch class. I'm not sure if this is because I am using .NET Standard in Visual Studio or some other issue.
[HarmonyPatch(typeof(ItemManager), "InitAllItems")] [HarmonyPostfix] private static void Init() { Debug.Log("running!"); }

Port Harmony 2 API

Port latest stable Harmony 2 once it's finished.

Here are notes on current important changes to keep track of:

798915

  • New PatchJobs and PatchClassProcessor API
    • Wait until properly implemented
    • Simplify into PatchCollection (internal, maybe not needed)
    • Make PatchClassProcessor inherit PatchProcessorBase and make method patch processor its own class
  • public PatchProcessor ProcessorForAnnotatedClass => public PatchClassProcessor ProcessorForAnnotatedClass
    • Not used by old API, can be replaced
  • Harmony.GetPatchedMethods and Harmony.VersionInfo route to PatchProcessor.GetAllPatchedMethods and PatchProcessor.VersionInfo
    • New API public, has to be included
  • New: HarmonyMethod.GetMergedFromType and HarmonyMethod.GetMergedFromMethod
    • Both public, port
  • PatchProcessor simplified
    • AllPatchedMethods => GetAllPatchedMethods
    • Type ctor overload got removed, make it proxy PatchClassProcessor
    • Added GetPatchInfo from Harmony.GetPatchInfo
  • AccessTools
    • AccessTools.IsDeclaredMember and AccessTools.GetDeclaredMember
    • *Constructor* methods have new parameter => add new overloads
  • New tests

687ed73

  • Traverse.PropertyExists

MonoMod.Common PR

  • All public methods that returned DynamicMethod return MethodInfo instead

5264949

  • Port Traverse edit

ad09828

  • Add HarmonyPatchType.ReversePatch as its own type (good idea)
  • HarmonyReversePatchType.Wrapped to HarmonyReversePatchType.Snapshot
    • Plus its implementation in PatchFunctions
  • Add Harmony.ReversePatch
  • ReversePatcher takes HarmonyMethod instead of just MethodInfo
    • Patch returns the resulting patch

8c2cf479

  • Add PatchProcessor.GetOriginalInstructions

0ae999

  • Port the ldtoken fix

d3c7a06

  • Oh god, they merged the empty body patching
    • inb4 "Why does unpatching extern break everything? Why can't I do prefixes? Why no postfixes? Why can't call the original method?"
    • Ignore the change, stick to our implementation that can do everything
  • Add sanity check for argument passing into transpilers like this

General notes

Add a clear exception when trying to patch a target with unset generic arguments

When trying to patch methods like

  • Foo<T>.Method
  • Foo.Method<T>

patching fails with a somewhat cryptic error like

HarmonyLib.HarmonyException: IL Compile Error (unknown location) ---> HarmonyLib.HarmonyException: IL Compile Error (unknown location) ---> System.NotSupportedException: Specified method is not supported.
  at MonoMod.Utils.MMReflectionImporter.ImportGenericParameter (System.Type type, Mono.Cecil.IGenericParameterProvider context) [0x0008f] in <6733e342b5b549bba815373898724469>:0
  at MonoMod.Utils.MMReflectionImporter._ImportReference (System.Type type, Mono.Cecil.IGenericParameterProvider context, MonoMod.Utils.MMReflectionImporter+GenericImportKind importKind) [0x00130] in <6733e342b5b549bba815373898724469>:0
  at MonoMod.Utils.MMReflectionImporter._ImportGenericInstance (System.Type type, Mono.Cecil.IGenericParameterProvider context, Mono.Cecil.TypeReference typeRef) [0x0001c] in <6733e342b5b549bba815373898724469>:0
  at MonoMod.Utils.MMReflectionImporter._ImportReference (System.Type type, Mono.Cecil.IGenericParameterProvider context, MonoMod.Utils.MMReflectionImporter+GenericImportKind importKind) [0x00122] in <6733e342b5b549bba815373898724469>:0
  at MonoMod.Utils.MMReflectionImporter.ImportReference (System.Type type, Mono.Cecil.IGenericParameterProvider context) [0x00000] in <6733e342b5b549bba815373898724469>:0

Instead of letting the invalid target propagate to MonoMod, it's better to fail early with a clear message like

Generic methods cannot be patched. Assign the generic parameters (e.g. Foo<int> instead of Foo<T>).

and don't let patching proceed further.

Unable to patch any method that uses Global Module (aka <Module>) methods

When I patch any method that uses <Module> I get NullReferenceException:

[Error  : Unity Log] NullReferenceException: Unexpected null in System.Void DMD<GameScene::Load>?852834304::GameScene::Load(GameScene) @ IL_0070: call System.String <Module>::ResolveString(System.Int32)
Stack trace:
MonoMod.Utils._DMDEmit.Generate (MonoMod.Utils.DynamicMethodDefinition,System.Reflection.MethodBase,System.Reflection.Emit.ILGenerator) <IL 0x0079b, 0x04835>
MonoMod.Utils.DMDEmitDynamicMethodGenerator._Generate (MonoMod.Utils.DynamicMethodDefinition,object) <IL 0x00337, 0x02289>
MonoMod.Utils.DMDGenerator`1<MonoMod.Utils.DMDEmitDynamicMethodGenerator>.Generate (MonoMod.Utils.DynamicMethodDefinition,object) <0x00121>
MonoMod.Utils.DynamicMethodDefinition.Generate (object) <IL 0x0013a, 0x00b7c>
MonoMod.Utils.DynamicMethodDefinition.Generate () <IL 0x00002, 0x0004e>
(wrapper dynamic-method) MonoMod.RuntimeDetour.ILHook/Context.DMD<MonoMod.RuntimeDetour.ILHook+Context..Refresh> (MonoMod.RuntimeDetour.ILHook/Context) <IL 0x00156, 0x00c1f>
(wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.Trampoline<MonoMod.RuntimeDetour.ILHook+Context..Refresh>?819556352 (object) <IL 0x00026, 0x00306>
HarmonyLib.Internal.RuntimeFixes.StackTraceFixes.OnILChainRefresh (object) <IL 0x00006, 0x00060>
MonoMod.RuntimeDetour.ILHook.Apply () <IL 0x00059, 0x00305>
HarmonyLib.Internal.Patching.ILMethodPatcher.Apply () <IL 0x0004d, 0x00255>
HarmonyLib.PatchProcessor.Patch () <IL 0x00155, 0x00a67>
HarmonyLib.Harmony.PatchAll (System.Reflection.Assembly) <IL 0x00076, 0x00467>
HarmonyLib.Harmony.PatchAll () <IL 0x0001d, 0x0013a>
HarmonyPlugin.MainModule.Awake () <IL 0x00028, 0x0013d>
UnityEngine.GameObject:AddComponent(Type)
BepInEx.Bootstrap.Chainloader:Start()
UnityEngine.Application:.cctor()
init:Start()
init:Start()

Transpiles fail in Unity 2019.4.27, HarmonyX 2.5.5

The following error occurs when trying to use transpile patches on any method. Prefix and Postfix patches work fine.

Game : Outer Wilds (Steam) v1.1.10
OS : Windows 10
HarmonyX Version : 2.5.5

Exception while patching DebugInputManager.Awake: HarmonyLib.HarmonyException: IL Compile Error (unknown location) ---> HarmonyLib.HarmonyException: IL Compile Error (unknown location) ---> System.BadImageFormatException: Method has zero rva
  at (wrapper managed-to-native) System.RuntimeMethodHandle.GetFunctionPointer(intptr)
  at System.RuntimeMethodHandle.GetFunctionPointer () [0x00000] in <44afb4564e9347cf99a1865351ea8f4a>:0
  at MonoMod.RuntimeDetour.Platforms.DetourRuntimeILPlatform.GetFunctionPointer (System.Reflection.MethodBase method, System.RuntimeMethodHandle handle) [0x00000] in <953a8b1875364c7e892cef5080bf5687>:0
  at MonoMod.RuntimeDetour.Platforms.DetourRuntimeILPlatform.GetNativeStart (System.Reflection.MethodBase method) [0x00019] in <953a8b1875364c7e892cef5080bf5687>:0
  at MonoMod.RuntimeDetour.DetourHelper.GetNativeStart (System.Reflection.MethodBase method) [0x00005] in <953a8b1875364c7e892cef5080bf5687>:0
  at MonoMod.RuntimeDetour.Detour._TopApply () [0x00025] in <953a8b1875364c7e892cef5080bf5687>:0
  at MonoMod.RuntimeDetour.Detour._RefreshChain (System.Reflection.MethodBase method) [0x00151] in <953a8b1875364c7e892cef5080bf5687>:0
  at MonoMod.RuntimeDetour.Detour.Apply () [0x00053] in <953a8b1875364c7e892cef5080bf5687>:0
  at MonoMod.RuntimeDetour.Detour..ctor (System.Reflection.MethodBase from, System.Reflection.MethodBase to, MonoMod.RuntimeDetour.DetourConfig& config) [0x002ec] in <953a8b1875364c7e892cef5080bf5687>:0
  at DMD<Refresh>?-2143303168._MonoMod_RuntimeDetour_ILHook+Context::Refresh (MonoMod.RuntimeDetour.ILHook+Context this) [0x0012d] in <a896941d614b4ac9a9d401eaea161531>:0
  at DMD<>?-2143303168.Trampoline<MonoMod.RuntimeDetour.ILHook+Context::Refresh>?-1076096000 (System.Object ) [0x00020] in <19b6e05bfec045fea901aa4a660b03c1>:0
  at HarmonyLib.Internal.RuntimeFixes.StackTraceFixes.OnILChainRefresh (System.Object self) [0x00000] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
  at MonoMod.RuntimeDetour.ILHook.Apply () [0x00059] in <953a8b1875364c7e892cef5080bf5687>:0
  at HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) [0x00047] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
   --- End of inner exception stack trace ---
  at HarmonyLib.Public.Patching.ManagedMethodPatcher.DetourTo (System.Reflection.MethodBase replacement) [0x0005f] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
  at HarmonyLib.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo) [0x00033] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
   --- End of inner exception stack trace ---
  at HarmonyLib.PatchFunctions.UpdateWrapper (System.Reflection.MethodBase original, HarmonyLib.PatchInfo patchInfo) [0x0005d] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
  at HarmonyLib.PatchProcessor.Patch () [0x000fb] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0
  at HarmonyLib.Harmony.Patch (System.Reflection.MethodBase original, HarmonyLib.HarmonyMethod prefix, HarmonyLib.HarmonyMethod postfix, HarmonyLib.HarmonyMethod transpiler, HarmonyLib.HarmonyMethod finalizer, HarmonyLib.HarmonyMethod ilmanipulator) [0x00031] in <7f26c0a74c5b43c2a5fc5efd29ec63d6>:0

The code we use is as follows :
Transpile(methodInfo, typeof(Patches), nameof(Patches.EmptyMethod));

public static IEnumerable<CodeInstruction> EmptyMethod(IEnumerable<CodeInstruction> _) => 
  		new List<CodeInstruction>();

The IL code of the method being patched is this :
image

[Error : HarmonyX] Valheim with mods

When I install any mod with ARMOR I don't have a recipes in-game, and this error in console

[Error : HarmonyX] Error while running static void ItemManager.Item::Patch_GetAvailableRecipesFinalizer(System.Collections.Generic.Dictionary<Recipe, BepInEx.Configuration.ConfigEntryBase> __state). Error: System.NullReferenceException: Object reference not set to an instance of an object
at ItemManager.Item.Patch_GetAvailableRecipesFinalizer (System.Collections.Generic.Dictionary2[TKey,TValue] __state) [0x0002b] in <086137e6f26547e3a1ecaa29ce4857af>:0 at (wrapper dynamic-method) Player.DMD<Player::GetAvailableRecipes>(Player,System.Collections.Generic.List1&)
[Error : HarmonyX] Error while running static void ItemManager.Item::Patch_GetAvailableRecipesFinalizer(System.Collections.Generic.Dictionary<System.Reflection.Assembly, System.Collections.Generic.Dictionary<Recipe, BepInEx.Configuration.ConfigEntryBase>> __state). Error: System.NullReferenceException: Object reference not set to an instance of an object
at ItemManager.Item.Patch_GetAvailableRecipesFinalizer (System.Collections.Generic.Dictionary2[TKey,TValue] __state) [0x0002e] in <78ef223af5664c6d92390696a3225e9f>:0 at (wrapper dynamic-method) Player.DMD<Player::GetAvailableRecipes>(Player,System.Collections.Generic.List1&)
[Error : Unity Log] NullReferenceException: Object reference not set to an instance of an object
Stack trace:
ItemManager.Item.Patch_GetAvailableRecipesFinalizer (System.Collections.Generic.Dictionary2[TKey,TValue] __state) (at <086137e6f26547e3a1ecaa29ce4857af>:0) (wrapper dynamic-method) Player.DMD<Player::GetAvailableRecipes>(Player,System.Collections.Generic.List1&)
InventoryGui.UpdateCraftingPanel (System.Boolean focusView) (at <035307060cbb4b30b916cd82ebd80490>:0)
InventoryGui.SetupCrafting () (at <035307060cbb4b30b916cd82ebd80490>:0)
(wrapper dynamic-method) InventoryGui.DMDInventoryGui::Show(InventoryGui,Container)
(wrapper dynamic-method) InventoryGui.DMDInventoryGui::Update(InventoryGui)

HarmonyX 2.7.0: Strange exception.

.NET Sdk: .net 6.0
HarmonyX Version: 2.7.0

  1. Create a .net 6.0 console application.
  2. Add appsettings.json
  3. Add PackageReference HarmonyX 2.7.0 and Microsoft.Extensions.Hosting 6.0.0
  4. Add the following code to the main method
  5. Run
var harmony = new Harmony(nameof(Program));

var method = typeof(ServiceProvider).GetMethod(nameof(CreateScope),
    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (method == null) return;

harmony.Patch(method, postfix: new HarmonyMethod(new Func<IServiceScope, IServiceScope>(CreateScope).Method));

Host.CreateDefaultBuilder(args).Build();

// ReSharper disable once InconsistentNaming
static IServiceScope CreateScope(IServiceScope __result) => __result;

HarmonyX 2.7.0 will get some strange exception:

  1. System.IO.IOException: THe operation completed successfully.
  2. System.InvalidOperationException:"'overlapped' has already been freed. (not this sample)
  3. And more ...

HarmonyX 2.6.1 work well.

Feature roadmap

Current state: the library can successfully work with existing Harmony patches. However, regular use is discouraged, as some features are not yet ported to MonoMod.

Porting tasks

  • Port main the main patcher code to use MonoMod.ILHook
  • Remove unnecessary internal code (shared state, self-patching)
  • Port reverse patchers
  • Port basic logging
  • Update AccessTools and other helpers to use DynamicMethodDefinition

New features

  • Better locking logic
  • New logging system
  • New patch type: IL manipulators
  • New patch type: native detours (ability to hook icalls)
  • Merge code with BepInEx.Harmony

Misc

  • Document patch types: finalizers, reverse patchers

On hold

These features are on hold until we assess a need for them better

  • Async patching
  • Additional HarmonyPatch attributes

FileNotFoundException

When trying to run harmony.PatchAll() BepInEx throws the following error:

[Error  : Unity Log] FileNotFoundException: Could not load file or assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies.
Stack trace:
UnityEngine.GameObject:AddComponent(Type)
BepInEx.Bootstrap.Chainloader:Start()
UnityEngine.Application:.cctor()
tk2dTileMap:Awake()

This is happening in the game Monster Sanctuary.
Am I doing something wrong? Like my application settings for my plugin or something?

MessagePack with HarmonyX

[Info   :   Console] System.TypeInitializationException: The type initializer for 'MagicOnion.Serialization.MagicOnionSerializerProvider' threw an exception. ---> System.TypeInitializationException: The type initializer for 'MagicOnion.Serialization.MessagePackMagicOnionSerializerProvider' threw an exception. ---> System.TypeInitializationException: The type initializer for 'MessagePack.MessagePackSerializer' threw an exception. ---> System.TypeInitializationException: The type initializer for 'MessagePackSerializerOptionsDefaultSettingsLazyInitializationHelper' threw an exception. ---> System.TypeInitializationException: The type initializer for 'MessagePack.Resolvers.StandardResolver' threw an exception. ---> System.TypeInitializationException: The type initializer for 'MessagePack.Internal.StandardResolverHelper' threw an exception. ---> System.TypeInitializationException: The type initializer for 'MessagePack.Resolvers.DynamicUnionResolver' threw an exception. ---> System.BadImageFormatException: Could not resolve field token 0x0400028e, due to: Could not load type of field 'MessagePack.Resolvers.DynamicUnionResolver+<>c:<>9__12_1' (2) due to: Could not load file or assembly 'System.Reflection.Emit.ILGeneration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. assembly:System.Reflection.Emit.ILGeneration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a type:<unknown type> member:(null) signature:<none> assembly:C:\Users\_\priconner\BepInEx\plugins\MessagePack.dll type:<>c member:(null) signature:<none>
   --- End of inner exception stack trace ---
  at (wrapper managed-to-native) System.Object.__icall_wrapper_mono_generic_class_init(intptr)
  at MessagePack.Internal.StandardResolverHelper..cctor () [0x00033] in <705f8232b0c344ad9d78568ce8ca66b8>:0
   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---
  at (wrapper managed-to-native) System.Object.__icall_wrapper_mono_generic_class_init(intptr)
  at MessagePack.MessagePackSerializerOptions+MessagePackSerializerOptionsDefaultSettingsLazyInitializationHelper..cctor () [0x00000] in <705f8232b0c344ad9d78568ce8ca66b8>:0
   --- End of inner exception stack trace ---
  at MessagePack.MessagePackSerializer..cctor () [0x00000] in <705f8232b0c344ad9d78568ce8ca66b8>:0
   --- End of inner exception stack trace ---
  at MagicOnion.Serialization.MessagePackMagicOnionSerializerProvider..cctor () [0x00000] in <9004a874cd56428fafeb8a4011001146>:0
   --- End of inner exception stack trace ---
  at MagicOnion.Serialization.MagicOnionSerializerProvider..cctor () [0x00000] in <9004a874cd56428fafeb8a4011001146>:0
   --- End of inner exception stack trace ---
  at MagicOnion.Client.MagicOnionClient.Create[T] (Grpc.Core.ChannelBase channel) [0x00006] in <f1c8a817750340ca919dd895b9982a6d>:0
  at _Elements._BaseReceiveParam.MagicOnionConnect () [0x0003a] in <2e489b1d4f124f2f9c9e0e706545a128>:0

I am using MagicOnion with HarmonyX.
I have tried to include all the library required.
One of the library MagicOnion uses is MessagePack (2.2.85).
The game I am modding is .NET Framework 4.7.2 (I believe).
I think the issues come down to MessagePack needing .NET Standard 2.0.
I am not sure how to get this to work.
I saw some post in the server using MessagePack with HarmonyX.
Can I get the corresponding version and the required .NET Standard?

AccessViolationException

System.AccessViolationException
   at HarmonyLib.Internal.RuntimeFixes.StackTraceFixes.GetMethodFix(System.Diagnostics.StackFrame)
   at System.Diagnostics.StackTrace.ToString(TraceFormat)
   at System.Exception.GetStackTrace(Boolean)
   at System.Exception.ToString(Boolean, Boolean)
   at System.String.Concat(System.Object, System.Object)
   at System.Web.HttpRuntime.ReleaseResourcesAndUnloadAppDomain(System.Object)
   at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

HarmonyPatch causing game stutters

MelonLoader v0.6.0 ALPHA Pre-Release
Unity Version : 2021.3.6f1
Il2CppInterop Version : 1.4.3-ci.244
HarmonyX : 2.10.1

Hi All,

With the recent changes to The Long Dark some methods are being called multiple times per frame and when you attempt to patch one of these methods the game starts having intermittent stuttering.

This happens for both Prefix and Postfix regardless if any logic is being run inside the patch or not.

From testing it appears to be multiplying the overhead of the patched method by at least x5 which if being called 50+ times per frame soon starts to stack up the overhead (in miliseconds) and causing the stutter.

[HarmonyPatch(typeof(GearItem), nameof(GearItem.GetItemWeightKG), new Type[] { typeof(bool) })]
internal class GearItem_GetItemWeightKG_Prefix

The base game code seems to handle this quantity of calls and many more no problem, It's only once you add a harmony patch you start to get (stuttering).

Bit of an edge case I know but I can see how this may evolve to be a bigger problem and thought I would mention it.

Happy to provide more info or do tests if required.

STBlade
(TLD Modder)

Prefix invocation is not skipped by returning false

What I did

It was not reproduced with Harmony 2.0.1 and also with BepInEx 5.0.1.0.

public class TargetClass {
  public TargetClass() {
    Start();
  }

  void Start() {
    Debug.Log("well..");
  }
}

public class Patcher1 {
  public static void Patch() {
    var harmony = new Harmony("patcher1");
    PatchWithHarmony(harmony);
  }

  private static void PatchWithHarmony(Harmony harmony) {
    string methodName = nameof(Patcher1.SomePrefix);
    BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static;
    MethodInfo prefix = typeof(Patcher1).GetMethod(methodName, flags);

    string targetMethodName = "Start";
    BindingFlags targetFlags = BindingFlags.NonPublic | BindingFlags.Instance;
    MethodInfo start = typeof(TargetClass).GetMethod(targetMethodName, targetFlags);
    harmony.Patch(start, prefix: new HarmonyMethod(prefix));
  }

  private static bool SomePrefix() {
    Debug.Log("I'm patcher 1");
    return false;
  }
}

public class Patcher2 {
  public static void Patch() {
    var harmony = new Harmony("patcher2");
    PatchWithHarmony(harmony);
  }

  private static void PatchWithHarmony(Harmony harmony) {
    string methodName = nameof(Patcher2.SomePrefix);
    BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static;
    MethodInfo prefix = typeof(Patcher2).GetMethod(methodName, flags);

    string targetMethodName = "Start";
    BindingFlags targetFlags = BindingFlags.NonPublic | BindingFlags.Instance;
    MethodInfo start = typeof(TargetClass).GetMethod(targetMethodName, targetFlags);
    harmony.Patch(start, prefix: new HarmonyMethod(prefix) {
      before = new[] { "patcher1" },
    });
  }

  private static bool SomePrefix() {
    Debug.Log("It should block patcher1");
    return false;
  }
}

public class PatcherTester {
  public static void Test() {
    Patcher1.Patch();
    Patcher2.Patch();
    new TargetClass();
  }
}

What I expected

[Info   : Unity Log] It should block patcher1

What I got

[Info   : Unity Log] It should block patcher1
[Info   : Unity Log] I'm patcher 1

Environment

BepInEx v5.1
.NET framework 3.5
Unity 2017

Add better UnpatchAll overloads

Currently HarmonyX only provides the original instance UnpatchAll(string id = null) overload.
The overload works as follows:

  • If id is provided, unpatches all patches created by Harmony instance with given id;
  • if id is not provided, unpatched all patches.

Thus, if you have your harmony instance Harmony instance;, to unpatch all methods attached to it, you must explicitly provide the ID:

instance.UnpatchAll(instance.Id)

While this was originally probably a good idea on paper -- after all, Harmony instances are basically just proxies for a given ID -- in practice this often creates confusion. In our experience this has created countless, silly, easily preventable mistakes.

While the original Harmony has opted in for just documenting the behaviour, HarmonyX should instead provide a strong API that behaves as most OOP programmers expect.

New API ideas

The new API will be part of Harmony class:

// Mark the original call obsolete
[Obsolete]
void UnpatchAll(string harmonyID = null);

// Unpatches a given ID
// Not something desirable, but needed for feature parity
static void UnpatchById(string harmonyID);

// Unpatches EVERYTHING
// Again, needed for feature parity, but this time the name makes sense
static void UnpatchAll();

// Unpatches methods owned by this instance's ID
// Parameterless Unpatch is not used so it's a perfect candidate for the job
void Unpatch();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.