Modding:Monkey patching

From Vintage Story Wiki
(Redirected from Monkey patching)

This page was last verified for Vintage Story version 1.19.3.


Monkey patching (changing code at runtime) in Vintage Story is available through Harmony. With Harmony you can change methods, constructors and properties. Full documentation on harmony is available here..

It is recommended that you use dnSpy on any dlls or mods you wish to patch, rather than viewing the source code on GitHub. This is because there is often more in the dlls than can be found on GitHub. On Linux, this executable can be run through wine.

Getting Started

To use Harmony in you Vintage Story mod you first have to reference the "0Harmony.dll" file in the Vintage Story "Lib" directory (VintageStory\Lib\0Harmony.dll) in your project.

Then, in whatever Start() method you use, you have to create a Harmony-instance by calling the constructor with your unique identifier, preferrably your modid (this lets harmony differentiate between different patches and allows other people to patch your patches).

var harmony = new Harmony("MyModId");

Now you are all set to patch everything inside the game like described in the documentation.

Basic Patching

The simplest and easiest way to patch code is to run harmony.PatchAll() after initializing harmony with the constructor, and to put your patches into various "multi-patch classes". To mark a class as one of these multi-patch classes, put the [HarmonyPatch] annotation at the top.

[HarmonyPatch] // Place on any class with harmony patches
public class MyModSystem : ModSystem {
    public static ICoreAPI api;
    public Harmony harmony;
    
    public override void Start(ICoreAPI api) {
        MyModSystem.api = api;
        // The mod is started once for the server and once for the client.
        // Prevent the patches from being applied by both in the same process.
        if (!Harmony.HasAnyPatches(Mod.Info.ModID)) {
            harmony = new Harmony(Mod.Info.ModID);
            harmony.PatchAll(); // Applies all harmony patches
        }
    }
    // ... Harmony patch functions here

Inside a multi-patch class, add [HarmonyPrefix], [HarmonyPostix], and [HarmonyPatch] annotations to tell functions to run before or after a certain in-game function is run. A prefix will run the function before the original function specified by [HarmonyPatch], while a postfix will run the function after it. These functions should be marked public static void or, if you're writing a prefix and would like to prevent the original function from running, use public static bool and return false to cancel the original (return true to run the original anyway).

Every patch function can be given parameters with the same names as the original, or special names with double underscores at the start. For a full list of special names parameters can have, see this page of the harmony documentation.

This function will print "{item stack} is about to take {damage} damage!" to the console whenever an item entity in the world is about to receive damage.

    [HarmonyPrefix]
    [HarmonyPatch(typeof(EntityItem), "ReceiveDamage")]
    // Note that the name of the function does not matter
    public static void ItemReceivedDamage(EntityItem __instance, float damage) { // For methods, use __instance to obtain the caller object
        api.Logger.Event("{0} is about to take {1} damage!", __instance.Slot.Itemstack, damage);
    }

Changing it to a postfix will instead run the function after the item entity has taken the damage.

    [HarmonyPostfix]
    [HarmonyPatch(typeof(EntityItem), "ReceiveDamage")]
    public static void ItemReceivedDamageAfter(EntityItem __instance, float damage) {
        api.Logger.Event("{0} took {1} damage!", __instance.Slot.Itemstack, damage);
        api.Logger.Event("Is it still alive? {0}", __instance.Alive);
    }

Or, by changing the prefix to return false, we can make items never take damage.

    [HarmonyPrefix]
    [HarmonyPatch(typeof(EntityItem), "ReceiveDamage")]
    // Parameters are not explicitly required.
    public static bool ItemReceivedDamageCancel() {
        return false;
    }

Transpilers

Transpilers are a special kind of patch that allow you to change a small part of the code of a function, without canceling the whole thing with a prefix and rewriting it.

To understand transpilers, you first have to understand IL code. IL code is what C# compiles into, and is what is stored and run in any .NET executable or .NET dll files. It's an assembly-like language which uses a stack to manage data. Instructions generally will pop data from the stack, perform some calculation on that data, then push the result back onto the stack. For a full list of IL instructions, look at the MS docs.

To view the IL code of any in-game function, use a disassembler such as dnSpy. When you know what instruction(s) you want to change, add, or delete, you'll want to use the [HarmonyTranspiler] annotation in combination with a CodeMatcher object to locate and modify the IL code. You could also yield return instructions manually, though that is far more time consuming. For a full list of CodeMatcher methods, see the harmony documentation on the CodeMatcher object.

This method patches the crash handler to include a little watermark.

    [HarmonyTranspiler]
    [HarmonyPatch(typeof(CrashReporter), "Crash")]
    public static IEnumerable<CodeInstruction> Crash(IEnumerable<CodeInstruction> instructions, ILGenerator generator) {
        return new CodeMatcher(instructions, generator)
            // This method finds the first time a {ldstr "Game Version: "} instruction occurs.
            // It then places the cursor at the start of it.
            .MatchStartForward(new CodeMatch(OpCodes.Ldstr, "Game Version: "))
            // This method replaces the operand of the instruction that starts at the cursor.
            // Specifically, it replaces the string "Game Version: " with a new string including some extra text.
            .SetOperandAndAdvance("Hello from the wiki!" + Environment.NewLine + "Game Version: ")
            // This method finalizes our changes with the methods above.
            .InstructionEnumeration();
    }

Cleaning Up

For good practice, if you patch the game with your mod, make sure you unpatch it when your mod unloads. The easiest way to do this is to run harmony.UnpatchAll("MyModId") in the Dispose method of a ModSystem. Make sure you include the id in the call to UnpatchAll(), as otherwise you may accidentally unpatch other mods before they expect.

    public override void Dispose() {
        harmony?.UnpatchAll(Mod.Info.ModID);
    }
}

Remarks

  • Just because you can use monkey patching, does not mean you should. Patches are often fragile and break easily (especially if you access internal private methods). So if you can use normal means of modding you probably should.
  • You can't use harmony to patch fields (because they are not called), so in order to change field access you have to patch all calls to this field with patches. You can patch getters and setters, if they are available.


Icon Sign.png

Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.

Modding
Modding Introduction Getting Started Theme Pack
Content Modding Content Mods Developing a Content Mod Basic Tutorials Intermediate Tutorials Advanced Tutorials Content Mod Concepts
Code Modding Code Mods Setting up your Development Environment
Property Overview ItemEntityBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository