diff options
| author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-09-13 18:24:54 -0400 |
|---|---|---|
| committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-09-13 18:24:54 -0400 |
| commit | 6521df7b131924835eb797251c1e956fae0d6e13 (patch) | |
| tree | b704dc64b6b6fef72615bac8950d5eff3c80ea89 /src/SMAPI | |
| parent | e22a54212182d0adc443ac95bc791e83c90f7e10 (diff) | |
| parent | b7b8b001c5c2dc5d2c9fc1347532ca29368c2325 (diff) | |
| download | SMAPI-6521df7b131924835eb797251c1e956fae0d6e13.tar.gz SMAPI-6521df7b131924835eb797251c1e956fae0d6e13.tar.bz2 SMAPI-6521df7b131924835eb797251c1e956fae0d6e13.zip | |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI')
| -rw-r--r-- | src/SMAPI/Constants.cs | 2 | ||||
| -rw-r--r-- | src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 2 | ||||
| -rw-r--r-- | src/SMAPI/Framework/DeprecationManager.cs | 2 | ||||
| -rw-r--r-- | src/SMAPI/Framework/Monitor.cs | 2 | ||||
| -rw-r--r-- | src/SMAPI/Framework/SCore.cs | 13 | ||||
| -rw-r--r-- | src/SMAPI/Framework/SGame.cs | 4 | ||||
| -rw-r--r-- | src/SMAPI/IMonitor.cs | 2 | ||||
| -rw-r--r-- | src/SMAPI/Patches/DialogueErrorPatch.cs | 49 | ||||
| -rw-r--r-- | src/SMAPI/Patches/EventErrorPatch.cs | 84 | ||||
| -rw-r--r-- | src/SMAPI/Patches/LoadContextPatch.cs (renamed from src/SMAPI/Patches/LoadForNewGamePatch.cs) | 49 | ||||
| -rw-r--r-- | src/SMAPI/Patches/ObjectErrorPatch.cs | 32 | ||||
| -rw-r--r-- | src/SMAPI/Program.cs | 16 | ||||
| -rw-r--r-- | src/SMAPI/StardewModdingAPI.config.json | 114 | ||||
| -rw-r--r-- | src/SMAPI/StardewModdingAPI.csproj | 380 |
14 files changed, 285 insertions, 466 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index cb3aa26e..9d686e2f 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -20,7 +20,7 @@ namespace StardewModdingAPI ** Public ****/ /// <summary>SMAPI's current semantic version.</summary> - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.11.2"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.11.3"); /// <summary>The minimum supported version of Stardew Valley.</summary> public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.36"); diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 6485b3d4..2c50ec04 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -136,7 +136,7 @@ namespace StardewModdingAPI.Framework.ContentManagers throw GetContentError($"can't read unpacked map file directly from the underlying content manager. It must be loaded through the mod's {typeof(IModHelper)}.{nameof(IModHelper.Content)} helper."); default: - throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.png', '.tbin', or '.xnb'."); + throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.json', '.png', '.tbin', or '.xnb'."); } } catch (Exception ex) when (!(ex is SContentLoadException)) diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index 3153bbb4..984bb487 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -132,7 +132,7 @@ namespace StardewModdingAPI.Framework else { this.Monitor.Log(message, level); - this.Monitor.Log(warning.StackTrace); + this.Monitor.Log(warning.StackTrace, LogLevel.Debug); } } } diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index 47ebc2d7..617bfd85 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -78,7 +78,7 @@ namespace StardewModdingAPI.Framework /// <summary>Log a message for the player or developer.</summary> /// <param name="message">The message to log.</param> /// <param name="level">The log severity level.</param> - public void Log(string message, LogLevel level = LogLevel.Debug) + public void Log(string message, LogLevel level = LogLevel.Trace) { this.LogImpl(this.Source, message, (ConsoleLogLevel)level); } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 9ffa46a5..5dd52992 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -160,7 +160,7 @@ namespace StardewModdingAPI.Framework // init logging this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); - this.Monitor.Log($"Mods go here: {modsPath}"); + this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info); if (modsPath != Constants.DefaultModsPath) this.Monitor.Log("(Using custom --mods-path argument.)", LogLevel.Trace); this.Monitor.Log($"Log started at {DateTime.UtcNow:s} UTC", LogLevel.Trace); @@ -233,9 +233,10 @@ namespace StardewModdingAPI.Framework // apply game patches new GamePatcher(this.Monitor).Apply( + new EventErrorPatch(this.MonitorForGame), new DialogueErrorPatch(this.MonitorForGame, this.Reflection), new ObjectErrorPatch(), - new LoadForNewGamePatch(this.Reflection, this.GameInstance.OnLoadStageChanged) + new LoadContextPatch(this.Reflection, this.GameInstance.OnLoadStageChanged) ); // add exit handler @@ -251,7 +252,7 @@ namespace StardewModdingAPI.Framework } catch (Exception ex) { - this.Monitor.Log($"SMAPI failed trying to track the crash details: {ex.GetLogSummary()}"); + this.Monitor.Log($"SMAPI failed trying to track the crash details: {ex.GetLogSummary()}", LogLevel.Error); } this.GameInstance.Exit(); @@ -576,7 +577,7 @@ namespace StardewModdingAPI.Framework if (latestStable == null && response.Errors.Any()) { this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn); - this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}"); + this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}", LogLevel.Trace); } else if (this.IsValidUpdate(Constants.ApiVersion, latestBeta, this.Settings.UseBetaChannel)) { @@ -596,7 +597,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you won't be notified of new versions if this keeps happening.", LogLevel.Warn); this.Monitor.Log(ex is WebException && ex.InnerException == null ? $"Error: {ex.Message}" - : $"Error: {ex.GetLogSummary()}" + : $"Error: {ex.GetLogSummary()}", LogLevel.Trace ); } @@ -689,7 +690,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Couldn't check for new mod versions. This won't affect your game, but you won't be notified of mod updates if this keeps happening.", LogLevel.Warn); this.Monitor.Log(ex is WebException && ex.InnerException == null ? ex.Message - : ex.ToString() + : ex.ToString(), LogLevel.Trace ); } } diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 9818314a..704eb6bc 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -705,8 +705,8 @@ namespace StardewModdingAPI.Framework if (this.Monitor.IsVerbose) { - string addedText = this.Watchers.LocationsWatcher.Added.Any() ? string.Join(", ", added.Select(p => p.Name)) : "none"; - string removedText = this.Watchers.LocationsWatcher.Removed.Any() ? string.Join(", ", removed.Select(p => p.Name)) : "none"; + string addedText = added.Any() ? string.Join(", ", added.Select(p => p.Name)) : "none"; + string removedText = removed.Any() ? string.Join(", ", removed.Select(p => p.Name)) : "none"; this.Monitor.Log($"Context: location list changed (added {addedText}; removed {removedText}).", LogLevel.Trace); } diff --git a/src/SMAPI/IMonitor.cs b/src/SMAPI/IMonitor.cs index 0f153e10..943c1c59 100644 --- a/src/SMAPI/IMonitor.cs +++ b/src/SMAPI/IMonitor.cs @@ -19,7 +19,7 @@ namespace StardewModdingAPI /// <summary>Log a message for the player or developer.</summary> /// <param name="message">The message to log.</param> /// <param name="level">The log severity level.</param> - void Log(string message, LogLevel level = LogLevel.Debug); + void Log(string message, LogLevel level = LogLevel.Trace); /// <summary>Log a message that only appears when <see cref="IsVerbose"/> is enabled.</summary> /// <param name="message">The message to log.</param> diff --git a/src/SMAPI/Patches/DialogueErrorPatch.cs b/src/SMAPI/Patches/DialogueErrorPatch.cs index d8905fd1..f1c25c05 100644 --- a/src/SMAPI/Patches/DialogueErrorPatch.cs +++ b/src/SMAPI/Patches/DialogueErrorPatch.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Patches internal class DialogueErrorPatch : IHarmonyPatch { /********* - ** Private methods + ** Fields *********/ /// <summary>Writes messages to the console and log file on behalf of the game.</summary> private static IMonitor MonitorForGame; @@ -21,6 +21,9 @@ 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 @@ -46,10 +49,14 @@ namespace StardewModdingAPI.Patches /// <param name="harmony">The Harmony instance.</param> public void Apply(HarmonyInstance harmony) { - ConstructorInfo constructor = AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }); - MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(DialogueErrorPatch.Prefix)); - - harmony.Patch(constructor, new HarmonyMethod(prefix), null); + 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)) + ); } @@ -63,7 +70,7 @@ namespace StardewModdingAPI.Patches /// <returns>Returns whether to execute the original method.</returns> /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] - private static bool Prefix(Dialogue __instance, string masterDialogue, NPC speaker) + 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(); @@ -96,5 +103,35 @@ namespace StardewModdingAPI.Patches 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> + /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] + private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod) + { + if (DialogueErrorPatch.IsInterceptingCurrentDialogue) + return true; + + try + { + DialogueErrorPatch.IsInterceptingCurrentDialogue = true; + __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 + { + DialogueErrorPatch.IsInterceptingCurrentDialogue = false; + } + } } } diff --git a/src/SMAPI/Patches/EventErrorPatch.cs b/src/SMAPI/Patches/EventErrorPatch.cs new file mode 100644 index 00000000..cd530616 --- /dev/null +++ b/src/SMAPI/Patches/EventErrorPatch.cs @@ -0,0 +1,84 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Harmony; +using StardewModdingAPI.Framework.Patching; +using StardewValley; + +namespace StardewModdingAPI.Patches +{ + /// <summary>A Harmony patch for the <see cref="Dialogue"/> constructor which intercepts invalid dialogue lines and logs an error instead of crashing.</summary> + internal class EventErrorPatch : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// <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 + *********/ + /// <summary>A unique name for this patch.</summary> + public string Name => $"{nameof(EventErrorPatch)}"; + + + /********* + ** 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 EventErrorPatch(IMonitor monitorForGame) + { + EventErrorPatch.MonitorForGame = monitorForGame; + } + + /// <summary>Apply the Harmony patch.</summary> + /// <param name="harmony">The Harmony instance.</param> + public void Apply(HarmonyInstance harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"), + prefix: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition)) + ); + } + + + /********* + ** Private methods + *********/ + /// <summary>The method to call instead of the 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> + /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] + private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod) + { + if (EventErrorPatch.IsIntercepted) + return true; + + try + { + EventErrorPatch.IsIntercepted = true; + __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 + { + EventErrorPatch.IsIntercepted = false; + } + } + } +} diff --git a/src/SMAPI/Patches/LoadForNewGamePatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs index 9e788e84..3f86c9a9 100644 --- a/src/SMAPI/Patches/LoadForNewGamePatch.cs +++ b/src/SMAPI/Patches/LoadContextPatch.cs @@ -1,7 +1,6 @@ using System; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Reflection; using Harmony; using StardewModdingAPI.Enums; using StardewModdingAPI.Framework.Patching; @@ -13,10 +12,10 @@ namespace StardewModdingAPI.Patches { /// <summary>A Harmony patch for <see cref="Game1.loadForNewGame"/> which notifies SMAPI for save creation load stages.</summary> /// <remarks>This patch hooks into <see cref="Game1.loadForNewGame"/>, checks if <c>TitleMenu.transitioningCharacterCreationMenu</c> is true (which means the player is creating a new save file), then raises <see cref="LoadStage.CreatedBasicInfo"/> after the location list is cleared twice (the second clear happens right before locations are created), and <see cref="LoadStage.CreatedLocations"/> when the method ends.</remarks> - internal class LoadForNewGamePatch : IHarmonyPatch + internal class LoadContextPatch : IHarmonyPatch { /********* - ** Accessors + ** Fields *********/ /// <summary>Simplifies access to private code.</summary> private static Reflector Reflection; @@ -28,14 +27,14 @@ namespace StardewModdingAPI.Patches private static bool IsCreating; /// <summary>The number of times that <see cref="Game1.locations"/> has been cleared since <see cref="Game1.loadForNewGame"/> started.</summary> - private static int TimesLocationsCleared = 0; + private static int TimesLocationsCleared; /********* ** Accessors *********/ /// <summary>A unique name for this patch.</summary> - public string Name => $"{nameof(LoadForNewGamePatch)}"; + public string Name => $"{nameof(LoadContextPatch)}"; /********* @@ -44,21 +43,21 @@ namespace StardewModdingAPI.Patches /// <summary>Construct an instance.</summary> /// <param name="reflection">Simplifies access to private code.</param> /// <param name="onStageChanged">A callback to invoke when the load stage changes.</param> - public LoadForNewGamePatch(Reflector reflection, Action<LoadStage> onStageChanged) + public LoadContextPatch(Reflector reflection, Action<LoadStage> onStageChanged) { - LoadForNewGamePatch.Reflection = reflection; - LoadForNewGamePatch.OnStageChanged = onStageChanged; + LoadContextPatch.Reflection = reflection; + LoadContextPatch.OnStageChanged = onStageChanged; } /// <summary>Apply the Harmony patch.</summary> /// <param name="harmony">The Harmony instance.</param> public void Apply(HarmonyInstance harmony) { - MethodInfo method = AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)); - MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Prefix)); - MethodInfo postfix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Postfix)); - - harmony.Patch(method, new HarmonyMethod(prefix), new HarmonyMethod(postfix)); + harmony.Patch( + original: AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)), + prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_LoadForNewGame)), + postfix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.After_Game1_LoadForNewGame)) + ); } @@ -68,15 +67,15 @@ namespace StardewModdingAPI.Patches /// <summary>The method to call instead of <see cref="Game1.loadForNewGame"/>.</summary> /// <returns>Returns whether to execute the original method.</returns> /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - private static bool Prefix() + private static bool Before_Game1_LoadForNewGame() { - LoadForNewGamePatch.IsCreating = Game1.activeClickableMenu is TitleMenu menu && LoadForNewGamePatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue(); - LoadForNewGamePatch.TimesLocationsCleared = 0; - if (LoadForNewGamePatch.IsCreating) + LoadContextPatch.IsCreating = Game1.activeClickableMenu is TitleMenu menu && LoadContextPatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue(); + LoadContextPatch.TimesLocationsCleared = 0; + if (LoadContextPatch.IsCreating) { // raise CreatedBasicInfo after locations are cleared twice ObservableCollection<GameLocation> locations = (ObservableCollection<GameLocation>)Game1.locations; - locations.CollectionChanged += LoadForNewGamePatch.OnLocationListChanged; + locations.CollectionChanged += LoadContextPatch.OnLocationListChanged; } return true; @@ -84,16 +83,16 @@ namespace StardewModdingAPI.Patches /// <summary>The method to call instead after <see cref="Game1.loadForNewGame"/>.</summary> /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - private static void Postfix() + private static void After_Game1_LoadForNewGame() { - if (LoadForNewGamePatch.IsCreating) + if (LoadContextPatch.IsCreating) { // clean up - ObservableCollection<GameLocation> locations = (ObservableCollection<GameLocation>) Game1.locations; - locations.CollectionChanged -= LoadForNewGamePatch.OnLocationListChanged; + ObservableCollection<GameLocation> locations = (ObservableCollection<GameLocation>)Game1.locations; + locations.CollectionChanged -= LoadContextPatch.OnLocationListChanged; // raise stage changed - LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedLocations); + LoadContextPatch.OnStageChanged(LoadStage.CreatedLocations); } } @@ -102,8 +101,8 @@ namespace StardewModdingAPI.Patches /// <param name="e">The event arguments.</param> private static void OnLocationListChanged(object sender, NotifyCollectionChangedEventArgs e) { - if (++LoadForNewGamePatch.TimesLocationsCleared == 2) - LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedBasicInfo); + if (++LoadContextPatch.TimesLocationsCleared == 2) + LoadContextPatch.OnStageChanged(LoadStage.CreatedBasicInfo); } } } diff --git a/src/SMAPI/Patches/ObjectErrorPatch.cs b/src/SMAPI/Patches/ObjectErrorPatch.cs index 0481259d..5b918d39 100644 --- a/src/SMAPI/Patches/ObjectErrorPatch.cs +++ b/src/SMAPI/Patches/ObjectErrorPatch.cs @@ -1,8 +1,8 @@ using System.Diagnostics.CodeAnalysis; -using System.Reflection; using Harmony; using StardewModdingAPI.Framework.Patching; using StardewValley; +using StardewValley.Menus; using SObject = StardewValley.Object; namespace StardewModdingAPI.Patches @@ -24,10 +24,17 @@ namespace StardewModdingAPI.Patches /// <param name="harmony">The Harmony instance.</param> public void Apply(HarmonyInstance harmony) { - MethodInfo method = AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)); - MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(ObjectErrorPatch.Prefix)); + // object.getDescription + harmony.Patch( + original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)), + prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_GetDescription)) + ); - harmony.Patch(method, new HarmonyMethod(prefix), null); + // IClickableMenu.drawToolTip + harmony.Patch( + original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)), + prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_IClickableMenu_DrawTooltip)) + ); } @@ -40,7 +47,7 @@ namespace StardewModdingAPI.Patches /// <returns>Returns whether to execute the original method.</returns> /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] - private static bool Prefix(SObject __instance, ref string __result) + private static bool Before_Object_GetDescription(SObject __instance, ref string __result) { // invalid bigcraftables crash instead of showing '???' like invalid non-bigcraftables if (!__instance.IsRecipe && __instance.bigCraftable.Value && !Game1.bigCraftablesInformation.ContainsKey(__instance.ParentSheetIndex)) @@ -51,5 +58,20 @@ namespace StardewModdingAPI.Patches return true; } + + /// <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> + /// <returns>Returns whether to execute the original method.</returns> + /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] + private static bool Before_IClickableMenu_DrawTooltip(IClickableMenu __instance, Item hoveredItem) + { + // invalid edible item cause crash when drawing tooltips + if (hoveredItem is SObject obj && obj.Edibility != -300 && !Game1.objectInformation.ContainsKey(obj.ParentSheetIndex)) + return false; + + return true; + } } } diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 2eec371c..3a34872a 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -30,10 +30,18 @@ namespace StardewModdingAPI /// <param name="args">The command-line arguments.</param> public static void Main(string[] args) { - AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve; - Program.AssertGamePresent(); - Program.AssertGameVersion(); - Program.Start(args); + try + { + AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve; + Program.AssertGamePresent(); + Program.AssertGameVersion(); + Program.Start(args); + } + catch (Exception ex) + { + Console.WriteLine($"SMAPI failed to initialise: {ex}"); + Program.PressAnyKeyToExit(true); + } } diff --git a/src/SMAPI/StardewModdingAPI.config.json b/src/SMAPI/StardewModdingAPI.config.json index ad908fc0..c04cceee 100644 --- a/src/SMAPI/StardewModdingAPI.config.json +++ b/src/SMAPI/StardewModdingAPI.config.json @@ -8,70 +8,70 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha */ { - /** - * The console color theme to use. The possible values are: - * - AutoDetect: SMAPI will assume a light background on Mac, and detect the background color automatically on Linux or Windows. - * - LightBackground: use darker text colors that look better on a white or light background. - * - DarkBackground: use lighter text colors that look better on a black or dark background. - */ - "ColorScheme": "AutoDetect", + /** + * The console color theme to use. The possible values are: + * - AutoDetect: SMAPI will assume a light background on Mac, and detect the background color automatically on Linux or Windows. + * - LightBackground: use darker text colors that look better on a white or light background. + * - DarkBackground: use lighter text colors that look better on a black or dark background. + */ + "ColorScheme": "AutoDetect", - /** - * Whether SMAPI should check for newer versions of SMAPI and mods when you load the game. If new - * versions are available, an alert will be shown in the console. This doesn't affect the load - * time even if your connection is offline or slow, because it happens in the background. - */ - "CheckForUpdates": true, + /** + * Whether SMAPI should check for newer versions of SMAPI and mods when you load the game. If new + * versions are available, an alert will be shown in the console. This doesn't affect the load + * time even if your connection is offline or slow, because it happens in the background. + */ + "CheckForUpdates": true, - /** - * Whether to enable features intended for mod developers. Currently this only makes TRACE-level - * messages appear in the console. - */ - "DeveloperMode": true, + /** + * Whether to enable features intended for mod developers. Currently this only makes TRACE-level + * messages appear in the console. + */ + "DeveloperMode": true, - /** - * Whether to add a section to the 'mod issues' list for mods which directly use potentially - * sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as - * part of their normal functionality, so these warnings are meaningless without further - * investigation. When this is commented out, it'll be true for local debug builds and false - * otherwise. - */ - //"ParanoidWarnings": true, + /** + * Whether to add a section to the 'mod issues' list for mods which directly use potentially + * sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as + * part of their normal functionality, so these warnings are meaningless without further + * investigation. When this is commented out, it'll be true for local debug builds and false + * otherwise. + */ + //"ParanoidWarnings": true, - /** - * Whether SMAPI should show newer beta versions as an available update. When this is commented - * out, it'll be true if the current SMAPI version is beta, and false otherwise. - */ - //"UseBetaChannel": true, + /** + * Whether SMAPI should show newer beta versions as an available update. When this is commented + * out, it'll be true if the current SMAPI version is beta, and false otherwise. + */ + //"UseBetaChannel": true, - /** - * SMAPI's GitHub project name, used to perform update checks. - */ - "GitHubProjectName": "Pathoschild/SMAPI", + /** + * SMAPI's GitHub project name, used to perform update checks. + */ + "GitHubProjectName": "Pathoschild/SMAPI", - /** - * The base URL for SMAPI's web API, used to perform update checks. - * Note: the protocol will be changed to http:// on Linux/Mac due to OpenSSL issues with the - * game's bundled Mono. - */ - "WebApiBaseUrl": "https://api.smapi.io", + /** + * The base URL for SMAPI's web API, used to perform update checks. + * Note: the protocol will be changed to http:// on Linux/Mac due to OpenSSL issues with the + * game's bundled Mono. + */ + "WebApiBaseUrl": "https://api.smapi.io", - /** - * Whether SMAPI should log more information about the game context. - */ - "VerboseLogging": false, + /** + * Whether SMAPI should log more information about the game context. + */ + "VerboseLogging": false, - /** - * Whether to generate a 'SMAPI-latest.metadata-dump.json' file in the logs folder with the full mod - * metadata for detected mods. This is only needed when troubleshooting some cases. - */ - "DumpMetadata": false, + /** + * Whether to generate a 'SMAPI-latest.metadata-dump.json' file in the logs folder with the full mod + * metadata for detected mods. This is only needed when troubleshooting some cases. + */ + "DumpMetadata": false, - /** - * The mod IDs SMAPI should ignore when performing update checks or validating update keys. - */ - "SuppressUpdateChecks": [ - "SMAPI.ConsoleCommands", - "SMAPI.SaveBackup" - ] + /** + * The mod IDs SMAPI should ignore when performing update checks or validating update keys. + */ + "SuppressUpdateChecks": [ + "SMAPI.ConsoleCommands", + "SMAPI.SaveBackup" + ] } diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index b6562eca..eda53025 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -1,69 +1,28 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> +<Project Sdk="Microsoft.NET.Sdk"> + |
