Confirmedusers
6
edits
Ninjanomnom (talk | contribs) m (Double checked information and added version) |
(Added more info about harmony patches) |
||
Line 2: | Line 2: | ||
== Monkey Patching == | == Monkey Patching == | ||
=== Overview === | === Overview === | ||
Monkey patching (changing code at runtime) in Vintage Story is available through [https://harmony.pardeike.net/articles/basics.html Harmony]. | Monkey patching (changing code at runtime) in Vintage Story is available through [https://harmony.pardeike.net/articles/basics.html Harmony]. | ||
With Harmony you can change methods, constructors and properties. | With Harmony you can change methods, constructors and properties. | ||
Full documentation on harmony is available [https://harmony.pardeike.net/articles/basics.html#creating-a-harmony-instance here.]. | |||
It is recommended that you use [https://github.com/dnSpyEx/dnSpy 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 === | === Getting started === | ||
To use Harmony in you Vintage Story mod you first have to reference the | 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 you have to create a Harmony-instance by calling the constructor with | Then, in whatever <code>Start()</code> 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). | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
var harmony = new Harmony("MyModId"); | var harmony = new Harmony("MyModId"); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Now you are all set to patch everything inside the game like described in the documentation. | 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 <code>harmony.PatchAll()</code> 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 <code>[HarmonyPatch]</code> annotation at the top. | |||
<syntaxhighlight lang="c#"> | |||
[HarmonyPatch] // Place on any class with harmony patches | |||
public class MyModSystem : ModSystem { | |||
public ICoreAPI api; | |||
public Harmony harmony; | |||
public void Start(ICoreAPI api) { | |||
this.api = api; | |||
harmony = new Harmony("MyModId"); | |||
harmony.PatchAll(); // Applies all harmony patches | |||
} | |||
// ... Harmony patch functions here | |||
</syntaxhighlight> | |||
Inside a multi-patch class, add <code>[HarmonyPrefix]</code>, <code>[HarmonyPostix]</code>, and <code>[HarmonyPatch]</code> annotations to tell functions to run before or after a certain in-game function is run. A prefix will run the function <em>before</em> the original function specified by <code>[HarmonyPatch]</code>, while a postfix will run the function <em>after</em> it. These functions should be marked <code>public static void</code> or, if you're writing a prefix and would like to prevent the original function from running, use <code>public static bool</code> and <code>return false</code> to cancel the original (<code>return true</code> 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, [https://harmony.pardeike.net/articles/patching-injections.html 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. | |||
<syntaxhighlight lang="c#"> | |||
[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); | |||
} | |||
</syntaxhighlight> | |||
Changing it to a postfix will instead run the function after the item entity has taken the damage. | |||
<syntaxhighlight lang="c#"> | |||
[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); | |||
} | |||
</syntaxhighlight> | |||
Or, by changing the prefix to return false, we can make items never take damage. | |||
<syntaxhighlight lang="c#"> | |||
[HarmonyPrefix] | |||
[HarmonyPatch(typeof(EntityItem), "ReceiveDamage")] | |||
public static bool ItemReceivedDamageCancel(EntityItem __instance, float damage) { | |||
return false; | |||
} | |||
</syntaxhighlight> | |||
=== 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 [https://en.wikipedia.org/wiki/Stack_(abstract_data_type) 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, [https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes?view=net-7.0#fields look at the MS docs]. | |||
To view the IL code of any in-game function, use a disassembler such as [https://github.com/dnSpyEx/dnSpy dnSpy]. When you know what instruction(s) you want to change, add, or delete, you'll want to use the <code>[HarmonyTranspiler]</code> annotation in combination with a <code>CodeMatcher</code> object to locate and modify the IL code. You could also <code>yield return</code> instructions manually, though that is far more time consuming. For a full list of CodeMatcher methods, [https://harmony.pardeike.net/api/HarmonyLib.CodeMatcher.html see the harmony documentation on the CodeMatcher object]. | |||
This method patches the crash handler to include a little watermark. | |||
<syntaxhighlight lang="c#"> | |||
[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(); | |||
} | |||
</syntaxhighlight> | |||
=== 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 <code>harmony.UnpatchAll("MyModId")</code> in the <code>Dispose</code> method of a ModSystem. Make sure you include the id in the call to <code>UnpatchAll()</code>, as otherwise you may accidentally unpatch other mods before they expect to. | |||
<syntaxhighlight lang="c#"> | |||
public void Dispose() { | |||
harmony.UnpatchAll("MyModId"); | |||
} | |||
} | |||
</syntaxhighlight> | |||
=== Remarks === | === 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. | * 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'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. | ||
{{Navbox/modding|Vintage Story}} | {{Navbox/modding|Vintage Story}} |