Skip to content

Method Body Reader

BlazingTwist edited this page Dec 19, 2022 · 1 revision

Lets you read the instructions of Methods without needing a Transpiler.

Example

Suppose there is a method with a complicated section that you want to copy to a different method.

public class ExampleClass {
    public static void ExampleMethod(int input) {
        Console.WriteLine("msg0");
        int importantValue = (int) Math.Max(input * 12.2f, input + 10);
        Console.WriteLine("msg1 - " + importantValue);
    }
}

You don't want to call ExampleMethod, because it contains a lot more code than you need.
Ideally you could make a copy of just the code you care about, like this:

public class MyClass {
    public static int ImportantValueCalculator(int input) {
        return (int) Math.Max(input * 12.2f, input + 10);
    }
}

Also, you know that several Modders are patching that section, because it's really that important.
Let's write a transpiler that copies the target section, including changes from other Mods, to our own Method.

public class MyClass {
    public static int ImportantValueCalculator(int input) {
        throw new InvalidProgramException("Harmony failed to generate the method body.");
    }
}

[HarmonyPatch(declaringType: typeof(MyClass))]
public class Example_Patch {
    private static readonly ManualLogSource logger = Logger.CreateLogSource(nameof(Example_Patch));

    [HarmonyPatch(methodName: nameof(MyClass.ImportantValueCalculator))]
    [HarmonyTranspiler]
    private static IEnumerable<CodeInstruction> ImportantValueCalculator_Transpiler(IEnumerable<CodeInstruction> originalInstructions) {
        MethodBodyReader reader = new MethodBodyReader(AccessTools.DeclaredMethod(typeof(ExampleClass), nameof(ExampleClass.ExampleMethod)));
        List<CodeInstruction> exampleMethodInstructions = reader.ReadInstructions();

        List<InstructionMask> prefixSequence = new List<InstructionMask> {
                InstructionMask.MatchInstruction(OpCodes.Ldstr, "msg0"),
                InstructionMask.MatchOpCode(OpCodes.Call),
        };

        List<InstructionMask> postfixSequence = new List<InstructionMask> {
                InstructionMask.MatchOpCode(OpCodes.Stloc),
                InstructionMask.MatchInstruction(OpCodes.Ldstr, "msg1 - "),
        };

        List<int> prefixSequenceIndices = InstructionUtils.FindInstructionSequence(exampleMethodInstructions, prefixSequence);
        if (prefixSequenceIndices.Count != 1) {
            logger.LogError($"ImportantValueCalculator_Transpiler - expected 1 match for prefix sequence, but found: {prefixSequenceIndices.Count}");
            return originalInstructions;
        }

        List<int> postfixSequenceIndices = InstructionUtils.FindInstructionSequence(exampleMethodInstructions, postfixSequence);
        if (postfixSequenceIndices.Count != 1) {
            logger.LogError($"ImportantValueCalculator_Transpiler - expected 1 match for postfix sequence, but found: {postfixSequenceIndices.Count}");
            return originalInstructions;
        }

        List<CodeInstruction> resultInstructions = new List<CodeInstruction>();
        resultInstructions.AddRange(exampleMethodInstructions.GetRange(
                prefixSequenceIndices[0] + prefixSequence.Count,
                postfixSequenceIndices[0] - (prefixSequenceIndices[0] + prefixSequence.Count)
        ));
        resultInstructions.Add(new CodeInstruction(OpCodes.Ret));
        return resultInstructions;
    }
}

See also:

Clone this wiki locally