diff --git a/docs/release-notes.md b/docs/release-notes.md
index fabe7572..5409d9ff 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -13,6 +13,7 @@
* For the Error Handler mod:
* Added in SMAPI 3.9. This has vanilla error-handling that was previously added by SMAPI directly. That simplifies the core SMAPI logic, and lets players or modders disable it if needed.
+ * Added a detailed message for the _Input string was not in a correct format_ error when the game fails to parse an item text description.
## 3.8.4
Released 15 January 2021 for Stardew Valley 1.5.3 or later.
diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
index fa9338f4..2f6f1939 100644
--- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
+++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
@@ -40,7 +40,8 @@ namespace StardewModdingAPI.Mods.ErrorHandler
new DialogueErrorPatch(logManager.MonitorForGame, this.Helper.Reflection),
new ObjectErrorPatch(),
new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved),
- new ScheduleErrorPatch(logManager.MonitorForGame)
+ new ScheduleErrorPatch(logManager.MonitorForGame),
+ new UtilityErrorPatches()
);
// hook events
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs
new file mode 100644
index 00000000..481c881e
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs
@@ -0,0 +1,96 @@
+#if HARMONY_2
+using System;
+using HarmonyLib;
+#else
+using Harmony;
+#endif
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using StardewModdingAPI.Framework.Patching;
+using StardewValley;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// A Harmony patch for methods to log more detailed errors.
+ /// Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.
+ [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 UtilityErrorPatches : IHarmonyPatch
+ {
+ /*********
+ ** Accessors
+ *********/
+ ///
+ public string Name => nameof(UtilityErrorPatches);
+
+
+ /*********
+ ** Public methods
+ *********/
+ ///
+#if HARMONY_2
+ public void Apply(Harmony harmony)
+ {
+ harmony.Patch(
+ original: AccessTools.Method(typeof(Utility), nameof(Utility.getItemFromStandardTextDescription)),
+ finalizer: new HarmonyMethod(this.GetType(), nameof(UtilityErrorPatches.Finalize_Utility_GetItemFromStandardTextDescription))
+ );
+ }
+#else
+ public void Apply(HarmonyInstance harmony)
+ {
+ harmony.Patch(
+ original: AccessTools.Method(typeof(Utility), nameof(Utility.getItemFromStandardTextDescription)),
+ prefix: new HarmonyMethod(this.GetType(), nameof(UtilityErrorPatches.Before_Utility_GetItemFromStandardTextDescription))
+ );
+ }
+#endif
+
+
+ /*********
+ ** Private methods
+ *********/
+#if HARMONY_2
+ /// The method to call instead of .
+ /// The item text description to parse.
+ /// The delimiter by which to split the text description.
+ /// The exception thrown by the wrapped method, if any.
+ /// Returns the exception to throw, if any.
+ private static Exception Finalize_Utility_GetItemFromStandardTextDescription(string description, char delimiter, ref Exception __exception)
+ {
+ return __exception != null
+ ? new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", __exception)
+ : null;
+ }
+#else
+ /// The method to call instead of .
+ /// The return value of the original method.
+ /// The item text description to parse.
+ /// The player for which the item is being parsed.
+ /// The delimiter by which to split the text description.
+ /// The method being wrapped.
+ /// Returns whether to execute the original method.
+ private static bool Before_Utility_GetItemFromStandardTextDescription(ref Item __result, string description, Farmer who, char delimiter, MethodInfo __originalMethod)
+ {
+ const string key = nameof(UtilityErrorPatches.Before_Utility_GetItemFromStandardTextDescription);
+ if (!PatchHelper.StartIntercept(key))
+ return true;
+
+ try
+ {
+ __result = (Item)__originalMethod.Invoke(null, new object[] { description, who, delimiter });
+ return false;
+ }
+ catch (TargetInvocationException ex)
+ {
+ throw new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", ex.InnerException);
+ }
+ finally
+ {
+ PatchHelper.StopIntercept(key);
+ }
+ }
+#endif
+ }
+}