From bd20c2e1375ed5e32315ef5e292802bccc42f530 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 11 Jul 2021 01:44:02 -0400 Subject: alias Mono.Cecil references (#711) This is needed to migrate to Harmony 2.0 because it uses MonoMod, which has a copy of Mono.Cecil merged into its assembly, which leads to "type X exists in both 0Harmony.dll and Mono.Cecil.dll" errors. We can't use the version bundled with MonoMod since only some of the types are publicly accessible. --- src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs | 4 +++- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs | 4 +++- src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs | 6 ++++-- .../Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 6 ++++-- .../ModLoading/Finders/ReferenceToMissingMemberFinder.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs | 4 +++- src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs | 4 +++- .../Framework/ModLoading/Framework/BaseInstructionHandler.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs | 8 +++++--- src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/IInstructionHandler.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs | 4 +++- src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs | 6 ++++-- .../Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs | 6 ++++-- .../Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs | 6 ++++-- .../Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs | 6 ++++-- src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs | 4 +++- src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs | 4 +++- src/SMAPI/SMAPI.csproj | 4 +++- 24 files changed, 89 insertions(+), 41 deletions(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index aefb0126..9867ed8b 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -1,5 +1,7 @@ +extern alias MonoCecilPackage; + using System.Collections.Generic; -using Mono.Cecil; +using MonoCecilPackage.Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 3606eb66..8311a4b9 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -1,10 +1,12 @@ +extern alias MonoCecilPackage; + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.ModLoading.Framework; using StardewModdingAPI.Metadata; diff --git a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs index b56a776c..6493ccf8 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs @@ -1,5 +1,7 @@ +extern alias MonoCecilPackage; + using System.IO; -using Mono.Cecil; +using MonoCecilPackage.Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 01ed153b..2e349616 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -1,5 +1,7 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; +extern alias MonoCecilPackage; + +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index 2c062243..434ff5ab 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -1,5 +1,7 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; +extern alias MonoCecilPackage; + +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index d2340f01..80229155 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -1,5 +1,7 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; +extern alias MonoCecilPackage; + +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index 99344848..5ef66df4 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -1,5 +1,7 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; +extern alias MonoCecilPackage; + +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index b01a3240..80296e38 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,7 +1,9 @@ +extern alias MonoCecilPackage; + using System.Collections.Generic; using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index b64a255e..fae8ee95 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,6 +1,8 @@ +extern alias MonoCecilPackage; + using System.Collections.Generic; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs index 24ab2eca..3e107b90 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -1,5 +1,7 @@ +extern alias MonoCecilPackage; + using System; -using Mono.Cecil; +using MonoCecilPackage.Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index bbd081e8..9453c39f 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -1,5 +1,7 @@ +extern alias MonoCecilPackage; + using System; -using Mono.Cecil; +using MonoCecilPackage.Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index 624113b3..3979d431 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -1,7 +1,9 @@ +extern alias MonoCecilPackage; + using System; using System.Collections.Generic; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading.Framework { diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index 10f68f0d..e79bb488 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -1,9 +1,11 @@ +extern alias MonoCecilPackage; + using System; using System.Collections.Generic; using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Collections.Generic; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Collections.Generic; namespace StardewModdingAPI.Framework.ModLoading.Framework { diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 60bbd2c7..e88b75ed 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -1,8 +1,10 @@ +extern alias MonoCecilPackage; + using System; using System.Linq; using System.Reflection; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage::Mono.Cecil; +using MonoCecilPackage::Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading.Framework { diff --git a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs index 17c9ba68..342721cd 100644 --- a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs @@ -1,7 +1,9 @@ +extern alias MonoCecilPackage; + using System; using System.Collections.Generic; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs index d4366294..ee123e75 100644 --- a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs +++ b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs @@ -1,8 +1,10 @@ +extern alias MonoCecilPackage; + using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Mono.Cecil; +using MonoCecilPackage.Mono.Cecil; using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModLoading diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 0b679e9d..d40d4111 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -1,7 +1,9 @@ +extern alias MonoCecilPackage; + using System; using System.Reflection; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index 4b3675bc..147a12f9 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -1,8 +1,10 @@ #if HARMONY_2 +extern alias MonoCecilPackage; + using System; using HarmonyLib; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; using StardewModdingAPI.Framework.ModLoading.RewriteFacades; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index f59a6ab1..c381e4e5 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -1,7 +1,9 @@ +extern alias MonoCecilPackage; + using System.Collections.Generic; using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index e133b6fa..748c1e6f 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -1,7 +1,9 @@ +extern alias MonoCecilPackage; + using System.Collections.Generic; using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index 9933e2ca..5e0d9c70 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -1,7 +1,9 @@ +extern alias MonoCecilPackage; + using System; using System.Linq; -using Mono.Cecil; -using Mono.Cecil.Cil; +using MonoCecilPackage.Mono.Cecil; +using MonoCecilPackage.Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index ad5cb96f..a4d09ae9 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -1,5 +1,7 @@ +extern alias MonoCecilPackage; + using System; -using Mono.Cecil; +using MonoCecilPackage.Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs index a4ac54e2..fab424f3 100644 --- a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs +++ b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs @@ -1,7 +1,9 @@ +extern alias MonoCecilPackage; + using System; using System.Collections.Generic; using System.Linq; -using Mono.Cecil; +using MonoCecilPackage.Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 413d9f33..d36d0d7a 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -20,7 +20,9 @@ - + + MonoCecilPackage + -- cgit From 8df578edb6d135796a48b219ecc7a7291c7ef460 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 13 Jul 2021 09:14:07 -0400 Subject: migrate to Harmony 2.1 (#711) --- build/0Harmony.dll | Bin 115200 -> 802816 bytes build/0Harmony.xml | 3130 ++++++++++++++++++++ build/common.targets | 1 + build/prepare-install-package.targets | 1 + docs/release-notes.md | 6 +- docs/technical/smapi.md | 1 - .../Patches/DialogueErrorPatch.cs | 92 +- .../Patches/EventPatches.cs | 8 - .../Patches/GameLocationPatches.cs | 87 +- .../Patches/LoadErrorPatch.cs | 8 - .../Patches/ObjectErrorPatch.cs | 50 +- .../Patches/ScheduleErrorPatch.cs | 50 +- .../Patches/SpriteBatchValidationPatches.cs | 10 +- .../Patches/UtilityErrorPatches.cs | 48 +- .../Framework/Commands/HarmonySummaryCommand.cs | 26 - .../ModLoading/RewriteFacades/AccessToolsFacade.cs | 2 - .../RewriteFacades/HarmonyInstanceFacade.cs | 2 - .../RewriteFacades/HarmonyMethodFacade.cs | 2 - .../Rewriters/Harmony1AssemblyRewriter.cs | 4 +- src/SMAPI/Framework/Patching/GamePatcher.cs | 8 - src/SMAPI/Framework/Patching/IHarmonyPatch.cs | 8 - src/SMAPI/Framework/Patching/PatchHelper.cs | 36 - src/SMAPI/Metadata/InstructionMetadata.cs | 8 +- src/SMAPI/Patches/LoadContextPatch.cs | 8 - 24 files changed, 3149 insertions(+), 447 deletions(-) create mode 100644 build/0Harmony.xml delete mode 100644 src/SMAPI/Framework/Patching/PatchHelper.cs (limited to 'src/SMAPI') diff --git a/build/0Harmony.dll b/build/0Harmony.dll index 2e893d0e..1c5b9c09 100644 Binary files a/build/0Harmony.dll and b/build/0Harmony.dll differ diff --git a/build/0Harmony.xml b/build/0Harmony.xml new file mode 100644 index 00000000..ba2f340e --- /dev/null +++ b/build/0Harmony.xml @@ -0,0 +1,3130 @@ + + + + 0Harmony + + + + A factory to create delegate types + + + Default constructor + + + Creates a delegate type for a method + The method + The new delegate type + + + + A getter delegate type + Type that getter gets field/property value from + Type of the value that getter gets + The instance get getter uses + An delegate + + + + A setter delegate type + Type that setter sets field/property value for + Type of the value that setter sets + The instance the setter uses + The value the setter uses + An delegate + + + + A constructor delegate type + Type that constructor creates + An delegate + + + + A helper class for fast access to getters and setters + + + Creates an instantiation delegate + Type that constructor creates + The new instantiation delegate + + + + Creates an getter delegate for a property + Type that getter reads property from + Type of the property that gets accessed + The property + The new getter delegate + + + + Creates an getter delegate for a field + Type that getter reads field from + Type of the field that gets accessed + The field + The new getter delegate + + + + Creates an getter delegate for a field (with a list of possible field names) + Type that getter reads field/property from + Type of the field/property that gets accessed + A list of possible field names + The new getter delegate + + + + Creates an setter delegate + Type that setter assigns property value to + Type of the property that gets assigned + The property + The new setter delegate + + + + Creates an setter delegate for a field + Type that setter assigns field value to + Type of the field that gets assigned + The field + The new getter delegate + + + + A delegate to invoke a method + The instance + The method parameters + The method result + + + A helper class to invoke method with delegates + + + Creates a fast invocation handler from a method + The method to invoke + Controls if boxed value object is accessed/updated directly + The + + + The directBoxValueAccess option controls how value types passed by reference (e.g. ref int, out my_struct) are handled in the arguments array + passed to the fast invocation handler. + Since the arguments array is an object array, any value types contained within it are actually references to a boxed value object. + Like any other object, there can be other references to such boxed value objects, other than the reference within the arguments array. + For example, + + var val = 5; + var box = (object)val; + var arr = new object[] { box }; + handler(arr); // for a method with parameter signature: ref/out/in int + + + + + If directBoxValueAccess is true, the boxed value object is accessed (and potentially updated) directly when the handler is called, + such that all references to the boxed object reflect the potentially updated value. + In the above example, if the method associated with the handler updates the passed (boxed) value to 10, both box and arr[0] + now reflect the value 10. Note that the original val is not updated, since boxing always copies the value into the new boxed value object. + + + If directBoxValueAccess is false (default), the boxed value object in the arguments array is replaced with a "reboxed" value object, + such that potential updates to the value are reflected only in the arguments array. + In the above example, if the method associated with the handler updates the passed (boxed) value to 10, only arr[0] now reflects the value 10. + + + + + A low level memory helper + + + + Mark method for no inlining (currently only works on Mono) + The method/constructor to change + + + + Detours a method + The original method/constructor + The replacement method/constructor + An error string + + + + Writes a jump to memory + The memory address + Jump destination + An error string + + + + Gets the start of a method in memory + The method/constructor + [out] Details of the exception + The method start address + + + + special parameter names that can be used in prefix and postfix methods + + + Patch function helpers + + + Sorts patch methods by their priority rules + The original method + Patches to sort + Use debug mode + The sorted patch methods + + + + Creates new replacement method with the latest patches and detours the original method + The original method + Information describing the patches + The newly created replacement method + + + + Creates a patch sorter + Array of patches that will be sorted + Use debugging + + + Sorts internal PatchSortingWrapper collection and caches the results. + After first run the result is provided from the cache. + The original method + The sorted patch methods + + + Checks if the sorter was created with the same patch list and as a result can be reused to + get the sorted order of the patches. + List of patches to check against + true if equal + + + Removes one unresolved dependency from the least important patch. + + + Outputs all unblocked patches from the waiting list to results list + + + Adds patch to both results list and handled patches set + Patch to add + + + Wrapper used over the Patch object to allow faster dependency access and + dependency removal in case of cyclic dependencies + + + Create patch wrapper object used for sorting + Patch to wrap + + + Determines how patches sort + The other patch + integer to define sort order (-1, 0, 1) + + + Determines whether patches are equal + The other patch + true if equal + + + Hash function + A hash code + + + Bidirectionally registers Patches as after dependencies + List of dependencies to register + + + Bidirectionally registers Patches as before dependencies + List of dependencies to register + + + Bidirectionally removes Patch from after dependencies + Patch to remove + + + Bidirectionally removes Patch from before dependencies + Patch to remove + + + Specifies the type of method + + + + This is a normal method + + + This is a getter + + + This is a setter + + + This is a constructor + + + This is a static constructor + + + Specifies the type of argument + + + + This is a normal argument + + + This is a reference argument (ref) + + + This is an out argument (out) + + + This is a pointer argument (&) + + + Specifies the type of patch + + + + Any patch + + + A prefix patch + + + A postfix patch + + + A transpiler + + + A finalizer + + + A reverse patch + + + Specifies the type of reverse patch + + + + Use the unmodified original method (directly from IL) + + + Use the original as it is right now including previous patches but excluding future ones + + + Specifies the type of method call dispatching mechanics + + + + Call the method using dynamic dispatching if method is virtual (including overriden) + + + This is the built-in form of late binding (a.k.a. dynamic binding) and is the default dispatching mechanic in C#. + This directly corresponds with the instruction. + + + For virtual (including overriden) methods, the instance type's most-derived/overriden implementation of the method is called. + For non-virtual (including static) methods, same behavior as : the exact specified method implementation is called. + + + Note: This is not a fully dynamic dispatch, since non-virtual (including static) methods are still called non-virtually. + A fully dynamic dispatch in C# involves using + the dynamic type + (actually a fully dynamic binding, since even the name and overload resolution happens at runtime), which does not support. + + + + + Call the method using static dispatching, regardless of whether method is virtual (including overriden) or non-virtual (including static) + + + a.k.a. non-virtual dispatching, early binding, or static binding. + This directly corresponds with the instruction. + + + For both virtual (including overriden) and non-virtual (including static) methods, the exact specified method implementation is called, without virtual/override mechanics. + + + + + The base class for all Harmony annotations (not meant to be used directly) + + + + The common information for all attributes + + + Annotation to define your Harmony patch methods + + + + An empty annotation can be used together with TargetMethod(s) + + + + An annotation that specifies a class to patch + The declaring class/type + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The argument types of the method or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + An array of argument types to target overloads + Array of + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + An array of argument types to target overloads + Array of + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + The + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + The + + + + An annotation that specifies a method, property or constructor to patch + The + + + + An annotation that specifies a method, property or constructor to patch + The + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + An array of argument types to target overloads + An array of + + + + Annotation to define the original method for delegate injection + + + + An annotation that specifies a class to patch + The declaring class/type + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The argument types of the method or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + An array of argument types to target overloads + Array of + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + An array of argument types to target overloads + Array of + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + The + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + The + + + + An annotation that specifies call dispatching mechanics for the delegate + The + + + + An annotation that specifies a method, property or constructor to patch + The + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + An array of argument types to target overloads + An array of + + + + Annotation to define your standin methods for reverse patching + + + + An annotation that specifies the type of reverse patching + The of the reverse patch + + + + A Harmony annotation to define that all methods in a class are to be patched + + + + A Harmony annotation + + + + A Harmony annotation to define patch priority + The priority + + + + A Harmony annotation + + + + A Harmony annotation to define that a patch comes before another patch + The array of harmony IDs of the other patches + + + + A Harmony annotation + + + A Harmony annotation to define that a patch comes after another patch + The array of harmony IDs of the other patches + + + + A Harmony annotation + + + A Harmony annotation to debug a patch (output uses to log to your Desktop) + + + + Specifies the Prepare function in a patch class + + + + Specifies the Cleanup function in a patch class + + + + Specifies the TargetMethod function in a patch class + + + + Specifies the TargetMethods function in a patch class + + + + Specifies the Prefix function in a patch class + + + + Specifies the Postfix function in a patch class + + + + Specifies the Transpiler function in a patch class + + + + Specifies the Finalizer function in a patch class + + + + A Harmony annotation + + + + The name of the original argument + + + + The index of the original argument + + + + The new name of the original argument + + + + An annotation to declare injected arguments by name + + + + An annotation to declare injected arguments by index + Zero-based index + + + + An annotation to declare injected arguments by renaming them + Name of the original argument + New name + + + + An annotation to declare injected arguments by index and renaming them + Zero-based index + New name + + + + An abstract wrapper around OpCode and their operands. Used by transpilers + + + + The opcode + + + + The operand + + + + All labels defined on this instruction + + + + All exception block boundaries defined on this instruction + + + + Creates a new CodeInstruction with a given opcode and optional operand + The opcode + The operand + + + + Create a full copy (including labels and exception blocks) of a CodeInstruction + The to copy + + + + Clones a CodeInstruction and resets its labels and exception blocks + A lightweight copy of this code instruction + + + + Clones a CodeInstruction, resets labels and exception blocks and sets its opcode + The opcode + A copy of this CodeInstruction with a new opcode + + + + Clones a CodeInstruction, resets labels and exception blocks and sets its operand + The operand + A copy of this CodeInstruction with a new operand + + + + Creates a CodeInstruction calling a method (CALL) + The class/type where the method is declared + The name of the method (case sensitive) + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A code instruction that calls the method matching the arguments + + + + Creates a CodeInstruction calling a method (CALL) + The target method in the form TypeFullName:MethodName, where the type name matches a form recognized by Type.GetType like Some.Namespace.Type. + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A code instruction that calls the method matching the arguments + + + + Creates a CodeInstruction calling a method (CALL) + The lambda expression using the method + + + + + Creates a CodeInstruction calling a method (CALL) + The lambda expression using the method + + + + + Creates a CodeInstruction calling a method (CALL) + The lambda expression using the method + + + + + Creates a CodeInstruction calling a method (CALL) + The lambda expression using the method + + + + + Creates a CodeInstruction loading a field (LD[S]FLD[A]) + The class/type where the field is defined + The name of the field (case sensitive) + Use address of field + + + + Creates a CodeInstruction storing to a field (ST[S]FLD) + The class/type where the field is defined + The name of the field (case sensitive) + + + + Returns a string representation of the code instruction + A string representation of the code instruction + + + + Exception block types + + + + The beginning of an exception block + + + + The beginning of a catch block + + + + The beginning of an except filter block + + + + The beginning of a fault block + + + + The beginning of a finally block + + + + The end of an exception block + + + + An exception block + + + + Block type + + + + Catch type + + + + Creates an exception block + The + The catch type + + + + The Harmony instance is the main entry to Harmony. After creating one with an unique identifier, it is used to patch and query the current application domain + + + + The unique identifier + + + + Set to true before instantiating Harmony to debug Harmony or use an environment variable to set HARMONY_DEBUG to '1' like this: cmd /C "set HARMONY_DEBUG=1 && game.exe" + This is for full debugging. To debug only specific patches, use the attribute + + + + Creates a new Harmony instance + A unique identifier (you choose your own) + A Harmony instance + + + + Searches the current assembly for Harmony annotations and uses them to create patches + This method can fail to use the correct assembly when being inlined. It calls StackTrace.GetFrame(1) which can point to the wrong method/assembly. If you are unsure or run into problems, use PatchAll(Assembly.GetExecutingAssembly()) instead. + + + + Creates a empty patch processor for an original method + The original method/constructor + A new instance + + + + Creates a patch class processor from an annotated class + The class/type + A new instance + + + + Creates a reverse patcher for one of your stub methods + The original method/constructor + The stand-in stub method as + A new instance + + + + Searches an assembly for Harmony annotations and uses them to create patches + The assembly + + + + Creates patches by manually specifying the methods + The original method/constructor + An optional prefix method wrapped in a object + An optional postfix method wrapped in a object + An optional transpiler method wrapped in a object + An optional finalizer method wrapped in a object + The replacement method that was created to patch the original method + + + + Patches a foreign method onto a stub method of yours and optionally applies transpilers during the process + The original method/constructor you want to duplicate + Your stub method as that will become the original. Needs to have the correct signature (either original or whatever your transpilers generates) + An optional transpiler as method that will be applied during the process + The replacement method that was created to patch the stub method + + + + Unpatches methods by patching them with zero patches. Fully unpatching is not supported. Be careful, unpatching is global + The optional Harmony ID to restrict unpatching to a specific Harmony instance + This method could be static if it wasn't for the fact that unpatching creates a new replacement method that contains your harmony ID + + + + Unpatches a method by patching it with zero patches. Fully unpatching is not supported. Be careful, unpatching is global + The original method/constructor + The + The optional Harmony ID to restrict unpatching to a specific Harmony instance + + + + Unpatches a method by patching it with zero patches. Fully unpatching is not supported. Be careful, unpatching is global + The original method/constructor + The patch method as method to remove + + + + Test for patches from a specific Harmony ID + The Harmony ID + True if patches for this ID exist + + + + Gets patch information for a given original method + The original method/constructor + The patch information as + + + + Gets the methods this instance has patched + An enumeration of original methods/constructors + + + + Gets all patched original methods in the appdomain + An enumeration of patched original methods/constructors + + + + Gets the original method from a given replacement method + A replacement method, for example from a stacktrace + The original method/constructor or null if not found + + + + Tries to get the method from a stackframe including dynamic replacement methods + The + For normal frames, frame.GetMethod() is returned. For frames containing patched methods, the replacement method is returned or null if no method can be found + + + + Gets Harmony version for all active Harmony instances + [out] The current Harmony version + A dictionary containing assembly versions keyed by Harmony IDs + + + + Under Mono, HarmonyException wraps IL compile errors with detailed information about the failure + + + + Default serialization constructor (not implemented) + The info + The context + + + + Get a list of IL instructions in pairs of offset+code + A list of key/value pairs which represent an offset and the code at that offset + + + + Get a list of IL instructions without offsets + A list of + + + + Get the error offset of the errornous IL instruction + The offset + + + + Get the index of the errornous IL instruction + The index into the list of instructions or -1 if not found + + + + A wrapper around a method to use it as a patch (for example a Prefix) + + + + The original method + + + + Class/type declaring this patch + + + + Patch method name + + + + Optional patch + + + + Array of argument types of the patch method + + + + of the patch + + + + Install this patch before patches with these Harmony IDs + + + + Install this patch after patches with these Harmony IDs + + + + Reverse patch type, see + + + + Create debug output for this patch + + + + Whether to use (true) or (false) mechanics + for -attributed delegate + + + + Default constructor + + + + Creates a patch from a given method + The original method + + + + Creates a patch from a given method + The original method + The patch + A list of harmony IDs that should come after this patch + A list of harmony IDs that should come before this patch + Set to true to generate debug output + + + + Creates a patch from a given method + The patch class/type + The patch method name + The optional argument types of the patch method (for overloaded methods) + + + + Gets the names of all internal patch info fields + A list of field names + + + + Merges annotations + The list of to merge + The merged + + + + Returns a string that represents the annotation + A string representation + + + + Annotation extensions + + + + Copies annotation information + The source + The destination + + + + Clones an annotation + The to clone + A copied + + + + Merges annotations + The master + The detail + A new, merged + + + + Gets all annotations on a class/type + The class/type + A list of all + + + + Gets merged annotations on a class/type + The class/type + The merged + + + + Gets all annotations on a method + The method/constructor + A list of + + + + Gets merged annotations on a method + The method/constructor + The merged + + + + + A mutable representation of an inline signature, similar to Mono.Cecil's CallSite. + Used by the calli instruction, can be used by transpilers + + + + + See + + + + See + + + + See + + + + The list of all parameter types or function pointer signatures received by the call site + + + + The return type or function pointer signature returned by the call site + + + + Returns a string representation of the inline signature + A string representation of the inline signature + + + + + A mutable representation of a parameter type with an attached type modifier, + similar to Mono.Cecil's OptionalModifierType / RequiredModifierType and C#'s modopt / modreq + + + + + Whether this is a modopt (optional modifier type) or a modreq (required modifier type) + + + + The modifier type attached to the parameter type + + + + The modified parameter type + + + + Returns a string representation of the modifier type + A string representation of the modifier type + + + + Patch serialization + + + + Control the binding of a serialized object to a type + Specifies the assembly name of the serialized object + Specifies the type name of the serialized object + The type of the object the formatter creates a new instance of + + + + Serializes a patch info + The + The serialized data + + + + Deserialize a patch info + The serialized data + A + + + + Compare function to sort patch priorities + The patch + Zero-based index + The priority + A standard sort integer (-1, 0, 1) + + + + Serializable patch information + + + + Prefixes as an array of + + + + Postfixes as an array of + + + + Transpilers as an array of + + + + Finalizers as an array of + + + + Returns if any of the patches wants debugging turned on + + + + Adds prefixes + An owner (Harmony ID) + The patch methods + + + + Adds a prefix + + + Removes prefixes + The owner of the prefixes, or * for all + + + + Adds postfixes + An owner (Harmony ID) + The patch methods + + + + Adds a postfix + + + Removes postfixes + The owner of the postfixes, or * for all + + + + Adds transpilers + An owner (Harmony ID) + The patch methods + + + + Adds a transpiler + + + Removes transpilers + The owner of the transpilers, or * for all + + + + Adds finalizers + An owner (Harmony ID) + The patch methods + + + + Adds a finalizer + + + Removes finalizers + The owner of the finalizers, or * for all + + + + Removes a patch using its method + The method of the patch to remove + + + + Gets a concatenated list of patches + The Harmony instance ID adding the new patches + The patches to add + The current patches + + + + Gets a list of patches with any from the given owner removed + The owner of the methods, or * for all + The current patches + + + + A serializable patch + + + + Zero-based index + + + + The owner (Harmony ID) + + + + The priority, see + + + + Keep this patch before the patches indicated in the list of Harmony IDs + + + + Keep this patch after the patches indicated in the list of Harmony IDs + + + + A flag that will log the replacement method via every time this patch is used to build the replacement, even in the future + + + + The method of the static patch method + + + + Creates a patch + The method of the patch + Zero-based index + An owner (Harmony ID) + The priority, see + A list of Harmony IDs for patches that should run after this patch + A list of Harmony IDs for patches that should run before this patch + A flag that will log the replacement method via every time this patch is used to build the replacement, even in the future + + + + Creates a patch + The method of the patch + Zero-based index + An owner (Harmony ID) + + + Get the patch method or a DynamicMethod if original patch method is a patch factory + The original method/constructor + The method of the patch + + + + Determines whether patches are equal + The other patch + true if equal + + + + Determines how patches sort + The other patch + integer to define sort order (-1, 0, 1) + + + + Hash function + A hash code + + + + A PatchClassProcessor used to turn on a class/type into patches + + + + Creates a patch class processor by pointing out a class. Similar to PatchAll() but without searching through all classes. + The Harmony instance + The class to process (need to have at least a [HarmonyPatch] attribute) + + + + Applies the patches + A list of all created replacement methods or null if patch class is not annotated + + + + A group of patches + + + + A collection of prefix + + + + A collection of postfix + + + + A collection of transpiler + + + + A collection of finalizer + + + + Gets all owners (Harmony IDs) or all known patches + The patch owners + + + + Creates a group of patches + An array of prefixes as + An array of postfixes as + An array of transpileres as + An array of finalizeres as + + + + A PatchProcessor handles patches on a method/constructor + + + + Creates an empty patch processor + The Harmony instance + The original method/constructor + + + + Adds a prefix + The prefix as a + A for chaining calls + + + + Adds a prefix + The prefix method + A for chaining calls + + + + Adds a postfix + The postfix as a + A for chaining calls + + + + Adds a postfix + The postfix method + A for chaining calls + + + + Adds a transpiler + The transpiler as a + A for chaining calls + + + + Adds a transpiler + The transpiler method + A for chaining calls + + + + Adds a finalizer + The finalizer as a + A for chaining calls + + + + Adds a finalizer + The finalizer method + A for chaining calls + + + + Gets all patched original methods in the appdomain + An enumeration of patched method/constructor + + + + Applies all registered patches + The generated replacement method + + + + Unpatches patches of a given type and/or Harmony ID + The patch type + Harmony ID or * for any + A for chaining calls + + + + Unpatches a specific patch + The method of the patch + A for chaining calls + + + + Gets patch information on an original + The original method/constructor + The patch information as + + + + Sort patch methods by their priority rules + The original method + Patches to sort + The sorted patch methods + + + + Gets Harmony version for all active Harmony instances + [out] The current Harmony version + A dictionary containing assembly version keyed by Harmony ID + + + + Creates a new empty generator to use when reading method bodies + A new + + + + Creates a new generator matching the method/constructor to use when reading method bodies + The original method/constructor to copy method information from + A new + + + + Returns the methods unmodified list of code instructions + The original method/constructor + Optionally an existing generator that will be used to create all local variables and labels contained in the result (if not specified, an internal generator is used) + A list containing all the original + + + + Returns the methods unmodified list of code instructions + The original method/constructor + A new generator that now contains all local variables and labels contained in the result + A list containing all the original + + + + Returns the methods current list of code instructions after all existing transpilers have been applied + The original method/constructor + Apply only the first count of transpilers + Optionally an existing generator that will be used to create all local variables and labels contained in the result (if not specified, an internal generator is used) + A list of + + + + Returns the methods current list of code instructions after all existing transpilers have been applied + The original method/constructor + A new generator that now contains all local variables and labels contained in the result + Apply only the first count of transpilers + A list of + + + + A low level way to read the body of a method. Used for quick searching in methods + The original method + All instructions as opcode/operand pairs + + + + A low level way to read the body of a method. Used for quick searching in methods + The original method + An existing generator that will be used to create all local variables and labels contained in the result + All instructions as opcode/operand pairs + + + + A patch priority + + + + Patch last + + + + Patch with very low priority + + + + Patch with low priority + + + + Patch with lower than normal priority + + + + Patch with normal priority + + + + Patch with higher than normal priority + + + + Patch with high priority + + + + Patch with very high priority + + + + Patch first + + + + A reverse patcher + + + + Creates a reverse patcher + The Harmony instance + The original method/constructor + Your stand-in stub method as + + + + Applies the patch + The type of patch, see + The generated replacement method + + + + A collection of commonly used transpilers + + + + A transpiler that replaces all occurrences of a given method with another one using the same signature + The enumeration of to act on + Method or constructor to search for + Method or constructor to replace with + Modified enumeration of + + + + A transpiler that alters instructions that match a predicate by calling an action + The enumeration of to act on + A predicate selecting the instructions to change + An action to apply to matching instructions + Modified enumeration of + + + + A transpiler that logs a text at the beginning of the method + The instructions to act on + The log text + Modified enumeration of + + + + A helper class for reflection related functions + + + + Shortcut for to simplify the use of reflections and make it work for any access level + + + + Shortcut for to simplify the use of reflections and make it work for any access level but only within the current type + + + + Enumerates all assemblies in the current app domain, excluding visual studio assemblies + An enumeration of + + + Gets a type by name. Prefers a full name with namespace but falls back to the first type matching the name otherwise + The name + A type or null if not found + + + + Gets all successfully loaded types from a given assembly + The assembly + An array of types + + This calls and returns , while catching any thrown . + If such an exception is thrown, returns the successfully loaded types (, + filtered for non-null values). + + + + + Enumerates all successfully loaded types in the current app domain, excluding visual studio assemblies + An enumeration of all in all assemblies, excluding visual studio assemblies + + + Applies a function going up the type hierarchy and stops at the first non-null result + Result type of func() + The class/type to start with + The evaluation function returning T + The first non-null result, or null if no match + + The type hierarchy of a class or value type (including struct) does NOT include implemented interfaces, + and the type hierarchy of an interface is only itself (regardless of whether that interface implements other interfaces). + The top-most type in the type hierarchy of all non-interface types (including value types) is . + + + + + Applies a function going into inner types and stops at the first non-null result + Generic type parameter + The class/type to start with + The evaluation function returning T + The first non-null result, or null if no match + + + + Gets the reflection information for a directly declared field + The class/type where the field is defined + The name of the field + A field or null when type/name is null or when the field cannot be found + + + + Gets the reflection information for a field by searching the type and all its super types + The class/type where the field is defined + The name of the field (case sensitive) + A field or null when type/name is null or when the field cannot be found + + + + Gets the reflection information for a field + The class/type where the field is declared + The zero-based index of the field inside the class definition + A field or null when type is null or when the field cannot be found + + + + Gets the reflection information for a directly declared property + The class/type where the property is declared + The name of the property (case sensitive) + A property or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the getter method of a directly declared property + The class/type where the property is declared + The name of the property (case sensitive) + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the setter method of a directly declared property + The class/type where the property is declared + The name of the property (case sensitive) + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for a property by searching the type and all its super types + The class/type + The name + A property or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the getter method of a property by searching the type and all its super types + The class/type + The name + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the setter method of a property by searching the type and all its super types + The class/type + The name + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for a directly declared method + The class/type where the method is declared + The name of the method (case sensitive) + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A method or null when type/name is null or when the method cannot be found + + + + Gets the reflection information for a method by searching the type and all its super types + The class/type where the method is declared + The name of the method (case sensitive) + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A method or null when type/name is null or when the method cannot be found + + + + Gets the reflection information for a method by searching the type and all its super types + The target method in the form TypeFullName:MethodName, where the type name matches a form recognized by Type.GetType like Some.Namespace.Type. + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A method or null when type/name is null or when the method cannot be found + + + + Gets the names of all method that are declared in a type + The declaring class/type + A list of method names + + + + Gets the names of all method that are declared in the type of the instance + An instance of the type to search in + A list of method names + + + + Gets the names of all fields that are declared in a type + The declaring class/type + A list of field names + + + + Gets the names of all fields that are declared in the type of the instance + An instance of the type to search in + A list of field names + + + + Gets the names of all properties that are declared in a type + The declaring class/type + A list of property names + + + + Gets the names of all properties that are declared in the type of the instance + An instance of the type to search in + A list of property names + + + + Gets the type of any class member of + A member + The class/type of this member + + + + Test if a class member is actually an concrete implementation + A member + True if the member is a declared + + + + Gets the real implementation of a class member + A member + The member itself if its declared. Otherwise the member that is actually implemented in some base type + + + + Gets the reflection information for a directly declared constructor + The class/type where the constructor is declared + Optional parameters to target a specific overload of the constructor + Optional parameters to only consider static constructors + A constructor info or null when type is null or when the constructor cannot be found + + + + Gets the reflection information for a constructor by searching the type and all its super types + The class/type where the constructor is declared + Optional parameters to target a specific overload of the method + Optional parameters to only consider static constructors + A constructor info or null when type is null or when the method cannot be found + + + + Gets reflection information for all declared constructors + The class/type where the constructors are declared + Optional parameters to only consider static constructors + A list of constructor infos + + + + Gets reflection information for all declared methods + The class/type where the methods are declared + A list of methods + + + + Gets reflection information for all declared properties + The class/type where the properties are declared + A list of properties + + + + Gets reflection information for all declared fields + The class/type where the fields are declared + A list of fields + + + + Gets the return type of a method or constructor + The method/constructor + The return type + + + + Given a type, returns the first inner type matching a recursive search by name + The class/type to start searching at + The name of the inner type (case sensitive) + The inner type or null if type/name is null or if a type with that name cannot be found + + + + Given a type, returns the first inner type matching a recursive search with a predicate + The class/type to start searching at + The predicate to search with + The inner type or null if type/predicate is null or if a type with that name cannot be found + + + + Given a type, returns the first method matching a predicate + The class/type to start searching at + The predicate to search with + The method or null if type/predicate is null or if a type with that name cannot be found + + + + Given a type, returns the first constructor matching a predicate + The class/type to start searching at + The predicate to search with + The constructor info or null if type/predicate is null or if a type with that name cannot be found + + + + Given a type, returns the first property matching a predicate + The class/type to start searching at + The predicate to search with + The property or null if type/predicate is null or if a type with that name cannot be found + + + + Returns an array containing the type of each object in the given array + An array of objects + An array of types or an empty array if parameters is null (if an object is null, the type for it will be object) + + + + Creates an array of input parameters for a given method and a given set of potential inputs + The method/constructor you are planing to call + The possible input parameters in any order + An object array matching the method signature + + + + A readable/assignable reference delegate to an instance field of a class or static field (NOT an instance field of a struct) + + An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including ), + implemented interface, or derived class of this type + + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The runtime instance to access the field (ignored and can be omitted for static fields) + A readable/assignable reference to the field + Null instance passed to a non-static field ref delegate + + Instance of invalid type passed to a non-static field ref delegate + (this can happen if is a parent class or interface of the field's declaring type) + + + + This delegate cannot be used for instance fields of structs, since a struct instance passed to the delegate would be passed by + value and thus would be a copy that only exists within the delegate's invocation. This is fine for a readonly reference, + but makes assignment futile. Use instead. + + + Note that is not required to be the field's declaring type. It can be a parent class (including ), + implemented interface, or a derived class of the field's declaring type ("instanceOfT is FieldDeclaringType" must be possible). + Specifically, must be assignable from OR to the field's declaring type. + Technically, this allows Nullable, although Nullable is only relevant for structs, and since only static fields of structs + are allowed for this delegate, and the instance passed to such a delegate is ignored, this hardly matters. + + + Similarly, is not required to be the field's field type, unless that type is a non-enum value type. + It can be a parent class (including object) or implemented interface of the field's field type. It cannot be a derived class. + This variance is not allowed for value types, since that would require boxing/unboxing, which is not allowed for ref values. + Special case for enum types: can also be the underlying integral type of the enum type. + Specifically, for reference types, must be assignable from + the field's field type; for non-enum value types, must be exactly the field's field type; for enum types, + must be either the field's field type or the underyling integral type of that field type. + + + This delegate supports static fields, even those defined in structs, for legacy reasons. + For such static fields, is effectively ignored. + Consider using (and StaticFieldRefAccess methods that return it) instead for static fields. + + + + + + Creates a field reference delegate for an instance field of a class + The class that defines the instance field, or derived class of this type + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The name of the field + A readable/assignable delegate + + + For backwards compatibility, there is no class constraint on . + Instead, the non-value-type check is done at runtime within the method. + + + + + + Creates an instance field reference for a specific instance of a class + The class that defines the instance field, or derived class of this type + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The instance + The name of the field + A readable/assignable reference to the field + + + This method is meant for one-off access to a field's value for a single instance. + If you need to access a field's value for potentially multiple instances, use instead. + FieldRefAccess<T, F>(instance, fieldName) is functionally equivalent to FieldRefAccess<T, F>(fieldName)(instance). + + + For backwards compatibility, there is no class constraint on . + Instead, the non-value-type check is done at runtime within the method. + + + + + + Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct) + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + + The type that defines the field, or derived class of this type; must not be a struct type unless the field is static + + The name of the field + + A readable/assignable delegate with T=object + (for static fields, the instance delegate parameter is ignored) + + + + This method is meant for cases where the given type is only known at runtime and thus can't be used as a type parameter T + in e.g. . + + + This method supports static fields, even those defined in structs, for legacy reasons. + Consider using (and other overloads) instead for static fields. + + + + + + Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct) + + An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including ), + implemented interface, or derived class of this type ("instanceOfT is FieldDeclaringType" must be possible) + + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The field + A readable/assignable delegate + + + This method is meant for cases where the field has already been obtained, avoiding the field searching cost in + e.g. . + + + This method supports static fields, even those defined in structs, for legacy reasons. + For such static fields, is effectively ignored. + Consider using (and other overloads) instead for static fields. + + + For backwards compatibility, there is no class constraint on . + Instead, the non-value-type check is done at runtime within the method. + + + + + + Creates a field reference for an instance field of a class + + The type that defines the field; or a parent class (including ), implemented interface, or derived class of this type + ("instanceOfT is FieldDeclaringType" must be possible) + + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The instance + The field + A readable/assignable reference to the field + + + This method is meant for one-off access to a field's value for a single instance and where the field has already been obtained. + If you need to access a field's value for potentially multiple instances, use instead. + FieldRefAccess<T, F>(instance, fieldInfo) is functionally equivalent to FieldRefAccess<T, F>(fieldInfo)(instance). + + + For backwards compatibility, there is no class constraint on . + Instead, the non-value-type check is done at runtime within the method. + + + + + + A readable/assignable reference delegate to an instance field of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + A reference to the runtime instance to access the field + A readable/assignable reference to the field + + + + Creates a field reference delegate for an instance field of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The name of the field + A readable/assignable delegate + + + + Creates an instance field reference for a specific instance of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The instance + The name of the field + A readable/assignable reference to the field + + + This method is meant for one-off access to a field's value for a single instance. + If you need to access a field's value for potentially multiple instances, use instead. + StructFieldRefAccess<T, F>(ref instance, fieldName) is functionally equivalent to StructFieldRefAccess<T, F>(fieldName)(ref instance). + + + + + + Creates a field reference delegate for an instance field of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The field + A readable/assignable delegate + + + This method is meant for cases where the field has already been obtained, avoiding the field searching cost in + e.g. . + + + + + + Creates a field reference for an instance field of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The instance + The field + A readable/assignable reference to the field + + + This method is meant for one-off access to a field's value for a single instance and where the field has already been obtained. + If you need to access a field's value for potentially multiple instances, use instead. + StructFieldRefAccess<T, F>(ref instance, fieldInfo) is functionally equivalent to StructFieldRefAccess<T, F>(fieldInfo)(ref instance). + + + + + + A readable/assignable reference delegate to a static field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + A readable/assignable reference to the field + + + + Creates a static field reference + The type (can be class or struct) the field is defined in + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The name of the field + A readable/assignable reference to the field + + + + Creates a static field reference + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The type (can be class or struct) the field is defined in + The name of the field + A readable/assignable reference to the field + + + + Creates a static field reference + An arbitrary type (by convention, the type the field is defined in) + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The field + A readable/assignable reference to the field + + The type parameter is only used in exception messaging and to distinguish between this method overload + and the overload (which returns a rather than a reference). + + + + + Creates a static field reference delegate + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The field + A readable/assignable delegate + + + + Creates a delegate to a given method + The delegate Type + The method to create a delegate from. + + Only applies for instance methods. If null (default), returned delegate is an open (a.k.a. unbound) instance delegate + where an instance is supplied as the first argument to the delegate invocation; else, delegate is a closed (a.k.a. bound) + instance delegate where the delegate invocation always applies to the given . + + + Only applies for instance methods. If true (default) and is virtual, invocation of the delegate + calls the instance method virtually (the instance type's most-derived/overriden implementation of the method is called); + else, invocation of the delegate calls the exact specified (this is useful for calling base class methods) + Note: if false and is an interface method, an ArgumentException is thrown. + + A delegate of given to given + + + Delegate invocation is more performant and more convenient to use than + at a one-time setup cost. + + + Works for both type of static and instance methods, both open and closed (a.k.a. unbound and bound) instance methods, + and both class and struct methods. + + + + + + Creates a delegate for a given delegate definition, attributed with [] + The delegate Type, attributed with [] + + Only applies for instance methods. If null (default), returned delegate is an open (a.k.a. unbound) instance delegate + where an instance is supplied as the first argument to the delegate invocation; else, delegate is a closed (a.k.a. bound) + instance delegate where the delegate invocation always applies to the given . + + A delegate of given to the method specified via [] + attributes on + + This calls with the method and virtualCall arguments + determined from the [] attributes on , + and the given (for closed instance delegates). + + + + + Returns who called the current method + The calling method/constructor (excluding the caller) + + + + Rethrows an exception while preserving its stack trace (throw statement typically clobbers existing stack traces) + The exception to rethrow + + + + True if the current runtime is based on Mono, false otherwise (.NET) + + + + True if the current runtime is .NET Framework, false otherwise (.NET Core or Mono, although latter isn't guaranteed) + + + + True if the current runtime is .NET Core, false otherwise (Mono or .NET Framework) + + + + Throws a missing member runtime exception + The type that is involved + A list of names + + + + Gets default value for a specific type + The class/type + The default value + + + + Creates an (possibly uninitialized) instance of a given type + The class/type + The new instance + + + + Creates an (possibly uninitialized) instance of a given type + The class/type + The new instance + + + + + A cache for the or similar Add methods for different types. + + + + Makes a deep copy of any object + The type of the instance that should be created; for legacy reasons, this must be a class or interface + The original object + A copy of the original object but of type T + + + + Makes a deep copy of any object + The type of the instance that should be created + The original object + [out] The copy of the original object + Optional value transformation function (taking a field name and src/dst instances) + The optional path root to start with + + + + Makes a deep copy of any object + The original object + The type of the instance that should be created + Optional value transformation function (taking a field name and src/dst instances) + The optional path root to start with + The copy of the original object + + + + Tests if a type is a struct + The type + True if the type is a struct + + + + Tests if a type is a class + The type + True if the type is a class + + + + Tests if a type is a value type + The type + True if the type is a value type + + + + Tests if a type is an integer type + The type + True if the type represents some integer + + + + Tests if a type is a floating point type + The type + True if the type represents some floating point + + + + Tests if a type is a numerical type + The type + True if the type represents some number + + + + Tests if a type is void + The type + True if the type is void + + + + Test whether an instance is of a nullable type + Type of instance + An instance to test + True if instance is of nullable type, false if not + + + + Tests whether a type or member is static, as defined in C# + The type or member + True if the type or member is static + + + + Tests whether a type is static, as defined in C# + The type + True if the type is static + + + + Tests whether a property is static, as defined in C# + The property + True if the property is static + + + + Tests whether an event is static, as defined in C# + The event + True if the event is static + + + + Calculates a combined hash code for an enumeration of objects + The objects + The hash code + + + + General extensions for common cases + + + + Joins an enumeration with a value converter and a delimiter to a string + The inner type of the enumeration + The enumeration + An optional value converter (from T to string) + An optional delimiter + The values joined into a string + + + + Converts an array of types (for example methods arguments) into a human readable form + The array of types + A human readable description including brackets + + + + A full description of a type + The type + A human readable description + + + + A a full description of a method or a constructor without assembly details but with generics + The method/constructor + A human readable description + + + + A helper converting parameter infos to types + The array of parameter infos + An array of types + + + + A helper to access a value via key from a dictionary + The key type + The value type + The dictionary + The key + The value for the key or the default value (of T) if that key does not exist + + + + A helper to access a value via key from a dictionary with extra casting + The value type + The dictionary + The key + The value for the key or the default value (of T) if that key does not exist or cannot be cast to T + + + + Escapes Unicode and ASCII non printable characters + The string to convert + The string to convert + A string literal surrounded by + + + + Extensions for + + + + Shortcut for testing whether the operand is equal to a non-null value + The + The value + True if the operand has the same type and is equal to the value + + + + Shortcut for testing whether the operand is equal to a non-null value + The + The value + True if the operand is equal to the value + This is an optimized version of for + + + + Shortcut for code.opcode == opcode && code.OperandIs(operand) + The + The + The operand value + True if the opcode is equal to the given opcode and the operand has the same type and is equal to the given operand + + + + Shortcut for code.opcode == opcode && code.OperandIs(operand) + The + The + The operand value + True if the opcode is equal to the given opcode and the operand is equal to the given operand + This is an optimized version of for + + + + Tests for any form of Ldarg* + The + The (optional) index + True if it matches one of the variations + + + + Tests for Ldarga/Ldarga_S + The + The (optional) index + True if it matches one of the variations + + + + Tests for Starg/Starg_S + The + The (optional) index + True if it matches one of the variations + + + + Tests for any form of Ldloc* + The + The optional local variable + True if it matches one of the variations + + + + Tests for any form of Stloc* + The + The optional local variable + True if it matches one of the variations + + + + Tests if the code instruction branches + The + The label if the instruction is a branch operation or if not + True if the instruction branches + + + + Tests if the code instruction calls the method/constructor + The + The method + True if the instruction calls the method or constructor + + + + Tests if the code instruction loads a constant + The + True if the instruction loads a constant + + + + Tests if the code instruction loads an integer constant + The + The integer constant + True if the instruction loads the constant + + + + Tests if the code instruction loads a floating point constant + The + The floating point constant + True if the instruction loads the constant + + + + Tests if the code instruction loads an enum constant + The + The enum + True if the instruction loads the constant + + + + Tests if the code instruction loads a field + The + The field + Set to true if the address of the field is loaded + True if the instruction loads the field + + + + Tests if the code instruction stores a field + The + The field + True if the instruction stores this field + + + + Adds labels to the code instruction and return it + The + One or several to add + The same code instruction + + + Adds labels to the code instruction and return it + The + An enumeration of + The same code instruction + + + Extracts all labels from the code instruction and returns them + The + A list of + + + Moves all labels from the code instruction to a different one + The to move the labels from + The to move the labels to + The code instruction labels were moved from (now empty) + + + Moves all labels from a different code instruction to the current one + The to move the labels from + The to move the labels to + The code instruction that received the labels + + + Adds ExceptionBlocks to the code instruction and return it + The + One or several to add + The same code instruction + + + Adds ExceptionBlocks to the code instruction and return it + The + An enumeration of + The same code instruction + + + Extracts all ExceptionBlocks from the code instruction and returns them + The + A list of + + + Moves all ExceptionBlocks from the code instruction to a different one + The to move the ExceptionBlocks from + The to move the ExceptionBlocks to + The code instruction blocks were moved from (now empty) + + + Moves all ExceptionBlocks from a different code instruction to the current one + The to move the ExceptionBlocks from + The to move the ExceptionBlocks to + The code instruction that received the blocks + + + General extensions for collections + + + + A simple way to execute code for every element in a collection + The inner type of the collection + The collection + The action to execute + + + + A simple way to execute code for elements in a collection matching a condition + The inner type of the collection + The collection + The predicate + The action to execute + + + + A helper to add an item to a collection + The inner type of the collection + The collection + The item to add + The collection containing the item + + + + A helper to add an item to an array + The inner type of the collection + The array + The item to add + The array containing the item + + + + A helper to add items to an array + The inner type of the collection + The array + The items to add + The array containing the items + + + + General extensions for collections + + + + Tests a class member if it has an IL method body (external methods for example don't have a body) + The member to test + Returns true if the member has an IL body or false if not + + + A file log for debugging + + + + Full pathname of the log file, defaults to a file called harmony.log.txt on your Desktop + + + + The indent character. The default is tab + + + + The current indent level + + + + Changes the indentation level + The value to add to the indentation level + + + + Log a string in a buffered way. Use this method only if you are sure that FlushBuffer will be called + or else logging information is incomplete in case of a crash + The string to log + + + + Logs a list of string in a buffered way. Use this method only if you are sure that FlushBuffer will be called + or else logging information is incomplete in case of a crash + A list of strings to log (they will not be re-indented) + + + + Returns the log buffer and optionally empties it + True to empty the buffer + The buffer. + + + + Replaces the buffer with new lines + The lines to store + + + + Flushes the log buffer to disk (use in combination with LogBuffered) + + + + Log a string directly to disk. Slower method that prevents missing information in case of a crash + The string to log. + + + + Resets and deletes the log + + + + Logs some bytes as hex values + The pointer to some memory + The length of bytes to log + + + + A helper class to retrieve reflection info for non-private methods + + + + Given a lambda expression that calls a method, returns the method info + The lambda expression using the method + The method in the lambda expression + + + + Given a lambda expression that calls a method, returns the method info + The generic type + The lambda expression using the method + The method in the lambda expression + + + + Given a lambda expression that calls a method, returns the method info + The generic type + The generic result type + The lambda expression using the method + The method in the lambda expression + + + + Given a lambda expression that calls a method, returns the method info + The lambda expression using the method + The method in the lambda expression + + + + A reflection helper to read and write private elements + The result type defined by GetValue() + + + + Creates a traverse instance from an existing instance + The existing instance + + + + Gets/Sets the current value + The value to read or write + + + + A reflection helper to read and write private elements + + + + Creates a new traverse instance from a class/type + The class/type + A instance + + + + Creates a new traverse instance from a class T + The class + A instance + + + + Creates a new traverse instance from an instance + The object + A instance + + + + Creates a new traverse instance from a named type + The type name, for format see + A instance + + + + Creates a new and empty traverse instance + + + + Creates a new traverse instance from a class/type + The class/type + + + + Creates a new traverse instance from an instance + The object + + + + Gets the current value + The value + + + + Gets the current value + The type of the value + The value + + + + Invokes the current method with arguments and returns the result + The method arguments + The value returned by the method + + + + Invokes the current method with arguments and returns the result + The type of the value + The method arguments + The value returned by the method + + + + Sets a value of the current field or property + The value + The same traverse instance + + + + Gets the type of the current field or property + The type + + + + Moves the current traverse instance to a inner type + The type name + A traverse instance + + + + Moves the current traverse instance to a field + The type name + A traverse instance + + + + Moves the current traverse instance to a field + The type of the field + The type name + A traverse instance + + + + Gets all fields of the current type + A list of field names + + + + Moves the current traverse instance to a property + The type name + Optional property index + A traverse instance + + + + Moves the current traverse instance to a field + The type of the property + The type name + Optional property index + A traverse instance + + + + Gets all properties of the current type + A list of property names + + + + Moves the current traverse instance to a method + The name of the method + The arguments defining the argument types of the method overload + A traverse instance + + + + Moves the current traverse instance to a method + The name of the method + The argument types of the method + The arguments for the method + A traverse instance + + + + Gets all methods of the current type + A list of method names + + + + Checks if the current traverse instance is for a field + True if its a field + + + + Checks if the current traverse instance is for a property + True if its a property + + + + Checks if the current traverse instance is for a method + True if its a method + + + + Checks if the current traverse instance is for a type + True if its a type + + + + Iterates over all fields of the current type and executes a traverse action + Original object + The action receiving a instance for each field + + + + Iterates over all fields of the current type and executes a traverse action + Original object + Target object + The action receiving a pair of instances for each field pair + + + + Iterates over all fields of the current type and executes a traverse action + Original object + Target object + The action receiving a dot path representing the field pair and the instances + + + + Iterates over all properties of the current type and executes a traverse action + Original object + The action receiving a instance for each property + + + + Iterates over all properties of the current type and executes a traverse action + Original object + Target object + The action receiving a pair of instances for each property pair + + + + Iterates over all properties of the current type and executes a traverse action + Original object + Target object + The action receiving a dot path representing the property pair and the instances + + + + A default field action that copies fields to fields + + + + Returns a string that represents the current traverse + A string representation + + + + diff --git a/build/common.targets b/build/common.targets index ce805402..f5d01606 100644 --- a/build/common.targets +++ b/build/common.targets @@ -38,6 +38,7 @@ + diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets index 408230a9..0c1a6c1a 100644 --- a/build/prepare-install-package.targets +++ b/build/prepare-install-package.targets @@ -45,6 +45,7 @@ + diff --git a/docs/release-notes.md b/docs/release-notes.md index d0aa2fbf..50ed5f29 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,11 +1,9 @@ ← [README](README.md) # Release notes - + * Updated Harmony 1.2.0.1 to 2.1.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). ## 3.11.0 Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295). diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index b64239c1..586b17aa 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -59,7 +59,6 @@ flag | purpose `SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled for Windows; if not set, the code assumes Linux/macOS. Set automatically in `common.targets`. `SMAPI_FOR_WINDOWS_64BIT_HACK` | Whether SMAPI is being [compiled for Windows with a 64-bit Linux version of the game](https://github.com/Pathoschild/SMAPI/issues/767). This is highly specialized and shouldn't be used in most cases. False by default. `SMAPI_FOR_XNA` | Whether SMAPI is being compiled for XNA Framework; if not set, the code assumes MonoGame. Set automatically in `common.targets` with the same value as `SMAPI_FOR_WINDOWS` (unless `SMAPI_FOR_WINDOWS_64BIT_HACK` is set). -`HARMONY_2` | Whether to enable experimental Harmony 2.0 support and rewrite existing Harmony 1._x_ mods for compatibility. Note that you need to replace `build/0Harmony.dll` with a Harmony 2.0 build (or switch to a package reference) to use this flag. ## For SMAPI developers ### Compiling from source 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; } - /// -#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 /// The method to call after the Dialogue constructor. /// The instance being patched. /// The dialogue being parsed. @@ -113,72 +92,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return null; } -#else - - /// The method to call instead of the Dialogue constructor. - /// The instance being patched. - /// The dialogue being parsed. - /// The NPC for which the dialogue is being parsed. - /// Returns whether to execute the original method. - private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker) - { - // get private members - bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField(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(); - - // 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; - } - - /// The method to call instead of . - /// The instance being patched. - /// The return value of the original method. - /// The method being wrapped. - /// Returns whether to execute the original method. - private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack __result, MethodInfo __originalMethod) - { - const string key = nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue); - if (!PatchHelper.StartIntercept(key)) - return true; - - try - { - __result = (Stack)__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(); - 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 } /// -#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 } /// -#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 /// The method to call instead of GameLocation.checkEventPrecondition. /// The return value of the original method. /// The precondition to be parsed. @@ -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 - /// The method to call instead of . - /// The instance being patched. - /// The return value of the original method. - /// The precondition to be parsed. - /// The method being wrapped. - /// Returns whether to execute the original method. - 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 /// The method to call instead of . /// The instance being patched. /// The map whose tilesheets to update. @@ -126,33 +75,5 @@ harmony.Patch( return null; } -#else - /// The method to call instead of . - /// The instance being patched. - /// The map whose tilesheets to update. - /// The method being wrapped. - /// Returns whether to execute the original method. - 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 /// -#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 *********/ /// -#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 /// The method to call after . /// The patched method's return value. /// The exception thrown by the wrapped method, if any. @@ -88,38 +74,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return __exception; } -#else - /// The method to call instead of . - /// The instance being patched. - /// The patched method's return value. - /// The method being wrapped. - /// Returns whether to execute the original method. - 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 /// The method to call instead of . /// The item for which to draw a tooltip. 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 } /// -#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 /// The method to call instead of . /// The raw schedule data to parse. /// The instance being patched. @@ -74,35 +60,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return null; } -#else - /// The method to call instead of . - /// The raw schedule data to parse. - /// The instance being patched. - /// The patched method's return value. - /// The method being wrapped. - /// Returns whether to execute the original method. - private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary __result, MethodInfo __originalMethod) - { - const string key = nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule); - if (!PatchHelper.StartIntercept(key)) - return true; - - try - { - __result = (Dictionary)__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(); - 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 *********/ /// -#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 *********/ /// -#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 /// The method to call instead of . /// The item text description to parse. /// The delimiter by which to split the text description. @@ -56,34 +39,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches ? 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 } } 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 { /// The 'harmony_summary' SMAPI console command. internal class HarmonySummaryCommand : IInternalCommand { -#if !HARMONY_2 - /********* - ** Fields - *********/ - /// The Harmony instance through which to fetch patch info. - 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 /// Get all current Harmony patches. private IEnumerable 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> patchGroups = new Dictionary> { [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 /// A postfix patch. Postfix, -#if HARMONY_2 /// A finalizer patch. Finalizer, -#endif /// A transpiler patch. 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 } /// - public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action 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 /// The patches to apply. 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 *********/ /// Apply the Harmony patch. /// The Harmony instance. -#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 -{ - /// Provides generic methods for implementing Harmony patches. - internal class PatchHelper - { - /********* - ** Fields - *********/ - /// The interception keys currently being intercepted. - private static readonly HashSet InterceptingKeys = new HashSet(StringComparer.OrdinalIgnoreCase); - - - /********* - ** Public methods - *********/ - /// Track a method that will be intercepted. - /// The intercept key. - /// Returns false if the method was already marked for interception, else true. - public static bool StartIntercept(string key) - { - return PatchHelper.InterceptingKeys.Add(key); - } - - /// Track a method as no longer being intercepted. - /// The intercept key. - public static void StopIntercept(string key) - { - PatchHelper.InterceptingKeys.Remove(key); - } - } -} -#endif 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 } /// -#if HARMONY_2 public void Apply(Harmony harmony) -#else - public void Apply(HarmonyInstance harmony) -#endif { // detect CreatedBasicInfo harmony.Patch( -- cgit From 72b3c9d14314a3f69a0ca9d6ac9de9de1d31943d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 14 Jul 2021 18:02:13 -0400 Subject: add workaround for Harmony 2.x breaking XNA content pipeline for some assets (#711, #722) --- src/SMAPI/Framework/SCore.cs | 2 + .../Framework/TemporaryHacks/MiniMonoModHotfix.cs | 176 +++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs (limited to 'src/SMAPI') 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 _CacheFixed = new ConditionalWeakTable(); + + 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 ResolveTokenFix(IEnumerable 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(cache, m_RuntimeTypeCache_GetPropertyList); + _FixReflectionCacheOrder(cache, m_RuntimeTypeCache_GetFieldList); + + return new object(); + }); + } + } + + private static void _FixReflectionCacheOrder(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 list = new List(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); + } + + } +} -- cgit From 735893c1d5549915c2874a9e17dc1d9844408710 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 17 Jul 2021 18:52:06 -0400 Subject: add error if player manually installs wrong SMAPI bitness --- docs/release-notes.md | 4 ++++ src/SMAPI.Installer/InteractiveInstaller.cs | 3 ++- src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs | 9 ++++++++- src/SMAPI/Program.cs | 13 ++++++++++++- 4 files changed, 26 insertions(+), 3 deletions(-) (limited to 'src/SMAPI') diff --git a/docs/release-notes.md b/docs/release-notes.md index d0aa2fbf..9727dd6a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,10 @@ * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). --> +## Upcoming release +* For players: + * Added error message if you manually install the wrong SMAPI bitness (e.g. 32-bit SMAPI with 64-bit game). + ## 3.11.0 Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295). diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 55e9c064..ab07c864 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -9,6 +9,7 @@ using StardewModdingApi.Installer.Enums; using StardewModdingAPI.Installer.Framework; using StardewModdingAPI.Internal.ConsoleWriting; using StardewModdingAPI.Toolkit; +using StardewModdingAPI.Toolkit.Framework; using StardewModdingAPI.Toolkit.Framework.ModScanning; using StardewModdingAPI.Toolkit.Utilities; @@ -571,7 +572,7 @@ namespace StardewModdingApi.Installer /// The absolute path to the executable file. private bool Is64Bit(string executablePath) { - return AssemblyName.GetAssemblyName(executablePath).ProcessorArchitecture != ProcessorArchitecture.X86; + return LowLevelEnvironmentUtility.Is64BitAssembly(executablePath); } /// Get the display text for a color scheme. diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs index 8cbd8e51..be0c18ce 100644 --- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; #if SMAPI_FOR_WINDOWS using System.Management; #endif @@ -48,7 +49,6 @@ namespace StardewModdingAPI.Toolkit.Framework } } - /// Get the human-readable OS name and version. /// The current platform. [SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")] @@ -89,6 +89,13 @@ namespace StardewModdingAPI.Toolkit.Framework : "StardewValley.exe"; } + /// Get whether an executable is 64-bit. + /// The absolute path to the executable file. + public static bool Is64BitAssembly(string executablePath) + { + return AssemblyName.GetAssemblyName(executablePath).ProcessorArchitecture != ProcessorArchitecture.X86; + } + /********* ** Private methods diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index e830f799..0257a03e 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Threading; using StardewModdingAPI.Framework; +using StardewModdingAPI.Toolkit.Framework; namespace StardewModdingAPI { @@ -107,8 +108,18 @@ namespace StardewModdingAPI } // max version - else if (Constants.MaximumGameVersion != null && Constants.GameVersion.IsNewerThan(Constants.MaximumGameVersion)) + if (Constants.MaximumGameVersion != null && Constants.GameVersion.IsNewerThan(Constants.MaximumGameVersion)) Program.PrintErrorAndExit($"Oops! You're running Stardew Valley {Constants.GameVersion}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.MaximumGameVersion}. Please check for a newer version of SMAPI: https://smapi.io."); + + // bitness + bool is64BitGame = LowLevelEnvironmentUtility.Is64BitAssembly(Path.Combine(EarlyConstants.ExecutionPath, $"{EarlyConstants.GameAssemblyName}.exe")); +#if SMAPI_FOR_WINDOWS_64BIT_HACK + if (!is64bit) + Program.PrintErrorAndExit("Oops! This is the 64-bit version of SMAPI, but you have the 32-bit version of Stardew Valley. You can reinstall SMAPI using its installer to automatically install the correct version of SMAPI."); +#elif SMAPI_FOR_WINDOWS + if (is64BitGame) + Program.PrintErrorAndExit("Oops! This is the 32-bit version of SMAPI, but you have the 64-bit version of Stardew Valley. You can reinstall SMAPI using its installer to automatically install the correct version of SMAPI."); +#endif } /// Initialize SMAPI and launch the game. -- cgit From defa1b9a95c6bcb680bef3506ab94a71ed6189d6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 20 Jul 2021 18:43:56 -0400 Subject: fix concurrency issue in interface proxying --- docs/release-notes.md | 1 + .../Framework/Reflection/InterfaceProxyFactory.cs | 33 ++++++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) (limited to 'src/SMAPI') diff --git a/docs/release-notes.md b/docs/release-notes.md index 9727dd6a..bfea8bfb 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ## Upcoming release * For players: * Added error message if you manually install the wrong SMAPI bitness (e.g. 32-bit SMAPI with 64-bit game). + * Fixed intermittent error if a mod fetches mod-provided APIs asynchronously. ## 3.11.0 Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295). diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index 464367b6..8d1b6034 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -36,23 +36,26 @@ namespace StardewModdingAPI.Framework.Reflection public TInterface CreateProxy(object instance, string sourceModID, string targetModID) where TInterface : class { - // validate - if (instance == null) - throw new InvalidOperationException("Can't proxy access to a null API."); - if (!typeof(TInterface).IsInterface) - throw new InvalidOperationException("The proxy type must be an interface, not a class."); - - // get proxy type - Type targetType = instance.GetType(); - string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>"; - if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) + lock (this.Builders) { - builder = new InterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType); - this.Builders[proxyTypeName] = builder; - } + // validate + if (instance == null) + throw new InvalidOperationException("Can't proxy access to a null API."); + if (!typeof(TInterface).IsInterface) + throw new InvalidOperationException("The proxy type must be an interface, not a class."); - // create instance - return (TInterface)builder.CreateInstance(instance); + // get proxy type + Type targetType = instance.GetType(); + string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>"; + if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) + { + builder = new InterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType); + this.Builders[proxyTypeName] = builder; + } + + // create instance + return (TInterface)builder.CreateInstance(instance); + } } } } -- cgit From c74702b027aeab927b4e038e440cbbb24d859cfd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 20 Jul 2021 22:18:57 -0400 Subject: fix error loading .xnb files from the local mod folder since SMAPI 3.0 --- docs/release-notes.md | 3 +++ src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'src/SMAPI') diff --git a/docs/release-notes.md b/docs/release-notes.md index bfea8bfb..fea93104 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,6 +12,9 @@ * Added error message if you manually install the wrong SMAPI bitness (e.g. 32-bit SMAPI with 64-bit game). * Fixed intermittent error if a mod fetches mod-provided APIs asynchronously. +* For mod authors: + * Fixed error loading `.xnb` files from the local mod folder since SMAPI 3.0. + ## 3.11.0 Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295). diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 4f6aa775..bc5a8b74 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -77,6 +77,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// public override T Load(string assetName, LanguageCode language, bool useCache) { + // normalize key + bool isXnbFile = Path.GetExtension(assetName).ToLower() == ".xnb"; assetName = this.AssertAndNormalizeAssetName(assetName); // disable caching @@ -108,7 +110,7 @@ namespace StardewModdingAPI.Framework.ContentManagers try { // get file - FileInfo file = this.GetModFile(assetName); + FileInfo file = this.GetModFile(isXnbFile ? $"{assetName}.xnb" : assetName); // .xnb extension is stripped from asset names passed to the content manager if (!file.Exists) throw GetContentError("the specified path doesn't exist."); -- cgit From 7e5d77fb8c2606795af239717a596de460bc58f7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 21 Jul 2021 00:43:43 -0400 Subject: add error if some SMAPI DLLs have mismatched versions --- docs/release-notes.md | 3 ++- src/SMAPI/Program.cs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'src/SMAPI') diff --git a/docs/release-notes.md b/docs/release-notes.md index fea93104..1614f169 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,7 +9,8 @@ ## Upcoming release * For players: - * Added error message if you manually install the wrong SMAPI bitness (e.g. 32-bit SMAPI with 64-bit game). + * Added error if the wrong SMAPI bitness is installed (e.g. 32-bit SMAPI with 64-bit game). + * Added error if some SMAPI files aren't updated correctly. * Fixed intermittent error if a mod fetches mod-provided APIs asynchronously. * For mod authors: diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 0257a03e..e6e51ac6 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading; using StardewModdingAPI.Framework; using StardewModdingAPI.Toolkit.Framework; +using StardewModdingAPI.Toolkit.Serialization.Models; namespace StardewModdingAPI { @@ -32,6 +33,7 @@ namespace StardewModdingAPI AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve; Program.AssertGamePresent(); Program.AssertGameVersion(); + Program.AssertSmapiVersions(); Program.Start(args); } catch (BadImageFormatException ex) when (ex.FileName == "StardewValley" || ex.FileName == "Stardew Valley") // don't use EarlyConstants.GameAssemblyName, since we want to check both possible names @@ -122,6 +124,20 @@ namespace StardewModdingAPI #endif } + /// Assert that the versions of all SMAPI components are correct. + /// Players sometimes have mismatched versions (particularly when installed through Vortex), which can cause some very confusing bugs without this check. + private static void AssertSmapiVersions() + { + // SMAPI toolkit + foreach (var type in new[] { typeof(IManifest), typeof(Manifest) }) + { + Assembly assembly = type.Assembly; + var assemblyVersion = new SemanticVersion(assembly.GetName().Version); + if (!assemblyVersion.Equals(Constants.ApiVersion)) + Program.PrintErrorAndExit($"Oops! The 'smapi-internal/{assembly.GetName().Name}.dll' file is version {assemblyVersion} instead of the required {Constants.ApiVersion}. SMAPI doesn't seem to be installed correctly."); + } + } + /// Initialize SMAPI and launch the game. /// The command-line arguments. /// This method is separate from because that can't contain any references to assemblies loaded by (e.g. via ), or Mono will incorrectly show an assembly resolution error before assembly resolution is set up. -- cgit From 88be0cee94cced9a31efe7ff76684514fcdc638d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 21 Jul 2021 23:28:18 -0400 Subject: fix new validation checks --- src/SMAPI/Program.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index e6e51ac6..3249e02f 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -116,7 +116,7 @@ namespace StardewModdingAPI // bitness bool is64BitGame = LowLevelEnvironmentUtility.Is64BitAssembly(Path.Combine(EarlyConstants.ExecutionPath, $"{EarlyConstants.GameAssemblyName}.exe")); #if SMAPI_FOR_WINDOWS_64BIT_HACK - if (!is64bit) + if (!is64BitGame) Program.PrintErrorAndExit("Oops! This is the 64-bit version of SMAPI, but you have the 32-bit version of Stardew Valley. You can reinstall SMAPI using its installer to automatically install the correct version of SMAPI."); #elif SMAPI_FOR_WINDOWS if (is64BitGame) @@ -128,13 +128,16 @@ namespace StardewModdingAPI /// Players sometimes have mismatched versions (particularly when installed through Vortex), which can cause some very confusing bugs without this check. private static void AssertSmapiVersions() { - // SMAPI toolkit + // get SMAPI version without prerelease suffix (since we can't get that from the assembly versions) + ISemanticVersion smapiVersion = new SemanticVersion(Constants.ApiVersion.MajorVersion, Constants.ApiVersion.MinorVersion, Constants.ApiVersion.PatchVersion); + + // compare with assembly versions foreach (var type in new[] { typeof(IManifest), typeof(Manifest) }) { - Assembly assembly = type.Assembly; - var assemblyVersion = new SemanticVersion(assembly.GetName().Version); - if (!assemblyVersion.Equals(Constants.ApiVersion)) - Program.PrintErrorAndExit($"Oops! The 'smapi-internal/{assembly.GetName().Name}.dll' file is version {assemblyVersion} instead of the required {Constants.ApiVersion}. SMAPI doesn't seem to be installed correctly."); + AssemblyName assemblyName = type.Assembly.GetName(); + ISemanticVersion assemblyVersion = new SemanticVersion(assemblyName.Version); + if (!assemblyVersion.Equals(smapiVersion)) + Program.PrintErrorAndExit($"Oops! The 'smapi-internal/{assemblyName.Name}.dll' file is version {assemblyVersion} instead of the required {Constants.ApiVersion}. SMAPI doesn't seem to be installed correctly."); } } -- cgit From 167d5831d12f0b7c58b3533b716f9041f6ae02e7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 23 Jul 2021 20:29:44 -0400 Subject: use unmerged Harmony assembly (#711) Harmony merges Mono.Cecil and MonoMod.Common into its DLL, and keeps some (but not all) of the merged types public. That causes type conflicts in SMAPI's code since it uses both Harmony and Mono.Cecil, and extern aliases break on Linux due to IDE/compiler limitations. This commit uses a custom build of Harmony without the assembly merging, so SMAPI can use and manage Mono.Cecil itself. --- build/0Harmony.dll | Bin 802816 -> 166912 bytes build/common.targets | 3 +++ build/prepare-install-package.targets | 3 +++ .../Framework/TemporaryHacks/MiniMonoModHotfix.cs | 2 +- src/SMAPI/SMAPI.csproj | 3 ++- 5 files changed, 9 insertions(+), 2 deletions(-) (limited to 'src/SMAPI') diff --git a/build/0Harmony.dll b/build/0Harmony.dll index 1c5b9c09..bab3bb4d 100644 Binary files a/build/0Harmony.dll and b/build/0Harmony.dll differ diff --git a/build/common.targets b/build/common.targets index f5d01606..aed619da 100644 --- a/build/common.targets +++ b/build/common.targets @@ -40,6 +40,9 @@ + + + diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets index 0c1a6c1a..88e565f9 100644 --- a/build/prepare-install-package.targets +++ b/build/prepare-install-package.targets @@ -47,6 +47,9 @@ + + + diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs index 6814abea..9f5819d7 100644 --- a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -69,7 +69,7 @@ namespace MonoMod.Utils ); harmony.Patch( - original: typeof(Harmony).Assembly + original: typeof(MonoMod.Utils.ReflectionHelper).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)) diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index d36d0d7a..d06e3364 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -20,9 +20,10 @@ - + MonoCecilPackage + -- cgit From 175eaad68373185b010410c5e2de1af9afbba1f5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 23 Jul 2021 20:37:26 -0400 Subject: remove now-unneeded Mono.Cecil aliases (#711) --- src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs | 4 +--- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 6 ++---- src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs | 4 +--- src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs | 6 ++---- src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs | 6 ++---- src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs | 6 ++---- src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs | 6 ++---- .../Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 6 ++---- .../ModLoading/Finders/ReferenceToMissingMemberFinder.cs | 6 ++---- src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs | 4 +--- src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs | 4 +--- .../Framework/ModLoading/Framework/BaseInstructionHandler.cs | 6 ++---- src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs | 8 +++----- src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs | 6 ++---- src/SMAPI/Framework/ModLoading/IInstructionHandler.cs | 6 ++---- src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs | 4 +--- src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs | 6 ++---- .../Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs | 6 ++---- .../Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs | 6 ++---- .../Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs | 6 ++---- src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs | 6 ++---- src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs | 4 +--- src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs | 4 +--- src/SMAPI/SMAPI.csproj | 4 +--- 24 files changed, 41 insertions(+), 89 deletions(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index 9867ed8b..aefb0126 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - using System.Collections.Generic; -using MonoCecilPackage.Mono.Cecil; +using Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 8311a4b9..3606eb66 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -1,12 +1,10 @@ -extern alias MonoCecilPackage; - using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.ModLoading.Framework; using StardewModdingAPI.Metadata; diff --git a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs index 6493ccf8..b56a776c 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - using System.IO; -using MonoCecilPackage.Mono.Cecil; +using Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 2e349616..01ed153b 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index 434ff5ab..2c062243 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index 80229155..d2340f01 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index 5ef66df4..99344848 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index 80296e38..b01a3240 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System.Collections.Generic; using System.Linq; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index fae8ee95..b64a255e 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,8 +1,6 @@ -extern alias MonoCecilPackage; - using System.Collections.Generic; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs index 3e107b90..24ab2eca 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - using System; -using MonoCecilPackage.Mono.Cecil; +using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index 9453c39f..bbd081e8 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - using System; -using MonoCecilPackage.Mono.Cecil; +using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Finders diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index 3979d431..624113b3 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System; using System.Collections.Generic; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading.Framework { diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index e79bb488..10f68f0d 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -1,11 +1,9 @@ -extern alias MonoCecilPackage; - using System; using System.Collections.Generic; using System.Linq; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; -using MonoCecilPackage.Mono.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Collections.Generic; namespace StardewModdingAPI.Framework.ModLoading.Framework { diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index e88b75ed..60bbd2c7 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -1,10 +1,8 @@ -extern alias MonoCecilPackage; - using System; using System.Linq; using System.Reflection; -using MonoCecilPackage::Mono.Cecil; -using MonoCecilPackage::Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading.Framework { diff --git a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs index 342721cd..17c9ba68 100644 --- a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System; using System.Collections.Generic; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs index ee123e75..d4366294 100644 --- a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs +++ b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs @@ -1,10 +1,8 @@ -extern alias MonoCecilPackage; - using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using MonoCecilPackage.Mono.Cecil; +using Mono.Cecil; using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModLoading diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index d40d4111..0b679e9d 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System; using System.Reflection; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index 5a84a16a..723983be 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System; using HarmonyLib; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; using StardewModdingAPI.Framework.ModLoading.RewriteFacades; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index c381e4e5..f59a6ab1 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System.Collections.Generic; using System.Linq; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index 748c1e6f..e133b6fa 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System.Collections.Generic; using System.Linq; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index 5e0d9c70..9933e2ca 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System; using System.Linq; -using MonoCecilPackage.Mono.Cecil; -using MonoCecilPackage.Mono.Cecil.Cil; +using Mono.Cecil; +using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index a4d09ae9..ad5cb96f 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -1,7 +1,5 @@ -extern alias MonoCecilPackage; - using System; -using MonoCecilPackage.Mono.Cecil; +using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters diff --git a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs index fab424f3..a4ac54e2 100644 --- a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs +++ b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs @@ -1,9 +1,7 @@ -extern alias MonoCecilPackage; - using System; using System.Collections.Generic; using System.Linq; -using MonoCecilPackage.Mono.Cecil; +using Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading { diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index d06e3364..0c6cfdd3 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -20,9 +20,7 @@ - - MonoCecilPackage - + -- cgit From bdae52c9ae76303fd8082b10763b9ab7660fbd35 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 26 Jul 2021 22:28:32 -0400 Subject: fix rewriting for Harmony ExceptionBlock type (#711) --- .../Rewriters/Harmony1AssemblyRewriter.cs | 46 +++++++++++----------- 1 file changed, 22 insertions(+), 24 deletions(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index 723983be..3197c151 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, TypeReference type, Action replaceWith) { // rewrite Harmony 1.x type to Harmony 2.0 type - if (type.Scope is AssemblyNameReference scope && scope.Name == "0Harmony" && scope.Version.Major == 1) + if (type.Scope is AssemblyNameReference { Name: "0Harmony" } scope && scope.Version.Major == 1) { Type targetType = this.GetMappedType(type); replaceWith(module.ImportReference(targetType)); @@ -72,24 +72,15 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // not Harmony (or already using Harmony 2.0) // get facade type - Type toType; - switch (methodRef?.DeclaringType.FullName) + Type toType = methodRef?.DeclaringType.FullName switch { - case "HarmonyLib.Harmony": - toType = typeof(HarmonyInstanceFacade); - break; - - case "HarmonyLib.AccessTools": - toType = typeof(AccessToolsFacade); - break; - - case "HarmonyLib.HarmonyMethod": - toType = typeof(HarmonyMethodFacade); - break; - - default: - return false; - } + "HarmonyLib.Harmony" => typeof(HarmonyInstanceFacade), + "HarmonyLib.AccessTools" => typeof(AccessToolsFacade), + "HarmonyLib.HarmonyMethod" => typeof(HarmonyMethodFacade), + _ => null + }; + if (toType == null) + return false; // map if there's a matching method if (RewriteHelper.HasMatchingSignature(toType, methodRef)) @@ -102,16 +93,23 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } /// Get an equivalent Harmony 2.x type. - /// The Harmony 1.x method. + /// The Harmony 1.x type. private Type GetMappedType(TypeReference type) { - // main Harmony object - if (type.FullName == "Harmony.HarmonyInstance") - return typeof(Harmony); + return type.FullName switch + { + "Harmony.HarmonyInstance" => typeof(Harmony), + "Harmony.ILCopying.ExceptionBlock" => typeof(ExceptionBlock), + _ => this.GetMappedTypeByConvention(type) + }; + } - // other objects + /// Get an equivalent Harmony 2.x type using the convention expected for most types. + /// The Harmony 1.x type. + private Type GetMappedTypeByConvention(TypeReference type) + { string fullName = type.FullName.Replace("Harmony.", "HarmonyLib."); - string targetName = typeof(Harmony).AssemblyQualifiedName.Replace(typeof(Harmony).FullName, fullName); + string targetName = typeof(Harmony).AssemblyQualifiedName!.Replace(typeof(Harmony).FullName!, fullName); return Type.GetType(targetName, throwOnError: true); } } -- cgit From 97710d6f47ca180540d8b5c203ae6f4ca089fcb8 Mon Sep 17 00:00:00 2001 From: bladeoflight16 <1159076+bladeoflight16@users.noreply.github.com> Date: Tue, 27 Jul 2021 17:17:56 -0400 Subject: Fixing mono incompatibility (case exception type statement without variable) --- src/SMAPI/Framework/Logging/LogManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index a4df3c18..2cd512e0 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -258,7 +258,7 @@ namespace StardewModdingAPI.Framework.Logging break; // path too long exception - case PathTooLongException: + case PathTooLongException _: { string[] affectedPaths = PathUtilities.GetTooLongPaths(Constants.ModsPath).ToArray(); string message = affectedPaths.Any() -- cgit From b4f307e1ba88df072efed3b94975fa45789ae1ef Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 28 Jul 2021 00:51:45 -0400 Subject: fix rewritten Harmony 1.x code not raising 'detected game patch' flag (#711) --- .../ModLoading/Rewriters/Harmony1AssemblyRewriter.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs index 3197c151..7a3b428d 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters { Type targetType = this.GetMappedType(type); replaceWith(module.ImportReference(targetType)); - this.MarkRewritten(); + this.OnChanged(); this.ReplacedTypes = true; return true; } @@ -46,14 +46,20 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters // rewrite Harmony 1.x methods to Harmony 2.0 MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); if (this.TryRewriteMethodsToFacade(module, methodRef)) + { + this.OnChanged(); return true; + } // rewrite renamed fields FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { if (fieldRef.DeclaringType.FullName == "HarmonyLib.HarmonyMethod" && fieldRef.Name == "prioritiy") + { fieldRef.Name = nameof(HarmonyMethod.priority); + this.OnChanged(); + } } return false; @@ -63,6 +69,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /********* ** Private methods *********/ + /// Update the mod metadata when any Harmony 1.x code is migrated. + private void OnChanged() + { + this.MarkRewritten(); + this.MarkFlag(InstructionHandleResult.DetectedGamePatch); + } + /// Rewrite methods to use Harmony facades if needed. /// The assembly module containing the method reference. /// The method reference to map. -- cgit From e8ad5d0a2404720ec4f8cc75117e62bcc72cd068 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 28 Jul 2021 18:03:49 -0400 Subject: fix Data\Movies error regression when patching dictionary (#711) --- .../Framework/TemporaryHacks/MiniMonoModHotfix.cs | 101 +++++++++++++++++---- 1 file changed, 82 insertions(+), 19 deletions(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs index 9f5819d7..9d63ab2c 100644 --- a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -8,12 +8,12 @@ // Special thanks to 0x0ade for submitting this worokaround! Copy/pasted and adapted from MonoMod. using System; +using System.Reflection; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Reflection.Emit; using System.Runtime.CompilerServices; using HarmonyLib; +using System.Reflection.Emit; // ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author. namespace MonoMod.Utils @@ -24,38 +24,38 @@ namespace MonoMod.Utils { // .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 object[] _NoArgs = new object[0]; + private static object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null }; - private static readonly Type t_RuntimeModule = + private static Type t_RuntimeModule = typeof(Module).Assembly .GetType("System.Reflection.RuntimeModule"); - private static readonly PropertyInfo p_RuntimeModule_RuntimeType = + private static 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 = + private static Type t_RuntimeType = typeof(Type).Assembly .GetType("System.RuntimeType"); - private static readonly PropertyInfo p_RuntimeType_Cache = + private static 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 = + private static 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 = + private static MethodInfo m_RuntimeTypeCache_GetPropertyList = typeof(Type).Assembly .GetType("System.RuntimeType+RuntimeTypeCache") ?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly ConditionalWeakTable _CacheFixed = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable _CacheFixed = new ConditionalWeakTable(); public static void Apply() { @@ -143,24 +143,80 @@ namespace MonoMod.Utils if (!t_RuntimeType.IsInstanceOfType(type)) continue; - _CacheFixed.GetValue(type, rt => - { + CacheFixEntry entry = _CacheFixed.GetValue(type, rt => { + CacheFixEntry entryNew = new CacheFixEntry(); + object cache; + Array properties, fields; + + // All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null. + entryNew.Cache = cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs); + entryNew.Properties = properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList); + entryNew.Fields = fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); - object cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs); - _FixReflectionCacheOrder(cache, m_RuntimeTypeCache_GetPropertyList); - _FixReflectionCacheOrder(cache, m_RuntimeTypeCache_GetFieldList); + _FixReflectionCacheOrder(properties); + _FixReflectionCacheOrder(fields); - return new object(); + entryNew.NeedsVerify = false; + return entryNew; }); + + if (entry.NeedsVerify && !_Verify(entry, type)) + { + lock (entry) + { + _FixReflectionCacheOrder(entry.Properties); + _FixReflectionCacheOrder(entry.Fields); + } + } + + entry.NeedsVerify = true; } } - private static void _FixReflectionCacheOrder(object cache, MethodInfo getter) where T : MemberInfo + private static bool _Verify(CacheFixEntry entry, Type type) + { + object cache; + Array properties, fields; + + // The cache can sometimes be invalidated. + // TODO: Figure out if only the arrays get replaced or if the entire cache object gets replaced! + if (entry.Cache != (cache = p_RuntimeType_Cache.GetValue(type, _NoArgs))) + { + entry.Cache = cache; + entry.Properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList); + entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); + return false; + + } + else if (entry.Properties != (properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList))) + { + entry.Properties = properties; + entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); + return false; + + } + else if (entry.Fields != (fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList))) + { + entry.Fields = fields; + return false; + + } + else + { + // Cache should still be the same, no re-fix necessary. + return true; + } + } + + private static Array _GetArray(object cache, MethodInfo getter) { // 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); + return (Array)getter.Invoke(cache, _CacheGetterArgs); + } + private static void _FixReflectionCacheOrder(Array orig) where T : MemberInfo + { // Sort using a short-lived list. List list = new List(orig.Length); for (int i = 0; i < orig.Length; i++) @@ -172,5 +228,12 @@ namespace MonoMod.Utils orig.SetValue(list[i], i); } + private class CacheFixEntry + { + public object Cache; + public Array Properties; + public Array Fields; + public bool NeedsVerify; + } } } -- cgit From 880cd7b8bacfcee2cc6a8e15b6b8ea5e05e9467c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 28 Jul 2021 21:20:44 -0400 Subject: fix handling of Unicode characters in console --- docs/release-notes.md | 1 + src/SMAPI/Framework/Logging/LogManager.cs | 5 +++++ 2 files changed, 6 insertions(+) (limited to 'src/SMAPI') diff --git a/docs/release-notes.md b/docs/release-notes.md index 782d64bb..0f144bf5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,7 @@ * Added error if the wrong SMAPI bitness is installed (e.g. 32-bit SMAPI with 64-bit game). * Added error if some SMAPI files aren't updated correctly. * Added `removable` option to the `world_clear` console command (thanks to bladeoflight16!). + * Fixed handling of Unicode characters in console commands. * Fixed intermittent error if a mod fetches mod-provided APIs asynchronously. * For mod authors: diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index 2cd512e0..e16b5c0d 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using System.Threading; using StardewModdingAPI.Framework.Commands; @@ -106,6 +107,10 @@ namespace StardewModdingAPI.Framework.Logging if (writeToConsole) output.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message); Console.SetOut(output); + + // enable Unicode handling + Console.InputEncoding = Encoding.Unicode; + Console.OutputEncoding = Encoding.Unicode; } /// Get a monitor instance derived from SMAPI's current settings. -- cgit From 737a434ad6704d22776f463c192b9a9748ce2a21 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 29 Jul 2021 22:50:50 -0400 Subject: reduce ErrorHandler's direct references to internal SMAPI code That will allow removing the InternalsVisibleTo attribute to avoid namespace conflicts in an upcoming commit. --- src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 16 +++++++++++----- src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs | 3 ++- src/SMAPI/Framework/SCore.cs | 9 +++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index 719eddab..b1081218 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -1,3 +1,4 @@ +using System; using System.Reflection; using StardewModdingAPI.Events; using StardewModdingAPI.Framework; @@ -71,12 +72,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler /// Get the monitor with which to log game errors. 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/LoadErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs index e14dc662..cc0e5e52 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using HarmonyLib; +using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Patching; using StardewValley; @@ -82,7 +83,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches { BluePrint _ = new BluePrint(building.buildingType.Value); } - catch (SContentLoadException) + catch (ContentLoadException) { LoadErrorPatch.Monitor.Log($"Removed invalid building type '{building.buildingType.Value}' in {location.Name} ({building.tileX}, {building.tileY}) to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom building mod?)", LogLevel.Warn); location.buildings.Remove(building); diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 4211abc5..b607f95d 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net; @@ -310,6 +311,14 @@ namespace StardewModdingAPI.Framework } } + /// Get the core logger and monitor on behalf of the game. + /// This method is called using reflection by the ErrorHandler mod to log game errors. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via reflection")] + public IMonitor GetMonitorForGame() + { + return this.LogManager.MonitorForGame; + } + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { -- cgit From aa65b2e2f6ae2578f9d1f81d6fc1f4c5a261d90f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 30 Jul 2021 00:34:53 -0400 Subject: split patch classes which target multiple types --- src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 3 +- .../Patches/DialogueErrorPatch.cs | 21 -------- .../Patches/IClickableMenuPatcher.cs | 44 +++++++++++++++++ .../Patches/ObjectErrorPatch.cs | 19 -------- .../Patches/ScheduleErrorPatch.cs | 21 ++++++++ src/SMAPI/Framework/SCore.cs | 3 +- src/SMAPI/Patches/LoadContextPatch.cs | 15 ------ src/SMAPI/Patches/TitleMenuPatcher.cs | 56 ++++++++++++++++++++++ 8 files changed, 124 insertions(+), 58 deletions(-) create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs create mode 100644 src/SMAPI/Patches/TitleMenuPatcher.cs (limited to 'src/SMAPI') diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index b1081218..f6f9a150 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -1,8 +1,6 @@ using System; using System.Reflection; using StardewModdingAPI.Events; -using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.Logging; using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Mods.ErrorHandler.Patches; using StardewValley; @@ -35,6 +33,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler new DictionaryPatches(this.Helper.Reflection), new EventPatches(monitorForGame), new GameLocationPatches(monitorForGame), + new IClickablePatcher(), new ObjectErrorPatch(), new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved), new ScheduleErrorPatch(monitorForGame), diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs index a065e3d7..ca5bb0bf 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using HarmonyLib; using StardewModdingAPI.Framework; @@ -43,10 +42,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches 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)) - ); } @@ -76,21 +71,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return null; } - - /// The method to call after . - /// The instance being patched. - /// The return value of the original method. - /// The exception thrown by the wrapped method, if any. - /// Returns the exception to throw, if any. - private static Exception Finalize_NPC_CurrentDialogue(NPC __instance, ref Stack __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(); - - return null; - } } } diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs new file mode 100644 index 00000000..a5a90db2 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs @@ -0,0 +1,44 @@ +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Framework.Patching; +using StardewValley; +using StardewValley.Menus; +using SObject = StardewValley.Object; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// A Harmony patch for which intercepts crashes due to invalid items. + /// 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 IClickablePatcher : IHarmonyPatch + { + /********* + ** Public methods + *********/ + /// + public void Apply(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)), + prefix: new HarmonyMethod(this.GetType(), nameof(IClickablePatcher.Before_IClickableMenu_DrawTooltip)) + ); + } + + + /********* + ** Private methods + *********/ + /// The method to call instead of . + /// The item for which to draw a tooltip. + /// Returns whether to execute the original method. + private static bool Before_IClickableMenu_DrawTooltip(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.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs index a68e359c..7a4b3cfa 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using HarmonyLib; using StardewModdingAPI.Framework.Patching; using StardewValley; -using StardewValley.Menus; using SObject = StardewValley.Object; namespace StardewModdingAPI.Mods.ErrorHandler.Patches @@ -32,12 +31,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches original: AccessTools.Method(typeof(SObject), "loadDisplayName"), finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Finalize_Object_loadDisplayName)) ); - - // IClickableMenu.drawToolTip - harmony.Patch( - original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)), - prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_IClickableMenu_DrawTooltip)) - ); } @@ -74,17 +67,5 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return __exception; } - - /// The method to call instead of . - /// The item for which to draw a tooltip. - /// Returns whether to execute the original method. - private static bool Before_IClickableMenu_DrawTooltip(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.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs index d2e332b4..b8ab9361 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs @@ -34,6 +34,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// public void Apply(Harmony harmony) { + harmony.Patch( + original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod, + finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_CurrentDialogue)) + ); + harmony.Patch( original: AccessTools.Method(typeof(NPC), nameof(NPC.parseMasterSchedule)), finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule)) @@ -44,6 +49,22 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /********* ** Private methods *********/ + /// The method to call after . + /// The instance being patched. + /// The return value of the original method. + /// The exception thrown by the wrapped method, if any. + /// Returns the exception to throw, if any. + private static Exception Finalize_NPC_CurrentDialogue(NPC __instance, ref Stack __result, Exception __exception) + { + if (__exception == null) + return null; + + ScheduleErrorPatch.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{__exception.GetLogSummary()}", LogLevel.Error); + __result = new Stack(); + + return null; + } + /// The method to call instead of . /// The raw schedule data to parse. /// The instance being patched. diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index b607f95d..419afd4b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -255,7 +255,8 @@ namespace StardewModdingAPI.Framework // apply game patches MiniMonoModHotfix.Apply(); new GamePatcher(this.Monitor).Apply( - new LoadContextPatch(this.Reflection, this.OnLoadStageChanged) + new LoadContextPatch(this.Reflection, this.OnLoadStageChanged), + new TitleMenuPatcher(this.OnLoadStageChanged) ); // add exit handler diff --git a/src/SMAPI/Patches/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs index 721cf53d..c7f7d986 100644 --- a/src/SMAPI/Patches/LoadContextPatch.cs +++ b/src/SMAPI/Patches/LoadContextPatch.cs @@ -44,12 +44,6 @@ namespace StardewModdingAPI.Patches /// public void Apply(Harmony harmony) { - // detect CreatedBasicInfo - harmony.Patch( - original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.createdNewCharacter)), - prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_TitleMenu_CreatedNewCharacter)) - ); - // detect CreatedInitialLocations and SaveAddedLocations harmony.Patch( original: AccessTools.Method(typeof(Game1), nameof(Game1.AddModNPCs)), @@ -74,15 +68,6 @@ namespace StardewModdingAPI.Patches /********* ** Private methods *********/ - /// Called before . - /// Returns whether to execute the original method. - /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static bool Before_TitleMenu_CreatedNewCharacter() - { - LoadContextPatch.OnStageChanged(LoadStage.CreatedBasicInfo); - return true; - } - /// Called before . /// Returns whether to execute the original method. /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. diff --git a/src/SMAPI/Patches/TitleMenuPatcher.cs b/src/SMAPI/Patches/TitleMenuPatcher.cs new file mode 100644 index 00000000..a889adfc --- /dev/null +++ b/src/SMAPI/Patches/TitleMenuPatcher.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Enums; +using StardewModdingAPI.Framework.Patching; +using StardewValley.Menus; + +namespace StardewModdingAPI.Patches +{ + /// Harmony patches which notify SMAPI for save creation load stages. + /// 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 TitleMenuPatcher : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// A callback to invoke when the load stage changes. + private static Action OnStageChanged; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// A callback to invoke when the load stage changes. + public TitleMenuPatcher(Action onStageChanged) + { + TitleMenuPatcher.OnStageChanged = onStageChanged; + } + + /// + public void Apply(Harmony harmony) + { + // detect CreatedBasicInfo + harmony.Patch( + original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.createdNewCharacter)), + prefix: new HarmonyMethod(this.GetType(), nameof(TitleMenuPatcher.Before_TitleMenu_CreatedNewCharacter)) + ); + } + + + /********* + ** Private methods + *********/ + /// Called before . + /// Returns whether to execute the original method. + /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. + private static bool Before_TitleMenu_CreatedNewCharacter() + { + TitleMenuPatcher.OnStageChanged(LoadStage.CreatedBasicInfo); + return true; + } + } +} -- cgit From 4074f697d73f5cac6699836550b144fd0c4e2803 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 30 Jul 2021 00:40:12 -0400 Subject: rename patch classes for consistency --- src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 20 +-- .../Patches/DialogueErrorPatch.cs | 75 ----------- .../Patches/DialoguePatcher.cs | 75 +++++++++++ .../Patches/DictionaryPatcher.cs | 81 ++++++++++++ .../Patches/DictionaryPatches.cs | 81 ------------ .../Patches/EventPatcher.cs | 52 ++++++++ .../Patches/EventPatches.cs | 52 -------- .../Patches/GameLocationPatcher.cs | 79 ++++++++++++ .../Patches/GameLocationPatches.cs | 79 ------------ .../Patches/IClickableMenuPatcher.cs | 4 +- .../Patches/LoadErrorPatch.cs | 143 --------------------- src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs | 85 ++++++++++++ .../Patches/ObjectErrorPatch.cs | 71 ---------- .../Patches/ObjectPatcher.cs | 71 ++++++++++ .../Patches/SaveGamePatcher.cs | 142 ++++++++++++++++++++ .../Patches/ScheduleErrorPatch.cs | 85 ------------ .../Patches/SpriteBatchPatcher.cs | 46 +++++++ .../Patches/SpriteBatchValidationPatches.cs | 46 ------- .../Patches/UtilityErrorPatches.cs | 43 ------- .../Patches/UtilityPatcher.cs | 43 +++++++ src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/Patches/Game1Patcher.cs | 125 ++++++++++++++++++ src/SMAPI/Patches/LoadContextPatch.cs | 125 ------------------ 23 files changed, 812 insertions(+), 813 deletions(-) delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatches.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs delete mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs create mode 100644 src/SMAPI/Patches/Game1Patcher.cs delete mode 100644 src/SMAPI/Patches/LoadContextPatch.cs (limited to 'src/SMAPI') diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index f6f9a150..ac9d1b94 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -29,16 +29,16 @@ namespace StardewModdingAPI.Mods.ErrorHandler // apply patches new GamePatcher(this.Monitor).Apply( - new DialogueErrorPatch(monitorForGame, this.Helper.Reflection), - new DictionaryPatches(this.Helper.Reflection), - new EventPatches(monitorForGame), - new GameLocationPatches(monitorForGame), - new IClickablePatcher(), - new ObjectErrorPatch(), - new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved), - new ScheduleErrorPatch(monitorForGame), - new SpriteBatchValidationPatches(), - new UtilityErrorPatches() + 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 diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs deleted file mode 100644 index ca5bb0bf..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.Patching; -using StardewValley; - -namespace StardewModdingAPI.Mods.ErrorHandler.Patches -{ - /// A Harmony patch for the constructor which intercepts invalid dialogue lines and logs an error instead of crashing. - /// 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 DialogueErrorPatch : IHarmonyPatch - { - /********* - ** Fields - *********/ - /// Writes messages to the console and log file on behalf of the game. - private static IMonitor MonitorForGame; - - /// Simplifies access to private code. - private static IReflectionHelper Reflection; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Writes messages to the console and log file on behalf of the game. - /// Simplifies access to private code. - public DialogueErrorPatch(IMonitor monitorForGame, IReflectionHelper reflector) - { - DialogueErrorPatch.MonitorForGame = monitorForGame; - DialogueErrorPatch.Reflection = reflector; - } - - /// - 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)) - ); - } - - - /********* - ** Private methods - *********/ - /// The method to call after the Dialogue constructor. - /// The instance being patched. - /// The dialogue being parsed. - /// The NPC for which the dialogue is being parsed. - /// The exception thrown by the wrapped method, if any. - /// Returns the exception to throw, if any. - 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; - } - } -} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs new file mode 100644 index 00000000..7b730ee5 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs @@ -0,0 +1,75 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Patching; +using StardewValley; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// A Harmony patch for the constructor which intercepts invalid dialogue lines and logs an error instead of crashing. + /// 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 DialoguePatcher : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// Writes messages to the console and log file on behalf of the game. + private static IMonitor MonitorForGame; + + /// Simplifies access to private code. + private static IReflectionHelper Reflection; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Writes messages to the console and log file on behalf of the game. + /// Simplifies access to private code. + public DialoguePatcher(IMonitor monitorForGame, IReflectionHelper reflector) + { + DialoguePatcher.MonitorForGame = monitorForGame; + DialoguePatcher.Reflection = reflector; + } + + /// + public void Apply(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }), + finalizer: new HarmonyMethod(this.GetType(), nameof(DialoguePatcher.Finalize_Dialogue_Constructor)) + ); + } + + + /********* + ** Private methods + *********/ + /// The method to call after the Dialogue constructor. + /// The instance being patched. + /// The dialogue being parsed. + /// The NPC for which the dialogue is being parsed. + /// The exception thrown by the wrapped method, if any. + /// Returns the exception to throw, if any. + 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; + 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..3c5240b6 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Framework.Patching; +using StardewValley.GameData; +using StardewValley.GameData.HomeRenovations; +using StardewValley.GameData.Movies; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// A Harmony patch for which adds the accessed key to exceptions. + /// 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 DictionaryPatcher : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// Simplifies access to private code. + private static IReflectionHelper Reflection; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Simplifies access to private code. + public DictionaryPatcher(IReflectionHelper reflector) + { + DictionaryPatcher.Reflection = reflector; + } + + /// + public void Apply(Harmony harmony) + { + 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"), + finalizer: new HarmonyMethod(this.GetType(), nameof(DictionaryPatcher.Finalize_GetItem)) + ); + } + } + } + + + /********* + ** Private methods + *********/ + /// The method to call after the dictionary indexer throws an exception. + /// The dictionary key being fetched. + /// The exception thrown by the wrapped method, if any. + /// Returns the exception to throw, if any. + private static Exception Finalize_GetItem(object key, Exception __exception) + { + if (__exception is KeyNotFoundException) + AddKeyTo(__exception, key?.ToString()); + + return __exception; + } + + /// Add the accessed key to an exception message. + /// The exception to modify. + /// The dictionary key. + private static void AddKeyTo(Exception exception, string key) + { + DictionaryPatcher.Reflection + .GetField(exception, "_message") + .SetValue($"{exception.Message}\nkey: '{key}'"); + } + } +} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatches.cs deleted file mode 100644 index ce88999d..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatches.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework.Patching; -using StardewValley.GameData; -using StardewValley.GameData.HomeRenovations; -using StardewValley.GameData.Movies; - -namespace StardewModdingAPI.Mods.ErrorHandler.Patches -{ - /// A Harmony patch for which adds the accessed key to exceptions. - /// 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 DictionaryPatches : IHarmonyPatch - { - /********* - ** Fields - *********/ - /// Simplifies access to private code. - private static IReflectionHelper Reflection; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Simplifies access to private code. - public DictionaryPatches(IReflectionHelper reflector) - { - DictionaryPatches.Reflection = reflector; - } - - /// - public void Apply(Harmony harmony) - { - 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"), - finalizer: new HarmonyMethod(this.GetType(), nameof(DictionaryPatches.Finalize_GetItem)) - ); - } - } - } - - - /********* - ** Private methods - *********/ - /// The method to call after the dictionary indexer throws an exception. - /// The dictionary key being fetched. - /// The exception thrown by the wrapped method, if any. - /// Returns the exception to throw, if any. - private static Exception Finalize_GetItem(object key, Exception __exception) - { - if (__exception is KeyNotFoundException) - AddKeyTo(__exception, key?.ToString()); - - return __exception; - } - - /// Add the accessed key to an exception message. - /// The exception to modify. - /// The dictionary key. - private static void AddKeyTo(Exception exception, string key) - { - DictionaryPatches.Reflection - .GetField(exception, "_message") - .SetValue($"{exception.Message}\nkey: '{key}'"); - } - } -} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs new file mode 100644 index 00000000..9a7b34d8 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs @@ -0,0 +1,52 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Framework.Patching; +using StardewValley; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// Harmony patches for which intercept errors to log more details. + /// 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 EventPatcher : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// Writes messages to the console and log file on behalf of the game. + private static IMonitor MonitorForGame; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Writes messages to the console and log file on behalf of the game. + public EventPatcher(IMonitor monitorForGame) + { + EventPatcher.MonitorForGame = monitorForGame; + } + + /// + public void Apply(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)), + postfix: new HarmonyMethod(this.GetType(), nameof(EventPatcher.After_Event_LogErrorAndHalt)) + ); + } + + + /********* + ** Private methods + *********/ + /// The method to call after . + /// The exception being logged. + private static void After_Event_LogErrorAndHalt(Exception e) + { + EventPatcher.MonitorForGame.Log(e.ToString(), LogLevel.Error); + } + } +} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs deleted file mode 100644 index 390aa778..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework.Patching; -using StardewValley; - -namespace StardewModdingAPI.Mods.ErrorHandler.Patches -{ - /// Harmony patches for which intercept errors to log more details. - /// 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 EventPatches : IHarmonyPatch - { - /********* - ** Fields - *********/ - /// Writes messages to the console and log file on behalf of the game. - private static IMonitor MonitorForGame; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Writes messages to the console and log file on behalf of the game. - public EventPatches(IMonitor monitorForGame) - { - EventPatches.MonitorForGame = monitorForGame; - } - - /// - public void Apply(Harmony harmony) - { - harmony.Patch( - original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)), - postfix: new HarmonyMethod(this.GetType(), nameof(EventPatches.After_Event_LogErrorAndHalt)) - ); - } - - - /********* - ** Private methods - *********/ - /// The method to call after . - /// The exception being logged. - private static void After_Event_LogErrorAndHalt(Exception e) - { - EventPatches.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..7427fe48 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs @@ -0,0 +1,79 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Framework.Patching; +using StardewValley; +using xTile; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// Harmony patches for and which intercept errors instead of crashing. + /// 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 GameLocationPatcher : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// Writes messages to the console and log file on behalf of the game. + private static IMonitor MonitorForGame; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Writes messages to the console and log file on behalf of the game. + public GameLocationPatcher(IMonitor monitorForGame) + { + GameLocationPatcher.MonitorForGame = monitorForGame; + } + + /// + public void Apply(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)), + finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatcher.Finalize_GameLocation_CheckEventPrecondition)) + ); + harmony.Patch( + original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)), + finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatcher.Before_GameLocation_UpdateSeasonalTileSheets)) + ); + } + + + /********* + ** Private methods + *********/ + /// The method to call instead of GameLocation.checkEventPrecondition. + /// The return value of the original method. + /// The precondition to be parsed. + /// The exception thrown by the wrapped method, if any. + /// Returns the exception to throw, if any. + private static Exception Finalize_GameLocation_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; + } + + /// The method to call instead of . + /// The instance being patched. + /// The map whose tilesheets to update. + /// The exception thrown by the wrapped method, if any. + /// Returns the exception to throw, if any. + private static Exception Before_GameLocation_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 ec809fba..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework.Patching; -using StardewValley; -using xTile; - -namespace StardewModdingAPI.Mods.ErrorHandler.Patches -{ - /// Harmony patches for and which intercept errors instead of crashing. - /// 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 GameLocationPatches : IHarmonyPatch - { - /********* - ** Fields - *********/ - /// Writes messages to the console and log file on behalf of the game. - private static IMonitor MonitorForGame; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Writes messages to the console and log file on behalf of the game. - public GameLocationPatches(IMonitor monitorForGame) - { - GameLocationPatches.MonitorForGame = monitorForGame; - } - - /// - public void Apply(Harmony harmony) - { - harmony.Patch( - original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)), - finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Finalize_GameLocation_CheckEventPrecondition)) - ); - harmony.Patch( - original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)), - finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets)) - ); - } - - - /********* - ** Private methods - *********/ - /// The method to call instead of GameLocation.checkEventPrecondition. - /// The return value of the original method. - /// The precondition to be parsed. - /// The exception thrown by the wrapped method, if any. - /// Returns the exception to throw, if any. - private static Exception Finalize_GameLocation_CheckEventPrecondition(ref int __result, string precondition, Exception __exception) - { - if (__exception != null) - { - __result = -1; - GameLocationPatches.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{__exception.InnerException}", LogLevel.Error); - } - - return null; - } - - /// The method to call instead of . - /// The instance being patched. - /// The map whose tilesheets to update. - /// The exception thrown by the wrapped method, if any. - /// Returns the exception to throw, if any. - private static Exception Before_GameLocation_UpdateSeasonalTileSheets(GameLocation __instance, Map map, Exception __exception) - { - if (__exception != null) - GameLocationPatches.MonitorForGame.Log($"Failed updating seasonal tilesheets for location '{__instance.NameOrUniqueName}': \n{__exception}", LogLevel.Error); - - return null; - } - } -} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs index a5a90db2..d6f9fbf4 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs @@ -11,7 +11,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// 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 IClickablePatcher : IHarmonyPatch + internal class IClickableMenuPatcher : IHarmonyPatch { /********* ** Public methods @@ -21,7 +21,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches { harmony.Patch( original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)), - prefix: new HarmonyMethod(this.GetType(), nameof(IClickablePatcher.Before_IClickableMenu_DrawTooltip)) + prefix: new HarmonyMethod(this.GetType(), nameof(IClickableMenuPatcher.Before_IClickableMenu_DrawTooltip)) ); } diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs deleted file mode 100644 index cc0e5e52..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using HarmonyLib; -using Microsoft.Xna.Framework.Content; -using StardewModdingAPI.Framework.Exceptions; -using StardewModdingAPI.Framework.Patching; -using StardewValley; -using StardewValley.Buildings; -using StardewValley.Locations; - -namespace StardewModdingAPI.Mods.ErrorHandler.Patches -{ - /// A Harmony patch for which prevents some errors due to broken save data. - /// 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 LoadErrorPatch : IHarmonyPatch - { - /********* - ** Fields - *********/ - /// Writes messages to the console and log file. - private static IMonitor Monitor; - - /// A callback invoked when custom content is removed from the save data to avoid a crash. - private static Action OnContentRemoved; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Writes messages to the console and log file. - /// A callback invoked when custom content is removed from the save data to avoid a crash. - public LoadErrorPatch(IMonitor monitor, Action onContentRemoved) - { - LoadErrorPatch.Monitor = monitor; - LoadErrorPatch.OnContentRemoved = onContentRemoved; - } - - - /// - public void Apply(Harmony harmony) - { - harmony.Patch( - original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)), - prefix: new HarmonyMethod(this.GetType(), nameof(LoadErrorPatch.Before_SaveGame_LoadDataToLocations)) - ); - } - - - /********* - ** Private methods - *********/ - /// The method to call instead of . - /// The game locations being loaded. - /// Returns whether to execute the original method. - private static bool Before_SaveGame_LoadDataToLocations(List gamelocations) - { - bool removedAny = - LoadErrorPatch.RemoveBrokenBuildings(gamelocations) - | LoadErrorPatch.RemoveInvalidNpcs(gamelocations); - - if (removedAny) - LoadErrorPatch.OnContentRemoved(); - - return true; - } - - /// Remove buildings which don't exist in the game data. - /// The current game locations. - private static bool RemoveBrokenBuildings(IEnumerable locations) - { - bool removedAny = false; - - foreach (BuildableGameLocation location in locations.OfType()) - { - foreach (Building building in location.buildings.ToArray()) - { - try - { - BluePrint _ = new BluePrint(building.buildingType.Value); - } - catch (ContentLoadException) - { - LoadErrorPatch.Monitor.Log($"Removed invalid building type '{building.buildingType.Value}' in {location.Name} ({building.tileX}, {building.tileY}) to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom building mod?)", LogLevel.Warn); - location.buildings.Remove(building); - removedAny = true; - } - } - } - - return removedAny; - } - - /// Remove NPCs which don't exist in the game data. - /// The current game locations. - private static bool RemoveInvalidNpcs(IEnumerable locations) - { - bool removedAny = false; - - IDictionary data = Game1.content.Load>("Data\\NPCDispositions"); - foreach (GameLocation location in LoadErrorPatch.GetAllLocations(locations)) - { - foreach (NPC npc in location.characters.ToArray()) - { - if (npc.isVillager() && !data.ContainsKey(npc.Name)) - { - try - { - npc.reloadSprite(); // this won't crash for special villagers like Bouncer - } - catch - { - LoadErrorPatch.Monitor.Log($"Removed invalid villager '{npc.Name}' in {location.Name} ({npc.getTileLocation()}) to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom NPC mod?)", LogLevel.Warn); - location.characters.Remove(npc); - removedAny = true; - } - } - } - } - - return removedAny; - } - - /// Get all locations, including building interiors. - /// The main game locations. - private static IEnumerable GetAllLocations(IEnumerable locations) - { - foreach (GameLocation location in locations) - { - yield return location; - if (location is BuildableGameLocation buildableLocation) - { - foreach (GameLocation interior in buildableLocation.buildings.Select(p => p.indoors.Value).Where(p => p != null)) - yield return interior; - } - } - } - } -} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs new file mode 100644 index 00000000..4a07ea1d --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Patching; +using StardewValley; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// A Harmony patch for which intercepts crashes due to invalid schedule data. + /// 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 NpcPatcher : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// Writes messages to the console and log file on behalf of the game. + private static IMonitor MonitorForGame; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Writes messages to the console and log file on behalf of the game. + public NpcPatcher(IMonitor monitorForGame) + { + NpcPatcher.MonitorForGame = monitorForGame; + } + + /// + public void Apply(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod, + finalizer: new HarmonyMethod(this.GetType(), nameof(NpcPatcher.Finalize_NPC_CurrentDialogue)) + ); + + harmony.Patch( + original: AccessTools.Method(typeof(NPC), nameof(NPC.parseMasterSchedule)), + finalizer: new HarmonyMethod(this.GetType(), nameof(NpcPatcher.Finalize_NPC_parseMasterSchedule)) + ); + } + + + /********* + ** Private methods + *********/ + /// The method to call after . + /// The instance being patched. + /// The return value of the original method. + /// The exception thrown by the wrapped method, if any. + /// Returns the exception to throw, if any. + private static Exception Finalize_NPC_CurrentDialogue(NPC __instance, ref Stack __result, Exception __exception) + { + if (__exception == null) + return null; + + NpcPatcher.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{__exception.GetLogSummary()}", LogLevel.Error); + __result = new Stack(); + + return null; + } + + /// The method to call instead of . + /// The raw schedule data to parse. + /// The instance being patched. + /// The patched method's return value. + /// The exception thrown by the wrapped method, if any. + /// Returns the exception to throw, if any. + private static Exception Finalize_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary __result, Exception __exception) + { + if (__exception != null) + { + NpcPatcher.MonitorForGame.Log($"Failed parsing schedule for NPC {__instance.Name}:\n{rawData}\n{__exception.GetLogSummary()}", LogLevel.Error); + __result = new Dictionary(); + } + + return null; + } + } +} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs deleted file mode 100644 index 7a4b3cfa..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework.Patching; -using StardewValley; -using SObject = StardewValley.Object; - -namespace StardewModdingAPI.Mods.ErrorHandler.Patches -{ - /// A Harmony patch for which intercepts crashes due to the item no longer existing. - /// 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 ObjectErrorPatch : IHarmonyPatch - { - /********* - ** Public methods - *********/ - /// - public void Apply(Harmony harmony) - { - // object.getDescription - harmony.Patch( - original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)), - prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_Object_GetDescription)) - ); - - // object.getDisplayName - harmony.Patch( - original: AccessTools.Method(typeof(SObject), "loadDisplayName"), - finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Finalize_Object_loadDisplayName)) - ); - } - - - /********* - ** Private methods - *********/ - /// The method to call instead of . - /// The instance being patched. - /// The patched method's return value. - /// Returns whether to execute the original method. - 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)) - { - __result = "???"; - return false; - } - - return true; - } - - /// The method to call after . - /// The patched method's return value. - /// The exception thrown by the wrapped method, if any. - /// Returns the exception to throw, if any. - private static Exception Finalize_Object_loadDisplayName(ref string __result, Exception __exception) - { - if (__exception is KeyNotFoundException) - { - __result = "???"; - return null; - } - - return __exception; - } - } -} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs new file mode 100644 index 00000000..c4b25b96 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Framework.Patching; +using StardewValley; +using SObject = StardewValley.Object; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// A Harmony patch for which intercepts crashes due to the item no longer existing. + /// 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 ObjectPatcher : IHarmonyPatch + { + /********* + ** Public methods + *********/ + /// + public void Apply(Harmony harmony) + { + // object.getDescription + harmony.Patch( + original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)), + prefix: new HarmonyMethod(this.GetType(), nameof(ObjectPatcher.Before_Object_GetDescription)) + ); + + // object.getDisplayName + harmony.Patch( + original: AccessTools.Method(typeof(SObject), "loadDisplayName"), + finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectPatcher.Finalize_Object_loadDisplayName)) + ); + } + + + /********* + ** Private methods + *********/ + /// The method to call instead of . + /// The instance being patched. + /// The patched method's return value. + /// Returns whether to execute the original method. + 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)) + { + __result = "???"; + return false; + } + + return true; + } + + /// The method to call after . + /// The patched method's return value. + /// The exception thrown by the wrapped method, if any. + /// Returns the exception to throw, if any. + private static Exception Finalize_Object_loadDisplayName(ref string __result, Exception __exception) + { + if (__exception is KeyNotFoundException) + { + __result = "???"; + return null; + } + + return __exception; + } + } +} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs new file mode 100644 index 00000000..ef165831 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using HarmonyLib; +using Microsoft.Xna.Framework.Content; +using StardewModdingAPI.Framework.Patching; +using StardewValley; +using StardewValley.Buildings; +using StardewValley.Locations; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// A Harmony patch for which prevents some errors due to broken save data. + /// 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 SaveGamePatcher : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// Writes messages to the console and log file. + private static IMonitor Monitor; + + /// A callback invoked when custom content is removed from the save data to avoid a crash. + private static Action OnContentRemoved; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Writes messages to the console and log file. + /// A callback invoked when custom content is removed from the save data to avoid a crash. + public SaveGamePatcher(IMonitor monitor, Action onContentRemoved) + { + SaveGamePatcher.Monitor = monitor; + SaveGamePatcher.OnContentRemoved = onContentRemoved; + } + + + /// + public void Apply(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)), + prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatcher.Before_SaveGame_LoadDataToLocations)) + ); + } + + + /********* + ** Private methods + *********/ + /// The method to call instead of . + /// The game locations being loaded. + /// Returns whether to execute the original method. + private static bool Before_SaveGame_LoadDataToLocations(List gamelocations) + { + bool removedAny = + SaveGamePatcher.RemoveBrokenBuildings(gamelocations) + | SaveGamePatcher.RemoveInvalidNpcs(gamelocations); + + if (removedAny) + SaveGamePatcher.OnContentRemoved(); + + return true; + } + + /// Remove buildings which don't exist in the game data. + /// The current game locations. + private static bool RemoveBrokenBuildings(IEnumerable locations) + { + bool removedAny = false; + + foreach (BuildableGameLocation location in locations.OfType()) + { + foreach (Building building in location.buildings.ToArray()) + { + try + { + BluePrint _ = new BluePrint(building.buildingType.Value); + } + catch (ContentLoadException) + { + SaveGamePatcher.Monitor.Log($"Removed invalid building type '{building.buildingType.Value}' in {location.Name} ({building.tileX}, {building.tileY}) to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom building mod?)", LogLevel.Warn); + location.buildings.Remove(building); + removedAny = true; + } + } + } + + return removedAny; + } + + /// Remove NPCs which don't exist in the game data. + /// The current game locations. + private static bool RemoveInvalidNpcs(IEnumerable locations) + { + bool removedAny = false; + + IDictionary data = Game1.content.Load>("Data\\NPCDispositions"); + foreach (GameLocation location in SaveGamePatcher.GetAllLocations(locations)) + { + foreach (NPC npc in location.characters.ToArray()) + { + if (npc.isVillager() && !data.ContainsKey(npc.Name)) + { + try + { + npc.reloadSprite(); // this won't crash for special villagers like Bouncer + } + catch + { + SaveGamePatcher.Monitor.Log($"Removed invalid villager '{npc.Name}' in {location.Name} ({npc.getTileLocation()}) to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom NPC mod?)", LogLevel.Warn); + location.characters.Remove(npc); + removedAny = true; + } + } + } + } + + return removedAny; + } + + /// Get all locations, including building interiors. + /// The main game locations. + private static IEnumerable GetAllLocations(IEnumerable locations) + { + foreach (GameLocation location in locations) + { + yield return location; + if (location is BuildableGameLocation buildableLocation) + { + foreach (GameLocation interior in buildableLocation.buildings.Select(p => p.indoors.Value).Where(p => p != null)) + yield return interior; + } + } + } + } +} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs deleted file mode 100644 index b8ab9361..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.Patching; -using StardewValley; - -namespace StardewModdingAPI.Mods.ErrorHandler.Patches -{ - /// A Harmony patch for which intercepts crashes due to invalid schedule data. - /// 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 ScheduleErrorPatch : IHarmonyPatch - { - /********* - ** Fields - *********/ - /// Writes messages to the console and log file on behalf of the game. - private static IMonitor MonitorForGame; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Writes messages to the console and log file on behalf of the game. - public ScheduleErrorPatch(IMonitor monitorForGame) - { - ScheduleErrorPatch.MonitorForGame = monitorForGame; - } - - /// - public void Apply(Harmony harmony) - { - harmony.Patch( - original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod, - finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_CurrentDialogue)) - ); - - harmony.Patch( - original: AccessTools.Method(typeof(NPC), nameof(NPC.parseMasterSchedule)), - finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule)) - ); - } - - - /********* - ** Private methods - *********/ - /// The method to call after . - /// The instance being patched. - /// The return value of the original method. - /// The exception thrown by the wrapped method, if any. - /// Returns the exception to throw, if any. - private static Exception Finalize_NPC_CurrentDialogue(NPC __instance, ref Stack __result, Exception __exception) - { - if (__exception == null) - return null; - - ScheduleErrorPatch.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{__exception.GetLogSummary()}", LogLevel.Error); - __result = new Stack(); - - return null; - } - - /// The method to call instead of . - /// The raw schedule data to parse. - /// The instance being patched. - /// The patched method's return value. - /// The exception thrown by the wrapped method, if any. - /// Returns the exception to throw, if any. - private static Exception Finalize_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary __result, Exception __exception) - { - if (__exception != null) - { - ScheduleErrorPatch.MonitorForGame.Log($"Failed parsing schedule for NPC {__instance.Name}:\n{rawData}\n{__exception.GetLogSummary()}", LogLevel.Error); - __result = new Dictionary(); - } - - return null; - } - } -} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs new file mode 100644 index 00000000..1099afee --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Patching; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// Harmony patch for to validate textures earlier. + /// 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 SpriteBatchPatcher : IHarmonyPatch + { + /********* + ** Public methods + *********/ + /// + public void Apply(Harmony harmony) + { + harmony.Patch( + original: Constants.GameFramework == GameFramework.Xna + ? AccessTools.Method(typeof(SpriteBatch), "InternalDraw") + : AccessTools.Method(typeof(SpriteBatch), "CheckValid", new[] { typeof(Texture2D) }), + postfix: new HarmonyMethod(this.GetType(), nameof(SpriteBatchPatcher.After_SpriteBatch_CheckValid)) + ); + } + + + /********* + ** Private methods + *********/ +#if SMAPI_FOR_XNA + /// The method to call instead of . + /// The texture to validate. +#else + /// The method to call instead of . + /// The texture to validate. +#endif + private static void After_SpriteBatch_CheckValid(Texture2D texture) + { + if (texture?.IsDisposed == true) + throw new ObjectDisposedException("Cannot draw this texture because it's disposed."); + } + } +} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs deleted file mode 100644 index 7fdf5a48..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI.Framework.Patching; - -namespace StardewModdingAPI.Mods.ErrorHandler.Patches -{ - /// Harmony patch for to validate textures earlier. - /// 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 SpriteBatchValidationPatches : IHarmonyPatch - { - /********* - ** Public methods - *********/ - /// - public void Apply(Harmony harmony) - { - harmony.Patch( - original: Constants.GameFramework == GameFramework.Xna - ? AccessTools.Method(typeof(SpriteBatch), "InternalDraw") - : AccessTools.Method(typeof(SpriteBatch), "CheckValid", new[] { typeof(Texture2D) }), - postfix: new HarmonyMethod(this.GetType(), nameof(SpriteBatchValidationPatches.After_SpriteBatch_CheckValid)) - ); - } - - - /********* - ** Private methods - *********/ -#if SMAPI_FOR_XNA - /// The method to call instead of . - /// The texture to validate. -#else - /// The method to call instead of . - /// The texture to validate. -#endif - private static void After_SpriteBatch_CheckValid(Texture2D texture) - { - if (texture?.IsDisposed == true) - throw new ObjectDisposedException("Cannot draw this texture because it's disposed."); - } - } -} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs deleted file mode 100644 index cd57736f..00000000 --- a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -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 - { - /********* - ** Public methods - *********/ - /// - 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)) - ); - } - - - /********* - ** Private methods - *********/ - /// 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; - } - } -} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs new file mode 100644 index 00000000..e29e3030 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs @@ -0,0 +1,43 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +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 UtilityPatcher : IHarmonyPatch + { + /********* + ** Public methods + *********/ + /// + public void Apply(Harmony harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(Utility), nameof(Utility.getItemFromStandardTextDescription)), + finalizer: new HarmonyMethod(this.GetType(), nameof(UtilityPatcher.Finalize_Utility_GetItemFromStandardTextDescription)) + ); + } + + + /********* + ** Private methods + *********/ + /// 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; + } + } +} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 419afd4b..35db2da2 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -255,7 +255,7 @@ namespace StardewModdingAPI.Framework // apply game patches MiniMonoModHotfix.Apply(); new GamePatcher(this.Monitor).Apply( - new LoadContextPatch(this.Reflection, this.OnLoadStageChanged), + new Game1Patcher(this.Reflection, this.OnLoadStageChanged), new TitleMenuPatcher(this.OnLoadStageChanged) ); diff --git a/src/SMAPI/Patches/Game1Patcher.cs b/src/SMAPI/Patches/Game1Patcher.cs new file mode 100644 index 00000000..82b13869 --- /dev/null +++ b/src/SMAPI/Patches/Game1Patcher.cs @@ -0,0 +1,125 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using HarmonyLib; +using StardewModdingAPI.Enums; +using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; +using StardewValley.Menus; +using StardewValley.Minigames; + +namespace StardewModdingAPI.Patches +{ + /// Harmony patches which notify SMAPI for save creation load stages. + /// 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 Game1Patcher : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// Simplifies access to private code. + private static Reflector Reflection; + + /// A callback to invoke when the load stage changes. + private static Action OnStageChanged; + + /// Whether the game is running running the code in . + private static bool IsInLoadForNewGame; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Simplifies access to private code. + /// A callback to invoke when the load stage changes. + public Game1Patcher(Reflector reflection, Action onStageChanged) + { + Game1Patcher.Reflection = reflection; + Game1Patcher.OnStageChanged = onStageChanged; + } + + /// + public void Apply(Harmony harmony) + { + // detect CreatedInitialLocations and SaveAddedLocations + harmony.Patch( + original: AccessTools.Method(typeof(Game1), nameof(Game1.AddModNPCs)), + prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_AddModNPCs)) + ); + + // detect CreatedLocations, and track IsInLoadForNewGame + harmony.Patch( + original: AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)), + prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_LoadForNewGame)), + postfix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.After_Game1_LoadForNewGame)) + ); + + // detect ReturningToTitle + harmony.Patch( + original: AccessTools.Method(typeof(Game1), nameof(Game1.CleanupReturningToTitle)), + prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_CleanupReturningToTitle)) + ); + } + + + /********* + ** Private methods + *********/ + /// Called before . + /// Returns whether to execute the original method. + /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. + private static bool Before_Game1_AddModNPCs() + { + // When this method is called from Game1.loadForNewGame, it happens right after adding the vanilla + // locations but before initializing them. + if (Game1Patcher.IsInLoadForNewGame) + { + Game1Patcher.OnStageChanged(Game1Patcher.IsCreating() + ? LoadStage.CreatedInitialLocations + : LoadStage.SaveAddedLocations + ); + } + + return true; + } + + /// Called before . + /// Returns whether to execute the original method. + /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. + private static bool Before_Game1_CleanupReturningToTitle() + { + Game1Patcher.OnStageChanged(LoadStage.ReturningToTitle); + return true; + } + + /// Called before . + /// Returns whether to execute the original method. + /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. + private static bool Before_Game1_LoadForNewGame() + { + Game1Patcher.IsInLoadForNewGame = true; + return true; + } + + /// Called after . + /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. + private static void After_Game1_LoadForNewGame() + { + Game1Patcher.IsInLoadForNewGame = false; + + if (Game1Patcher.IsCreating()) + Game1Patcher.OnStageChanged(LoadStage.CreatedLocations); + } + + /// Get whether the save file is currently being created. + private static bool IsCreating() + { + return + (Game1.currentMinigame is Intro) // creating save with intro + || (Game1.activeClickableMenu is TitleMenu menu && Game1Patcher.Reflection.GetField(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro + } + } +} diff --git a/src/SMAPI/Patches/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs deleted file mode 100644 index c7f7d986..00000000 --- a/src/SMAPI/Patches/LoadContextPatch.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using HarmonyLib; -using StardewModdingAPI.Enums; -using StardewModdingAPI.Framework.Patching; -using StardewModdingAPI.Framework.Reflection; -using StardewValley; -using StardewValley.Menus; -using StardewValley.Minigames; - -namespace StardewModdingAPI.Patches -{ - /// Harmony patches which notify SMAPI for save creation load stages. - /// 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 LoadContextPatch : IHarmonyPatch - { - /********* - ** Fields - *********/ - /// Simplifies access to private code. - private static Reflector Reflection; - - /// A callback to invoke when the load stage changes. - private static Action OnStageChanged; - - /// Whether the game is running running the code in . - private static bool IsInLoadForNewGame; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Simplifies access to private code. - /// A callback to invoke when the load stage changes. - public LoadContextPatch(Reflector reflection, Action onStageChanged) - { - LoadContextPatch.Reflection = reflection; - LoadContextPatch.OnStageChanged = onStageChanged; - } - - /// - public void Apply(Harmony harmony) - { - // detect CreatedInitialLocations and SaveAddedLocations - harmony.Patch( - original: AccessTools.Method(typeof(Game1), nameof(Game1.AddModNPCs)), - prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_AddModNPCs)) - ); - - // detect CreatedLocations, and track IsInLoadForNewGame - 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)) - ); - - // detect ReturningToTitle - harmony.Patch( - original: AccessTools.Method(typeof(Game1), nameof(Game1.CleanupReturningToTitle)), - prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_CleanupReturningToTitle)) - ); - } - - - /********* - ** Private methods - *********/ - /// Called before . - /// Returns whether to execute the original method. - /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static bool Before_Game1_AddModNPCs() - { - // When this method is called from Game1.loadForNewGame, it happens right after adding the vanilla - // locations but before initializing them. - if (LoadContextPatch.IsInLoadForNewGame) - { - LoadContextPatch.OnStageChanged(LoadContextPatch.IsCreating() - ? LoadStage.CreatedInitialLocations - : LoadStage.SaveAddedLocations - ); - } - - return true; - } - - /// Called before . - /// Returns whether to execute the original method. - /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static bool Before_Game1_CleanupReturningToTitle() - { - LoadContextPatch.OnStageChanged(LoadStage.ReturningToTitle); - return true; - } - - /// Called before . - /// Returns whether to execute the original method. - /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static bool Before_Game1_LoadForNewGame() - { - LoadContextPatch.IsInLoadForNewGame = true; - return true; - } - - /// Called after . - /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static void After_Game1_LoadForNewGame() - { - LoadContextPatch.IsInLoadForNewGame = false; - - if (LoadContextPatch.IsCreating()) - LoadContextPatch.OnStageChanged(LoadStage.CreatedLocations); - } - - /// Get whether the save file is currently being created. - private static bool IsCreating() - { - return - (Game1.currentMinigame is Intro) // creating save with intro - || (Game1.activeClickableMenu is TitleMenu menu && LoadContextPatch.Reflection.GetField(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro - } - } -} -- cgit From 948c800a98f00b1bdcfd05ee6228e3423f9eb465 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 30 Jul 2021 00:54:15 -0400 Subject: migrate to the new Harmony patch pattern used in my mods That improves validation and error-handling. --- src/SMAPI.Internal.Patching/BasePatcher.cs | 54 +++++++++++++++ src/SMAPI.Internal.Patching/HarmonyPatcher.cs | 36 ++++++++++ src/SMAPI.Internal.Patching/IPatcher.cs | 16 +++++ src/SMAPI.Internal.Patching/PatchHelper.cs | 77 ++++++++++++++++++++++ .../SMAPI.Internal.Patching.projitems | 17 +++++ .../SMAPI.Internal.Patching.shproj | 13 ++++ src/SMAPI.Internal/ExceptionExtensions.cs | 41 ++++++++++++ src/SMAPI.Internal/SMAPI.Internal.projitems | 1 + src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 4 +- .../Patches/DialoguePatcher.cs | 18 ++--- .../Patches/DictionaryPatcher.cs | 28 ++++---- .../Patches/EventPatcher.cs | 12 ++-- .../Patches/GameLocationPatcher.cs | 24 +++---- .../Patches/IClickableMenuPatcher.cs | 14 ++-- src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs | 24 +++---- .../Patches/ObjectPatcher.cs | 16 ++--- .../Patches/SaveGamePatcher.cs | 15 ++--- .../Patches/SpriteBatchPatcher.cs | 20 +++--- .../Patches/UtilityPatcher.cs | 14 ++-- .../SMAPI.Mods.ErrorHandler.csproj | 1 + src/SMAPI.sln | 6 ++ .../Framework/Content/AssetInterceptorChange.cs | 1 + .../ContentManagers/GameContentManager.cs | 1 + .../Framework/ContentManagers/ModContentManager.cs | 1 + src/SMAPI/Framework/Events/ManagedEvent.cs | 1 + src/SMAPI/Framework/InternalExtensions.cs | 35 +--------- src/SMAPI/Framework/Logging/LogManager.cs | 1 + src/SMAPI/Framework/Patching/GamePatcher.cs | 45 ------------- src/SMAPI/Framework/Patching/IHarmonyPatch.cs | 15 ----- src/SMAPI/Framework/SCore.cs | 5 +- src/SMAPI/Framework/SGame.cs | 1 + src/SMAPI/Metadata/CoreAssetPropagator.cs | 1 + src/SMAPI/Patches/Game1Patcher.cs | 38 +++++------ src/SMAPI/Patches/TitleMenuPatcher.cs | 17 +++-- src/SMAPI/Properties/AssemblyInfo.cs | 1 - src/SMAPI/SMAPI.csproj | 1 + 36 files changed, 394 insertions(+), 221 deletions(-) create mode 100644 src/SMAPI.Internal.Patching/BasePatcher.cs create mode 100644 src/SMAPI.Internal.Patching/HarmonyPatcher.cs create mode 100644 src/SMAPI.Internal.Patching/IPatcher.cs create mode 100644 src/SMAPI.Internal.Patching/PatchHelper.cs create mode 100644 src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.projitems create mode 100644 src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.shproj create mode 100644 src/SMAPI.Internal/ExceptionExtensions.cs delete mode 100644 src/SMAPI/Framework/Patching/GamePatcher.cs delete mode 100644 src/SMAPI/Framework/Patching/IHarmonyPatch.cs (limited to 'src/SMAPI') diff --git a/src/SMAPI.Internal.Patching/BasePatcher.cs b/src/SMAPI.Internal.Patching/BasePatcher.cs new file mode 100644 index 00000000..87155d7f --- /dev/null +++ b/src/SMAPI.Internal.Patching/BasePatcher.cs @@ -0,0 +1,54 @@ +using System; +using System.Reflection; +using HarmonyLib; + +namespace StardewModdingAPI.Internal.Patching +{ + /// Provides base implementation logic for instances. + internal abstract class BasePatcher : IPatcher + { + /********* + ** Public methods + *********/ + /// + public abstract void Apply(Harmony harmony, IMonitor monitor); + + + /********* + ** Protected methods + *********/ + /// Get a method and assert that it was found. + /// The type containing the method. + /// The method parameter types, or null if it's not overloaded. + protected ConstructorInfo RequireConstructor(params Type[] parameters) + { + return PatchHelper.RequireConstructor(parameters); + } + + /// Get a method and assert that it was found. + /// The type containing the method. + /// The method name. + /// The method parameter types, or null if it's not overloaded. + /// The method generic types, or null if it's not generic. + protected MethodInfo RequireMethod(string name, Type[] parameters = null, Type[] generics = null) + { + return PatchHelper.RequireMethod(name, parameters, generics); + } + + /// Get a Harmony patch method on the current patcher instance. + /// The method name. + /// The patch priority to apply, usually specified using Harmony's enum, or null to keep the default value. + protected HarmonyMethod GetHarmonyMethod(string name, int? priority = null) + { + var method = new HarmonyMethod( + AccessTools.Method(this.GetType(), name) + ?? throw new InvalidOperationException($"Can't find patcher method {PatchHelper.GetMethodString(this.GetType(), name)}.") + ); + + if (priority.HasValue) + method.priority = priority.Value; + + return method; + } + } +} diff --git a/src/SMAPI.Internal.Patching/HarmonyPatcher.cs b/src/SMAPI.Internal.Patching/HarmonyPatcher.cs new file mode 100644 index 00000000..c07e3b41 --- /dev/null +++ b/src/SMAPI.Internal.Patching/HarmonyPatcher.cs @@ -0,0 +1,36 @@ +using System; +using HarmonyLib; + +namespace StardewModdingAPI.Internal.Patching +{ + /// Simplifies applying instances to the game. + internal static class HarmonyPatcher + { + /********* + ** Public methods + *********/ + /// Apply the given Harmony patchers. + /// The mod ID applying the patchers. + /// The monitor with which to log any errors. + /// The patchers to apply. + public static Harmony Apply(string id, IMonitor monitor, params IPatcher[] patchers) + { + Harmony harmony = new Harmony(id); + + foreach (IPatcher patcher in patchers) + { + try + { + patcher.Apply(harmony, monitor); + } + catch (Exception ex) + { + monitor.Log($"Couldn't apply runtime patch '{patcher.GetType().Name}' to the game. Some SMAPI features may not work correctly. See log file for details.", LogLevel.Error); + monitor.Log($"Technical details:\n{ex.GetLogSummary()}"); + } + } + + return harmony; + } + } +} diff --git a/src/SMAPI.Internal.Patching/IPatcher.cs b/src/SMAPI.Internal.Patching/IPatcher.cs new file mode 100644 index 00000000..a732d64f --- /dev/null +++ b/src/SMAPI.Internal.Patching/IPatcher.cs @@ -0,0 +1,16 @@ +using HarmonyLib; + +namespace StardewModdingAPI.Internal.Patching +{ + /// A set of Harmony patches to apply. + internal interface IPatcher + { + /********* + ** Public methods + *********/ + /// Apply the Harmony patches for this instance. + /// The Harmony instance. + /// The monitor with which to log any errors. + public void Apply(Harmony harmony, IMonitor monitor); + } +} diff --git a/src/SMAPI.Internal.Patching/PatchHelper.cs b/src/SMAPI.Internal.Patching/PatchHelper.cs new file mode 100644 index 00000000..c9758616 --- /dev/null +++ b/src/SMAPI.Internal.Patching/PatchHelper.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Text; +using HarmonyLib; + +namespace StardewModdingAPI.Internal.Patching +{ + /// Provides utility methods for patching game code with Harmony. + internal static class PatchHelper + { + /********* + ** Public methods + *********/ + /// Get a constructor and assert that it was found. + /// The type containing the method. + /// The method parameter types, or null if it's not overloaded. + /// The type has no matching constructor. + public static ConstructorInfo RequireConstructor(Type[] parameters = null) + { + return + AccessTools.Constructor(typeof(TTarget), parameters) + ?? throw new InvalidOperationException($"Can't find constructor {PatchHelper.GetMethodString(typeof(TTarget), null, parameters)} to patch."); + } + + /// Get a method and assert that it was found. + /// The type containing the method. + /// The method name. + /// The method parameter types, or null if it's not overloaded. + /// The method generic types, or null if it's not generic. + /// The type has no matching method. + public static MethodInfo RequireMethod(string name, Type[] parameters = null, Type[] generics = null) + { + return + AccessTools.Method(typeof(TTarget), name, parameters, generics) + ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(typeof(TTarget), name, parameters, generics)} to patch."); + } + + /// Get a human-readable representation of a method target. + /// The type containing the method. + /// The method name, or null for a constructor. + /// The method parameter types, or null if it's not overloaded. + /// The method generic types, or null if it's not generic. + public static string GetMethodString(Type type, string name, Type[] parameters = null, Type[] generics = null) + { + StringBuilder str = new(); + + // type + str.Append(type.FullName); + + // method name (if not constructor) + if (name != null) + { + str.Append('.'); + str.Append(name); + } + + // generics + if (generics?.Any() == true) + { + str.Append('<'); + str.Append(string.Join(", ", generics.Select(p => p.FullName))); + str.Append('>'); + } + + // parameters + if (parameters?.Any() == true) + { + str.Append('('); + str.Append(string.Join(", ", parameters.Select(p => p.FullName))); + str.Append(')'); + } + + return str.ToString(); + } + } +} diff --git a/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.projitems b/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.projitems new file mode 100644 index 00000000..4fa2a062 --- /dev/null +++ b/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.projitems @@ -0,0 +1,17 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 6c16e948-3e5c-47a7-bf4b-07a7469a87a5 + + + SMAPI.Internal.Patching + + + + + + + + \ No newline at end of file diff --git a/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.shproj b/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.shproj new file mode 100644 index 00000000..1a102c82 --- /dev/null +++ b/src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.shproj @@ -0,0 +1,13 @@ + + + + 6c16e948-3e5c-47a7-bf4b-07a7469a87a5 + 14.0 + + + + + + + + diff --git a/src/SMAPI.Internal/ExceptionExtensions.cs b/src/SMAPI.Internal/ExceptionExtensions.cs new file mode 100644 index 00000000..d7a2252b --- /dev/null +++ b/src/SMAPI.Internal/ExceptionExtensions.cs @@ -0,0 +1,41 @@ +using System; +using System.Reflection; + +namespace StardewModdingAPI.Internal +{ + /// Provides extension methods for handling exceptions. + internal static class ExceptionExtensions + { + /********* + ** Public methods + *********/ + /// Get a string representation of an exception suitable for writing to the error log. + /// The error to summarize. + public static string GetLogSummary(this Exception exception) + { + switch (exception) + { + case TypeLoadException ex: + return $"Failed loading type '{ex.TypeName}': {exception}"; + + case ReflectionTypeLoadException ex: + string summary = exception.ToString(); + foreach (Exception childEx in ex.LoaderExceptions) + summary += $"\n\n{childEx.GetLogSummary()}"; + return summary; + + default: + return exception.ToString(); + } + } + + /// Get the lowest exception in an exception stack. + /// The exception from which to search. + public static Exception GetInnermostException(this Exception exception) + { + while (exception.InnerException != null) + exception = exception.InnerException; + return exception; + } + } +} diff --git a/src/SMAPI.Internal/SMAPI.Internal.projitems b/src/SMAPI.Internal/SMAPI.Internal.projitems index 0d583a6d..0ee94a5b 100644 --- a/src/SMAPI.Internal/SMAPI.Internal.projitems +++ b/src/SMAPI.Internal/SMAPI.Internal.projitems @@ -14,5 +14,6 @@ + \ No newline at end of file diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index ac9d1b94..067f6a8d 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using StardewModdingAPI.Events; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewModdingAPI.Mods.ErrorHandler.Patches; using StardewValley; @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler IMonitor monitorForGame = this.GetMonitorForGame(); // apply patches - new GamePatcher(this.Monitor).Apply( + HarmonyPatcher.Apply(this.ModManifest.UniqueID, this.Monitor, new DialoguePatcher(monitorForGame, this.Helper.Reflection), new DictionaryPatcher(this.Helper.Reflection), new EventPatcher(monitorForGame), diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs index 7b730ee5..7a3af39c 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs @@ -1,17 +1,17 @@ using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal; +using StardewModdingAPI.Internal.Patching; using StardewValley; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { - /// A Harmony patch for the constructor which intercepts invalid dialogue lines and logs an error instead of crashing. + /// Harmony patches for which intercept invalid dialogue lines and logs an error instead of crashing. /// 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 DialoguePatcher : IHarmonyPatch + internal class DialoguePatcher : BasePatcher { /********* ** Fields @@ -36,11 +36,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches } /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { harmony.Patch( - original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }), - finalizer: new HarmonyMethod(this.GetType(), nameof(DialoguePatcher.Finalize_Dialogue_Constructor)) + original: this.RequireConstructor(typeof(string), typeof(NPC)), + finalizer: this.GetHarmonyMethod(nameof(DialoguePatcher.Finalize_Constructor)) ); } @@ -48,13 +48,13 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /********* ** Private methods *********/ - /// The method to call after the Dialogue constructor. + /// The method to call when the Dialogue constructor throws an exception. /// The instance being patched. /// The dialogue being parsed. /// The NPC for which the dialogue is being parsed. /// The exception thrown by the wrapped method, if any. /// Returns the exception to throw, if any. - private static Exception Finalize_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker, Exception __exception) + private static Exception Finalize_Constructor(Dialogue __instance, string masterDialogue, NPC speaker, Exception __exception) { if (__exception != null) { diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs index 3c5240b6..6ad64e16 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs @@ -2,18 +2,18 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewValley.GameData; using StardewValley.GameData.HomeRenovations; using StardewValley.GameData.Movies; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { - /// A Harmony patch for which adds the accessed key to exceptions. + /// Harmony patches for which add the accessed key to exceptions. /// 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 DictionaryPatcher : IHarmonyPatch + internal class DictionaryPatcher : BasePatcher { /********* ** Fields @@ -33,7 +33,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches } /// - public void Apply(Harmony harmony) + 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) }; @@ -45,8 +45,8 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); harmony.Patch( - original: AccessTools.Method(dictionaryType, "get_Item"), - finalizer: new HarmonyMethod(this.GetType(), nameof(DictionaryPatcher.Finalize_GetItem)) + 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)) ); } } @@ -63,19 +63,13 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches private static Exception Finalize_GetItem(object key, Exception __exception) { if (__exception is KeyNotFoundException) - AddKeyTo(__exception, key?.ToString()); + { + DictionaryPatcher.Reflection + .GetField(__exception, "_message") + .SetValue($"{__exception.Message}\nkey: '{key}'"); + } return __exception; } - - /// Add the accessed key to an exception message. - /// The exception to modify. - /// The dictionary key. - private static void AddKeyTo(Exception exception, string key) - { - DictionaryPatcher.Reflection - .GetField(exception, "_message") - .SetValue($"{exception.Message}\nkey: '{key}'"); - } } } diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs index 9a7b34d8..1b706147 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewValley; namespace StardewModdingAPI.Mods.ErrorHandler.Patches @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// 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 EventPatcher : IHarmonyPatch + internal class EventPatcher : BasePatcher { /********* ** Fields @@ -30,11 +30,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches } /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { harmony.Patch( - original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)), - postfix: new HarmonyMethod(this.GetType(), nameof(EventPatcher.After_Event_LogErrorAndHalt)) + original: this.RequireMethod(nameof(Event.LogErrorAndHalt)), + postfix: this.GetHarmonyMethod(nameof(EventPatcher.After_LogErrorAndHalt)) ); } @@ -44,7 +44,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches *********/ /// The method to call after . /// The exception being logged. - private static void After_Event_LogErrorAndHalt(Exception e) + private static void After_LogErrorAndHalt(Exception e) { 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 index 7427fe48..7df6b0a2 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs @@ -1,17 +1,17 @@ using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewValley; using xTile; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { - /// Harmony patches for and which intercept errors instead of crashing. + /// Harmony patches for which intercept errors instead of crashing. /// 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 GameLocationPatcher : IHarmonyPatch + internal class GameLocationPatcher : BasePatcher { /********* ** Fields @@ -31,15 +31,15 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches } /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { harmony.Patch( - original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)), - finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatcher.Finalize_GameLocation_CheckEventPrecondition)) + original: this.RequireMethod(nameof(GameLocation.checkEventPrecondition)), + finalizer: this.GetHarmonyMethod(nameof(GameLocationPatcher.Finalize_CheckEventPrecondition)) ); harmony.Patch( - original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)), - finalizer: new HarmonyMethod(this.GetType(), nameof(GameLocationPatcher.Before_GameLocation_UpdateSeasonalTileSheets)) + original: this.RequireMethod(nameof(GameLocation.updateSeasonalTileSheets)), + finalizer: this.GetHarmonyMethod(nameof(GameLocationPatcher.Finalize_UpdateSeasonalTileSheets)) ); } @@ -47,12 +47,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /********* ** Private methods *********/ - /// The method to call instead of GameLocation.checkEventPrecondition. + /// The method to call when throws an exception. /// The return value of the original method. /// The precondition to be parsed. /// The exception thrown by the wrapped method, if any. /// Returns the exception to throw, if any. - private static Exception Finalize_GameLocation_CheckEventPrecondition(ref int __result, string precondition, Exception __exception) + private static Exception Finalize_CheckEventPrecondition(ref int __result, string precondition, Exception __exception) { if (__exception != null) { @@ -63,12 +63,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches return null; } - /// The method to call instead of . + /// The method to call when throws an exception. /// The instance being patched. /// The map whose tilesheets to update. /// The exception thrown by the wrapped method, if any. /// Returns the exception to throw, if any. - private static Exception Before_GameLocation_UpdateSeasonalTileSheets(GameLocation __instance, Map map, Exception __exception) + 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); diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs index d6f9fbf4..b65a695a 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs @@ -1,27 +1,27 @@ using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewValley; using StardewValley.Menus; using SObject = StardewValley.Object; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { - /// A Harmony patch for which intercepts crashes due to invalid items. + /// Harmony patches for which intercept crashes due to invalid items. /// 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 IClickableMenuPatcher : IHarmonyPatch + internal class IClickableMenuPatcher : BasePatcher { /********* ** Public methods *********/ /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { harmony.Patch( - original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)), - prefix: new HarmonyMethod(this.GetType(), nameof(IClickableMenuPatcher.Before_IClickableMenu_DrawTooltip)) + original: this.RequireMethod(nameof(IClickableMenu.drawToolTip)), + prefix: this.GetHarmonyMethod(nameof(IClickableMenuPatcher.Before_DrawTooltip)) ); } @@ -32,7 +32,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// The method to call instead of . /// The item for which to draw a tooltip. /// Returns whether to execute the original method. - private static bool Before_IClickableMenu_DrawTooltip(Item hoveredItem) + private static bool Before_DrawTooltip(Item hoveredItem) { // invalid edible item cause crash when drawing tooltips if (hoveredItem is SObject obj && obj.Edibility != -300 && !Game1.objectInformation.ContainsKey(obj.ParentSheetIndex)) diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs index 4a07ea1d..275bb5bf 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs @@ -2,17 +2,17 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal; +using StardewModdingAPI.Internal.Patching; using StardewValley; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { - /// A Harmony patch for which intercepts crashes due to invalid schedule data. + /// Harmony patches for which intercept crashes due to invalid schedule data. /// 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 NpcPatcher : IHarmonyPatch + internal class NpcPatcher : BasePatcher { /********* ** Fields @@ -32,16 +32,16 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches } /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { harmony.Patch( - original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod, - finalizer: new HarmonyMethod(this.GetType(), nameof(NpcPatcher.Finalize_NPC_CurrentDialogue)) + original: this.RequireMethod($"get_{nameof(NPC.CurrentDialogue)}"), + finalizer: this.GetHarmonyMethod(nameof(NpcPatcher.Finalize_CurrentDialogue)) ); harmony.Patch( - original: AccessTools.Method(typeof(NPC), nameof(NPC.parseMasterSchedule)), - finalizer: new HarmonyMethod(this.GetType(), nameof(NpcPatcher.Finalize_NPC_parseMasterSchedule)) + original: this.RequireMethod(nameof(NPC.parseMasterSchedule)), + finalizer: this.GetHarmonyMethod(nameof(NpcPatcher.Finalize_ParseMasterSchedule)) ); } @@ -49,12 +49,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /********* ** Private methods *********/ - /// The method to call after . + /// The method to call when throws an exception. /// The instance being patched. /// The return value of the original method. /// The exception thrown by the wrapped method, if any. /// Returns the exception to throw, if any. - private static Exception Finalize_NPC_CurrentDialogue(NPC __instance, ref Stack __result, Exception __exception) + private static Exception Finalize_CurrentDialogue(NPC __instance, ref Stack __result, Exception __exception) { if (__exception == null) return null; @@ -71,7 +71,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// The patched method's return value. /// The exception thrown by the wrapped method, if any. /// Returns the exception to throw, if any. - private static Exception Finalize_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary __result, Exception __exception) + private static Exception Finalize_ParseMasterSchedule(string rawData, NPC __instance, ref Dictionary __result, Exception __exception) { if (__exception != null) { diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs index c4b25b96..fd4ea35c 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs @@ -2,34 +2,34 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewValley; using SObject = StardewValley.Object; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { - /// A Harmony patch for which intercepts crashes due to the item no longer existing. + /// Harmony patches for which intercept crashes due to invalid items. /// 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 ObjectPatcher : IHarmonyPatch + internal class ObjectPatcher : BasePatcher { /********* ** Public methods *********/ /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { // object.getDescription harmony.Patch( - original: AccessTools.Method(typeof(SObject), nameof(SObject.getDescription)), - prefix: new HarmonyMethod(this.GetType(), nameof(ObjectPatcher.Before_Object_GetDescription)) + original: this.RequireMethod(nameof(SObject.getDescription)), + prefix: this.GetHarmonyMethod(nameof(ObjectPatcher.Before_Object_GetDescription)) ); // object.getDisplayName harmony.Patch( - original: AccessTools.Method(typeof(SObject), "loadDisplayName"), - finalizer: new HarmonyMethod(this.GetType(), nameof(ObjectPatcher.Finalize_Object_loadDisplayName)) + original: this.RequireMethod("loadDisplayName"), + finalizer: this.GetHarmonyMethod(nameof(ObjectPatcher.Finalize_Object_loadDisplayName)) ); } diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs index ef165831..8945e4f3 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs @@ -4,18 +4,18 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using HarmonyLib; using Microsoft.Xna.Framework.Content; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewValley; using StardewValley.Buildings; using StardewValley.Locations; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { - /// A Harmony patch for which prevents some errors due to broken save data. + /// Harmony patches for which prevent some errors due to broken save data. /// 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 SaveGamePatcher : IHarmonyPatch + internal class SaveGamePatcher : BasePatcher { /********* ** Fields @@ -39,13 +39,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches SaveGamePatcher.OnContentRemoved = onContentRemoved; } - /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { harmony.Patch( - original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)), - prefix: new HarmonyMethod(this.GetType(), nameof(SaveGamePatcher.Before_SaveGame_LoadDataToLocations)) + original: this.RequireMethod(nameof(SaveGame.loadDataToLocations)), + prefix: this.GetHarmonyMethod(nameof(SaveGamePatcher.Before_LoadDataToLocations)) ); } @@ -56,7 +55,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// The method to call instead of . /// The game locations being loaded. /// Returns whether to execute the original method. - private static bool Before_SaveGame_LoadDataToLocations(List gamelocations) + private static bool Before_LoadDataToLocations(List gamelocations) { bool removedAny = SaveGamePatcher.RemoveBrokenBuildings(gamelocations) diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs index 1099afee..6860a4ec 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs @@ -2,27 +2,27 @@ using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; namespace StardewModdingAPI.Mods.ErrorHandler.Patches { - /// Harmony patch for to validate textures earlier. + /// Harmony patches for which validate textures earlier. /// 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 SpriteBatchPatcher : IHarmonyPatch + internal class SpriteBatchPatcher : BasePatcher { /********* ** Public methods *********/ /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { harmony.Patch( original: Constants.GameFramework == GameFramework.Xna - ? AccessTools.Method(typeof(SpriteBatch), "InternalDraw") - : AccessTools.Method(typeof(SpriteBatch), "CheckValid", new[] { typeof(Texture2D) }), - postfix: new HarmonyMethod(this.GetType(), nameof(SpriteBatchPatcher.After_SpriteBatch_CheckValid)) + ? this.RequireMethod("InternalDraw") + : this.RequireMethod("CheckValid", new[] { typeof(Texture2D) }), + postfix: this.GetHarmonyMethod(nameof(SpriteBatchPatcher.After_CheckValid)) ); } @@ -31,13 +31,13 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches ** Private methods *********/ #if SMAPI_FOR_XNA - /// The method to call instead of . + /// The method to call after . /// The texture to validate. #else - /// The method to call instead of . + /// The method to call after . /// The texture to validate. #endif - private static void After_SpriteBatch_CheckValid(Texture2D texture) + private static void After_CheckValid(Texture2D texture) { if (texture?.IsDisposed == true) throw new ObjectDisposedException("Cannot draw this texture because it's disposed."); diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs index e29e3030..ce85d0c2 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewValley; namespace StardewModdingAPI.Mods.ErrorHandler.Patches @@ -10,17 +10,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// 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 UtilityPatcher : IHarmonyPatch + internal class UtilityPatcher : BasePatcher { /********* ** Public methods *********/ /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { harmony.Patch( - original: AccessTools.Method(typeof(Utility), nameof(Utility.getItemFromStandardTextDescription)), - finalizer: new HarmonyMethod(this.GetType(), nameof(UtilityPatcher.Finalize_Utility_GetItemFromStandardTextDescription)) + original: this.RequireMethod(nameof(Utility.getItemFromStandardTextDescription)), + finalizer: this.GetHarmonyMethod(nameof(UtilityPatcher.Finalize_GetItemFromStandardTextDescription)) ); } @@ -28,12 +28,12 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /********* ** Private methods *********/ - /// The method to call instead of . + /// The method to call when throws an exception. /// 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) + private static Exception Finalize_GetItemFromStandardTextDescription(string description, char delimiter, ref Exception __exception) { return __exception != null ? new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", __exception) diff --git a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj index 531d3699..ffda5f89 100644 --- a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj +++ b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj @@ -47,4 +47,5 @@ + diff --git a/src/SMAPI.sln b/src/SMAPI.sln index 58228ce9..92c6cb24 100644 --- a/src/SMAPI.sln +++ b/src/SMAPI.sln @@ -52,6 +52,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mods", "Mods", "{AE9A4D46-E EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SMAPI.Internal", "SMAPI.Internal\SMAPI.Internal.shproj", "{85208F8D-6FD1-4531-BE05-7142490F59FE}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SMAPI.Internal.Patching", "SMAPI.Internal.Patching\SMAPI.Internal.Patching.shproj", "{6C16E948-3E5C-47A7-BF4B-07A7469A87A5}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.ModBuildConfig.Analyzer.Tests", "SMAPI.ModBuildConfig.Analyzer.Tests\SMAPI.ModBuildConfig.Analyzer.Tests.csproj", "{680B2641-81EA-467C-86A5-0E81CDC57ED0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Tests", "SMAPI.Tests\SMAPI.Tests.csproj", "{AA95884B-7097-476E-92C8-D0500DE9D6D1}" @@ -86,10 +88,13 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution SMAPI.Internal\SMAPI.Internal.projitems*{0634ea4c-3b8f-42db-aea6-ca9e4ef6e92f}*SharedItemsImports = 5 SMAPI.Internal\SMAPI.Internal.projitems*{0a9bb24f-15ff-4c26-b1a2-81f7ae316518}*SharedItemsImports = 5 + SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{491e775b-ead0-44d4-b6ca-f1fc3e316d33}*SharedItemsImports = 5 SMAPI.Internal\SMAPI.Internal.projitems*{491e775b-ead0-44d4-b6ca-f1fc3e316d33}*SharedItemsImports = 5 + SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{6c16e948-3e5c-47a7-bf4b-07a7469a87a5}*SharedItemsImports = 13 SMAPI.Internal\SMAPI.Internal.projitems*{80efd92f-728f-41e0-8a5b-9f6f49a91899}*SharedItemsImports = 5 SMAPI.Internal\SMAPI.Internal.projitems*{85208f8d-6fd1-4531-be05-7142490f59fe}*SharedItemsImports = 13 SMAPI.Internal\SMAPI.Internal.projitems*{cd53ad6f-97f4-4872-a212-50c2a0fd3601}*SharedItemsImports = 5 + SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems*{e6da2198-7686-4f1d-b312-4a4dc70884c0}*SharedItemsImports = 5 SMAPI.Internal\SMAPI.Internal.projitems*{e6da2198-7686-4f1d-b312-4a4dc70884c0}*SharedItemsImports = 5 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -156,6 +161,7 @@ Global {EB35A917-67B9-4EFA-8DFC-4FB49B3949BB} = {86C452BE-D2D8-45B4-B63F-E329EB06CEDA} {5947303D-3512-413A-9009-7AC43F5D3513} = {EB35A917-67B9-4EFA-8DFC-4FB49B3949BB} {85208F8D-6FD1-4531-BE05-7142490F59FE} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11} + {6C16E948-3E5C-47A7-BF4B-07A7469A87A5} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11} {680B2641-81EA-467C-86A5-0E81CDC57ED0} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11} {AA95884B-7097-476E-92C8-D0500DE9D6D1} = {82D22ED7-A0A7-4D64-8E92-4B6A5E74ED11} {0634EA4C-3B8F-42DB-AEA6-CA9E4EF6E92F} = {AE9A4D46-E910-4293-8BC4-673F85FFF6A5} diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index 037d9f89..10488b84 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using StardewModdingAPI.Internal; namespace StardewModdingAPI.Framework.Content { diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 80a9937a..63cd1759 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -9,6 +9,7 @@ using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Internal; using StardewValley; using xTile; diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index bc5a8b74..d24ffb81 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Internal; using StardewModdingAPI.Toolkit.Serialization; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 2204966c..fa20a079 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using StardewModdingAPI.Events; +using StardewModdingAPI.Internal; namespace StardewModdingAPI.Framework.Events { diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index ab7f1e6c..6c9a5f3b 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -14,6 +14,9 @@ namespace StardewModdingAPI.Framework /// Provides extension methods for SMAPI's internal use. internal static class InternalExtensions { + /********* + ** Public methods + *********/ /**** ** IMonitor ****/ @@ -54,38 +57,6 @@ namespace StardewModdingAPI.Framework @event.Raise(Singleton.Instance); } - /**** - ** Exceptions - ****/ - /// Get a string representation of an exception suitable for writing to the error log. - /// The error to summarize. - public static string GetLogSummary(this Exception exception) - { - switch (exception) - { - case TypeLoadException ex: - return $"Failed loading type '{ex.TypeName}': {exception}"; - - case ReflectionTypeLoadException ex: - string summary = exception.ToString(); - foreach (Exception childEx in ex.LoaderExceptions) - summary += $"\n\n{childEx.GetLogSummary()}"; - return summary; - - default: - return exception.ToString(); - } - } - - /// Get the lowest exception in an exception stack. - /// The exception from which to search. - public static Exception GetInnermostException(this Exception exception) - { - while (exception.InnerException != null) - exception = exception.InnerException; - return exception; - } - /**** ** ReaderWriterLockSlim ****/ diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index e16b5c0d..a3d4f23d 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -9,6 +9,7 @@ using System.Threading; using StardewModdingAPI.Framework.Commands; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.ModLoading; +using StardewModdingAPI.Internal; using StardewModdingAPI.Internal.ConsoleWriting; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Utilities; diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs deleted file mode 100644 index 3ce22ee9..00000000 --- a/src/SMAPI/Framework/Patching/GamePatcher.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using HarmonyLib; - -namespace StardewModdingAPI.Framework.Patching -{ - /// Encapsulates applying Harmony patches to the game. - internal class GamePatcher - { - /********* - ** Fields - *********/ - /// Encapsulates monitoring and logging. - private readonly IMonitor Monitor; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Encapsulates monitoring and logging. - public GamePatcher(IMonitor monitor) - { - this.Monitor = monitor; - } - - /// Apply all loaded patches to the game. - /// The patches to apply. - public void Apply(params IHarmonyPatch[] patches) - { - Harmony harmony = new Harmony("SMAPI"); - foreach (IHarmonyPatch patch in patches) - { - try - { - patch.Apply(harmony); - } - catch (Exception ex) - { - this.Monitor.Log($"Couldn't apply runtime patch '{patch.GetType().Name}' to the game. Some SMAPI features may not work correctly. See log file for details.", LogLevel.Error); - this.Monitor.Log(ex.GetLogSummary(), LogLevel.Trace); - } - } - } - } -} diff --git a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs deleted file mode 100644 index c1ff3040..00000000 --- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs +++ /dev/null @@ -1,15 +0,0 @@ -using HarmonyLib; - -namespace StardewModdingAPI.Framework.Patching -{ - /// A Harmony patch to apply. - internal interface IHarmonyPatch - { - /********* - ** Methods - *********/ - /// Apply the Harmony patch. - /// The Harmony instance. - void Apply(Harmony harmony); - } -} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 35db2da2..a34b3eff 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -31,13 +31,14 @@ using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Framework.Networking; -using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Rendering; using StardewModdingAPI.Framework.Serialization; using StardewModdingAPI.Framework.StateTracking.Comparers; using StardewModdingAPI.Framework.StateTracking.Snapshots; using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Internal; +using StardewModdingAPI.Internal.Patching; using StardewModdingAPI.Patches; using StardewModdingAPI.Toolkit; using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; @@ -254,7 +255,7 @@ namespace StardewModdingAPI.Framework // apply game patches MiniMonoModHotfix.Apply(); - new GamePatcher(this.Monitor).Apply( + HarmonyPatcher.Apply("SMAPI", this.Monitor, new Game1Patcher(this.Reflection, this.OnLoadStageChanged), new TitleMenuPatcher(this.OnLoadStageChanged) ); diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index af7fa387..55ab8377 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -11,6 +11,7 @@ using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.StateTracking.Snapshots; using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Internal; using StardewModdingAPI.Utilities; using StardewValley; using StardewValley.BellsAndWhistles; diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 5641f90f..9273864e 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -8,6 +8,7 @@ using Netcode; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Internal; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; using StardewValley.BellsAndWhistles; diff --git a/src/SMAPI/Patches/Game1Patcher.cs b/src/SMAPI/Patches/Game1Patcher.cs index 82b13869..173a2055 100644 --- a/src/SMAPI/Patches/Game1Patcher.cs +++ b/src/SMAPI/Patches/Game1Patcher.cs @@ -2,19 +2,19 @@ using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; using StardewModdingAPI.Enums; -using StardewModdingAPI.Framework.Patching; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Internal.Patching; using StardewValley; using StardewValley.Menus; using StardewValley.Minigames; namespace StardewModdingAPI.Patches { - /// Harmony patches which notify SMAPI for save creation load stages. + /// Harmony patches for which notify SMAPI for save load stages. /// 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 Game1Patcher : IHarmonyPatch + internal class Game1Patcher : BasePatcher { /********* ** Fields @@ -42,25 +42,25 @@ namespace StardewModdingAPI.Patches } /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { // detect CreatedInitialLocations and SaveAddedLocations harmony.Patch( - original: AccessTools.Method(typeof(Game1), nameof(Game1.AddModNPCs)), - prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_AddModNPCs)) + original: this.RequireMethod(nameof(Game1.AddModNPCs)), + prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_AddModNpcs)) ); // detect CreatedLocations, and track IsInLoadForNewGame harmony.Patch( - original: AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)), - prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_LoadForNewGame)), - postfix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.After_Game1_LoadForNewGame)) + original: this.RequireMethod(nameof(Game1.loadForNewGame)), + prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_LoadForNewGame)), + postfix: this.GetHarmonyMethod(nameof(Game1Patcher.After_LoadForNewGame)) ); // detect ReturningToTitle harmony.Patch( - original: AccessTools.Method(typeof(Game1), nameof(Game1.CleanupReturningToTitle)), - prefix: new HarmonyMethod(this.GetType(), nameof(Game1Patcher.Before_Game1_CleanupReturningToTitle)) + original: this.RequireMethod(nameof(Game1.CleanupReturningToTitle)), + prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_CleanupReturningToTitle)) ); } @@ -68,10 +68,10 @@ namespace StardewModdingAPI.Patches /********* ** Private methods *********/ - /// Called before . + /// The method to call before . /// Returns whether to execute the original method. /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static bool Before_Game1_AddModNPCs() + private static bool Before_AddModNpcs() { // When this method is called from Game1.loadForNewGame, it happens right after adding the vanilla // locations but before initializing them. @@ -86,27 +86,27 @@ namespace StardewModdingAPI.Patches return true; } - /// Called before . + /// The method to call before . /// Returns whether to execute the original method. /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static bool Before_Game1_CleanupReturningToTitle() + private static bool Before_CleanupReturningToTitle() { Game1Patcher.OnStageChanged(LoadStage.ReturningToTitle); return true; } - /// Called before . + /// The method to call before . /// Returns whether to execute the original method. /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static bool Before_Game1_LoadForNewGame() + private static bool Before_LoadForNewGame() { Game1Patcher.IsInLoadForNewGame = true; return true; } - /// Called after . + /// The method to call after . /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static void After_Game1_LoadForNewGame() + private static void After_LoadForNewGame() { Game1Patcher.IsInLoadForNewGame = false; diff --git a/src/SMAPI/Patches/TitleMenuPatcher.cs b/src/SMAPI/Patches/TitleMenuPatcher.cs index a889adfc..b4320ce0 100644 --- a/src/SMAPI/Patches/TitleMenuPatcher.cs +++ b/src/SMAPI/Patches/TitleMenuPatcher.cs @@ -2,16 +2,16 @@ using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; using StardewModdingAPI.Enums; -using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Internal.Patching; using StardewValley.Menus; namespace StardewModdingAPI.Patches { - /// Harmony patches which notify SMAPI for save creation load stages. + /// Harmony patches for which notify SMAPI when a new character was created. /// 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 TitleMenuPatcher : IHarmonyPatch + internal class TitleMenuPatcher : BasePatcher { /********* ** Fields @@ -31,12 +31,11 @@ namespace StardewModdingAPI.Patches } /// - public void Apply(Harmony harmony) + public override void Apply(Harmony harmony, IMonitor monitor) { - // detect CreatedBasicInfo harmony.Patch( - original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.createdNewCharacter)), - prefix: new HarmonyMethod(this.GetType(), nameof(TitleMenuPatcher.Before_TitleMenu_CreatedNewCharacter)) + original: this.RequireMethod(nameof(TitleMenu.createdNewCharacter)), + prefix: this.GetHarmonyMethod(nameof(TitleMenuPatcher.Before_CreatedNewCharacter)) ); } @@ -44,10 +43,10 @@ namespace StardewModdingAPI.Patches /********* ** Private methods *********/ - /// Called before . + /// The method to call before . /// Returns whether to execute the original method. /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. - private static bool Before_TitleMenu_CreatedNewCharacter() + private static bool Before_CreatedNewCharacter() { TitleMenuPatcher.OnStageChanged(LoadStage.CreatedBasicInfo); return true; diff --git a/src/SMAPI/Properties/AssemblyInfo.cs b/src/SMAPI/Properties/AssemblyInfo.cs index ae758e9b..ee8a1674 100644 --- a/src/SMAPI/Properties/AssemblyInfo.cs +++ b/src/SMAPI/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SMAPI.Tests")] -[assembly: InternalsVisibleTo("ErrorHandler")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq for unit testing diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 0c6cfdd3..7d5e7ef9 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -76,4 +76,5 @@ + -- cgit From 80d5672cdb04e8cba40b085b32ffcaf1fea78552 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 31 Jul 2021 01:50:31 -0400 Subject: fix crash when farm name contains invalid-in-file-path characters (#791) --- docs/release-notes.md | 1 + src/SMAPI/Constants.cs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'src/SMAPI') diff --git a/docs/release-notes.md b/docs/release-notes.md index 44807dcb..9e26a974 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ * Added `removable` option to the `world_clear` console command (thanks to bladeoflight16!). * Fixed handling of Unicode characters in console commands. * Fixed intermittent error if a mod fetches mod-provided APIs asynchronously. + * Fixed crash when creating a farm name containing characters that aren't allowed in a folder path. * For mod authors: * Updated Harmony 1.2.0.1 to 2.1.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 9e93551c..6fb796de 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -351,9 +351,16 @@ namespace StardewModdingAPI DirectoryInfo folder = null; foreach (string saveName in new[] { rawSaveName, new string(rawSaveName.Where(char.IsLetterOrDigit).ToArray()) }) { - folder = new DirectoryInfo(Path.Combine(Constants.SavesPath, $"{saveName}_{saveID}")); - if (folder.Exists) - return folder; + try + { + folder = new DirectoryInfo(Path.Combine(Constants.SavesPath, $"{saveName}_{saveID}")); + if (folder.Exists) + return folder; + } + catch (ArgumentException) + { + // ignore invalid path + } } // if save doesn't exist yet, return the default one we expect to be created -- cgit From c15d43049a08e73090d77cc150ac48476011a68c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 31 Jul 2021 19:22:14 -0400 Subject: fix map reload not correctly reloading interior doors --- docs/release-notes.md | 1 + src/SMAPI.sln.DotSettings | 1 + src/SMAPI/Metadata/CoreAssetPropagator.cs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 8 deletions(-) (limited to 'src/SMAPI') diff --git a/docs/release-notes.md b/docs/release-notes.md index 9e26a974..4f874e38 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -15,6 +15,7 @@ * Updated Harmony 1.2.0.1 to 2.1.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). * SMAPI now intercepts `KeyNotFoundException` dictionary errors and adds the key to the error message to simplify troubleshooting. (Due to Harmony limitations, this only works for the dictionary types used by the game.) * Fixed error loading `.xnb` files from the local mod folder since SMAPI 3.0. + * Fixed reloading a map not correctly reapplying interior doors. ## 3.11.0 Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295). diff --git a/src/SMAPI.sln.DotSettings b/src/SMAPI.sln.DotSettings index 305b1c7d..9a6cad37 100644 --- a/src/SMAPI.sln.DotSettings +++ b/src/SMAPI.sln.DotSettings @@ -49,6 +49,7 @@ True True True + True True True True diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 9273864e..a8686ca4 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using Microsoft.Xna.Framework.Graphics; using Netcode; -using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Internal; @@ -939,16 +938,17 @@ namespace StardewModdingAPI.Metadata // reload map location.interiorDoors.Clear(); // prevent errors when doors try to update tiles which no longer exist location.reloadMap(); - location.updateWarps(); - location.MakeMapModifications(force: true); - // update interior doors + // reload interior doors location.interiorDoors.Clear(); - foreach (var entry in new InteriorDoorDictionary(location)) - location.interiorDoors.Add(entry); + location.interiorDoors.ResetSharedState(); // load doors from map properties + location.interiorDoors.ResetLocalState(); // reapply door tiles - // update doors - location.doors.Clear(); + // reapply map changes (after reloading doors so they apply theirs too) + location.MakeMapModifications(force: true); + + // update for changes + location.updateWarps(); location.updateDoors(); } -- cgit From d688cdf8c3c852d4b11cdd046d67c4b35443cc95 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 1 Aug 2021 13:11:27 -0400 Subject: prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 16 +++++++++------- src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.ErrorHandler/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- 6 files changed, 17 insertions(+), 15 deletions(-) (limited to 'src/SMAPI') diff --git a/build/common.targets b/build/common.targets index aed619da..d526a2bb 100644 --- a/build/common.targets +++ b/build/common.targets @@ -1,7 +1,7 @@ - 3.11.0 + 3.12.0 SMAPI latest $(AssemblySearchPaths);{GAC} diff --git a/docs/release-notes.md b/docs/release-notes.md index 4f874e38..36f07129 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,20 +1,22 @@ ← [README](README.md) # Release notes -## Upcoming release +## 3.12.0 +01 August 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/54388616). + * For players: - * Added automatic save recovery when custom content mods leave null objects in the save. + * Added save recovery when content mods leave null objects in the save (in _Error Handler_). * Added error if the wrong SMAPI bitness is installed (e.g. 32-bit SMAPI with 64-bit game). * Added error if some SMAPI files aren't updated correctly. - * Added `removable` option to the `world_clear` console command (thanks to bladeoflight16!). + * Added `removable` option to the `world_clear` console command (in _Console Commands_, thanks to bladeoflight16!). * Fixed handling of Unicode characters in console commands. - * Fixed intermittent error if a mod fetches mod-provided APIs asynchronously. + * Fixed intermittent error if a mod gets mod-provided APIs asynchronously. * Fixed crash when creating a farm name containing characters that aren't allowed in a folder path. * For mod authors: - * Updated Harmony 1.2.0.1 to 2.1.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). - * SMAPI now intercepts `KeyNotFoundException` dictionary errors and adds the key to the error message to simplify troubleshooting. (Due to Harmony limitations, this only works for the dictionary types used by the game.) - * Fixed error loading `.xnb` files from the local mod folder since SMAPI 3.0. + * **Updated Harmony 1.2.0.1 to 2.1.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).** + * SMAPI now intercepts `KeyNotFoundException` errors and adds the key to the error message to simplify troubleshooting. (Due to Harmony limitations, this only works for the dictionary types used by the game.) + * Fixed error loading `.xnb` files from the local mod folder. * Fixed reloading a map not correctly reapplying interior doors. ## 3.11.0 diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 1781c40d..09684f32 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.11.0", + "Version": "3.12.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.11.0" + "MinimumApiVersion": "3.12.0" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index 82e6152d..c83ca3d0 100644 --- a/src/SMAPI.Mods.ErrorHandler/manifest.json +++ b/src/SMAPI.Mods.ErrorHandler/manifest.json @@ -1,9 +1,9 @@ { "Name": "Error Handler", "Author": "SMAPI", - "Version": "3.11.0", + "Version": "3.12.0", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.11.0" + "MinimumApiVersion": "3.12.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 6042dee4..42f1af59 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.11.0", + "Version": "3.12.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.11.0" + "MinimumApiVersion": "3.12.0" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 6fb796de..3877e17a 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -61,7 +61,7 @@ namespace StardewModdingAPI internal static int? LogScreenId { get; set; } /// SMAPI's current raw semantic version. - internal static string RawApiVersion = "3.11.0"; + internal static string RawApiVersion = "3.12.0"; } /// Contains SMAPI's constants and assumptions. -- cgit