Modding:Monkey patching

From Vintage Story Wiki
This page contains changes which are not marked for translation.

This page was last verified for Vintage Story version 1.19.8.

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 or Visual Studio's Object Browser on any DLLs or mods you wish to patch, rather than viewing the source code on GitHub. This is because not every DLL's source code is publicly available. On Linux, dnSpy 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. If using the up-to-date modding templates, this will already be included and referenced.

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(Mod.Info.ModID);

Now you're all set to patch everything inside the game like described in Harmony's documentation.

Basic Patching

The simplest and easiest way to patch code is to run harmony.PatchAll() after initializing harmony with the constructor. Harmony will then search for all classes in the current assembly that are decorated with Harmony annotations.

// This patch targets the TriggerNewServerChatLine method in the ClientEventManager class.
[HarmonyPatch(typeof(Vintagestory.Client.NoObf.ClientEventManager), "TriggerNewServerChatLine")]
public class ModifyMessagesReceivedFromServer
{
    // Prefix patches execute before the original method.
    // To access the method's arguments, we must include its exact name and type.
    // To modify the argument, we must add the "ref" keyword, otherwise
    //     any changes we make won't carry over to the original method.
    public static bool Prefix(ref string message)
    {
        message = "New message from server"; // Replace the message content.
        return true; // Continue execution.
    }
}

return false; would prevent the original method from being executed, as well as similar patches.

To learn more about prefix, postfix, finalizer, and transpiler patches, refer to Harmony's documentation. See also the page about patch priorities.

As seen in the above example, patch methods can be given parameters with the same names as the original, but also injections with double underscore'd special names. For a full list, see this page about injections.

This example shows how the __instance parameter can give you access to an instance of the class that's calling your patched method.

[HarmonyPatch(typeof(ClientEventManager), "TriggerNewServerChatLine")]
public class ModifyMessagesReceivedFromServer
{
    public static bool Prefix(ClientEventManager __instance, ref string message)
    {
        // Since ClientEventManager has a logger, we can use it ourselves.
        __instance.Logger.Warning("Logging from patched ClientEventManager!");
        message = "New message from server";
        return true;
    }
}

Transpilers

Please note: These examples are out of date, you should refer to Harmony's official documentation on proper annotations and patch methods. Otherwise your patches may not be applied.

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();
    }

Directly Triggered Patches

In case you want to trigger certain patches conditionally at specific times and avoid the harmony.PatchAll(), you can directly trigger a patch in code by calling the harmony.Patch() method on your harmony instance.

The Patch() method has the following parameters:

Patch(
    MethodBase original,
    HarmonyMethod prefix = null,
    HarmonyMethod postfix = null,
    HarmonyMethod transpiler = null,
    HarmonyMethod finalizer = null
)

With it you define the original target method for the patch as well as any combination of references to your own methods as patches to apply to the original - for up to date information on different kinds of patches visit the Harmony patching documentation. Let's demonstrate it on a postfix patch (patch applied after the original method finishes completely). First we declare a method that will serve us as our postfix patch:

public class OurPatches
{
    public static void OurPostfix(object[] __args, ref bool __result) {}
}

Notice we are not using any Harmony annotations as they are not desired here.

We can provide the method with optional parameters of object[] __args, which will allow us to read the input arguments of the original method we are patching, and the ref bool __result, which is the result and return type of the original method - in this case a boolean. For this example we will just patch the method to flip the result of the original.

public class OurPatches
{
    public static void OurPostfix(object[] __args, ref bool __result) {
        __result = !__result
    }
}

Now we can trigger the patch by calling the harmony.Patch() in our code, using AccessTools of Harmony to target the desired methods and specifying that we are inserting our patch as the postfix: argument:

harmony.Patch(
    AccessTools.Method(typeof(OriginalMethodClass), "original_method_name"), 
    postfix: AccessTools.Method(typeof(OurPatches), "ourPostfix"));

This can be used to perform patches dynamically based on your needs. For a specific example of conditionally patching other mods, refer to Optional Mod Compatibility page.

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.