CodeMatcher
CodeMatcher is a cursor over IL instructions. Use CodeMatch and Code to find a sequence, then insert, remove, or replace instructions.
Use case
Suppose DamageHandler.Apply() calls Kill() when a character dies. To invoke your OnDeath event there without changing other calls to Kill(), find that call and replace it with MyDeathHandler().
ThrowIfInvalid() reports a missing match by throwing; ReportFailure() reports it through a callback and returns a boolean. These checks help detect broken patches after game updates.
[HarmonyPatch]
public static class DamageHandler_Apply_Patch
{
// See "Auxiliary methods"
static IEnumerable<MethodBase> TargetMethods()
{
var result = new List<MethodBase>();
// ... (targeting all DamageHandler.Apply derived)
return result;
}
static void MyDeathHandler(DamageHandler handler, Player player)
{
// ...
}
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions /*, ILGenerator generator*/)
{
// Without ILGenerator, the CodeMatcher will not be able to create labels
var codeMatcher = new CodeMatcher(instructions /*, ILGenerator generator*/);
codeMatcher.MatchStartForward(
CodeMatch.Calls(() => default(DamageHandler).Kill(default))
)
.ThrowIfInvalid("Could not find call to DamageHandler.Kill")
.RemoveInstruction()
.InsertAndAdvance(
CodeInstruction.Call(() => MyDeathHandler(default, default))
);
return codeMatcher.Instructions();
}
}
ThrowIfNotMatchForward() checks for a forward match from a valid cursor position without moving it. Here, Start() sets that position and MatchStartForward() then moves to the match:
codeMatcher.Start().ThrowIfNotMatchForward("Could not find call to DamageHandler.Kill",
CodeMatch.Calls(() => default(DamageHandler).Kill(default))
)
.MatchStartForward(CodeMatch.Calls(() => default(DamageHandler).Kill(default)))
.RemoveInstruction()
.InsertAndAdvance(
CodeInstruction.Call(() => MyDeathHandler(default, default))
);
If some targets legitimately lack the call, check whether the match succeeded. Start() resets the cursor for another search:
[HarmonyPatch]
public static class DamageHandler_Apply_Patch
{
static IEnumerable<MethodBase> TargetMethods()
{
var result = new List<MethodBase>();
// ... (targeting all DamageHandler.Apply derived)
return result;
}
static void MyDeathHandler(DamageHandler handler, Player player)
{
// ...
}
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions /*, ILGenerator generator*/)
{
var codeMatcher = new CodeMatcher(instructions /*, ILGenerator generator*/);
codeMatcher.MatchStartForward(
CodeMatch.Calls(() => default(DamageHandler).Kill(default))
);
if (codeMatcher.IsValid)
{
codeMatcher.RemoveInstruction()
.InsertAndAdvance(
CodeInstruction.Call(() => MyDeathHandler(default, default))
);
}
codeMatcher.Start();
// Other match...
return codeMatcher.Instructions();
}
}
For several matching calls, use Repeat(). It passes the current matcher to your action and accepts an optional callback for a missing match:
[HarmonyPatch]
public static class DamageHandler_Apply_Patch
{
static IEnumerable<MethodBase> TargetMethods()
{
var result = new List<MethodBase>();
// ... (targeting all DamageHandler.Apply derived)
return result;
}
static void MyDeathHandler(DamageHandler handler, Player player)
{
// ...
}
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions /*, ILGenerator generator*/)
{
var codeMatcher = new CodeMatcher(instructions /*, ILGenerator generator*/);
codeMatcher.MatchStartForward(
CodeMatch.Calls(() => default(DamageHandler).Kill(default))
)
// Only take the last Matching condition.
.Repeat(matchAction: cm =>
{
cm.RemoveInstruction();
cm.InsertAndAdvance(
CodeInstruction.Call(() => MyDeathHandler(default, default))
);
});
return codeMatcher.Instructions();
}
}
Repeat() repeats Match...() searches, not Search...(). If your action needs another Match...(), use a clone so you do not replace the pattern being repeated.