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

From Stardew Valley Wiki
Jump to navigation Jump to search
m (Replace deprecated <source> tags with <syntaxhighlight> tags; add new template Template:Modder compatibility header)
 
(10 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
←[[Modding:Index|Index]]
 
←[[Modding:Index|Index]]
  
<div style="border: 1px solid gray; border-left: 1em solid gray; padding: 0.5em 1em; border-radius: 5px;">
 
'''The following describes a future version of SMAPI, and may change before release.'''
 
</div>
 
 
{{Modder compatibility header}}
 
{{Modder compatibility header}}
This page explains how to update your mod code for Harmony 2.0. This only applies to mods which use Harmony directly; [[Modding:Modder Guide/APIs/Harmony|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.
+
 
 +
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.
  
 
__TOC__
 
__TOC__
 
==Overview==
 
==Overview==
 
===What's changing?===
 
===What's changing?===
[https://github.com/pardeike/Harmony/releases/tag/v2.0.0 Harmony 2.0] has many changes that benefit SMAPI and mods. Some notable changes:
+
[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-finalizer.html finalizers] and [https://harmony.pardeike.net/articles/reverse-patching.html reverse patches].
* Added <tt>CodeInstruction</tt> extensions, <tt>Manipulator</tt> utility, and other improvements for transpilers.
+
* Added [https://harmony.pardeike.net/articles/patching-postfix.html pass-through postfixes].
* Added more <tt>AccessTools.Is*</tt> methods.
+
* Added [https://harmony.pardeike.net/api/HarmonyLib.Transpilers.html <samp>Manipulator</samp>] utility, <samp>CodeInstruction</samp> extensions, and other improvements for transpilers.
* Transpilers can now default to the original input by returning <tt>null</tt>.
+
* Added more <samp>AccessTools.Is*</samp> methods.
* Better compatibility with Android modding.
+
* Added support for .NET 5.
* Better exception messages.
+
* Transpilers can now default to the original input by returning <samp>null</samp>.
* Better validation for invalid patches.
+
* 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 cases where methods were inlined and unpatchable on Linux/Mac.
 
* Fixed methods with struct return types being unpatchable.
 
* 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] and [https://harmony.pardeike.net Harmony 2.0 documentation] for more info.
+
* 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.
  
After waiting at least half a year to make sure the release is stable, SMAPI will transition to Harmony 2.0.
+
After waiting over a year to make sure the release is stable, SMAPI is transitioning to Harmony 2.1.
  
 
===Is this the modapocalypse?===
 
===Is this the modapocalypse?===
 
Nope. Although this is a major change, significant efforts were undertaken to minimise the impact:
 
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;
 
* mods which don't use Harmony directly aren't affected;
* most mods will be rewritten automatically for compatibility, so many mods will work without an update;
+
* most mods are rewritten automatically for compatibility, so they'll work without an update;
* pull requests will be submitted to update affected open-source mods;
+
* pull requests were submitted to update affected open-source mods;
* unofficial updates will be created for mods which haven't updated officially by the time SMAPI migrates;
+
* unofficial updates were created as needed for mods which hadn't updated officially;
* the changes will be actively communicated and documented to modders.
+
* the changes were actively communicated and documented to modders.
  
In addition, the current target is ''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.
+
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===
 
===How to update your mod===
Line 46: Line 46:
  
 
===Stricter validation===
 
===Stricter validation===
Harmony 2.0 has stricter validation in general, so invalid patches that would previously work (e.g. setting <tt>__result</tt> to the wrong type) will now cause errors. See the exception messages for help fixing these.
+
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.
  
 
===Patching static constructors===
 
===Patching static constructors===
The <tt>AccessTools</tt> methods for constructors (<tt>Constructor</tt>, <tt>DeclaredConstructor</tt>, and <tt>GetDeclaredConstructors</tt>) no longer match static constructors by default. Use the new <tt>searchForStatic</tt> argument if you need to match them:
+
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:
  
 
<syntaxhighlight lang="c#">
 
<syntaxhighlight lang="c#">
Line 64: Line 64:
  
 
===Patching virtual methods===
 
===Patching virtual methods===
You can no longer patch a non-implemented virtual method; doing so results in the error "''You can only patch implemented methods/constructors''".
+
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:
 
For example, consider this code:
Line 76: Line 76:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
<tt>Farm.cleanupBeforePlayerExit</tt> doesn't exist, so it's inherited 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).
+
<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).
  
===<tt>HarmonyMethod</tt> no longer allows null===
+
===<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.0 now throws an exception in that case, so you should check if the method might not exist:
+
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#">
 
<syntaxhighlight lang="c#">
 
MethodInfo prefix = AccessTools.Method(this.GetType(), "Prefix");
 
MethodInfo prefix = AccessTools.Method(this.GetType(), "Prefix");
Line 85: Line 85:
 
   harmony.Patch(original, new HarmonyMethod(prefix));
 
   harmony.Patch(original, new HarmonyMethod(prefix));
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
===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.