diff --git a/docs/release-notes.md b/docs/release-notes.md
index dd87c1fc..c47ee835 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,7 +1,12 @@
← [README](README.md)
# Release notes
-## Upcoming released
+## Upcoming release + 1
+* For modders:
+ * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
+ * Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter.
+
+## Upcoming release
* For players:
* Mod warnings are now listed alphabetically.
* MacOS files starting with `._` are now ignored and can no longer cause skipped mods.
@@ -17,12 +22,10 @@
* Internal changes to improve performance and reliability.
* For modders:
- * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
* Added [event priorities](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Custom_priority) (thanks to spacechase0!).
* Added [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys).
* Added `Multiplayer.PeerConnected` event.
* Added ability to override update keys from the compatibility list.
- * Added `harmony_summary` console command which lists all current Harmony patches, optionally with a search filter.
* Harmony mods which use the `[HarmonyPatch(type)]` attribute now work crossplatform. Previously SMAPI couldn't rewrite types in custom attributes for compatibility.
* Improved mod rewriting for compatibility:
* Fixed rewriting types in custom attributes.
@@ -33,6 +36,7 @@
* Fixed `ModMessageReceived` event handlers not tracked for performance monitoring.
* For SMAPI developers:
+ * Added support for bundling a custom Harmony build for upcoming use.
* Eliminated MongoDB storage in the web services, which complicated the code unnecessarily. The app still uses an abstract interface for storage, so we can wrap a distributed cache in the future if needed.
* Overhauled update checks to simplify individual clients, centralize common logic, and enable upcoming features.
* Merged the separate legacy redirects app on AWS into the main app on Azure.
diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md
index ca8a9c70..3b2d6e56 100644
--- a/docs/technical/smapi.md
+++ b/docs/technical/smapi.md
@@ -58,6 +58,7 @@ SMAPI uses a small number of conditional compilation constants, which you can se
flag | purpose
---- | -------
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled on Windows for players on Windows. Set automatically in `crossplatform.targets`.
+`HARMONY_2` | Whether to enable experimental Harmony 2.0 support. Existing Harmony 1._x_ mods will be rewritten automatically for compatibility.
## For SMAPI developers
### Compiling from source
diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
index 08233feb..8c20fbdd 100644
--- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
+++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
@@ -1,3 +1,4 @@
+#if HARMONY_2
using System;
using System.Collections.Generic;
using System.Linq;
@@ -166,3 +167,4 @@ namespace StardewModdingAPI.Framework.Commands
}
}
}
+#endif
diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs
index 8e4320b3..102f3364 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs
@@ -1,3 +1,4 @@
+#if HARMONY_2
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -40,3 +41,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
}
}
}
+#endif
diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs
index 54b91679..ad6d5e4f 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs
@@ -1,3 +1,4 @@
+#if HARMONY_2
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -80,3 +81,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
}
}
}
+#endif
diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs
index 44c97401..f3975558 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs
@@ -1,3 +1,4 @@
+#if HARMONY_2
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
@@ -43,3 +44,4 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
}
}
}
+#endif
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
index 8fed170a..b30d686e 100644
--- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
@@ -1,3 +1,4 @@
+#if HARMONY_2
using System;
using HarmonyLib;
using Mono.Cecil;
@@ -125,3 +126,4 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
}
}
}
+#endif
diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs
index dcad285a..82d7b9c8 100644
--- a/src/SMAPI/Framework/Patching/GamePatcher.cs
+++ b/src/SMAPI/Framework/Patching/GamePatcher.cs
@@ -1,5 +1,9 @@
using System;
+#if HARMONY_2
using HarmonyLib;
+#else
+using Harmony;
+#endif
namespace StardewModdingAPI.Framework.Patching
{
@@ -27,7 +31,11 @@ namespace StardewModdingAPI.Framework.Patching
/// The patches to apply.
public void Apply(params IHarmonyPatch[] patches)
{
+#if HARMONY_2
Harmony harmony = new Harmony("SMAPI");
+#else
+ HarmonyInstance harmony = HarmonyInstance.Create("SMAPI");
+#endif
foreach (IHarmonyPatch patch in patches)
{
try
diff --git a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
index 7d5eb3d4..922243fa 100644
--- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
+++ b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
@@ -1,4 +1,8 @@
+#if HARMONY_2
using HarmonyLib;
+#else
+using Harmony;
+#endif
namespace StardewModdingAPI.Framework.Patching
{
@@ -10,6 +14,10 @@ namespace StardewModdingAPI.Framework.Patching
/// Apply the Harmony patch.
/// The Harmony instance.
+#if HARMONY_2
void Apply(Harmony harmony);
+#else
+ void Apply(HarmonyInstance harmony);
+#endif
}
}
diff --git a/src/SMAPI/Framework/Patching/PatchHelper.cs b/src/SMAPI/Framework/Patching/PatchHelper.cs
new file mode 100644
index 00000000..d1aa0185
--- /dev/null
+++ b/src/SMAPI/Framework/Patching/PatchHelper.cs
@@ -0,0 +1,36 @@
+#if !HARMONY_2
+using System;
+using System.Collections.Generic;
+
+namespace StardewModdingAPI.Framework.Patching
+{
+ /// Provides generic methods for implementing Harmony patches.
+ internal class PatchHelper
+ {
+ /*********
+ ** Fields
+ *********/
+ /// The interception keys currently being intercepted.
+ private static readonly HashSet InterceptingKeys = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// Track a method that will be intercepted.
+ /// The intercept key.
+ /// Returns false if the method was already marked for interception, else true.
+ public static bool StartIntercept(string key)
+ {
+ return PatchHelper.InterceptingKeys.Add(key);
+ }
+
+ /// Track a method as no longer being intercepted.
+ /// The intercept key.
+ public static void StopIntercept(string key)
+ {
+ PatchHelper.InterceptingKeys.Remove(key);
+ }
+ }
+}
+#endif
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 530b6754..1a2c97f4 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -511,7 +511,9 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log("Type 'help' for help, or 'help ' for a command's usage", LogLevel.Info);
this.GameInstance.CommandManager
.Add(new HelpCommand(this.GameInstance.CommandManager), this.Monitor)
+#if HARMONY_2
.Add(new HarmonySummaryCommand(), this.Monitor)
+#endif
.Add(new ReloadI18nCommand(this.ReloadTranslations), this.Monitor);
// start handling command line input
diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs
index 89430a11..79d7a7a8 100644
--- a/src/SMAPI/Metadata/InstructionMetadata.cs
+++ b/src/SMAPI/Metadata/InstructionMetadata.cs
@@ -38,8 +38,10 @@ namespace StardewModdingAPI.Metadata
// rewrite for Stardew Valley 1.3
yield return new StaticFieldToConstantRewriter(typeof(Game1), "tileSize", Game1.tileSize);
+#if HARMONY_2
// rewrite for SMAPI 3.6 (Harmony 1.x => 2.0 update)
yield return new Harmony1AssemblyRewriter();
+#endif
/****
** detect mod issues
@@ -51,7 +53,11 @@ namespace StardewModdingAPI.Metadata
/****
** detect code which may impact game stability
****/
+#if HARMONY_2
yield return new TypeFinder(typeof(HarmonyLib.Harmony).FullName, InstructionHandleResult.DetectedGamePatch);
+#else
+ yield return new TypeFinder(typeof(Harmony.HarmonyInstance).FullName, InstructionHandleResult.DetectedGamePatch);
+#endif
yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic);
yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.serializer), InstructionHandleResult.DetectedSaveSerializer);
yield return new FieldFinder(typeof(SaveGame).FullName, nameof(SaveGame.farmerSerializer), InstructionHandleResult.DetectedSaveSerializer);
diff --git a/src/SMAPI/Patches/DialogueErrorPatch.cs b/src/SMAPI/Patches/DialogueErrorPatch.cs
index cddf29d6..8043eda3 100644
--- a/src/SMAPI/Patches/DialogueErrorPatch.cs
+++ b/src/SMAPI/Patches/DialogueErrorPatch.cs
@@ -1,11 +1,16 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using HarmonyLib;
-using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Patching;
using StardewModdingAPI.Framework.Reflection;
using StardewValley;
+#if HARMONY_2
+using HarmonyLib;
+using StardewModdingAPI.Framework;
+#else
+using System.Reflection;
+using Harmony;
+#endif
namespace StardewModdingAPI.Patches
{
@@ -47,6 +52,7 @@ namespace StardewModdingAPI.Patches
/// Apply the Harmony patch.
/// The Harmony instance.
+#if HARMONY_2
public void Apply(Harmony harmony)
{
harmony.Patch(
@@ -58,11 +64,24 @@ namespace StardewModdingAPI.Patches
finalizer: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Finalize_NPC_CurrentDialogue))
);
}
-
+#else
+ public void Apply(HarmonyInstance harmony)
+ {
+ harmony.Patch(
+ original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }),
+ prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_Dialogue_Constructor))
+ );
+ harmony.Patch(
+ original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod,
+ prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue))
+ );
+ }
+#endif
/*********
** Private methods
*********/
+#if HARMONY_2
/// The method to call after the Dialogue constructor.
/// The instance being patched.
/// The dialogue being parsed.
@@ -102,5 +121,74 @@ namespace StardewModdingAPI.Patches
return null;
}
+#else
+
+ /// The method to call instead of the Dialogue constructor.
+ /// The instance being patched.
+ /// The dialogue being parsed.
+ /// The NPC for which the dialogue is being parsed.
+ /// Returns whether to execute the original method.
+ private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker)
+ {
+ // get private members
+ bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField(typeof(Dialogue), "nameArraysTranslated").GetValue();
+ IReflectedMethod translateArraysOfStrings = DialogueErrorPatch.Reflection.GetMethod(typeof(Dialogue), "TranslateArraysOfStrings");
+ IReflectedMethod parseDialogueString = DialogueErrorPatch.Reflection.GetMethod(__instance, "parseDialogueString");
+ IReflectedMethod checkForSpecialDialogueAttributes = DialogueErrorPatch.Reflection.GetMethod(__instance, "checkForSpecialDialogueAttributes");
+ IReflectedField> dialogues = DialogueErrorPatch.Reflection.GetField>(__instance, "dialogues");
+
+ // replicate base constructor
+ if (dialogues.GetValue() == null)
+ dialogues.SetValue(new List());
+
+ // duplicate code with try..catch
+ try
+ {
+ if (!nameArraysTranslated)
+ translateArraysOfStrings.Invoke();
+ __instance.speaker = speaker;
+ parseDialogueString.Invoke(masterDialogue);
+ checkForSpecialDialogueAttributes.Invoke();
+ }
+ catch (Exception baseEx) when (baseEx.InnerException is TargetInvocationException invocationEx && invocationEx.InnerException is Exception ex)
+ {
+ string name = !string.IsNullOrWhiteSpace(speaker?.Name) ? speaker.Name : null;
+ DialogueErrorPatch.MonitorForGame.Log($"Failed parsing dialogue string{(name != null ? $" for {name}" : "")}:\n{masterDialogue}\n{ex}", LogLevel.Error);
+
+ parseDialogueString.Invoke("...");
+ checkForSpecialDialogueAttributes.Invoke();
+ }
+
+ return false;
+ }
+
+ /// The method to call instead of .
+ /// The instance being patched.
+ /// The return value of the original method.
+ /// The method being wrapped.
+ /// Returns whether to execute the original method.
+ private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack __result, MethodInfo __originalMethod)
+ {
+ const string key = nameof(Before_NPC_CurrentDialogue);
+ if (!PatchHelper.StartIntercept(key))
+ return true;
+
+ try
+ {
+ __result = (Stack)__originalMethod.Invoke(__instance, new object[0]);
+ return false;
+ }
+ catch (TargetInvocationException ex)
+ {
+ DialogueErrorPatch.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{ex.InnerException ?? ex}", LogLevel.Error);
+ __result = new Stack();
+ return false;
+ }
+ finally
+ {
+ PatchHelper.StopIntercept(key);
+ }
+ }
+#endif
}
}
diff --git a/src/SMAPI/Patches/EventErrorPatch.cs b/src/SMAPI/Patches/EventErrorPatch.cs
index de9dea29..4dbb25f3 100644
--- a/src/SMAPI/Patches/EventErrorPatch.cs
+++ b/src/SMAPI/Patches/EventErrorPatch.cs
@@ -1,6 +1,11 @@
-using System;
using System.Diagnostics.CodeAnalysis;
+#if HARMONY_2
+using System;
using HarmonyLib;
+#else
+using System.Reflection;
+using Harmony;
+#endif
using StardewModdingAPI.Framework.Patching;
using StardewValley;
@@ -38,6 +43,7 @@ namespace StardewModdingAPI.Patches
/// Apply the Harmony patch.
/// The Harmony instance.
+#if HARMONY_2
public void Apply(Harmony harmony)
{
harmony.Patch(
@@ -45,11 +51,21 @@ namespace StardewModdingAPI.Patches
finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Finalize_GameLocation_CheckEventPrecondition))
);
}
+#else
+ public void Apply(HarmonyInstance harmony)
+ {
+ harmony.Patch(
+ original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"),
+ prefix: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition))
+ );
+ }
+#endif
/*********
** Private methods
*********/
+#if HARMONY_2
/// The method to call instead of the GameLocation.CheckEventPrecondition.
/// The return value of the original method.
/// The precondition to be parsed.
@@ -65,5 +81,35 @@ namespace StardewModdingAPI.Patches
return null;
}
+#else
+ /// The method to call instead of the GameLocation.CheckEventPrecondition.
+ /// The instance being patched.
+ /// The return value of the original method.
+ /// The precondition to be parsed.
+ /// The method being wrapped.
+ /// Returns whether to execute the original method.
+ private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod)
+ {
+ const string key = nameof(Before_GameLocation_CheckEventPrecondition);
+ if (!PatchHelper.StartIntercept(key))
+ return true;
+
+ try
+ {
+ __result = (int)__originalMethod.Invoke(__instance, new object[] { precondition });
+ return false;
+ }
+ catch (TargetInvocationException ex)
+ {
+ __result = -1;
+ EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error);
+ return false;
+ }
+ finally
+ {
+ PatchHelper.StopIntercept(key);
+ }
+ }
+#endif
}
}
diff --git a/src/SMAPI/Patches/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs
index 9c707676..768ddd6b 100644
--- a/src/SMAPI/Patches/LoadContextPatch.cs
+++ b/src/SMAPI/Patches/LoadContextPatch.cs
@@ -1,6 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
+#if HARMONY_2
using HarmonyLib;
+#else
+using Harmony;
+#endif
using StardewModdingAPI.Enums;
using StardewModdingAPI.Framework.Patching;
using StardewModdingAPI.Framework.Reflection;
@@ -47,7 +51,11 @@ namespace StardewModdingAPI.Patches
/// Apply the Harmony patch.
/// The Harmony instance.
+#if HARMONY_2
public void Apply(Harmony harmony)
+#else
+ public void Apply(HarmonyInstance harmony)
+#endif
{
// detect CreatedBasicInfo
harmony.Patch(
diff --git a/src/SMAPI/Patches/LoadErrorPatch.cs b/src/SMAPI/Patches/LoadErrorPatch.cs
index f8ad6693..5e67b169 100644
--- a/src/SMAPI/Patches/LoadErrorPatch.cs
+++ b/src/SMAPI/Patches/LoadErrorPatch.cs
@@ -2,7 +2,11 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+#if HARMONY_2
using HarmonyLib;
+#else
+using Harmony;
+#endif
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Patching;
using StardewValley;
@@ -49,7 +53,11 @@ namespace StardewModdingAPI.Patches
/// Apply the Harmony patch.
/// The Harmony instance.
+#if HARMONY_2
public void Apply(Harmony harmony)
+#else
+ public void Apply(HarmonyInstance harmony)
+#endif
{
harmony.Patch(
original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)),
diff --git a/src/SMAPI/Patches/ObjectErrorPatch.cs b/src/SMAPI/Patches/ObjectErrorPatch.cs
index 189a14a0..4edcc64e 100644
--- a/src/SMAPI/Patches/ObjectErrorPatch.cs
+++ b/src/SMAPI/Patches/ObjectErrorPatch.cs
@@ -1,11 +1,16 @@
-using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using HarmonyLib;
using StardewModdingAPI.Framework.Patching;
using StardewValley;
using StardewValley.Menus;
using SObject = StardewValley.Object;
+#if HARMONY_2
+using System;
+using HarmonyLib;
+#else
+using System.Reflection;
+using Harmony;
+#endif
namespace StardewModdingAPI.Patches
{
@@ -27,7 +32,11 @@ namespace StardewModdingAPI.Patches
*********/
/// Apply the Harmony patch.
/// The Harmony instance.
+#if HARMONY_2
public void Apply(Harmony harmony)
+#else
+ public void Apply(HarmonyInstance harmony)
+#endif
{
// object.getDescription
harmony.Patch(
@@ -38,7 +47,11 @@ namespace StardewModdingAPI.Patches
// object.getDisplayName
harmony.Patch(
original: AccessTools.Method(typeof(SObject), "loadDisplayName"),
+#if HARMONY_2
finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Finalize_Object_loadDisplayName))
+#else
+ prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_loadDisplayName))
+#endif
);
// IClickableMenu.drawToolTip
@@ -68,6 +81,7 @@ namespace StardewModdingAPI.Patches
return true;
}
+#if HARMONY_2
/// The method to call after .
/// The patched method's return value.
/// The exception thrown by the wrapped method, if any.
@@ -82,6 +96,38 @@ namespace StardewModdingAPI.Patches
return __exception;
}
+#else
+ /// The method to call instead of .
+ /// The instance being patched.
+ /// The patched method's return value.
+ /// The method being wrapped.
+ /// Returns whether to execute the original method.
+ private static bool Before_Object_loadDisplayName(SObject __instance, ref string __result, MethodInfo __originalMethod)
+ {
+ const string key = nameof(Before_Object_loadDisplayName);
+ if (!PatchHelper.StartIntercept(key))
+ return true;
+
+ try
+ {
+ __result = (string)__originalMethod.Invoke(__instance, new object[0]);
+ return false;
+ }
+ catch (TargetInvocationException ex) when (ex.InnerException is KeyNotFoundException)
+ {
+ __result = "???";
+ return false;
+ }
+ catch
+ {
+ return true;
+ }
+ finally
+ {
+ PatchHelper.StopIntercept(key);
+ }
+ }
+#endif
/// The method to call instead of .
/// The item for which to draw a tooltip.
diff --git a/src/SMAPI/Patches/ScheduleErrorPatch.cs b/src/SMAPI/Patches/ScheduleErrorPatch.cs
index df6ffab3..cc2238b0 100644
--- a/src/SMAPI/Patches/ScheduleErrorPatch.cs
+++ b/src/SMAPI/Patches/ScheduleErrorPatch.cs
@@ -1,10 +1,15 @@
-using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using HarmonyLib;
-using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Patching;
using StardewValley;
+#if HARMONY_2
+using System;
+using HarmonyLib;
+using StardewModdingAPI.Framework;
+#else
+using System.Reflection;
+using Harmony;
+#endif
namespace StardewModdingAPI.Patches
{
@@ -40,11 +45,19 @@ namespace StardewModdingAPI.Patches
/// Apply the Harmony patch.
/// The Harmony instance.
+#if HARMONY_2
public void Apply(Harmony harmony)
+#else
+ public void Apply(HarmonyInstance harmony)
+#endif
{
harmony.Patch(
original: AccessTools.Method(typeof(NPC), "parseMasterSchedule"),
+#if HARMONY_2
finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule))
+#else
+ prefix: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule))
+#endif
);
}
@@ -52,6 +65,7 @@ namespace StardewModdingAPI.Patches
/*********
** Private methods
*********/
+#if HARMONY_2
/// The method to call instead of .
/// The raw schedule data to parse.
/// The instance being patched.
@@ -68,5 +82,35 @@ namespace StardewModdingAPI.Patches
return null;
}
+#else
+ /// The method to call instead of .
+ /// The raw schedule data to parse.
+ /// The instance being patched.
+ /// The patched method's return value.
+ /// The method being wrapped.
+ /// Returns whether to execute the original method.
+ private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary __result, MethodInfo __originalMethod)
+ {
+ const string key = nameof(Before_NPC_parseMasterSchedule);
+ if (!PatchHelper.StartIntercept(key))
+ return true;
+
+ try
+ {
+ __result = (Dictionary)__originalMethod.Invoke(__instance, new object[] { rawData });
+ return false;
+ }
+ catch (TargetInvocationException ex)
+ {
+ ScheduleErrorPatch.MonitorForGame.Log($"Failed parsing schedule for NPC {__instance.Name}:\n{rawData}\n{ex.InnerException ?? ex}", LogLevel.Error);
+ __result = new Dictionary();
+ return false;
+ }
+ finally
+ {
+ PatchHelper.StopIntercept(key);
+ }
+ }
+#endif
}
}
diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj
index 603b6fb5..c17de6d0 100644
--- a/src/SMAPI/SMAPI.csproj
+++ b/src/SMAPI/SMAPI.csproj
@@ -14,7 +14,7 @@
-
+