Modding:Migrate to Harmony 2.0

From Stardew Valley Wiki
Jump to: navigation, search

Index

The following describes a future version of SMAPI, and may change before release.

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

This page explains how to update your mod code for Harmony 2.0. This only applies to 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.

Overview

What's changing?

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

  • Added finalizers and reverse patches.
  • Added CodeInstruction extensions, Manipulator utility, and other improvements for transpilers.
  • Added more AccessTools.Is* methods.
  • Transpilers can now default to the original input by returning null.
  • Better compatibility with Android modding.
  • Better exception messages.
  • Better 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 and Harmony 2.0 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.

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 will be rewritten automatically for compatibility, so many mods will work without an update;
  • pull requests will be submitted to update affected open-source mods;
  • unofficial updates will be created for mods which haven't updated officially by the time SMAPI migrates;
  • the changes will be 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.

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.0 has stricter validation in general, so invalid patches that would previously 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

You can no longer patch a non-implemented virtual method; doing so 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.0 now throws an exception in that case, so you should check if the method might not exist:

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