Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use Harmony to repeatedly replace an arbitrary method?

Tags:

c#

cil

I need to replace a method with a call to a method with the same signature so that I can essentially replace the original method with a new method. Currently, I have the code below, which works, but when I try to patch the method again, it simply does nothing. I'm not sure if that's because Harmony doesn't like when I try to transpile it twice, or something else, either way it prevents me from repeatedly redirecting the original method.

// this is factored out of Transpiler() because yield return reasons
private static IEnumerable<CodeInstruction> TranspilerIterator(IEnumerable<CodeInstruction> instructions,
    MethodBase original) {
    var name = original.Name;
    var par = original.GetParameters();
    var method = newGuiType.GetMethod(name, (BindingFlags) FLAGS);
    Console.WriteLine($"{name} == null == {method == null}");

    if ((method.CallingConvention & CallingConventions.HasThis) != 0)
        yield return new CodeInstruction(OpCodes.Ldarg_0);

    for (var i = 0; i < par.Length; i++)
        yield return new CodeInstruction(OpCodes.Ldarg_S, par[i].Position + 1);

    yield return new CodeInstruction(OpCodes.Call, method);
    yield return new CodeInstruction(OpCodes.Ret);
}

which is called by this:

private void DoPatches() {
    Logger.Debug("Performing patches.");

    var methods = oldGuiType.GetMethods((BindingFlags) FLAGS);
    
    var t = this.GetType().GetMethod("Transpiler", BindingFlags.NonPublic | BindingFlags.Static);

    for (var i = 0; i < methods.Length; i++) {
        var name = methods[i].Name;
        Logger.Debug($"Transpiling {name}");
        harmony.Patch(methods[i], transpiler: new HarmonyMethod(t));
    }
}

I can't use a prefix because I need to know the signature to get the args in a prefix, and I don't know the signature.

I know there are other libraries to make essentially this, but the game I'm modding ships with Harmony so I don't have to ship a whole lib with my very small mod.

like image 693
VeryGoodDog Avatar asked Dec 20 '25 20:12

VeryGoodDog


1 Answers

For every change to an original method (like adding or removing a transpiler) Harmony will recalculate the replacement method by doing this (in pseudo code):

original_IL -> transpiler1 -> transpiler2 -> ... -> transpiler_n -> replacement_IL

where transpiler 1..n are all active transpilers. It then build the replacement by (roughly) structuring it like this:

// Note: the following is very simplified and only used to illustrate
// the difference between prefix/postfix and a transpiler.

REPLACEMENT()
{
   if (Prefix_1() == false) return
   // ...
   if (Prefix_n() == false) return

   // replacement_IL_here

   Postfix_1()
   // ...
   Postfix_n()
}

Internally, Harmony has to therefore keep track of all patches and since those can be from different Assemblies it would need to serialize/deserialize state - which is in principle impossible if you have arbitrary state. Therefore, it only stores the simplest key for each patch possible: its MethodInfo which must be a static method.

As a result you cannot apply the same patch multiple times. It makes little sense since you could easily put all your code into one patch anyway.

like image 84
Andreas Pardeike Avatar answered Dec 24 '25 10:12

Andreas Pardeike



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!