Skipping an instruction with harmony transpiler patch error

89 Views Asked by At

This is my first time using Harmony and IL and I am trying to do what I mention in the title. I want to skip the instruction because it calls a method that I do not want to be called. Here is what I have so far in my patch class:

// Logs to show when the instruction is called
static void TestMethod() {
    Plugin.pluginLog.LogWarning("TestMethod has been called!");
}

private static MethodInfo methodToSkip = AccessTools.Method(typeof(GameClass), "MethodToSkip");

static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) {

    int found = 0;
    foreach (CodeInstruction instr in instructions) {

        if (instr.opcode == OpCodes.Callvirt && instr.Calls(methodToSkip)) {
            found++;
            yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(TestPatch), nameof(TestMethod)));
        } 

        yield return instr;
    
    
    }

    if (found == 0) {
        Plugin.pluginLog.LogError($"Cannot find symbol {methodToSkip}");
    } else {
        Plugin.pluginLog.LogWarning($"Found {found} occurrences of {methodToSkip}");
    }
}

I am able to trigger the log when the instruction is called, but the method I am trying to skip is still called - probably because of the yield return instr line? The issue is if I put that line in an else block following the if statement I get the error "InvalidProgramException: Invalid IL code...". I'm not sure why skipping an instruction would make it invalid, unstable sure but why does Harmony fail to run it.

1

There are 1 best solutions below

2
On

Try setting a local variable and always returning it. This allows you to have a single yield return in the function.

foreach (CodeInstruction instr in instructions) 
{
    // local variable that will contain the original or the new instruction
    var instruction = instr; 

    if (instruction.opcode == OpCodes.Callvirt && instr.Calls(methodToSkip)) 
    {
        found++;
        instruction = new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(TestPatch), nameof(TestMethod)));
    } 

    // now there is only one return and it will contain the new instruction 
    // or the original depending on your if condition above
    yield return instruction;    
    
}