diff options
Diffstat (limited to 'src')
20 files changed, 193 insertions, 442 deletions
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs index cce13064..a065e3d7 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs @@ -1,15 +1,10 @@ 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 +using StardewModdingAPI.Framework.Patching; +using StardewValley; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { @@ -41,9 +36,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches DialogueErrorPatch.Reflection = reflector; } - /// <inheritdoc /> -#if HARMONY_2 public void Apply(Harmony harmony) { harmony.Patch( @@ -55,25 +48,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.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 /// <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> @@ -113,72 +92,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches 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/EventPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs index 72863d17..390aa778 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs @@ -1,10 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; -#if HARMONY_2 using HarmonyLib; -#else -using Harmony; -#endif using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -34,11 +30,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches } /// <inheritdoc /> -#if HARMONY_2 public void Apply(Harmony harmony) -#else - public void Apply(HarmonyInstance harmony) -#endif { harmony.Patch( original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)), diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs index 7a48133e..ec809fba 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs @@ -1,11 +1,6 @@ -using System.Diagnostics.CodeAnalysis; -#if HARMONY_2 using System; +using System.Diagnostics.CodeAnalysis; using HarmonyLib; -#else -using System.Reflection; -using Harmony; -#endif using StardewModdingAPI.Framework.Patching; using StardewValley; using xTile; @@ -36,37 +31,22 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches } /// <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)) + finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Finalize_GameLocation_CheckEventPrecondition)) ); harmony.Patch( original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)), - prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets)) + finalizer: 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> @@ -77,43 +57,12 @@ harmony.Patch( if (__exception != null) { __result = -1; - EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{__exception.InnerException}", LogLevel.Error); + GameLocationPatches.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(GameLocationPatches.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; - GameLocationPatches.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error); - return false; - } - finally - { - PatchHelper.StopIntercept(key); - } - } -#endif -#if HARMONY_2 /// <summary>The method to call instead of <see cref="GameLocation.updateSeasonalTileSheets"/>.</summary> /// <param name="__instance">The instance being patched.</param> /// <param name="map">The map whose tilesheets to update.</param> @@ -126,33 +75,5 @@ harmony.Patch( return null; } -#else - /// <summary>The method to call instead of <see cref="GameLocation.updateSeasonalTileSheets"/>.</summary> - /// <param name="__instance">The instance being patched.</param> - /// <param name="map">The map whose tilesheets to update.</param> - /// <param name="__originalMethod">The method being wrapped.</param> - /// <returns>Returns whether to execute the original method.</returns> - private static bool Before_GameLocation_UpdateSeasonalTileSheets(GameLocation __instance, Map map, MethodInfo __originalMethod) - { - const string key = nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets); - if (!PatchHelper.StartIntercept(key)) - return true; - - try - { - __originalMethod.Invoke(__instance, new object[] { map }); - return false; - } - catch (TargetInvocationException ex) - { - GameLocationPatches.MonitorForGame.Log($"Failed updating seasonal tilesheets for location '{__instance.NameOrUniqueName}'. Technical details:\n{ex.InnerException}", LogLevel.Error); - return false; - } - finally - { - PatchHelper.StopIntercept(key); - } - } -#endif } } diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs index 52d5f5a1..e14dc662 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs @@ -2,11 +2,7 @@ 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; @@ -45,11 +41,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// <inheritdoc /> -#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.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs index 9f8a98cd..a68e359c 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs @@ -1,16 +1,11 @@ +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.Mods.ErrorHandler.Patches { @@ -24,11 +19,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches ** Public methods *********/ /// <inheritdoc /> -#if HARMONY_2 public void Apply(Harmony harmony) -#else - public void Apply(HarmonyInstance harmony) -#endif { // object.getDescription harmony.Patch( @@ -39,11 +30,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.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 @@ -73,7 +60,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return true; } -#if HARMONY_2 /// <summary>The method to call after <see cref="StardewValley.Object.loadDisplayName"/>.</summary> /// <param name="__result">The patched method's return value.</param> /// <param name="__exception">The exception thrown by the wrapped method, if any.</param> @@ -88,38 +74,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return __exception; } -#else - /// <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(ObjectErrorPatch.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 /// <summary>The method to call instead of <see cref="IClickableMenu.drawToolTip"/>.</summary> /// <param name="hoveredItem">The item for which to draw a tooltip.</param> diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs index d2a5e988..d2e332b4 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs @@ -1,15 +1,10 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using StardewModdingAPI.Framework.Patching; -using StardewValley; -#if HARMONY_2 -using System; using HarmonyLib; using StardewModdingAPI.Framework; -#else -using System.Reflection; -using Harmony; -#endif +using StardewModdingAPI.Framework.Patching; +using StardewValley; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { @@ -37,19 +32,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches } /// <inheritdoc /> -#if HARMONY_2 public void Apply(Harmony harmony) -#else - public void Apply(HarmonyInstance harmony) -#endif { harmony.Patch( original: AccessTools.Method(typeof(NPC), nameof(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 ); } @@ -57,7 +44,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /********* ** Private methods *********/ -#if HARMONY_2 /// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary> /// <param name="rawData">The raw schedule data to parse.</param> /// <param name="__instance">The instance being patched.</param> @@ -74,35 +60,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return null; } -#else - /// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary> - /// <param name="rawData">The raw schedule data to parse.</param> - /// <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_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __result, MethodInfo __originalMethod) - { - const string key = nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule); - if (!PatchHelper.StartIntercept(key)) - return true; - - try - { - __result = (Dictionary<int, SchedulePathDescription>)__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<int, SchedulePathDescription>(); - return false; - } - finally - { - PatchHelper.StopIntercept(key); - } - } -#endif } } diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs index 8056fd71..7fdf5a48 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs @@ -1,10 +1,6 @@ -#if HARMONY_2 -using HarmonyLib; -#else -using Harmony; -#endif using System; using System.Diagnostics.CodeAnalysis; +using HarmonyLib; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Patching; @@ -20,11 +16,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches ** Public methods *********/ /// <inheritdoc /> -#if HARMONY_2 public void Apply(Harmony harmony) -#else - public void Apply(HarmonyInstance harmony) -#endif { harmony.Patch( original: Constants.GameFramework == GameFramework.Xna diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs index 1ddd407c..cd57736f 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs @@ -1,12 +1,6 @@ -#if HARMONY_2 -using System; -using HarmonyLib; -#else -using Harmony; -#endif using System; using System.Diagnostics.CodeAnalysis; -using System.Reflection; +using HarmonyLib; using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -22,7 +16,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches ** Public methods *********/ /// <inheritdoc /> -#if HARMONY_2 public void Apply(Harmony harmony) { harmony.Patch( @@ -30,21 +23,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches 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 /// <summary>The method to call instead of <see cref="Utility.getItemFromStandardTextDescription"/>.</summary> /// <param name="description">The item text description to parse.</param> /// <param name="delimiter">The delimiter by which to split the text description.</param> @@ -56,34 +39,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches ? new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", __exception) : null; } -#else - /// <summary>The method to call instead of <see cref="Utility.getItemFromStandardTextDescription"/>.</summary> - /// <param name="__result">The return value of the original method.</param> - /// <param name="description">The item text description to parse.</param> - /// <param name="who">The player for which the item is being parsed.</param> - /// <param name="delimiter">The delimiter by which to split the text description.</param> - /// <param name="__originalMethod">The method being wrapped.</param> - /// <returns>Returns whether to execute the original method.</returns> - 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 } } diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs index f3731d16..45b34556 100644 --- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs +++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs @@ -3,25 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; -#if HARMONY_2 using HarmonyLib; -#else -using Harmony; -#endif namespace StardewModdingAPI.Framework.Commands { /// <summary>The 'harmony_summary' SMAPI console command.</summary> internal class HarmonySummaryCommand : IInternalCommand { -#if !HARMONY_2 - /********* - ** Fields - *********/ - /// <summary>The Harmony instance through which to fetch patch info.</summary> - private readonly HarmonyInstance HarmonyInstance = HarmonyInstance.Create($"SMAPI.{nameof(HarmonySummaryCommand)}"); -#endif - /********* ** Accessors *********/ @@ -60,9 +48,7 @@ namespace StardewModdingAPI.Framework.Commands { PatchType.Prefix => 0, PatchType.Postfix => 1, -#if HARMONY_2 PatchType.Finalizer => 2, -#endif PatchType.Transpiler => 3, _ => 4 }); @@ -111,26 +97,16 @@ namespace StardewModdingAPI.Framework.Commands /// <summary>Get all current Harmony patches.</summary> private IEnumerable<SearchResult> GetAllPatches() { -#if HARMONY_2 foreach (MethodBase method in Harmony.GetAllPatchedMethods().ToArray()) -#else - foreach (MethodBase method in this.HarmonyInstance.GetPatchedMethods().ToArray()) -#endif { // get metadata for method -#if HARMONY_2 HarmonyLib.Patches patchInfo = Harmony.GetPatchInfo(method); -#else - Harmony.Patches patchInfo = this.HarmonyInstance.GetPatchInfo(method); -#endif IDictionary<PatchType, IReadOnlyCollection<Patch>> patchGroups = new Dictionary<PatchType, IReadOnlyCollection<Patch>> { [PatchType.Prefix] = patchInfo.Prefixes, [PatchType.Postfix] = patchInfo.Postfixes, -#if HARMONY_2 [PatchType.Finalizer] = patchInfo.Finalizers, -#endif [PatchType.Transpiler] = patchInfo.Transpilers }; @@ -160,10 +136,8 @@ namespace StardewModdingAPI.Framework.Commands /// <summary>A postfix patch.</summary> Postfix, -#if HARMONY_2 /// <summary>A finalizer patch.</summary> Finalizer, -#endif /// <summary>A transpiler patch.</summary> Transpiler diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs index 102f3364..8e4320b3 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs @@ -1,4 +1,3 @@ -#if HARMONY_2 using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -41,4 +40,3 @@ 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 ad6d5e4f..54b91679 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -1,4 +1,3 @@ -#if HARMONY_2 using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -81,4 +80,3 @@ 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 f3975558..44c97401 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs @@ -1,4 +1,3 @@ -#if HARMONY_2 using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -44,4 +43,3 @@ 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 147a12f9..5a84a16a 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -1,4 +1,3 @@ -#if HARMONY_2 extern alias MonoCecilPackage; using System; @@ -44,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } /// <inheritdoc /> - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith) + public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // rewrite Harmony 1.x methods to Harmony 2.0 MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); @@ -119,4 +118,3 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } } } -#endif diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs index ddecda08..3ce22ee9 100644 --- a/src/SMAPI/Framework/Patching/GamePatcher.cs +++ b/src/SMAPI/Framework/Patching/GamePatcher.cs @@ -1,9 +1,5 @@ using System; -#if HARMONY_2 using HarmonyLib; -#else -using Harmony; -#endif namespace StardewModdingAPI.Framework.Patching { @@ -31,11 +27,7 @@ namespace StardewModdingAPI.Framework.Patching /// <param name="patches">The patches to apply.</param> 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 38d30ab2..c1ff3040 100644 --- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs +++ b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs @@ -1,8 +1,4 @@ -#if HARMONY_2 using HarmonyLib; -#else -using Harmony; -#endif namespace StardewModdingAPI.Framework.Patching { @@ -14,10 +10,6 @@ namespace StardewModdingAPI.Framework.Patching *********/ /// <summary>Apply the Harmony patch.</summary> /// <param name="harmony">The Harmony instance.</param> -#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 deleted file mode 100644 index d1aa0185..00000000 --- a/src/SMAPI/Framework/Patching/PatchHelper.cs +++ /dev/null @@ -1,36 +0,0 @@ -#if !HARMONY_2 -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); - } - } -} -#endif diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index c3285979..4211abc5 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -46,6 +46,7 @@ using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Utilities; using StardewValley; using xTile.Display; +using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix; using SObject = StardewValley.Object; namespace StardewModdingAPI.Framework @@ -251,6 +252,7 @@ namespace StardewModdingAPI.Framework StardewValley.GameRunner.instance = this.Game; // apply game patches + MiniMonoModHotfix.Apply(); new GamePatcher(this.Monitor).Apply( new LoadContextPatch(this.Reflection, this.OnLoadStageChanged) ); diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs new file mode 100644 index 00000000..6814abea --- /dev/null +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -0,0 +1,176 @@ +// This temporary utility fixes an esoteric issue in XNA Framework where deserialization depends on +// the order of fields returned by Type.GetFields, but that order changes after Harmony/MonoMod use +// reflection to access the fields due to an issue in .NET Framework. +// https://twitter.com/0x0ade/status/1414992316964687873 +// +// This will be removed when Harmony/MonoMod are updated to incorporate the fix. +// +// Special thanks to 0x0ade for submitting this worokaround! Copy/pasted and adapted from MonoMod. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using HarmonyLib; + +// ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author. +namespace MonoMod.Utils +{ + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Temporary hotfix submitted by the MonoMod author.")] + [SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "Temporary hotfix submitted by the MonoMod author.")] + static class MiniMonoModHotfix + { + // .NET Framework can break member ordering if using Module.Resolve* on certain members. + + private static readonly object[] _NoArgs = new object[0]; + private static readonly object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null }; + + private static readonly Type t_RuntimeModule = + typeof(Module).Assembly + .GetType("System.Reflection.RuntimeModule"); + + private static readonly PropertyInfo p_RuntimeModule_RuntimeType = + typeof(Module).Assembly + .GetType("System.Reflection.RuntimeModule") + ?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + private static readonly Type t_RuntimeType = + typeof(Type).Assembly + .GetType("System.RuntimeType"); + + private static readonly PropertyInfo p_RuntimeType_Cache = + typeof(Type).Assembly + .GetType("System.RuntimeType") + ?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + private static readonly MethodInfo m_RuntimeTypeCache_GetFieldList = + typeof(Type).Assembly + .GetType("System.RuntimeType+RuntimeTypeCache") + ?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + private static readonly MethodInfo m_RuntimeTypeCache_GetPropertyList = + typeof(Type).Assembly + .GetType("System.RuntimeType+RuntimeTypeCache") + ?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + private static readonly ConditionalWeakTable<Type, object> _CacheFixed = new ConditionalWeakTable<Type, object>(); + + public static void Apply() + { + var harmony = new Harmony("MiniMonoModHotfix"); + + harmony.Patch( + original: typeof(Harmony).Assembly + .GetType("HarmonyLib.MethodBodyReader") + .GetMethod("ReadOperand", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), + transpiler: new HarmonyMethod(typeof(MiniMonoModHotfix), nameof(ResolveTokenFix)) + ); + + harmony.Patch( + original: typeof(Harmony).Assembly + .GetType("MonoMod.Utils.DynamicMethodDefinition+<>c__DisplayClass3_0") + .GetMethod("<_CopyMethodToDefinition>g__ResolveTokenAs|1", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), + transpiler: new HarmonyMethod(typeof(MiniMonoModHotfix), nameof(ResolveTokenFix)) + ); + + } + + private static IEnumerable<CodeInstruction> ResolveTokenFix(IEnumerable<CodeInstruction> instrs) + { + MethodInfo getdecl = typeof(MiniMonoModHotfix).GetMethod(nameof(GetRealDeclaringType)); + MethodInfo fixup = typeof(MiniMonoModHotfix).GetMethod(nameof(FixReflectionCache)); + + foreach (CodeInstruction instr in instrs) + { + yield return instr; + + if (instr.operand is MethodInfo called) + { + switch (called.Name) + { + case "ResolveType": + // type.FixReflectionCache(); + yield return new CodeInstruction(OpCodes.Dup); + yield return new CodeInstruction(OpCodes.Call, fixup); + break; + + case "ResolveMember": + case "ResolveMethod": + case "ResolveField": + // member.GetRealDeclaringType().FixReflectionCache(); + yield return new CodeInstruction(OpCodes.Dup); + yield return new CodeInstruction(OpCodes.Call, getdecl); + yield return new CodeInstruction(OpCodes.Call, fixup); + break; + } + } + } + } + + public static Type GetModuleType(this Module module) + { + // Sadly we can't blindly resolve type 0x02000001 as the runtime throws ArgumentException. + + if (module == null || t_RuntimeModule == null || !t_RuntimeModule.IsInstanceOfType(module)) + return null; + + // .NET + if (p_RuntimeModule_RuntimeType != null) + return (Type)p_RuntimeModule_RuntimeType.GetValue(module, _NoArgs); + + // The hotfix doesn't apply to Mono anyway, thus that's not copied over. + + return null; + } + + public static Type GetRealDeclaringType(this MemberInfo member) + => member.DeclaringType ?? member.Module?.GetModuleType(); + + public static void FixReflectionCache(this Type type) + { + if (t_RuntimeType == null || + p_RuntimeType_Cache == null || + m_RuntimeTypeCache_GetFieldList == null || + m_RuntimeTypeCache_GetPropertyList == null) + return; + + for (; type != null; type = type.DeclaringType) + { + // All types SHOULD inherit RuntimeType, including those built at runtime. + // One might never know what awaits us in the depths of reflection hell though. + if (!t_RuntimeType.IsInstanceOfType(type)) + continue; + + _CacheFixed.GetValue(type, rt => + { + + object cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs); + _FixReflectionCacheOrder<PropertyInfo>(cache, m_RuntimeTypeCache_GetPropertyList); + _FixReflectionCacheOrder<FieldInfo>(cache, m_RuntimeTypeCache_GetFieldList); + + return new object(); + }); + } + } + + private static void _FixReflectionCacheOrder<T>(object cache, MethodInfo getter) where T : MemberInfo + { + // Get and discard once, otherwise we might not be getting the actual backing array. + getter.Invoke(cache, _CacheGetterArgs); + Array orig = (Array)getter.Invoke(cache, _CacheGetterArgs); + + // Sort using a short-lived list. + List<T> list = new List<T>(orig.Length); + for (int i = 0; i < orig.Length; i++) + list.Add((T)orig.GetValue(i)); + + list.Sort((a, b) => a.MetadataToken - b.MetadataToken); + + for (int i = orig.Length - 1; i >= 0; --i) + orig.SetValue(list[i], i); + } + + } +} diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index d1699636..a787993a 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -48,10 +48,8 @@ namespace StardewModdingAPI.Metadata yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies); yield return new HeuristicMethodRewriter(this.ValidateReferencesToAssemblies); -#if HARMONY_2 - // rewrite for SMAPI 3.x (Harmony 1.x => 2.0 update) + // rewrite for SMAPI 3.12 (Harmony 1.x => 2.0 update) yield return new Harmony1AssemblyRewriter(); -#endif } /**** @@ -64,11 +62,7 @@ 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/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs index c43d7071..721cf53d 100644 --- a/src/SMAPI/Patches/LoadContextPatch.cs +++ b/src/SMAPI/Patches/LoadContextPatch.cs @@ -1,10 +1,6 @@ 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; @@ -46,11 +42,7 @@ namespace StardewModdingAPI.Patches } /// <inheritdoc /> -#if HARMONY_2 public void Apply(Harmony harmony) -#else - public void Apply(HarmonyInstance harmony) -#endif { // detect CreatedBasicInfo harmony.Patch( |