Skip to content

Midfix Patch

BlazingTwist edited this page Jan 4, 2023 · 3 revisions

Midfix Patches are designed to work like Harmony's Prefix patches that includes injections.

The main difference, is that they need a MidFixInstructionMatcher to know where to insert themselves.


Example

To understand how to use Midfix Patches, here's a somewhat realistic example. This is the class we want to patch:

public class Pokemon {
    public int MaxHealth { get; private set; }
    public int Health { get; private set; }
    public int Defense { get; private set; }
    public int CombatExperience { get; private set; }

    public Pokemon(int health, int defense, int combatExperience) {
        MaxHealth = health;
        Health = health;
        Defense = defense;
        CombatExperience = combatExperience;
    }

    public void TakeDamage(int damage) {
        GainCombatExperience((int) (damage * 0.25));
        Health -= (damage - Defense);
    }

    public void GainCombatExperience(int experience) {
        CombatExperience += experience;
    }
}

Suppose we want to add a mechanic that negates the damage a Pokemon takes, if the resulting damage would be less than 20% of its max health.
But we still want the Pokemon to gain combat experience, even if it does negate the damage.

To do that, we'll insert our Midfix patch in the 'TakeDamage' Method, after 'GainCombatExperience' is called.

Here's the Patcher class:

[HarmonyPatch(declaringType: typeof(Pokemon))]
public class Pokemon_Patch {

    [HarmonyPatch(methodName: nameof(Pokemon.TakeDamage))]
    [BTHarmonyMidFix(nameof(TakeDamage_Matcher))]
    private static bool TakeDamage_Midfix(Pokemon __instance, int damage) {
        int maxDamageNegate = (int) (__instance.MaxHealth * 0.2);
        int trueDamage = damage - __instance.Defense;
        return trueDamage > maxDamageNegate; // if damage <= maxDamageNegate, then exit early.
    }

    private static MidFixInstructionMatcher TakeDamage_Matcher() {
        MethodInfo gainCombatExperience = AccessTools.DeclaredMethod(typeof(Pokemon), nameof(Pokemon.GainCombatExperience));

        return new MidFixInstructionMatcher(
                expectedMatches: 1,
                prefixInstructionSequence: new[] {
                        InstructionMask.MatchInstruction(OpCodes.Call, gainCombatExperience),
                }
        );
    }
}

Then, in the same location you placed harmony.PatchAll(), you must add PatcherUtils.PatchAll(harmony)
Or, if you want to apply patch classes individually, you can use PatcherUtils.PatchAll(harmony, typeof(Pokemon_Patch))

Some clarifications:

  • [HarmonyPatch(declaringType: typeof(Pokemon))] - tells Harmony which Class you want to alter.
  • [HarmonyPatch(methodName: nameof(Pokemon.TakeDamage))] - tells Harmony which Method you want to alter.
  • [BTHarmonyMidFix(nameof(TakeDamage_Matcher))] - tells BTHU where to find the MidFixInstructionMatcher.
  • the return value of Midfix Patchers should be read as 'patched method should continue executing'.
    So if you return 'false' the patched Method should stop, if you return 'true' the patched method should continue.
    You can also use 'void' as your return type, which will be treated as returning 'true'.

Once the patch is applied, the 'TakeDamage' method will look like this instead:

public void TakeDamage(int damage) {
    GainCombatExperience((int) (damage * 0.25));
    if (!Pokemon_Patch.TakeDamage_Midfix(this, damage)) {
        return;
    }
    Health -= (damage - Defense);
}

Apply the Patch

Just like with Harmony's Attribute based Patches, you must tell BTHU to apply your MidFix Patches.

Harmony harmony = /* ... */
BTHarmonyUtils.PatcherUtils.PatchAll(harmony);

Other Features

  • Just like with Harmony, you can specify a priority to control the order in which Midfix patches are applied (higher priorities are applied first)
    for example: [BTHarmonyMidFix(nameof(TakeDamage_Matcher), priority: 42)]
  • If your MidFixInstructionMatcher Method is in a different class from the Midfix Patch, you can use the instructionMatcherDeclaringType parameter.
    for example, if it was in a class named 'MyMatchers' instead, you can do this: [BTHarmonyMidFix(typeof(MyMatcher), "TakeDamage_Matcher")]

See also: