Writing stub patch methods - TiberiumFusion/TTPlugins GitHub Wiki
This section explains how to create stub patch methods & define patch operations. This is how your plugin actually modifies Terraria's code.
TTPlugins uses Harmony 2 for dynamic patching. As such, when you write a patch method for you plugin, you are actually writing a Harmony stub method. Understanding how to write stub methods for Harmony is best explained by the Harmony 2 wiki. Specifically, these pages:
TTPlugins only exposes a subset of Harmony's features, which includes prefix patches, postfix patches, transpiler patches, and all variable injection options. Finalizers and reverse patches methods are currently not available in TTPlugins.
Actually changing Terraria's code requires two things:
- A
static
stub patch method somewhere in your plugin's class - Calling
CreateHPatchOperation()
on your plugin to tell the plugin framework what you want to patch and how you want to patch it.
This long article covers both. Make sure you scroll all the way to the bottom to read about using CreateHPatchOperation()
correctly!
Stub patch methods are specially-designated methods within your plugin which will be either prepended onto (prefixed), appended onto (postfixed), or interweaved with (transpiled) existing methods inside Terraria. To get your code to execute inside Terraria, you write a stub patch method in your plugin and use CreateHPatchOperation()
to tell the plugin framework where you want to apply your patch method. You can create as many stub patch methods as your plugin needs.
IMPORTANT: All stub patch methods must be static
!
Inside Terraria, there's type named Player
and it contains a method named Update(int i)
. That might look something like this:
public void Update(int i)
{
Debug.WriteLine("I am the original method.");
// other code
}
We want to patch this method, so we call this the target method or original method.
Let's say this is the stub patch method that we wrote in our plugin:
public static void Update()
{
Debug.WriteLine("I am the the patch method.");
}
If we were to prefix the stub method onto the original method, then run Terraria, we would see this in a debugger:
I am the patch method.
I am the original method.
If we were to postfix the stub method onto the original method, then run Terraria, we would see this in a debugger:
I am the original method.
I am the patch method.
Prefix patches are stub patch methods that execute immediately before the original method executes. Among other things, you can use a prefix patch to:
- Modify the input parameters that the original method will receive
- Run your own implementation of the original method, then prevent the original method from running
- Run code that's completely unrelated to the original method (i.e. simply using the original method as a specific target site to hook onto)
- Run code that works in cooperation with a postfix patch on the same target method
Prefix patches can have either void
or bool
for their return type.
- Basic prefixes use a
void
return type. They do not alter the execution flow. - Advanced prefixes use a
bool
return type and can prevent the original method from running.- If the prefix patch returns
true
, the original method is allowed to run. - If the prefix patch returns
false
, the original method is skipped. Additionally, any other prefix patches on the same original method will be skipped.
- If the prefix patch returns
Postfix patches are stub patch methods that execute immediately after the original method executes. Among other things, you can use a postfix patch to:
- Modify the return value of the original method
- Run code that's completely unrelated to the original method (i.e. simply using the original method as a specific target site to hook onto)
- Run code that works in cooperation with a prefix patch on the same target method
Postfix patches typically have a void
return type. For advanced usage, other return types can be used, as explained in the "Pass through postfixes" section on the Harmony Wiki.
Transpilers are a third type of dynamic patch which modifies the individual instructions that constitute the body of the target method. If, for example, your plugin needs to change a 0 to a 1 in the middle of some method, you will need to use a transpiler.
Harmony's transpilers do not allow you to directly modify the MSIL or metadata of the target method. Instead, the Harmony library provides an abstraction layer around the familiar Emit
namespace, via its own CodeInstruction
class. It is not Cecil, but the API is sufficient for manipulating any method's body to your plugin's needs. Transpilers are an advanced topic and are not explained here on the TTPlugins wiki. Please refer to the Harmony wiki pages on Transpilers and CodeInstruction
.
By adding specially named parameters to your stub patch method, you can:
- Read/modify the parameters that the original method receives
- Access the instance calling the method
- Access private fields on the instance calling the method
- Read/modify the return value of the original method
This is explained in full in the Injections article on the Harmony Wiki.
Simply add a parameter to your patch method that has the exact same type and name as the parameter on the original method.
For example, let's say we want to patch the Terraria.Player.Update()
method. Here's the signature:
public void Update(int i) { /* code */ }
If we want to access parameter i
in our stub patch method, we would simply add int i
to our patch method's parameters:
public static void Update_MyPrefixPatch(int i) { /* code */ }
NOTE: If you want to modify a parameter and it is a value type (like the int i
in the example above), then you need to ref
the parameter, like so:
public static void Update_MyPrefixPatch(ref int i) { /* code */ }
Otherwise, the parameter value you get will be passed by value and modifying it will have no effect on the original method. If you are modifying a pass by reference type, like StringBuilder
or Terraria.Player
, you don't need to use ref
.
NOTE: You can include as many of original parameters in your patch method as you need. Your patch method does not have to include all the parameters. Just include the ones you need to access.
By adding a parameter named __instance
to your patch method, you can access the instance which is calling the original method. In this way, you can modify instance fields and properties on the object that is calling the original method. Make sure the type of the __instance parameter matches the calling type or can be cast to it!
Continuing with the example from the last section, let's add Terraria.Player __instance
to our patch method.
Here's the original method again:
public void Update(int i) { /* code */ } // This method is defined in Terraria.Player
Here's our new patch method:
public static void Update_MyPrefixPatch(Terraria.Player __instance)
{
__instance.statDefense += 100;
}
Using __instance
, our patch method can access the method caller and add 100 to its public statDefense
field.
By adding a parameter with 3 underscores to your parameter, you can access private fields on the instance that is calling the original method. For example, if we add ___myPrivateField
to our patch method, we can access the instance field named myPrivateField
. If the field type is a pass by value type (such as int
or string
) and you want to modify the field, you must add ref
to the parameter.
Let's return to the Terraria.Player.Update()
example and use a triple underscore parameter to modify the private field itemGrabSpeed
.
Here's the original method again:
public void Update(int i) { /* code */ } // This method is defined in Terraria.Player
Here's our new patch method:
public static void Update_MyPrefixPatch(ref float ___itemGrabSpeed)
{
___itemGrabSpeed = 3f;
}
NOTE: TTPlugins provides a static HHelpers
class which contains helper methods for reading and writing to non-public fields & properties with Reflection. This will be more practical if you need to access many fields and don't want to clutter your patch method with 20+ ___params. For more information on the HHelpers
class, please refer to the article Helper methods.
By adding a parameter named __result
to your patch method, you can access the return value from the original method. You can simply read the return value, or alter it to be something different. Make sure the type of the __result parameter matches the original method's return type or can be cast to it!
This is generally only useful for postfix patches, since the original method will have already run and created a return value. If you add __result
to prefix patches, you'll get default(typeof(__result))
as a placeholder value.
NOTE: If the return type of the original method is a pass by value type (like int
or string
) and you want to modify it, you must add ref
to the __result
parameter.
For example, let's say we want to patch this method in Terraria.Player
:
private bool CanAcceptItemIntoInventory(Terraria.Item item) { /* code */ }
We can use a patch method like this to modify the return value:
public static void CanAcceptItemIntoInventory_MyPostfixPatch(ref bool __result)
{
__result = true;
}
Creating a stub patch method is only half of the recipe. You must also use one of the CreateHPatchOperation()
overloads to tell the plugin framework how you want to apply your stub patch method. Typically, you will want to use the most convenient overload, which will work in 99% of all situations:
protected void CreateHPatchOperation(
string targetTypeFullName,
string targetMethodName,
string stubMethodName,
HPatchLocation patchLocation,
int patchPriority = -1
)
Parameters
-
targetTypeFullName
- The full name of target type (in Terraria) that contains the target method. Example:"Terraria.Main"
. -
targetMethodName
- The name of the target method. Example:"UpdateEquips"
. -
stubMethodName
- The name of the static stub patch method inside your plugin's class. Example:"UpdateEquips_MyPrefixPatch"
. -
patchLocation
- EitherHPatchLocation.Prefix
,HPatchLocation.Postfix
, orHPatchLocation.Transpiler
. -
patchPriority
- Optional parameter. Can be used to order multiple patches on the target method. See the Harmony Wiki for more information.
Each time you call CreateHPatchOperation()
, you are defining a single patch operation for the plugin framework to perform. The best place to create your patch operations is in your plugin's PrePatch()
method.
Example:
public override void PrePatch()
{
CreateHPatchOperation("Terraria.Player", "UpdateEquips", "MyPrefixPatch", HPatchLocation.Prefix); // Define patch operation
}
public static void MyPrefixPatch(Terraria.Player __instance) // Stub patch method
{
__instance.jumpBoost = true;
}
Your plugin can define as many patch operations as your plugin needs.
IMPORTANT: Don't forget that all of your stub patch methods must be static
!
Depending on the kind of functionality you want your plugin to achieve, you will need to patch different parts of Terraria's code. Knowing where to patch is a matter of digging through the Terraria assembly structure, inspecting the execution flow, and testing patches in different locations to find a place that works well for your plugin.
To inspect the Terraria code, you can use a disassembly tool like ILSpy or a simpler reflector like the Visual Studio object browser.
To better understand how to write patch methods and define patch operations, take a look at the Example Plugins and the Harmony 2.0 Wiki.
Read the Using persistent savedata article to learn how to enable and use persistent savedata in your plugin.