CodeMatcher
To make a Transpiler
patch, first we need to find where to patch inside the IL instructions. Since it's an IEnumerable<CodeInstruction>
, we can simply iterate it and compare the IL instruction to our needs. Read about the basic usage here:
Match Direction
CodeMatcher
offers two basic match directions, Forward
and Backwards
, as the name suggests, they match accordingly from current position.
It's important to note that current position starts at 0
(at the beginning), and will be at the last matched position. If you start your code match with a MatchBackwards()
, you won't match anything - because there's nothing before position 0
. You'll need to use End()
to set the position to the end of the IL instructions.
Start/End
You can use MatchStart()
or MatchEnd()
to set the cursor position when a match qualifies. MatchStart()
will set the cursor to the beginning of the match group, MatchEnd()
will be at the end.
ldc.i4.5 <<- MatchStart()
div
sub
ldc.i4.1 <<- MatchEnd()
You may also use Advance(n)
to move the cursor position with an offset.
CodeMatch
A series of CodeMatch
will describe the IL instructions we want to match, you can refer to the usage linked above. Here's a simple rundown:
// match opcode only
new CodeMatch(OpCodes.Call);
// match opcode and operand
new CodeMatch(OpCodes.Call, methodInfo);
// match using predicate
new CodeMatch(o => o.opcode == OpCodes.Call &&
o.operand.ToString().Contains(methodName));
Instruction
Your custom IL instructions are constructed as CodeInstruction
:
new CodeInstruction(OpCodes.Call, myMethodInfo);
Branching
If you want to setup IL branches, including but not limited to Br
, Brfalse
, Brtrue
, you'll need to pass in ILGenerator
to the CodeMatcher
constructor. This argument is provided by Harmony:
internal static IEnumerable<CodeInstruction> Transpiler(
IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
return new CodeMatcher(instructions, generator)
.InstructionEnumeration();
}
NullRefException
If you do not pass in the ILGenerator
provided by Harmony to the CodeMatcher
, any label/branch related methods will throw NullRefException
.
Create Label
To create a label at the current cursor position:
return new CodeMatcher(instructions, generator)
.CreateLabel(out var label)
.InstructionEnumeration();
Consume Label
Depending on the situation, you may need to match the label related instructions first to setup the label, then match the target instructions to consume the label.
return new CodeMatcher(instructions, generator)
.MatchStartForward(
new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(
typeof(PCC),
nameof(PCC.pccm))),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(
typeof(PCCManager),
nameof(PCCManager.pixelize))),
new CodeMatch(OpCodes.Brtrue))
.CreateLabel(out var label)
.InsertAndAdvance(
new CodeInstruction(OpCodes.Ldarg_0),
Transpilers.EmitDelegate(RebuildSprites),
new CodeInstruction(OpCodes.Brfalse, label),
new CodeInstruction(OpCodes.Ret))
.InstructionEnumeration();
Match a Branch
Sometimes you see decompiled IL instructions like these:
brtrue.s
brfalse.s
br.s
These branching instructions are shown in short form. However, when you use Harmony Transpilers, they're replace them with brtrue
, brfalse
, and br
at runtime. So if you cannot qualify a match for a branch instruction, replace your CodeMatch
with the long form instead.