Difference between revisions of "Modding:Modder Guide/APIs/Harmony"

From Stardew Valley Wiki
Jump to navigation Jump to search
(→‎Common gotchas: rename to best practices, expand a bit)
(→‎Best practices: + attribute limitation no longer applies in SMAPI 3.6)
Line 42: Line 42:
 
<ol>
 
<ol>
 
<li>See [[#When to use Harmony|when to use Harmony]].</li>
 
<li>See [[#When to use Harmony|when to use Harmony]].</li>
<li>For crossplatform compatibility, you '''must''' use the code API to register patches. The attribute approach won't work crossplatform.</li>
+
<li>For crossplatform compatibility, you '''must''' use the code API to register patches. The attribute approach won't work crossplatform.
 +
{{SMAPI upcoming|3.6|This limitation no longer applies.}}</li>
 
<li>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:
 
<li>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:
 
<source lang="C#">
 
<source lang="C#">

Revision as of 04:34, 27 May 2020

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 wiki for tutorials and documentation.

Best practices

  1. See when to use Harmony.
  2. For crossplatform compatibility, you must use the code API to register patches. The attribute approach won't work crossplatform.
    The following describes the upcoming SMAPI 3.6, and may change before release.
    This limitation no longer applies.
  3. 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(SObject __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
            }
        }
    }
    
  4. Use postfixes when possible for best compatibility and stability.
  5. 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.