diff options
-rw-r--r-- | docs/release-notes.md | 1 | ||||
-rw-r--r-- | src/SMAPI/Framework/Patching/PatchHelper.cs | 34 | ||||
-rw-r--r-- | src/SMAPI/Patches/DialogueErrorPatch.cs | 9 | ||||
-rw-r--r-- | src/SMAPI/Patches/EventErrorPatch.cs | 9 | ||||
-rw-r--r-- | src/SMAPI/Patches/ObjectErrorPatch.cs | 39 | ||||
-rw-r--r-- | src/SMAPI/Patches/ScheduleErrorPatch.cs | 9 |
6 files changed, 83 insertions, 18 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md index d064f17f..d08e7476 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ## Upcoming release * For players: * Added config option to disable console colors. + * SMAPI now prevents more errors/crashes due to invalid item data. * Updated compatibility list. * For the Console Commands mod: diff --git a/src/SMAPI/Framework/Patching/PatchHelper.cs b/src/SMAPI/Framework/Patching/PatchHelper.cs new file mode 100644 index 00000000..4cb436f0 --- /dev/null +++ b/src/SMAPI/Framework/Patching/PatchHelper.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI.Framework.Patching +{ + /// <summary>Provides generic methods for implementing Harmony patches.</summary> + internal class PatchHelper + { + /********* + ** Fields + *********/ + /// <summary>The interception keys currently being intercepted.</summary> + private static readonly HashSet<string> InterceptingKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + + + /********* + ** Public methods + *********/ + /// <summary>Track a method that will be intercepted.</summary> + /// <param name="key">The intercept key.</param> + /// <returns>Returns false if the method was already marked for interception, else true.</returns> + public static bool StartIntercept(string key) + { + return PatchHelper.InterceptingKeys.Add(key); + } + + /// <summary>Track a method as no longer being intercepted.</summary> + /// <param name="key">The intercept key.</param> + public static void StopIntercept(string key) + { + PatchHelper.InterceptingKeys.Remove(key); + } + } +} diff --git a/src/SMAPI/Patches/DialogueErrorPatch.cs b/src/SMAPI/Patches/DialogueErrorPatch.cs index 24f97259..1e49826d 100644 --- a/src/SMAPI/Patches/DialogueErrorPatch.cs +++ b/src/SMAPI/Patches/DialogueErrorPatch.cs @@ -24,9 +24,6 @@ namespace StardewModdingAPI.Patches /// <summary>Simplifies access to private code.</summary> private static Reflector Reflection; - /// <summary>Whether the <see cref="NPC.CurrentDialogue"/> getter is currently being intercepted.</summary> - private static bool IsInterceptingCurrentDialogue; - /********* ** Accessors @@ -112,12 +109,12 @@ namespace StardewModdingAPI.Patches /// <returns>Returns whether to execute the original method.</returns> private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod) { - if (DialogueErrorPatch.IsInterceptingCurrentDialogue) + const string key = nameof(Before_NPC_CurrentDialogue); + if (!PatchHelper.StartIntercept(key)) return true; try { - DialogueErrorPatch.IsInterceptingCurrentDialogue = true; __result = (Stack<Dialogue>)__originalMethod.Invoke(__instance, new object[0]); return false; } @@ -129,7 +126,7 @@ namespace StardewModdingAPI.Patches } finally { - DialogueErrorPatch.IsInterceptingCurrentDialogue = false; + PatchHelper.StopIntercept(key); } } } diff --git a/src/SMAPI/Patches/EventErrorPatch.cs b/src/SMAPI/Patches/EventErrorPatch.cs index 1dc7e8c3..504d1d2e 100644 --- a/src/SMAPI/Patches/EventErrorPatch.cs +++ b/src/SMAPI/Patches/EventErrorPatch.cs @@ -18,9 +18,6 @@ namespace StardewModdingAPI.Patches /// <summary>Writes messages to the console and log file on behalf of the game.</summary> private static IMonitor MonitorForGame; - /// <summary>Whether the method is currently being intercepted.</summary> - private static bool IsIntercepted; - /********* ** Accessors @@ -61,12 +58,12 @@ namespace StardewModdingAPI.Patches /// <returns>Returns whether to execute the original method.</returns> private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod) { - if (EventErrorPatch.IsIntercepted) + const string key = nameof(Before_GameLocation_CheckEventPrecondition); + if (!PatchHelper.StartIntercept(key)) return true; try { - EventErrorPatch.IsIntercepted = true; __result = (int)__originalMethod.Invoke(__instance, new object[] { precondition }); return false; } @@ -78,7 +75,7 @@ namespace StardewModdingAPI.Patches } finally { - EventErrorPatch.IsIntercepted = false; + PatchHelper.StopIntercept(key); } } } diff --git a/src/SMAPI/Patches/ObjectErrorPatch.cs b/src/SMAPI/Patches/ObjectErrorPatch.cs index d716b29b..d3b8800a 100644 --- a/src/SMAPI/Patches/ObjectErrorPatch.cs +++ b/src/SMAPI/Patches/ObjectErrorPatch.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Reflection; using Harmony; using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -33,6 +35,12 @@ namespace StardewModdingAPI.Patches prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_GetDescription)) ); + // object.getDisplayName + harmony.Patch( + original: AccessTools.Method(typeof(SObject), "loadDisplayName"), + prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_loadDisplayName)) + ); + // IClickableMenu.drawToolTip harmony.Patch( original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)), @@ -60,6 +68,37 @@ namespace StardewModdingAPI.Patches return true; } + /// <summary>The method to call instead of <see cref="StardewValley.Object.loadDisplayName"/>.</summary> + /// <param name="__instance">The instance being patched.</param> + /// <param name="__result">The patched method's return value.</param> + /// <param name="__originalMethod">The method being wrapped.</param> + /// <returns>Returns whether to execute the original method.</returns> + 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); + } + } + /// <summary>The method to call instead of <see cref="IClickableMenu.drawToolTip"/>.</summary> /// <param name="__instance">The instance being patched.</param> /// <param name="hoveredItem">The item for which to draw a tooltip.</param> diff --git a/src/SMAPI/Patches/ScheduleErrorPatch.cs b/src/SMAPI/Patches/ScheduleErrorPatch.cs index a23aa645..799fcb40 100644 --- a/src/SMAPI/Patches/ScheduleErrorPatch.cs +++ b/src/SMAPI/Patches/ScheduleErrorPatch.cs @@ -19,9 +19,6 @@ namespace StardewModdingAPI.Patches /// <summary>Writes messages to the console and log file on behalf of the game.</summary> private static IMonitor MonitorForGame; - /// <summary>Whether the target is currently being intercepted.</summary> - private static bool IsIntercepting; - /********* ** Accessors @@ -62,12 +59,12 @@ namespace StardewModdingAPI.Patches /// <returns>Returns whether to execute the original method.</returns> private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __result, MethodInfo __originalMethod) { - if (ScheduleErrorPatch.IsIntercepting) + const string key = nameof(Before_NPC_parseMasterSchedule); + if (!PatchHelper.StartIntercept(key)) return true; try { - ScheduleErrorPatch.IsIntercepting = true; __result = (Dictionary<int, SchedulePathDescription>)__originalMethod.Invoke(__instance, new object[] { rawData }); return false; } @@ -79,7 +76,7 @@ namespace StardewModdingAPI.Patches } finally { - ScheduleErrorPatch.IsIntercepting = false; + PatchHelper.StopIntercept(key); } } } |