Tutorial: Transpilers
Last edited by AtlasVBot on 2025-09-13 18:58:02
Introduction
Here be dragons! This is an advanced-level guide for users who are already very comfortable working with C#. If you're still learning C#, Harmony, or programming in general, you may want to come back later. Generally speaking, transpilers are most commonly used to add extra code to or remove unwanted code from the middle of a method, where prefixes and postfixes can't reach. However, they can do much, much more.
Transpilers are both the most flexible and most powerful tool Harmony has to offer- they allow modders to directly control the code inside of a method. You can use them to change the code into anything you want, though it requires care and practice to make complicated changes without breaking others' mods or even your own. It can also create bugs and errors that can be difficult to diagnose and fix, so it's best to start with simple transpilers and work up to more complicated ones as you gain experience. Correctly written, a transpiler can make significant changes to the game with minimal compatibility issues.
What is a transpiler?
By this point, you are probably aware that the C# code you write is compiled before it is run. However, at least in the case of Stardew & mods, it's not compiled directly to machine-readable code. Instead, it is compiled to an intermediate format- one that is both more easily machine-readable and yet still portable/cross-platform. This is called IL or CIL (Common Intermediate Language). The IL code of both the game and all loaded mods and libraries is available while the game is running. Harmony transpilers take advantage of this to allow you to change that IL, so you can arbitrarily change the way a method works on the fly.
On the most basic level, a Transpiler is a static method that takes one IL sequence and outputs another IL sequence, or null if the patch should not be applied. If multiple transpilers target the same method, the output of one will be fed into the input of the next in order. By default, transpilers run in the order they are added, but this order can be changed using HarmonyBefore and HarmonyAfter annotations. Ideally, a transpiler should make as few changes as possible to maximize compatibility, as making drastic changes may cause transpilers from other mods to fail, and are more likely to break when the game updates.
Examining IL
To modify a method's IL, you need to have some idea of what it is, so you know where and how to make your changes. The easiest way to do this is to use dnSpy or ILSpy to decompile the game (and any mods you want to patch). The following directions in this section are meant for ILSpy, but the general concepts should work equally well with dnSpy. If you haven't decompiled the game before, you can do that now by pressing the folder button in the toolbar, navigating to your game folder, and selecting Stardew Valley.dll. From here, you can easily browse the decompiled c# code of classes and methods. Once you find the method you want to patch, go to the dropdown at the top labeled "C#", and select "IL with C#". This will display the IL of the method, as well as comments illustrating the equivalent lines of C#. Browsing through these can be daunting at first, but it's a good way to get a feel for how IL works, and how code constructs translate into IL sequences. If you are using Visual Studio, there is also an extension available called Microscope that allows you to view the IL of your own code as you edit it.
Architecture & Concepts
IL instructions are processed in order, from beginning to end. Each instruction does one specific operation, such as addition, reading a field, or calling a method. Each one has an OpCode that describes which operation it performs, and some may also have an Operand, which provides additional context for the instruction. For example, ldarg.0 will always get the first parameter of the current method, and does not need additional information, whereas call calls a method, and requires a MethodInfo as an operand to specify which method should be called. For more details on what kinds of opcodes exist, and what operands they require, see here.
The execution state is stored in a stack (FILO) structure. (This is not the same as The Stack managed by the operating system.) Most (but not all) operations will push a value onto the stack, pull one or more values off of the stack, or both. For example, add pulls two values to add and pushes the result, and ldstr pulls none and pushes a constant string value. call and callvirt are special- their behavior depends on the method being called. If the method is not void, it will push a value onto the stack. The number of values it pulls off of the stack is determined by the number of parameters the method has- for static methods, it is the same number, and for instance methods, it is the number of parameters plus one, and the first parameter must be the instance the method is called on. Although the stack is FILO, parameters are passed to a method in the order they appear on the stack, from oldest to newest.
Getting Started
TODO
Using CodeMatcher
TODO
Branch Logic
TODO
Tips & Tricks
TODO