Modding:Modder Guide/APIs/Harmony

From Stardew Valley Wiki
< Modding:Modder Guide‎ | APIs
Revision as of 18:23, 19 February 2021 by RetroEdit (talk | contribs) (Replace deprecated <source> tags with <syntaxhighlight> tags)
Jump to navigation Jump to search

Creating SMAPI mods SMAPI mascot.png


Modding:Index

“Here be dragons. Thou art forewarned.”

Harmony lets you patch or replace methods, effectively rewriting the game code. SMAPI includes a copy of Harmony for mods to use.

When to use Harmony

Harmony should be a last resort. It's a powerful tool that lets you do things that may be harder otherwise, but it comes with significant disadvantages:

  • It's very easy to cause crashes, errors, or subtle bugs, including difficult-to-diagnose memory corruption errors.
  • SMAPI often can't detect incompatible Harmony code.
  • Crossplatform compatibility isn't guaranteed.
  • Patches may conflict with other Harmony mods, sometimes in ways that are difficult to troubleshoot.
  • Patches may have unpredictable effects on other mods that aren't using Harmony. That may also irritate other modders, if players often report bugs to other mods due to your patches.
  • Patches may prevent you from attaching a debugger when testing (even when you're testing unrelated code).
  • If someone else's mod calls code you patched and that code crashes, the error will be logged under their mod's name (unless you handle errors). This can cause players to report bugs to other authors, which can be irritating if they know it's due to your mod.
  • SMAPI will show a warning when your mod is loaded saying it may affect game stability.

You should only use Harmony if you're sure what you want isn't feasible without it. For example: instead of patching logic that handles player interaction, you can detect and suppress the interaction using SMAPI's input API, and run your own code to handle it.

How to use it

Harmony should be a last resort (see the previous section).

  1. Edit your mod's .csproj project file, and add this to the first <PropertyGroup> section:
    <EnableHarmony>true</EnableHarmony>
    
  2. In your mod's Entry method, use Harmony's code API to register patches:
    var harmony = HarmonyInstance.Create(this.ModManifest.UniqueID);
    
    // example patch, you'll need to edit this for your patch
    harmony.Patch(
       original: AccessTools.Method(typeof(StardewValley.Object), nameof(StardewValley.Object.canBePlacedHere)),
       prefix: new HarmonyMethod(typeof(ObjectPatches), nameof(ObjectPatches.CanBePlacedHere_Prefix))
    );
    
  3. See the Harmony 1.2 wiki (not 2.0!) for tutorials and documentation.

Best practices

  1. See when to use Harmony.
  2. Unhandled errors in a patch can be hard to troubleshoot, which often leads to players asking for help on the SMAPI page instead of your mod. Please handle errors, log them, and default to the original code. For example:
    public class ObjectPatches
    {
        private static IMonitor Monitor;
    
        // call this method from your Entry class
        public static void Initialize(IMonitor monitor)
        {
            Monitor = monitor;
        }
    
        public static bool CanBePlacedHere_Prefix(StardewValley.Object __instance, GameLocation location, Vector2 tile, ref bool __result)
        {
            try
            {
                ...; // your patch logic here
                return false; // don't run original logic
            }
            catch (Exception ex)
            {
                Monitor.Log($"Failed in {nameof(CanBePlacedHere_Prefix)}:\n{ex}", LogLevel.Error);
                return true; // run original logic
            }
        }
    }
    
  3. Use postfixes when possible for best compatibility and stability.
  4. Don't use transpile patches unless you have no choice and you know what you're doing. This has a much higher chance of causing issues, is more fragile and likely to break in game updates, and makes it much less likely that other modders can help if you need it.