Mod development guide/Harmony

From Outward Wiki
Jump to navigation Jump to search

Harmony Patches are used to override Methods or to use them as an entry point for your code, and provide an easy way to add to or modify the game's behaviour. Harmony is a powerful hooking API, which comes with BepInEx. It can be accessed through the namespace HarmonyLib.

Basics of Harmony

First, let's take a look at the basics of Harmony. Before we get started writing a patch, there are some important things to remember:

  • Patch methods need to be static.
  • There are four main types of patches: Prefix, Postfix, Finalizer and Transpiler.

Before you actually write the hook, you'll need to set up the Harmony environment in your project. At the top of your class, add the following using directive:

using HarmonyLib;

In the Awake() of your mod, you need to create an instance of the Harmony class, and then call PatchAll().

This will find all the HarmonyPatch classes you have defined in your project (ie. your DLL, any namespace) and apply them.

internal void Awake()
{
    var harmony = new Harmony("com.author.project"); // rename "author" and "project"
    harmony.PatchAll();
}

Basic Example

For an example, we will patch the ResourcesPrefabManager.Load method with a Prefix, meaning that it will run before the original method, and then a Postfix that will run after it.

This method is when the game loads all Item and Effect prefabs from their resources bundles, so it's a good time to do lots of various things.

[HarmonyPatch(typeof(ResourcesPrefabManager), "Load")]
public class ResourcesPrefabManager_Load
{
    [HarmonyPrefix]
    public static void Prefix(ResourcesPrefabManager __instance)
    {
        // code here will execute before the original method. You could use this as an entry point, or to modify something.
    }

    [HarmonyPostfix]
    public static void Postfix(ResourcesPrefabManager __instance)
    {
        // code here will execute after the original method. You could use this if you want to do something with the result of the method.
    }
}

This was a very simple example, Harmony can do many more things such as modifying the input variables, passing variables between Prefix and Postfix, etc.

Ambiguous Methods

It is not uncommon to encounter multiple methods with the same name on a class, so how do we allow Harmony to distinguish between them? If we try to simply use the name of the method by itself like above, it results in an AmbiguousMatchException.

Well, one of the overloads for [HarmonyPatch(...)] is to include a Type[] array that corresponds to the types of arguments on the method. Let's look at the method QuestEventManager.AddEvent. There are actually two methods with this name: one which expects a string _eventUID, int _stackAmount and a bool _sendEvent, and another method which expects a QuestEventSignature _event and an int _stackAmount. So what would the argument type array look like for these?

// QuestEventManager.AddEvent(string _eventUID, int _stackAmount, bool _sendEvent)
[HarmonyPatch(typeof(QuestEventManager), "AddEvent", new Type[] { typeof(string), typeof(int), typeof(bool) })]

// QuestEventManager.AddEvent(QuestEventSignature _event, int _stackAmount)
[HarmonyPatch(typeof(QuestEventManager), "AddEvent", new Type[] { typeof(QuestEventSignature), typeof(int) })]

By including the Type[] array, Harmony can correctly identify the proper method that we want and avoid any exceptions.

Method Arguments

A lot of the power of Harmony comes from the arguments you can include in your Patch methods. By including ref before the argument, this will allow you to modify the value by reference instead of just getting the value.

Original Arguments

Firstly, you can include any original argument of the method by simply using the same argument Type and Name. For an example of this we will use the QuestEventManager.AddEvent method from above.

// This is an ambiguous method, so we need to pass the argument Type array
[HarmonyPatch(typeof(QuestEventManager), "AddEvent", new Type[] { typeof(string), typeof(int), typeof(bool) })]
public class QuestEventManager_AddEvent
{
    [HarmonyPrefix]
    public static void Prefix(ref string _eventUID, int _stackAmount, bool _sendEvent)
    {
        // We included "ref" before the _eventUID, so we can modify the actual reference object.
        // This is obviously not a valid UID and would break this method.
        _eventUID = "Hello World!"; 

        // We did not put a ref before "_stackAmount" and "_sendEvent", so we cannot modify those actual references.
        // We still got the value of those arguments if we want to debug or use them.
        Debug.Log("_sendEvent: " + _sendEvent);
        // However, changing a value reference that you did not put "ref" before will not change the value for the original method.
        _sendEvent = false; // would have no effect on the original method.
    }
}

Two Underscores

There are some special keyword arguments which require two underscores __ before them, and they are __instance, __result and __state.

The __instance argument will give you the object instance that is calling this method. This is only valid for non-static methods, because static methods are not called by instances, and therefore this keyword would be invalid for them. In other words, "__instance" is the equivalent of the C# keyword "this".

[HarmonyPatch(typeof(Item), "StartInit")]
public class Item_StartInit
{
    [HarmonyPrefix]
    public static void Prefix(Item __instance)
    {
        Debug.Log(__instance.Name);
    }
}

The __result keyword is the return value of the method, which would only be valid if the method does not return void. By including ref before __result, you can modify the return value. You can do this on Prefix, however if your Prefix allows the original to be executed then the result could be overridden by the original method, so normally Postfix or Finalizer is the best time to modify the result.

For example, let's imagine a method that returns a string:

[HarmonyPostfix]
public static void Postfix(ref string __result)
{
    // Let's append "hello" to the end of the string.
    __result += " hello";
}

The __state keyword is for passing data between the Prefix and Postfix. The __state can be ANY object, for example you can make it an object[] __state to pass limitless information between prefix and postfix, or it can just be one value, whatever you need.

Here's a quick example:

[HarmonyPrefix]
public static void Prefix(ref object[] __state)
{
    __state = new object[]
    {
        "1",
        "two",
        3
    };
}

[HarmonyPostfix]
public static void Postfix(object[] __state)
{
    foreach (var obj in __state)
    {
        Debug.Log(obj.ToString());
        // Prints "1", "two", "3"
    }
}

Prefix control-flow

In the case that you want to completely override the original method and never have it run at all, you can use a Prefix which returns false. Returning true or void will allow the method to run.

[HarmonyPatch(typeof(Class), "Method")]
public class Class_Method
{
    // use "static bool Prefix" instead of "static void Prefix"
    [HarmonyPrefix]
    public static bool Prefix()
    {
        // You can return true if you want the original to be called.

        // Let's do nothing, and not call the original method.
        return false;
    }
}

Ideally, you would want to avoid using this wherever possible, but sometimes it is necessary to achieve your desired effect. Note that two patches can't both do a prefix return false on the same method.

Finalizer

A finalizer patch will wrap the entire method inside a try/catch block, and allow you to override or catch any exceptions as well.

This is mainly used for two reasons:

  • To fix a bug with the game
  • To ensure that your code always runs, no matter what other Patches do to this method.

A finalizer patch can make use of an additional two-underscore keyword, __exception, which will be the Exception returned by the method (if any).

[HarmonyPatch(typeof(Class), "Method")]
public class Class_Method
{
    [HarmonyFinalizer]
    public static Exception Finalizer(Exception __exception)
    {
        if (__exception != null)
        {
             // An exception was thrown by the method!
             Debug.Log(__exception.Message);
             Debug.Log(__exception.StackTrace);
        }

        // return null so that no Exception is thrown. You could re-throw as a different Exception as well.
        return null; 
    }
}

Further Reading

For more details on using Harmony, see the Official Documentation and Guides here.

See Also