Browse documentation

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();
    }
}

note Repeat() repeats Match...() searches, not Search...(). If your action needs another Match...(), use a clone so you do not replace the pattern being repeated.

Harmony 3 preview

For the stable release, read the 2.x documentation.