summaryrefslogtreecommitdiff
path: root/src/SMAPI.Mods.ErrorHandler
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-08-01 13:11:51 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-08-01 13:11:51 -0400
commit5b31be76dd90711ff475341de8dfdb6e1f50b98a (patch)
treee120bc62af72e94e8e924ba124382d0c6f9304de /src/SMAPI.Mods.ErrorHandler
parent8f96a97f070d654764de3b138678d8f62707f485 (diff)
parentd688cdf8c3c852d4b11cdd046d67c4b35443cc95 (diff)
downloadSMAPI-5b31be76dd90711ff475341de8dfdb6e1f50b98a.tar.gz
SMAPI-5b31be76dd90711ff475341de8dfdb6e1f50b98a.tar.bz2
SMAPI-5b31be76dd90711ff475341de8dfdb6e1f50b98a.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI.Mods.ErrorHandler')
-rw-r--r--src/SMAPI.Mods.ErrorHandler/ModEntry.cs40
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs184
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs75
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs75
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs (renamed from src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs)26
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs79
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs158
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs44
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs150
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs85
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs136
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs71
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs145
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs108
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs (renamed from src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs)30
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs89
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs43
-rw-r--r--src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj2
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
19 files changed, 664 insertions, 880 deletions
diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
index d9426d75..067f6a8d 100644
--- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
+++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
@@ -1,8 +1,7 @@
+using System;
using System.Reflection;
using StardewModdingAPI.Events;
-using StardewModdingAPI.Framework;
-using StardewModdingAPI.Framework.Logging;
-using StardewModdingAPI.Framework.Patching;
+using StardewModdingAPI.Internal.Patching;
using StardewModdingAPI.Mods.ErrorHandler.Patches;
using StardewValley;
@@ -29,15 +28,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler
IMonitor monitorForGame = this.GetMonitorForGame();
// apply patches
- new GamePatcher(this.Monitor).Apply(
- new DialogueErrorPatch(monitorForGame, this.Helper.Reflection),
- new EventPatches(monitorForGame),
- new GameLocationPatches(monitorForGame),
- new ObjectErrorPatch(),
- new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved),
- new ScheduleErrorPatch(monitorForGame),
- new SpriteBatchValidationPatches(),
- new UtilityErrorPatches()
+ HarmonyPatcher.Apply(this.ModManifest.UniqueID, this.Monitor,
+ new DialoguePatcher(monitorForGame, this.Helper.Reflection),
+ new DictionaryPatcher(this.Helper.Reflection),
+ new EventPatcher(monitorForGame),
+ new GameLocationPatcher(monitorForGame),
+ new IClickableMenuPatcher(),
+ new NpcPatcher(monitorForGame),
+ new ObjectPatcher(),
+ new SaveGamePatcher(this.Monitor, this.OnSaveContentRemoved),
+ new SpriteBatchPatcher(),
+ new UtilityPatcher()
);
// hook events
@@ -70,12 +71,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler
/// <summary>Get the monitor with which to log game errors.</summary>
private IMonitor GetMonitorForGame()
{
- SCore core = SCore.Instance;
- LogManager logManager = core.GetType().GetField("LogManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(core) as LogManager;
- if (logManager == null)
- this.Monitor.Log("Can't access SMAPI's internal log manager. Some game errors may be reported as being from Error Handler.", LogLevel.Error);
+ // get SMAPI core
+ Type coreType = Type.GetType("StardewModdingAPI.Framework.SCore, StardewModdingAPI", throwOnError: false)
+ ?? throw new InvalidOperationException("Can't access SMAPI's core type. This mod may not work correctly.");
+ object core = coreType.GetProperty("Instance", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null)
+ ?? throw new InvalidOperationException("Can't access SMAPI's core instance. This mod may not work correctly.");
- return logManager?.MonitorForGame ?? this.Monitor;
+ // get monitor
+ MethodInfo getMonitorForGame = coreType.GetMethod("GetMonitorForGame")
+ ?? throw new InvalidOperationException("Can't access the SMAPI's 'GetMonitorForGame' method. This mod may not work correctly.");
+
+ return (IMonitor)getMonitorForGame.Invoke(core, new object[0]) ?? this.Monitor;
}
}
}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs
deleted file mode 100644
index cce13064..00000000
--- a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using StardewModdingAPI.Framework.Patching;
-using StardewValley;
-#if HARMONY_2
-using HarmonyLib;
-using StardewModdingAPI.Framework;
-#else
-using System.Reflection;
-using Harmony;
-#endif
-
-namespace StardewModdingAPI.Mods.ErrorHandler.Patches
-{
- /// <summary>A Harmony patch for the <see cref="Dialogue"/> constructor which intercepts invalid dialogue lines and logs an error instead of crashing.</summary>
- /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
- [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
- internal class DialogueErrorPatch : IHarmonyPatch
- {
- /*********
- ** Fields
- *********/
- /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
- private static IMonitor MonitorForGame;
-
- /// <summary>Simplifies access to private code.</summary>
- private static IReflectionHelper Reflection;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
- /// <param name="reflector">Simplifies access to private code.</param>
- public DialogueErrorPatch(IMonitor monitorForGame, IReflectionHelper reflector)
- {
- DialogueErrorPatch.MonitorForGame = monitorForGame;
- DialogueErrorPatch.Reflection = reflector;
- }
-
-
- /// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
- {
- harmony.Patch(
- original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }),
- finalizer: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Finalize_Dialogue_Constructor))
- );
- harmony.Patch(
- original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod,
- 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
- /// <summary>The method to call after the Dialogue constructor.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="masterDialogue">The dialogue being parsed.</param>
- /// <param name="speaker">The NPC for which the dialogue is being parsed.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- private static Exception Finalize_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker, Exception __exception)
- {
- if (__exception != null)
- {
- // log message
- string name = !string.IsNullOrWhiteSpace(speaker?.Name) ? speaker.Name : null;
- DialogueErrorPatch.MonitorForGame.Log($"Failed parsing dialogue string{(name != null ? $" for {name}" : "")}:\n{masterDialogue}\n{__exception.GetLogSummary()}", LogLevel.Error);
-
- // set default dialogue
- IReflectedMethod parseDialogueString = DialogueErrorPatch.Reflection.GetMethod(__instance, "parseDialogueString");
- IReflectedMethod checkForSpecialDialogueAttributes = DialogueErrorPatch.Reflection.GetMethod(__instance, "checkForSpecialDialogueAttributes");
- parseDialogueString.Invoke("...");
- checkForSpecialDialogueAttributes.Invoke();
- }
-
- return null;
- }
-
- /// <summary>The method to call after <see cref="NPC.CurrentDialogue"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- private static Exception Finalize_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, Exception __exception)
- {
- if (__exception == null)
- return null;
-
- DialogueErrorPatch.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{__exception.GetLogSummary()}", LogLevel.Error);
- __result = new Stack<Dialogue>();
-
- return null;
- }
-#else
-
- /// <summary>The method to call instead of the Dialogue constructor.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="masterDialogue">The dialogue being parsed.</param>
- /// <param name="speaker">The NPC for which the dialogue is being parsed.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker)
- {
- // get private members
- bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField<bool>(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");
-
- // replicate base constructor
- __instance.dialogues ??= new List<string>();
-
- // 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;
- }
-
- /// <summary>The method to call instead of <see cref="NPC.CurrentDialogue"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="__originalMethod">The method being wrapped.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod)
- {
- const string key = nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue);
- if (!PatchHelper.StartIntercept(key))
- return true;
-
- try
- {
- __result = (Stack<Dialogue>)__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<Dialogue>();
- return false;
- }
- finally
- {
- PatchHelper.StopIntercept(key);
- }
- }
-#endif
- }
-}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs
new file mode 100644
index 00000000..7a3af39c
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Internal;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="Dialogue"/> which intercept invalid dialogue lines and logs an error instead of crashing.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ internal class DialoguePatcher : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
+ private static IMonitor MonitorForGame;
+
+ /// <summary>Simplifies access to private code.</summary>
+ private static IReflectionHelper Reflection;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
+ /// <param name="reflector">Simplifies access to private code.</param>
+ public DialoguePatcher(IMonitor monitorForGame, IReflectionHelper reflector)
+ {
+ DialoguePatcher.MonitorForGame = monitorForGame;
+ DialoguePatcher.Reflection = reflector;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireConstructor<Dialogue>(typeof(string), typeof(NPC)),
+ finalizer: this.GetHarmonyMethod(nameof(DialoguePatcher.Finalize_Constructor))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call when the Dialogue constructor throws an exception.</summary>
+ /// <param name="__instance">The instance being patched.</param>
+ /// <param name="masterDialogue">The dialogue being parsed.</param>
+ /// <param name="speaker">The NPC for which the dialogue is being parsed.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_Constructor(Dialogue __instance, string masterDialogue, NPC speaker, Exception __exception)
+ {
+ if (__exception != null)
+ {
+ // log message
+ string name = !string.IsNullOrWhiteSpace(speaker?.Name) ? speaker.Name : null;
+ DialoguePatcher.MonitorForGame.Log($"Failed parsing dialogue string{(name != null ? $" for {name}" : "")}:\n{masterDialogue}\n{__exception.GetLogSummary()}", LogLevel.Error);
+
+ // set default dialogue
+ IReflectedMethod parseDialogueString = DialoguePatcher.Reflection.GetMethod(__instance, "parseDialogueString");
+ IReflectedMethod checkForSpecialDialogueAttributes = DialoguePatcher.Reflection.GetMethod(__instance, "checkForSpecialDialogueAttributes");
+ parseDialogueString.Invoke("...");
+ checkForSpecialDialogueAttributes.Invoke();
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
new file mode 100644
index 00000000..6ad64e16
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley.GameData;
+using StardewValley.GameData.HomeRenovations;
+using StardewValley.GameData.Movies;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="Dictionary{TKey,TValue}"/> which add the accessed key to <see cref="KeyNotFoundException"/> exceptions.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ internal class DictionaryPatcher : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Simplifies access to private code.</summary>
+ private static IReflectionHelper Reflection;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="reflector">Simplifies access to private code.</param>
+ public DictionaryPatcher(IReflectionHelper reflector)
+ {
+ DictionaryPatcher.Reflection = reflector;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ Type[] keyTypes = { typeof(int), typeof(string) };
+ Type[] valueTypes = { typeof(int), typeof(string), typeof(HomeRenovation), typeof(MovieData), typeof(SpecialOrderData) };
+
+ foreach (Type keyType in keyTypes)
+ {
+ foreach (Type valueType in valueTypes)
+ {
+ Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
+
+ harmony.Patch(
+ original: AccessTools.Method(dictionaryType, "get_Item") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "get_Item")} to patch."),
+ finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_GetItem))
+ );
+ }
+ }
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call after the dictionary indexer throws an exception.</summary>
+ /// <param name="key">The dictionary key being fetched.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_GetItem(object key, Exception __exception)
+ {
+ if (__exception is KeyNotFoundException)
+ {
+ DictionaryPatcher.Reflection
+ .GetField<string>(__exception, "_message")
+ .SetValue($"{__exception.Message}\nkey: '{key}'");
+ }
+
+ return __exception;
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs
index 72863d17..1b706147 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs
@@ -1,11 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
-#if HARMONY_2
using HarmonyLib;
-#else
-using Harmony;
-#endif
-using StardewModdingAPI.Framework.Patching;
+using StardewModdingAPI.Internal.Patching;
using StardewValley;
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
@@ -14,7 +10,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
- internal class EventPatches : IHarmonyPatch
+ internal class EventPatcher : BasePatcher
{
/*********
** Fields
@@ -28,21 +24,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
*********/
/// <summary>Construct an instance.</summary>
/// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
- public EventPatches(IMonitor monitorForGame)
+ public EventPatcher(IMonitor monitorForGame)
{
- EventPatches.MonitorForGame = monitorForGame;
+ EventPatcher.MonitorForGame = monitorForGame;
}
/// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
-#else
- public void Apply(HarmonyInstance harmony)
-#endif
+ public override void Apply(Harmony harmony, IMonitor monitor)
{
harmony.Patch(
- original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)),
- postfix: new HarmonyMethod(this.GetType(), nameof(EventPatches.After_Event_LogErrorAndHalt))
+ original: this.RequireMethod<Event>(nameof(Event.LogErrorAndHalt)),
+ postfix: this.GetHarmonyMethod(nameof(EventPatcher.After_LogErrorAndHalt))
);
}
@@ -52,9 +44,9 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
*********/
/// <summary>The method to call after <see cref="Event.LogErrorAndHalt"/>.</summary>
/// <param name="e">The exception being logged.</param>
- private static void After_Event_LogErrorAndHalt(Exception e)
+ private static void After_LogErrorAndHalt(Exception e)
{
- EventPatches.MonitorForGame.Log(e.ToString(), LogLevel.Error);
+ EventPatcher.MonitorForGame.Log(e.ToString(), LogLevel.Error);
}
}
}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs
new file mode 100644
index 00000000..7df6b0a2
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley;
+using xTile;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="GameLocation"/> which intercept errors instead of crashing.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ internal class GameLocationPatcher : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
+ private static IMonitor MonitorForGame;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
+ public GameLocationPatcher(IMonitor monitorForGame)
+ {
+ GameLocationPatcher.MonitorForGame = monitorForGame;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireMethod<GameLocation>(nameof(GameLocation.checkEventPrecondition)),
+ finalizer: this.GetHarmonyMethod(nameof(GameLocationPatcher.Finalize_CheckEventPrecondition))
+ );
+ harmony.Patch(
+ original: this.RequireMethod<GameLocation>(nameof(GameLocation.updateSeasonalTileSheets)),
+ finalizer: this.GetHarmonyMethod(nameof(GameLocationPatcher.Finalize_UpdateSeasonalTileSheets))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call when <see cref="GameLocation.checkEventPrecondition"/> throws an exception.</summary>
+ /// <param name="__result">The return value of the original method.</param>
+ /// <param name="precondition">The precondition to be parsed.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_CheckEventPrecondition(ref int __result, string precondition, Exception __exception)
+ {
+ if (__exception != null)
+ {
+ __result = -1;
+ GameLocationPatcher.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{__exception.InnerException}", LogLevel.Error);
+ }
+
+ return null;
+ }
+
+ /// <summary>The method to call when <see cref="GameLocation.updateSeasonalTileSheets"/> throws an exception.</summary>
+ /// <param name="__instance">The instance being patched.</param>
+ /// <param name="map">The map whose tilesheets to update.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_UpdateSeasonalTileSheets(GameLocation __instance, Map map, Exception __exception)
+ {
+ if (__exception != null)
+ GameLocationPatcher.MonitorForGame.Log($"Failed updating seasonal tilesheets for location '{__instance.NameOrUniqueName}': \n{__exception}", LogLevel.Error);
+
+ return null;
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs
deleted file mode 100644
index 7a48133e..00000000
--- a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs
+++ /dev/null
@@ -1,158 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-#if HARMONY_2
-using System;
-using HarmonyLib;
-#else
-using System.Reflection;
-using Harmony;
-#endif
-using StardewModdingAPI.Framework.Patching;
-using StardewValley;
-using xTile;
-
-namespace StardewModdingAPI.Mods.ErrorHandler.Patches
-{
- /// <summary>Harmony patches for <see cref="GameLocation.checkEventPrecondition"/> and <see cref="GameLocation.updateSeasonalTileSheets"/> which intercept errors instead of crashing.</summary>
- /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
- [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
- internal class GameLocationPatches : IHarmonyPatch
- {
- /*********
- ** Fields
- *********/
- /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
- private static IMonitor MonitorForGame;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
- public GameLocationPatches(IMonitor monitorForGame)
- {
- GameLocationPatches.MonitorForGame = monitorForGame;
- }
-
- /// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
- {
- harmony.Patch(
- original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)),
- finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Finalize_GameLocation_CheckEventPrecondition))
- );
-harmony.Patch(
- original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)),
- finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_UpdateSeasonalTileSheets))
- );
- }
-#else
- public void Apply(HarmonyInstance harmony)
- {
- harmony.Patch(
- original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)),
- prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition))
- );
- harmony.Patch(
- original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)),
- prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets))
- );
- }
-#endif
-
-
- /*********
- ** Private methods
- *********/
-#if HARMONY_2
- /// <summary>The method to call instead of GameLocation.checkEventPrecondition.</summary>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="precondition">The precondition to be parsed.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- private static Exception Finalize_GameLocation_CheckEventPrecondition(ref int __result, string precondition, Exception __exception)
- {
- if (__exception != null)
- {
- __result = -1;
- EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{__exception.InnerException}", LogLevel.Error);
- }
-
- return null;
- }
-#else
- /// <summary>The method to call instead of <see cref="GameLocation.checkEventPrecondition"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="precondition">The precondition to be parsed.</param>
- /// <param name="__originalMethod">The method being wrapped.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod)
- {
- const string key = nameof(G