Show / Hide Table of Contents

Patching

CodeInstruction

The workhorse of a transpiler is the type CodeInstruction.

CodeInstruction is an abstraction around the .NET Emit namespace. The idea is to abstract most of the specific things you can do in order to help multiple mods that patch/manipulate the same method to coexist. As such, specific instructions like "jump 4 instructions forward" are not possible and instead there are a couple of concepts that differ from the arguments that you know from the ordinary Emit arguments.

Every transpiler will receive a list of CodeInstruction and is expected to return a modified list of CodeInstruction. In addition, you can inject an ILGenerator into the transpiler to create one or more Label and instances of LocalBuilder which represent local variables.

Most arguments from Emit() are used in the same way:

  • Emit takes an OpCode and so does CodeInstruction
  • Operands are mostly the same:
    • Type
    • FieldInfo,
    • MethodInfo,
    • ConstructorInfo,
    • Int64/Int32/Int16/Single/Double/String/Byte

Some things though will be restricted:

  • Operands of jumps cannot be numeric, use Label instead
  • SignatureHelper support is experimental at best
  • You should avoid using indices when referring to local variables

Do not use ILGenerator.Emit(). While it does create IL code, Harmony is an abstraction over it. Harmony takes the returned IEnumerable<CodeInstruction> and after some manipulation, calls Emit() itself. ILGenerator should only be used for its methods to define blocks and labels. See Labels and Try/catch boundaries for more information.

In general, it is advised to reuse and to copy existing operands for things like labels and local variables. Search for a significant and unique location in the existing codes and grab the operand from there. This will allow you to refer to local variables and labels in a change-resistant way.

Local variables

The instructions that your transpiler will receive will contain existing local variables and Harmony will not alter the original operands of instructions. This means that you need to be prepared to deal with instructions that refer to local variables either with a number (like "2nd local variable") or with a LocalBuilder object. A LocalBuilder object is an opaque representation of a local variable and you can create a new one using the DeclareLocal method for ILGenerator or copy the operand of an existing instruction.

Labels

All original labels (or those generated by a previous transpiling) are represented by a Label object. They are used in operands of a CodeInstruction or in the labels field (of type Label[]) of it. When you want to create a jump, you specify the jump Opcode and the label as the operand. Then you append the label to the destinations labels.

To create a new label to jump to, use ILGenerator.DefineLabel() and put that label into the labels field of the target CodeInstruction. Use that label as the argument for a jump opcode as described above.

Try/catch boundaries

When constructing methods with instructions, you need to specify the exception block boundaries. Harmony will automatically create the necessary meta information from them. Use the blocks field of an instruction (of type ExceptionBlock[]) to mark the different types of boundaries. They are named in correspondence to the actual names. Please note that filter blocks are unsupported because it's not possible to build them dynamically into a method as of now.

Convenience methods

To create, search and compare instructions, Harmony defines a number of extension methods on CodeInstruction. Those methods make it easier to compare operands (which are defined as type object) with specific values as well as to find specific instructions easier.

You will find more information about those methods in the API documentation.

Pitfalls

A common error is to remove instructions that contain labels or exception blocks without dealing with the corresponding pair. You end up with a dynamic method compile error because an instruction will want to jump to a label that is not assigned to any other instruction. Another common case is if you copy an existing instruction and thus copy the labels and blocks fields with it. You end up with multiple defined labels/block boundaries. Makes you you clear those or use proper duplication.

To avoid such cases, it is important to keep an eye on the labels and blocks fields. CIL is quite sensitive to undefined or duplicate labels. Normally it is enough to move the contents of those fields around if you insert new instructions. One of the typical cases is inserting a new instruction at a place and pushing the old one one index up thus moving the labels on that instruction with it. Moving the labels/blocks from the old instruction to the newly inserted one usually solves the problem.

In general, it is useful to turn the Harmony debug log output on (set Harmony.DEBUG = true or use [HarmonyDebug]). The log will contain all your generated instructions, labels and blocks so you can verify the error more informed.

  • Improve this Doc
In This Article
Back to top Generated by DocFX