Reverse patch
A reverse patch copies the original, or part of it, into your own callable stub. Typical uses are:
- call a private method through a stub with a known signature
- call the unmodified original implementation
- extract part of a method using a transpiler
Once installed, you call the stub directly.
A reverse patch is a frozen copy. Use a delegate or reflection if you want calls to follow later patches instead.
Defining a reverse patch
Mark your stub with [HarmonyReversePatch] and identify the original with patch annotations:
Match the original's signature. A static stub for an instance method takes that instance as its first argument.
An instance stub's this must have the type expected by the copied IL. An unrelated patch-class instance will not work.
private class OriginalCode
{
private void Test(int counter, string name)
{
// ...
}
}
[HarmonyPatch]
public class Patch
{
[HarmonyReversePatch]
[HarmonyPatch(typeof(OriginalCode), "Test")]
public static void MyTest(object instance, int counter, string name) =>
// its a stub so it has no initial content
throw new NotImplementedException("It's a stub");
}
class Main
{
void Test() =>
// here we call OriginalCode.Test()
Patch.MyTest(originalInstance, 100, "hello");
}
Types of reverse patches
The [HarmonyReversePatch] attribute has two variants:
[HarmonyReversePatch(HarmonyReversePatchType.Original)]
[HarmonyReversePatch(HarmonyReversePatchType.Snapshot)]
The default is Original so you can write [HarmonyReversePatch].
Original gives you the unmodified original method as defined in the dll. No patches or transpilers have touched it.
Snapshot includes the ordinary transpilers registered at that moment, but no prefixes, postfixes, finalizers, or Infixes. Later patches do not update your stub.
Changing the content of the original
A reverse patch transpiler edits the IL copied into your stub. You can use it to extract just the part you need.
Example
Suppose a long method calculates a checksum. Copy it to a Checksum(...) stub and use a transpiler to remove the unrelated code.
The remaining IL must match the stub's inputs and output. If it consumes a string and leaves an integer, the stub could be static int Checksum(string txt).
To define a reverse patch transpiler, put a transpiler into your stub:
private class OriginalClass
{
private string SpecialCalculation(string original, int n)
{
var parts = original.Split('-');
var str = string.Join("", parts) + n;
return str + "Prolog";
}
}
[HarmonyPatch]
public class Patch
{
// When reverse patched, StringOperation will contain all the
// code from the original including the Join() but not the +n
//
// Basically
// var parts = original.Split('-');
// return string.Join("", parts)
//
[HarmonyReversePatch]
[HarmonyPatch(typeof(OriginalClass), "SpecialCalculation")]
public static string StringOperation(string original)
{
// This inner transpiler will be applied to the original and
// the result will replace this method
//
// That will allow this method to have a different signature
// than the original and it must match the transpiled result
//
IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
var list = Transpilers.Manipulator(instructions,
item => item.opcode == OpCodes.Ldarg_1,
item => item.opcode = OpCodes.Ldarg_0
).ToList();
var mJoin = SymbolExtensions.GetMethodInfo(() => string.Join(null, null));
var idx = list.FindIndex(item => item.opcode == OpCodes.Call && item.operand as MethodInfo == mJoin);
list.RemoveRange(idx + 1, list.Count - (idx + 1));
return list.AsEnumerable();
}
// make compiler happy
_ = Transpiler(null);
return original;
}
}