bepinex / harmonyx Goto Github PK
View Code? Open in Web Editor NEWHarmony built on top of MonoMod.RuntimeDetours with additional features
License: MIT License
Harmony built on top of MonoMod.RuntimeDetours with additional features
License: MIT License
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.
[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)
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();
}
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?
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:
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?
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):
HarmonyILManipulator
attributeILContext
as an (optional) parameter and MethodBase of original method as another (optional) parameterIt 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:
[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.
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:
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}])]
.
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);
This code works fine on Dyson Sphere Program 0.6.x (tested with BepInEx 5.4.5 & 5.4.9), however on 0.7.x with the 2 aforementioned versions of BepInEx and also 5.4.11 this code never runs. It works fine if each patch only targets each of the methods separately.
Is this something that can be/has to be fixed within HarmonyX?
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
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
Is there any way to debug using HarmonyX or I need to go to pre-patch assembly route?
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.
MonoMod.ILHook
AccessTools
and other helpers to use DynamicMethodDefinition
BepInEx.Harmony
These features are on hold until we assess a need for them better
HarmonyPatch
attributesThis link here does not work anymore
Wiki Site:
https://github.com/BepInEx/HarmonyX/wiki/Custom-MethodPatcher
Should point to here I guess: https://github.com/BepInEx/BepInEx/blob/ff4e1b7c7f40dbc812cb25c979fd946c5b7e0479/BepInEx.IL2CPP/Hook/IL2CPPDetourMethodPatcher.cs
https://github.com/BepInEx/HarmonyX/wiki/Patching-with-Harmony
PatchAll triggers type resolving for all types inside an assembly, which can in turn trigger unnecessary assemnly
resolving.
[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?
Currently HarmonyX only provides the original instance UnpatchAll(string id = null)
overload.
The overload works as follows:
id
is provided, unpatches all patches created by Harmony instance with given id;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.
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();
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);
}
}
}
Hello! I'm trying to switch the Bannerlord's ecosystem to HarmonyX, but there are mods that use publicly available Mono.Cecil.*
types like Mono.Cecil.ModuleDefinition
from the 0Harmony.dll
assembly.
Could Type Forwarding be added similar to how BepinEx does that? (https://github.com/BepInEx/BepInEx.MelonLoader.Loader/tree/master/MelonLoader/BackwardsCompatibility/ForwardingAttributes)
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:
HarmonyX/Harmony/Public/PatchProcessor.cs
Lines 159 to 169 in 66b8574
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:
A functionally more stable approach might be to back up the previous patch state so it can be quickly restored.
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?
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.
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.
Harmony 2 has finally finalized the reverse patcher API and it can be thus ported over
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!"); }
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()
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?
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).
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.List
1&)
[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.List
1&)
[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.List
1&)
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)
.NET Sdk: .net 6.0
HarmonyX Version: 2.7.0
appsettings.json
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:
HarmonyX 2.6.1 work well.
What is the up-to-date link on HarmonyX discord?
There is the overload PatchAll(System.Type type)
, that patches based on the annotations in the given type. Why not one for UnpatchAll? If it's because of a limitation, is there a workaround?
Similarly to pardeike/Harmony#408, this repository has the same problem: __state
is shared between all methods with the same fullname, but from different assemblies.
This was fixed in pardeike/Harmony@b906d04 by replacing occurrences of FullName
by AssemblyQualifiedName
in MethodPatcher.cs (here, the file is Harmony/Public/Patching/HarmonyManipulator.cs).
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>();
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:
namespace Assets.Scripts.Objects.Items
{
public class Stackable : Item, IQuantity
{
public virtual void SplitStack(Interaction interaction, int quantity);
}
}
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)
{
...
See below
[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:
<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:
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.
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
var method = typeof(HttpRuntime).GetMethod("ReleaseResourcesAndUnloadAppDomain", BindingFlags.Instance | BindingFlags.NonPublic);
var body = new DynamicMethodDefinition(method).Definition.Body;
Assert.NotNull(body);
_ = new ILManipulator(body, false); //NullReferenceException
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
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:
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!");
}
}
}
Attachments:
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
.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
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?)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
CodeMatch
so that you don't have to write new CodeMatch
in every case.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)
Hello, does Method patching work with il2cpp on android? About page a bit unclear about it.
Especially
extern
methodsIn 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.
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.
Install Beat Saber and BSIPA.
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;
}
}
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");
}
}
Put the mod into Beat Saber\Plugins
.
Run the game and start a level.
Alternatively, use the attached plugin: BSPlugin1.zip
The DefaultEnvironmentEventsFactory.InsertDefaultEnvironmentEvents
method shouldn't run and the postfix shouldn't log anything.
The DefaultEnvironmentEventsFactory.InsertDefaultEnvironmentEvents
method runs and the postfix is logging.
I made MonoMod dumps for the unpatched and patched method.
Here's a diff of the patched method, after applying the transpiler:
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.
Traverse.Field and some other methods don't show any warning in log if the member doesn't actually exist (unlike AccessTools), which makes debugging harder.
If the member doesn't exist then a warning should be logged.
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();
}
}
[Info : Unity Log] It should block patcher1
[Info : Unity Log] It should block patcher1
[Info : Unity Log] I'm patcher 1
BepInEx v5.1
.NET framework 3.5
Unity 2017
Port latest stable Harmony 2 once it's finished.
Here are notes on current important changes to keep track of:
PatchJobs
and PatchClassProcessor
API
PatchCollection
(internal, maybe not needed)PatchClassProcessor
inherit PatchProcessorBase
and make method patch processor its own classpublic PatchProcessor ProcessorForAnnotatedClass
=> public PatchClassProcessor ProcessorForAnnotatedClass
Harmony.GetPatchedMethods
and Harmony.VersionInfo
route to PatchProcessor.GetAllPatchedMethods
and PatchProcessor.VersionInfo
HarmonyMethod.GetMergedFromType
and HarmonyMethod.GetMergedFromMethod
PatchProcessor
simplified
AllPatchedMethods
=> GetAllPatchedMethods
PatchClassProcessor
GetPatchInfo
from Harmony.GetPatchInfo
AccessTools
AccessTools.IsDeclaredMember
and AccessTools.GetDeclaredMember
*Constructor*
methods have new parameter => add new overloadsTraverse.PropertyExists
DynamicMethod
return MethodInfo
instead
HarmonyPatchType.ReversePatch
as its own type (good idea)HarmonyReversePatchType.Wrapped
to HarmonyReversePatchType.Snapshot
PatchFunctions
Harmony.ReversePatch
ReversePatcher
takes HarmonyMethod
instead of just MethodInfo
Patch
returns the resulting patchPatchProcessor.GetOriginalInstructions
ldtoken
fixGeneral notes
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()
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.