Difference between revisions of "Modding:Migrate to Harmony 2.0"

From Stardew Valley Wiki
Jump to navigation Jump to search
(+ troubleshooting > "You can only patch implemented methods/constructors")
 
(18 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
←[[Modding:Index|Index]]
 
←[[Modding:Index|Index]]
  
{{SMAPI upcoming|3.''x''}}
+
{{Modder compatibility header}}
  
'''This page is for modders. Players: see [[Modding:Mod compatibility]] instead.'''
+
SMAPI 3.12 updates from Harmony 1.2.0.1 to Harmony 2.1. That only affects mods which use Harmony directly; that's [[Modding:Modder Guide/APIs/Harmony|discouraged in most cases]], isn't officially part of SMAPI's public API, and isn't subject to SMAPI's normal versioning policy. If you use Harmony in your mods, this page explains how the update affects them.
  
This page explains how to update your mods for compatibility with Harmony 2.0. This only applies to mods which use Harmony directly; [[Modding:Modder Guide/APIs/Harmony|this is discouraged]] in most cases, isn't officially part of SMAPI's public API, and isn't subject to SMAPI's normal versioning policy.
+
__TOC__
 +
==Overview==
 +
===What's changing?===
 +
[https://github.com/pardeike/Harmony/releases/tag/v2.0.0 Harmony 2.0] and [https://github.com/pardeike/Harmony/releases/tag/v2.1.0.0 2.1] have many changes that benefit SMAPI and mods. Some notable changes:
 +
* Added [https://harmony.pardeike.net/articles/patching-finalizer.html finalizers] and [https://harmony.pardeike.net/articles/reverse-patching.html reverse patches].
 +
* Added [https://harmony.pardeike.net/articles/patching-postfix.html pass-through postfixes].
 +
* Added [https://harmony.pardeike.net/api/HarmonyLib.Transpilers.html <samp>Manipulator</samp>] utility, <samp>CodeInstruction</samp> extensions, and other improvements for transpilers.
 +
* Added more <samp>AccessTools.Is*</samp> methods.
 +
* Added support for .NET 5.
 +
* Transpilers can now default to the original input by returning <samp>null</samp>.
 +
* Improved compatibility with Android modding.
 +
* Improved exception messages.
 +
* Improved validation for invalid patches.
 +
* Fixed cases where methods were inlined and unpatchable on Linux/Mac.
 +
* Fixed methods with struct return types being unpatchable.
 +
* Various other improvements and fixes; see the [https://github.com/pardeike/Harmony/releases/tag/v2.0.0 Harmony 2.0 release notes], [https://github.com/pardeike/Harmony/releases/tag/v2.1.0.0 Harmony 2.1 release notes], and [https://harmony.pardeike.net new Harmony documentation] for more info.
  
__TOC__
+
After waiting over a year to make sure the release is stable, SMAPI is transitioning to Harmony 2.1.
 +
 
 +
===Is this the modapocalypse?===
 +
Nope. Although this is a major change, significant efforts were undertaken to minimise the impact:
 +
* mods which don't use Harmony directly aren't affected;
 +
* most mods are rewritten automatically for compatibility, so they'll work without an update;
 +
* pull requests were submitted to update affected open-source mods;
 +
* unofficial updates were created as needed for mods which hadn't updated officially;
 +
* the changes were actively communicated and documented to modders.
  
==What's new?==
+
In addition, the target was ''at least'' 90% compatibility for open-source mods before SMAPI migrates. All of this means that the release should have minimal impact on mod compatibility, despite the scope of the changes.
See the [https://github.com/pardeike/Harmony/releases/tag/v2.0.0 Harmony 2.0 release notes] and [https://harmony.pardeike.net Harmony 2.0 documentation] for more info.
 
  
==Migration steps==
+
===How to update your mod===
# Make sure you follow best practices outlined in the [[Modding:Modder Guide/APIs/Harmony|Harmony guide]]. In particular, use the <code>EnableHarmony</code> option (don't reference the Harmony DLL directly) and use the code API.
+
# Make sure you follow best practices outlined in the [[Modding:Modder Guide/APIs/Harmony|Harmony guide]]. In particular, use the <code>EnableHarmony</code> option (don't reference the Harmony DLL directly).
 
# Change <code>using Harmony;</code> to <code>using HarmonyLib;</code>.
 
# Change <code>using Harmony;</code> to <code>using HarmonyLib;</code>.
 
# Change <code>HarmonyInstance harmony = HarmonyInstance.Create("your mod id");</code> to <code>Harmony harmony = new Harmony("your mod id");</code>.
 
# Change <code>HarmonyInstance harmony = HarmonyInstance.Create("your mod id");</code> to <code>Harmony harmony = new Harmony("your mod id");</code>.
 +
# Check if any [[#Breaking changes|breaking changes]] listed below apply to your mod.
 
# Recompile the mod.
 
# Recompile the mod.
  
That's it! Otherwise usage should be identical.
+
==Breaking changes==
 +
===API changes===
 +
See ''[[#How to update your mod|how to update your mod]]''.
 +
 
 +
===Stricter validation===
 +
Harmony 2.x has stricter validation in general, so invalid patches that would previously somewhat work (''e.g.,'' setting <samp>__result</samp> to the wrong type) will now cause errors. See the exception messages for help fixing these.
  
==Troubleshooting==
+
===Patching static constructors===
==="You can only patch implemented methods/constructors"===
+
The <samp>AccessTools</samp> methods for constructors (<samp>Constructor</samp>, <samp>DeclaredConstructor</samp>, and <samp>GetDeclaredConstructors</samp>) no longer match static constructors by default. Use the new <samp>searchForStatic</samp> argument if you need to match them:
Consider this example:
+
 
<source lang="C#">
+
<syntaxhighlight lang="c#">
 +
// match static constructors only
 +
var method = AccessTools.Constructor(typeof(ExampleType), searchForStatic: true);
 +
 
 +
// match static *or* instance constructors
 +
var method =
 +
  AccessTools.Constructor(typeof(ExampleType), searchForStatic: true)
 +
  ?? AccessTools.Constructor(typeof(ExampleType));
 +
</syntaxhighlight>
 +
 
 +
Note that Harmony no longer matches static constructors for a good reason — they're only called once for the type, so often static constructor patches won't work correctly.
 +
 
 +
===Patching virtual methods===
 +
When patching a virtual method, you must now patch the specific type which implements it. Patching the wrong type now results in the error "''You can only patch implemented methods/constructors''".
 +
 
 +
For example, consider this code:
 +
<syntaxhighlight lang="c#">
 
public class GameLocation
 
public class GameLocation
 
{
 
{
Line 30: Line 74:
  
 
public class Farm : GameLocation {}
 
public class Farm : GameLocation {}
</source>
+
</syntaxhighlight>
 +
 
 +
<samp>Farm.cleanupBeforePlayerExit</samp> doesn't exist, so it's inherited from <samp>GameLocation</samp>. Harmony 1.x would let you patch <samp>Farm.cleanupBeforePlayerExit</samp>, but in Harmony 2.x you must target the actual method (<samp>GameLocation.cleanupBeforePlayerExit</samp> in this example).
 +
 
 +
===<samp>HarmonyMethod</samp> no longer allows null===
 +
Harmony 1.x allowed <code>new HarmonyMethod(null)</code>, so you could safely use it with methods that might not exist. Harmony 2.x now throws an exception in that case, so you should check if you're not sure it exists:
 +
<syntaxhighlight lang="c#">
 +
MethodInfo prefix = AccessTools.Method(this.GetType(), "Prefix");
 +
if (prefix != null)
 +
  harmony.Patch(original, new HarmonyMethod(prefix));
 +
</syntaxhighlight>
  
<tt>Farm.cleanupBeforePlayerExit</tt> doesn't exist, so <tt>Farm</tt> inherits it from <tt>GameLocation</tt>. Harmony 1.x would let you patch <tt>Farm.cleanupBeforePlayerExit</tt>, but in Harmony 2.x you must target the actual method (<tt>GameLocation.cleanupBeforePlayerExit</tt> in this example).
+
===Transpiler changes===
 +
Harmony 2.x uses a new engine ({{github|MonoMod/MonoMod|MonoMod}}) under the hood, so there may be unexpected changes in the way transpiler patches work. For example, short-form branches may become long-form. If your mod uses transpilers, you should test each one to make sure it's working as you expect.
  
 
[[Category:Modding]]
 
[[Category:Modding]]

Latest revision as of 19:18, 1 August 2023

Index

This page is for mod authors. Players: see Modding:Mod compatibility instead.


SMAPI 3.12 updates from Harmony 1.2.0.1 to Harmony 2.1. That only affects mods which use Harmony directly; that's discouraged in most cases, isn't officially part of SMAPI's public API, and isn't subject to SMAPI's normal versioning policy. If you use Harmony in your mods, this page explains how the update affects them.

Overview

What's changing?

Harmony 2.0 and 2.1 have many changes that benefit SMAPI and mods. Some notable changes:

  • Added finalizers and reverse patches.
  • Added pass-through postfixes.
  • Added Manipulator utility, CodeInstruction extensions, and other improvements for transpilers.
  • Added more AccessTools.Is* methods.
  • Added support for .NET 5.
  • Transpilers can now default to the original input by returning null.
  • Improved compatibility with Android modding.
  • Improved exception messages.
  • Improved validation for invalid patches.
  • Fixed cases where methods were inlined and unpatchable on Linux/Mac.
  • Fixed methods with struct return types being unpatchable.
  • Various other improvements and fixes; see the Harmony 2.0 release notes, Harmony 2.1 release notes, and new Harmony documentation for more info.

After waiting over a year to make sure the release is stable, SMAPI is transitioning to Harmony 2.1.

Is this the modapocalypse?

Nope. Although this is a major change, significant efforts were undertaken to minimise the impact:

  • mods which don't use Harmony directly aren't affected;
  • most mods are rewritten automatically for compatibility, so they'll work without an update;
  • pull requests were submitted to update affected open-source mods;
  • unofficial updates were created as needed for mods which hadn't updated officially;
  • the changes were actively communicated and documented to modders.

In addition, the target was at least 90% compatibility for open-source mods before SMAPI migrates. All of this means that the release should have minimal impact on mod compatibility, despite the scope of the changes.

How to update your mod

  1. Make sure you follow best practices outlined in the Harmony guide. In particular, use the EnableHarmony option (don't reference the Harmony DLL directly).
  2. Change using Harmony; to using HarmonyLib;.
  3. Change HarmonyInstance harmony = HarmonyInstance.Create("your mod id"); to Harmony harmony = new Harmony("your mod id");.
  4. Check if any breaking changes listed below apply to your mod.
  5. Recompile the mod.

Breaking changes

API changes

See how to update your mod.

Stricter validation

Harmony 2.x has stricter validation in general, so invalid patches that would previously somewhat work (e.g., setting __result to the wrong type) will now cause errors. See the exception messages for help fixing these.

Patching static constructors

The AccessTools methods for constructors (Constructor, DeclaredConstructor, and GetDeclaredConstructors) no longer match static constructors by default. Use the new searchForStatic argument if you need to match them:

// match static constructors only
var method = AccessTools.Constructor(typeof(ExampleType), searchForStatic: true);

// match static *or* instance constructors
var method =
   AccessTools.Constructor(typeof(ExampleType), searchForStatic: true)
   ?? AccessTools.Constructor(typeof(ExampleType));

Note that Harmony no longer matches static constructors for a good reason — they're only called once for the type, so often static constructor patches won't work correctly.

Patching virtual methods

When patching a virtual method, you must now patch the specific type which implements it. Patching the wrong type now results in the error "You can only patch implemented methods/constructors".

For example, consider this code:

public class GameLocation
{
   public virtual void cleanupBeforePlayerExit() {}
}

public class Farm : GameLocation {}

Farm.cleanupBeforePlayerExit doesn't exist, so it's inherited from GameLocation. Harmony 1.x would let you patch Farm.cleanupBeforePlayerExit, but in Harmony 2.x you must target the actual method (GameLocation.cleanupBeforePlayerExit in this example).

HarmonyMethod no longer allows null

Harmony 1.x allowed new HarmonyMethod(null), so you could safely use it with methods that might not exist. Harmony 2.x now throws an exception in that case, so you should check if you're not sure it exists:

MethodInfo prefix = AccessTools.Method(this.GetType(), "Prefix");
if (prefix != null)
   harmony.Patch(original, new HarmonyMethod(prefix));

Transpiler changes

Harmony 2.x uses a new engine (MonoMod) under the hood, so there may be unexpected changes in the way transpiler patches work. For example, short-form branches may become long-form. If your mod uses transpilers, you should test each one to make sure it's working as you expect.