summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/0Harmony.dllbin115200 -> 166912 bytes
-rw-r--r--build/0Harmony.xml3130
-rw-r--r--build/common.targets6
-rw-r--r--build/prepare-install-package.targets4
-rw-r--r--docs/release-notes.md20
-rw-r--r--docs/technical/smapi.md1
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs3
-rw-r--r--src/SMAPI.Internal.Patching/BasePatcher.cs54
-rw-r--r--src/SMAPI.Internal.Patching/HarmonyPatcher.cs36
-rw-r--r--src/SMAPI.Internal.Patching/IPatcher.cs16
-rw-r--r--src/SMAPI.Internal.Patching/PatchHelper.cs77
-rw-r--r--src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.projitems17
-rw-r--r--src/SMAPI.Internal.Patching/SMAPI.Internal.Patching.shproj13
-rw-r--r--src/SMAPI.Internal/ExceptionExtensions.cs41
-rw-r--r--src/SMAPI.Internal/SMAPI.Internal.projitems1
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs10
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/ModEntry.cs40
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs184
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs75
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs75
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs (renamed from src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs)26
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs79
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs158
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs44
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs150
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs85
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs136
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs71
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs145
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs108
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs (renamed from src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs)30
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs89
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs43
-rw-r--r--src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj2
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs9
-rw-r--r--src/SMAPI.Toolkit/SMAPI.Toolkit.csproj2
-rw-r--r--src/SMAPI.sln6
-rw-r--r--src/SMAPI.sln.DotSettings2
-rw-r--r--src/SMAPI/Constants.cs15
-rw-r--r--src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs26
-rw-r--r--src/SMAPI/Framework/Content/AssetInterceptorChange.cs1
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs1
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs5
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs1
-rw-r--r--src/SMAPI/Framework/InternalExtensions.cs35
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs8
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs65
-rw-r--r--src/SMAPI/Framework/Patching/GamePatcher.cs53
-rw-r--r--src/SMAPI/Framework/Patching/IHarmonyPatch.cs23
-rw-r--r--src/SMAPI/Framework/Patching/PatchHelper.cs36
-rw-r--r--src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs33
-rw-r--r--src/SMAPI/Framework/SCore.cs19
-rw-r--r--src/SMAPI/Framework/SGame.cs1
-rw-r--r--src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs239
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs17
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs8
-rw-r--r--src/SMAPI/Patches/Game1Patcher.cs (renamed from src/SMAPI/Patches/LoadContextPatch.cs)83
-rw-r--r--src/SMAPI/Patches/TitleMenuPatcher.cs55
-rw-r--r--src/SMAPI/Program.cs32
-rw-r--r--src/SMAPI/Properties/AssemblyInfo.cs1
-rw-r--r--src/SMAPI/SMAPI.csproj4
67 files changed, 4570 insertions, 1197 deletions
diff --git a/build/0Harmony.dll b/build/0Harmony.dll
index 2e893d0e..bab3bb4d 100644
--- a/build/0Harmony.dll
+++ b/build/0Harmony.dll
Binary files 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 @@
+<?xml version="1.0"?>
+<doc>
+ <assembly>
+ <name>0Harmony</name>
+ </assembly>
+ <members>
+ <member name="T:HarmonyLib.DelegateTypeFactory">
+ <summary>A factory to create delegate types</summary>
+ </member>
+ <member name="M:HarmonyLib.DelegateTypeFactory.#ctor">
+ <summary>Default constructor</summary>
+ </member>
+ <member name="M:HarmonyLib.DelegateTypeFactory.CreateDelegateType(System.Reflection.MethodInfo)">
+ <summary>Creates a delegate type for a method</summary>
+ <param name="method">The method</param>
+ <returns>The new delegate type</returns>
+
+ </member>
+ <member name="T:HarmonyLib.GetterHandler`2">
+ <summary>A getter delegate type</summary>
+ <typeparam name="T">Type that getter gets field/property value from</typeparam>
+ <typeparam name="S">Type of the value that getter gets</typeparam>
+ <param name="source">The instance get getter uses</param>
+ <returns>An delegate</returns>
+
+ </member>
+ <member name="T:HarmonyLib.SetterHandler`2">
+ <summary>A setter delegate type</summary>
+ <typeparam name="T">Type that setter sets field/property value for</typeparam>
+ <typeparam name="S">Type of the value that setter sets</typeparam>
+ <param name="source">The instance the setter uses</param>
+ <param name="value">The value the setter uses</param>
+ <returns>An delegate</returns>
+
+ </member>
+ <member name="T:HarmonyLib.InstantiationHandler`1">
+ <summary>A constructor delegate type</summary>
+ <typeparam name="T">Type that constructor creates</typeparam>
+ <returns>An delegate</returns>
+
+ </member>
+ <member name="T:HarmonyLib.FastAccess">
+ <summary>A helper class for fast access to getters and setters</summary>
+ </member>
+ <member name="M:HarmonyLib.FastAccess.CreateInstantiationHandler``1">
+ <summary>Creates an instantiation delegate</summary>
+ <typeparam name="T">Type that constructor creates</typeparam>
+ <returns>The new instantiation delegate</returns>
+
+ </member>
+ <member name="M:HarmonyLib.FastAccess.CreateGetterHandler``2(System.Reflection.PropertyInfo)">
+ <summary>Creates an getter delegate for a property</summary>
+ <typeparam name="T">Type that getter reads property from</typeparam>
+ <typeparam name="S">Type of the property that gets accessed</typeparam>
+ <param name="propertyInfo">The property</param>
+ <returns>The new getter delegate</returns>
+
+ </member>
+ <member name="M:HarmonyLib.FastAccess.CreateGetterHandler``2(System.Reflection.FieldInfo)">
+ <summary>Creates an getter delegate for a field</summary>
+ <typeparam name="T">Type that getter reads field from</typeparam>
+ <typeparam name="S">Type of the field that gets accessed</typeparam>
+ <param name="fieldInfo">The field</param>
+ <returns>The new getter delegate</returns>
+
+ </member>
+ <member name="M:HarmonyLib.FastAccess.CreateFieldGetter``2(System.String[])">
+ <summary>Creates an getter delegate for a field (with a list of possible field names)</summary>
+ <typeparam name="T">Type that getter reads field/property from</typeparam>
+ <typeparam name="S">Type of the field/property that gets accessed</typeparam>
+ <param name="names">A list of possible field names</param>
+ <returns>The new getter delegate</returns>
+
+ </member>
+ <member name="M:HarmonyLib.FastAccess.CreateSetterHandler``2(System.Reflection.PropertyInfo)">
+ <summary>Creates an setter delegate</summary>
+ <typeparam name="T">Type that setter assigns property value to</typeparam>
+ <typeparam name="S">Type of the property that gets assigned</typeparam>
+ <param name="propertyInfo">The property</param>
+ <returns>The new setter delegate</returns>
+
+ </member>
+ <member name="M:HarmonyLib.FastAccess.CreateSetterHandler``2(System.Reflection.FieldInfo)">
+ <summary>Creates an setter delegate for a field</summary>
+ <typeparam name="T">Type that setter assigns field value to</typeparam>
+ <typeparam name="S">Type of the field that gets assigned</typeparam>
+ <param name="fieldInfo">The field</param>
+ <returns>The new getter delegate</returns>
+
+ </member>
+ <member name="T:HarmonyLib.FastInvokeHandler">
+ <summary>A delegate to invoke a method</summary>
+ <param name="target">The instance</param>
+ <param name="parameters">The method parameters</param>
+ <returns>The method result</returns>
+ </member>
+ <member name="T:HarmonyLib.MethodInvoker">
+ <summary>A helper class to invoke method with delegates</summary>
+ </member>
+ <member name="M:HarmonyLib.MethodInvoker.GetHandler(System.Reflection.MethodInfo,System.Boolean)">
+ <summary>Creates a fast invocation handler from a method</summary>
+ <param name="methodInfo">The method to invoke</param>
+ <param name="directBoxValueAccess">Controls if boxed value object is accessed/updated directly</param>
+ <returns>The <see cref="T:HarmonyLib.FastInvokeHandler"/></returns>
+ <remarks>
+ <para>
+ The <c>directBoxValueAccess</c> 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.
+ <example>For example,
+ <code>
+ var val = 5;
+ var box = (object)val;
+ var arr = new object[] { box };
+ handler(arr); // for a method with parameter signature: ref/out/in int
+ </code>
+ </example>
+ </para>
+ <para>
+ If <c>directBoxValueAccess</c> is <c>true</c>, 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 <c>box</c> and <c>arr[0]</c>
+ now reflect the value 10. Note that the original <c>val</c> is not updated, since boxing always copies the value into the new boxed value object.
+ </para>
+ <para>
+ If <c>directBoxValueAccess</c> is <c>false</c> (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 <c>arr[0]</c> now reflects the value 10.
+ </para>
+ </remarks>
+ </member>
+ <member name="T:HarmonyLib.Memory">
+ <summary>A low level memory helper</summary>
+
+ </member>
+ <member name="M:HarmonyLib.Memory.MarkForNoInlining(System.Reflection.MethodBase)">
+ <summary>Mark method for no inlining (currently only works on Mono)</summary>
+ <param name="method">The method/constructor to change</param>
+
+ </member>
+ <member name="M:HarmonyLib.Memory.DetourMethod(System.Reflection.MethodBase,System.Reflection.MethodBase)">
+ <summary>Detours a method</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="replacement">The replacement method/constructor</param>
+ <returns>An error string</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Memory.WriteJump(System.Int64,System.Int64)">
+ <summary>Writes a jump to memory</summary>
+ <param name="memory">The memory address</param>
+ <param name="destination">Jump destination</param>
+ <returns>An error string</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Memory.GetMethodStart(System.Reflection.MethodBase,System.Exception@)">
+ <summary>Gets the start of a method in memory</summary>
+ <param name="method">The method/constructor</param>
+ <param name="exception">[out] Details of the exception</param>
+ <returns>The method start address</returns>
+
+ </member>
+ <member name="F:HarmonyLib.MethodPatcher.INSTANCE_PARAM">
+ special parameter names that can be used in prefix and postfix methods
+ </member>
+ <member name="T:HarmonyLib.PatchFunctions">
+ <summary>Patch function helpers</summary>
+ </member>
+ <member name="M:HarmonyLib.PatchFunctions.GetSortedPatchMethods(System.Reflection.MethodBase,HarmonyLib.Patch[],System.Boolean)">
+ <summary>Sorts patch methods by their priority rules</summary>
+ <param name="original">The original method</param>
+ <param name="patches">Patches to sort</param>
+ <param name="debug">Use debug mode</param>
+ <returns>The sorted patch methods</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchFunctions.UpdateWrapper(System.Reflection.MethodBase,HarmonyLib.PatchInfo)">
+ <summary>Creates new replacement method with the latest patches and detours the original method</summary>
+ <param name="original">The original method</param>
+ <param name="patchInfo">Information describing the patches</param>
+ <returns>The newly created replacement method</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.#ctor(HarmonyLib.Patch[],System.Boolean)">
+ <summary>Creates a patch sorter</summary>
+ <param name="patches">Array of patches that will be sorted</param>
+ <param name="debug">Use debugging</param>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.Sort(System.Reflection.MethodBase)">
+ <summary>Sorts internal PatchSortingWrapper collection and caches the results.
+ After first run the result is provided from the cache.</summary>
+ <param name="original">The original method</param>
+ <returns>The sorted patch methods</returns>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.ComparePatchLists(HarmonyLib.Patch[])">
+ <summary>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.</summary>
+ <param name="patches">List of patches to check against</param>
+ <returns>true if equal</returns>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.CullDependency">
+ <summary>Removes one unresolved dependency from the least important patch.</summary>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.ProcessWaitingList">
+ <summary>Outputs all unblocked patches from the waiting list to results list</summary>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.AddNodeToResult(HarmonyLib.PatchSorter.PatchSortingWrapper)">
+ <summary>Adds patch to both results list and handled patches set</summary>
+ <param name="node">Patch to add</param>
+ </member>
+ <member name="T:HarmonyLib.PatchSorter.PatchSortingWrapper">
+ <summary>Wrapper used over the Patch object to allow faster dependency access and
+ dependency removal in case of cyclic dependencies</summary>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.PatchSortingWrapper.#ctor(HarmonyLib.Patch)">
+ <summary>Create patch wrapper object used for sorting</summary>
+ <param name="patch">Patch to wrap</param>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.PatchSortingWrapper.CompareTo(System.Object)">
+ <summary>Determines how patches sort</summary>
+ <param name="obj">The other patch</param>
+ <returns>integer to define sort order (-1, 0, 1)</returns>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.PatchSortingWrapper.Equals(System.Object)">
+ <summary>Determines whether patches are equal</summary>
+ <param name="obj">The other patch</param>
+ <returns>true if equal</returns>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.PatchSortingWrapper.GetHashCode">
+ <summary>Hash function</summary>
+ <returns>A hash code</returns>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.PatchSortingWrapper.AddBeforeDependency(System.Collections.Generic.IEnumerable{HarmonyLib.PatchSorter.PatchSortingWrapper})">
+ <summary>Bidirectionally registers Patches as after dependencies</summary>
+ <param name="dependencies">List of dependencies to register</param>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.PatchSortingWrapper.AddAfterDependency(System.Collections.Generic.IEnumerable{HarmonyLib.PatchSorter.PatchSortingWrapper})">
+ <summary>Bidirectionally registers Patches as before dependencies</summary>
+ <param name="dependencies">List of dependencies to register</param>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.PatchSortingWrapper.RemoveAfterDependency(HarmonyLib.PatchSorter.PatchSortingWrapper)">
+ <summary>Bidirectionally removes Patch from after dependencies</summary>
+ <param name="afterNode">Patch to remove</param>
+ </member>
+ <member name="M:HarmonyLib.PatchSorter.PatchSortingWrapper.RemoveBeforeDependency(HarmonyLib.PatchSorter.PatchSortingWrapper)">
+ <summary>Bidirectionally removes Patch from before dependencies</summary>
+ <param name="beforeNode">Patch to remove</param>
+ </member>
+ <member name="T:HarmonyLib.MethodType">
+ <summary>Specifies the type of method</summary>
+
+ </member>
+ <member name="F:HarmonyLib.MethodType.Normal">
+ <summary>This is a normal method</summary>
+ </member>
+ <member name="F:HarmonyLib.MethodType.Getter">
+ <summary>This is a getter</summary>
+ </member>
+ <member name="F:HarmonyLib.MethodType.Setter">
+ <summary>This is a setter</summary>
+ </member>
+ <member name="F:HarmonyLib.MethodType.Constructor">
+ <summary>This is a constructor</summary>
+ </member>
+ <member name="F:HarmonyLib.MethodType.StaticConstructor">
+ <summary>This is a static constructor</summary>
+ </member>
+ <member name="T:HarmonyLib.ArgumentType">
+ <summary>Specifies the type of argument</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ArgumentType.Normal">
+ <summary>This is a normal argument</summary>
+ </member>
+ <member name="F:HarmonyLib.ArgumentType.Ref">
+ <summary>This is a reference argument (ref)</summary>
+ </member>
+ <member name="F:HarmonyLib.ArgumentType.Out">
+ <summary>This is an out argument (out)</summary>
+ </member>
+ <member name="F:HarmonyLib.ArgumentType.Pointer">
+ <summary>This is a pointer argument (&amp;)</summary>
+ </member>
+ <member name="T:HarmonyLib.HarmonyPatchType">
+ <summary>Specifies the type of patch</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyPatchType.All">
+ <summary>Any patch</summary>
+ </member>
+ <member name="F:HarmonyLib.HarmonyPatchType.Prefix">
+ <summary>A prefix patch</summary>
+ </member>
+ <member name="F:HarmonyLib.HarmonyPatchType.Postfix">
+ <summary>A postfix patch</summary>
+ </member>
+ <member name="F:HarmonyLib.HarmonyPatchType.Transpiler">
+ <summary>A transpiler</summary>
+ </member>
+ <member name="F:HarmonyLib.HarmonyPatchType.Finalizer">
+ <summary>A finalizer</summary>
+ </member>
+ <member name="F:HarmonyLib.HarmonyPatchType.ReversePatch">
+ <summary>A reverse patch</summary>
+ </member>
+ <member name="T:HarmonyLib.HarmonyReversePatchType">
+ <summary>Specifies the type of reverse patch</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyReversePatchType.Original">
+ <summary>Use the unmodified original method (directly from IL)</summary>
+ </member>
+ <member name="F:HarmonyLib.HarmonyReversePatchType.Snapshot">
+ <summary>Use the original as it is right now including previous patches but excluding future ones</summary>
+ </member>
+ <member name="T:HarmonyLib.MethodDispatchType">
+ <summary>Specifies the type of method call dispatching mechanics</summary>
+
+ </member>
+ <member name="F:HarmonyLib.MethodDispatchType.VirtualCall">
+ <summary>Call the method using dynamic dispatching if method is virtual (including overriden)</summary>
+ <remarks>
+ <para>
+ 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 <see cref="F:System.Reflection.Emit.OpCodes.Callvirt"/> instruction.
+ </para>
+ <para>
+ 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 <see cref="F:HarmonyLib.MethodDispatchType.Call"/>: the exact specified method implementation is called.
+ </para>
+ <para>
+ 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 <see href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/reference-types#the-dynamic-type"><c>dynamic</c> type</see>
+ (actually a fully dynamic binding, since even the name and overload resolution happens at runtime), which <see cref="T:HarmonyLib.MethodDispatchType"/> does not support.
+ </para>
+ </remarks>
+ </member>
+ <member name="F:HarmonyLib.MethodDispatchType.Call">
+ <summary>Call the method using static dispatching, regardless of whether method is virtual (including overriden) or non-virtual (including static)</summary>
+ <remarks>
+ <para>
+ a.k.a. non-virtual dispatching, early binding, or static binding.
+ This directly corresponds with the <see cref="F:System.Reflection.Emit.OpCodes.Call"/> instruction.
+ </para>
+ <para>
+ For both virtual (including overriden) and non-virtual (including static) methods, the exact specified method implementation is called, without virtual/override mechanics.
+ </para>
+ </remarks>
+ </member>
+ <member name="T:HarmonyLib.HarmonyAttribute">
+ <summary>The base class for all Harmony annotations (not meant to be used directly)</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyAttribute.info">
+ <summary>The common information for all attributes</summary>
+ </member>
+ <member name="T:HarmonyLib.HarmonyPatch">
+ <summary>Annotation to define your Harmony patch methods</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor">
+ <summary>An empty annotation can be used together with TargetMethod(s)</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type)">
+ <summary>An annotation that specifies a class to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="argumentTypes">The argument types of the method or constructor to patch</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type,System.String)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type,System.String,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type,System.String,System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">Array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type,HarmonyLib.MethodType)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodType">The <see cref="T:HarmonyLib.MethodType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type,HarmonyLib.MethodType,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodType">The <see cref="T:HarmonyLib.MethodType"/></param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type,HarmonyLib.MethodType,System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodType">The <see cref="T:HarmonyLib.MethodType"/></param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">Array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type,System.String,HarmonyLib.MethodType)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="methodType">The <see cref="T:HarmonyLib.MethodType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.String)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.String,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.String,System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">An array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.String,HarmonyLib.MethodType)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="methodType">The <see cref="T:HarmonyLib.MethodType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(HarmonyLib.MethodType)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodType">The <see cref="T:HarmonyLib.MethodType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(HarmonyLib.MethodType,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodType">The <see cref="T:HarmonyLib.MethodType"/></param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(HarmonyLib.MethodType,System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodType">The <see cref="T:HarmonyLib.MethodType"/></param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">An array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPatch.#ctor(System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">An array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyDelegate">
+ <summary>Annotation to define the original method for delegate injection</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type)">
+ <summary>An annotation that specifies a class to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="argumentTypes">The argument types of the method or constructor to patch</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type,System.String)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type,System.String,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type,System.String,System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">Array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type,HarmonyLib.MethodDispatchType)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodDispatchType">The <see cref="T:HarmonyLib.MethodDispatchType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type,HarmonyLib.MethodDispatchType,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodDispatchType">The <see cref="T:HarmonyLib.MethodDispatchType"/></param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type,HarmonyLib.MethodDispatchType,System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodDispatchType">The <see cref="T:HarmonyLib.MethodDispatchType"/></param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">Array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type,System.String,HarmonyLib.MethodDispatchType)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="declaringType">The declaring class/type</param>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="methodDispatchType">The <see cref="T:HarmonyLib.MethodDispatchType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.String)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.String,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.String,System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">An array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.String,HarmonyLib.MethodDispatchType)">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodName">The name of the method, property or constructor to patch</param>
+ <param name="methodDispatchType">The <see cref="T:HarmonyLib.MethodDispatchType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(HarmonyLib.MethodDispatchType)">
+ <summary>An annotation that specifies call dispatching mechanics for the delegate</summary>
+ <param name="methodDispatchType">The <see cref="T:HarmonyLib.MethodDispatchType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(HarmonyLib.MethodDispatchType,System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodDispatchType">The <see cref="T:HarmonyLib.MethodDispatchType"/></param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(HarmonyLib.MethodDispatchType,System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="methodDispatchType">The <see cref="T:HarmonyLib.MethodDispatchType"/></param>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">An array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyDelegate.#ctor(System.Type[],HarmonyLib.ArgumentType[])">
+ <summary>An annotation that specifies a method, property or constructor to patch</summary>
+ <param name="argumentTypes">An array of argument types to target overloads</param>
+ <param name="argumentVariations">An array of <see cref="T:HarmonyLib.ArgumentType"/></param>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyReversePatch">
+ <summary>Annotation to define your standin methods for reverse patching</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyReversePatch.#ctor(HarmonyLib.HarmonyReversePatchType)">
+ <summary>An annotation that specifies the type of reverse patching</summary>
+ <param name="type">The <see cref="T:HarmonyLib.HarmonyReversePatchType"/> of the reverse patch</param>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyPatchAll">
+ <summary>A Harmony annotation to define that all methods in a class are to be patched</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyPriority">
+ <summary>A Harmony annotation</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyPriority.#ctor(System.Int32)">
+ <summary>A Harmony annotation to define patch priority</summary>
+ <param name="priority">The priority</param>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyBefore">
+ <summary>A Harmony annotation</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyBefore.#ctor(System.String[])">
+ <summary>A Harmony annotation to define that a patch comes before another patch</summary>
+ <param name="before">The array of harmony IDs of the other patches</param>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyAfter">
+ <summary>A Harmony annotation</summary>
+ </member>
+ <member name="M:HarmonyLib.HarmonyAfter.#ctor(System.String[])">
+ <summary>A Harmony annotation to define that a patch comes after another patch</summary>
+ <param name="after">The array of harmony IDs of the other patches</param>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyDebug">
+ <summary>A Harmony annotation</summary>
+ </member>
+ <member name="M:HarmonyLib.HarmonyDebug.#ctor">
+ <summary>A Harmony annotation to debug a patch (output uses <see cref="T:HarmonyLib.FileLog"/> to log to your Desktop)</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyPrepare">
+ <summary>Specifies the Prepare function in a patch class</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyCleanup">
+ <summary>Specifies the Cleanup function in a patch class</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyTargetMethod">
+ <summary>Specifies the TargetMethod function in a patch class</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyTargetMethods">
+ <summary>Specifies the TargetMethods function in a patch class</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyPrefix">
+ <summary>Specifies the Prefix function in a patch class</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyPostfix">
+ <summary>Specifies the Postfix function in a patch class</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyTranspiler">
+ <summary>Specifies the Transpiler function in a patch class</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyFinalizer">
+ <summary>Specifies the Finalizer function in a patch class</summary>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyArgument">
+ <summary>A Harmony annotation</summary>
+
+ </member>
+ <member name="P:HarmonyLib.HarmonyArgument.OriginalName">
+ <summary>The name of the original argument</summary>
+
+ </member>
+ <member name="P:HarmonyLib.HarmonyArgument.Index">
+ <summary>The index of the original argument</summary>
+
+ </member>
+ <member name="P:HarmonyLib.HarmonyArgument.NewName">
+ <summary>The new name of the original argument</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyArgument.#ctor(System.String)">
+ <summary>An annotation to declare injected arguments by name</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyArgument.#ctor(System.Int32)">
+ <summary>An annotation to declare injected arguments by index</summary>
+ <param name="index">Zero-based index</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyArgument.#ctor(System.String,System.String)">
+ <summary>An annotation to declare injected arguments by renaming them</summary>
+ <param name="originalName">Name of the original argument</param>
+ <param name="newName">New name</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyArgument.#ctor(System.Int32,System.String)">
+ <summary>An annotation to declare injected arguments by index and renaming them</summary>
+ <param name="index">Zero-based index</param>
+ <param name="name">New name</param>
+
+ </member>
+ <member name="T:HarmonyLib.CodeInstruction">
+ <summary>An abstract wrapper around OpCode and their operands. Used by transpilers</summary>
+
+ </member>
+ <member name="F:HarmonyLib.CodeInstruction.opcode">
+ <summary>The opcode</summary>
+
+ </member>
+ <member name="F:HarmonyLib.CodeInstruction.operand">
+ <summary>The operand</summary>
+
+ </member>
+ <member name="F:HarmonyLib.CodeInstruction.labels">
+ <summary>All labels defined on this instruction</summary>
+
+ </member>
+ <member name="F:HarmonyLib.CodeInstruction.blocks">
+ <summary>All exception block boundaries defined on this instruction</summary>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.#ctor(System.Reflection.Emit.OpCode,System.Object)">
+ <summary>Creates a new CodeInstruction with a given opcode and optional operand</summary>
+ <param name="opcode">The opcode</param>
+ <param name="operand">The operand</param>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.#ctor(HarmonyLib.CodeInstruction)">
+ <summary>Create a full copy (including labels and exception blocks) of a CodeInstruction</summary>
+ <param name="instruction">The <see cref="T:HarmonyLib.CodeInstruction"/> to copy</param>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Clone">
+ <summary>Clones a CodeInstruction and resets its labels and exception blocks</summary>
+ <returns>A lightweight copy of this code instruction</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Clone(System.Reflection.Emit.OpCode)">
+ <summary>Clones a CodeInstruction, resets labels and exception blocks and sets its opcode</summary>
+ <param name="opcode">The opcode</param>
+ <returns>A copy of this CodeInstruction with a new opcode</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Clone(System.Object)">
+ <summary>Clones a CodeInstruction, resets labels and exception blocks and sets its operand</summary>
+ <param name="operand">The operand</param>
+ <returns>A copy of this CodeInstruction with a new operand</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Call(System.Type,System.String,System.Type[],System.Type[])">
+ <summary>Creates a CodeInstruction calling a method (CALL)</summary>
+ <param name="type">The class/type where the method is declared</param>
+ <param name="name">The name of the method (case sensitive)</param>
+ <param name="parameters">Optional parameters to target a specific overload of the method</param>
+ <param name="generics">Optional list of types that define the generic version of the method</param>
+ <returns>A code instruction that calls the method matching the arguments</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Call(System.String,System.Type[],System.Type[])">
+ <summary>Creates a CodeInstruction calling a method (CALL)</summary>
+ <param name="typeColonMethodname">The target method in the form <c>TypeFullName:MethodName</c>, where the type name matches a form recognized by <a href="https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype">Type.GetType</a> like <c>Some.Namespace.Type</c>.</param>
+ <param name="parameters">Optional parameters to target a specific overload of the method</param>
+ <param name="generics">Optional list of types that define the generic version of the method</param>
+ <returns>A code instruction that calls the method matching the arguments</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Call(System.Linq.Expressions.Expression{System.Action})">
+ <summary>Creates a CodeInstruction calling a method (CALL)</summary>
+ <param name="expression">The lambda expression using the method</param>
+ <returns></returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Call``1(System.Linq.Expressions.Expression{System.Action{``0}})">
+ <summary>Creates a CodeInstruction calling a method (CALL)</summary>
+ <param name="expression">The lambda expression using the method</param>
+ <returns></returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Call``2(System.Linq.Expressions.Expression{System.Func{``0,``1}})">
+ <summary>Creates a CodeInstruction calling a method (CALL)</summary>
+ <param name="expression">The lambda expression using the method</param>
+ <returns></returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.Call(System.Linq.Expressions.LambdaExpression)">
+ <summary>Creates a CodeInstruction calling a method (CALL)</summary>
+ <param name="expression">The lambda expression using the method</param>
+ <returns></returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.LoadField(System.Type,System.String,System.Boolean)">
+ <summary>Creates a CodeInstruction loading a field (LD[S]FLD[A])</summary>
+ <param name="type">The class/type where the field is defined</param>
+ <param name="name">The name of the field (case sensitive)</param>
+ <param name="useAddress">Use address of field</param>
+ <returns></returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.StoreField(System.Type,System.String)">
+ <summary>Creates a CodeInstruction storing to a field (ST[S]FLD)</summary>
+ <param name="type">The class/type where the field is defined</param>
+ <param name="name">The name of the field (case sensitive)</param>
+ <returns></returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstruction.ToString">
+ <summary>Returns a string representation of the code instruction</summary>
+ <returns>A string representation of the code instruction</returns>
+
+ </member>
+ <member name="T:HarmonyLib.ExceptionBlockType">
+ <summary>Exception block types</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ExceptionBlockType.BeginExceptionBlock">
+ <summary>The beginning of an exception block</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ExceptionBlockType.BeginCatchBlock">
+ <summary>The beginning of a catch block</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ExceptionBlockType.BeginExceptFilterBlock">
+ <summary>The beginning of an except filter block</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ExceptionBlockType.BeginFaultBlock">
+ <summary>The beginning of a fault block</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ExceptionBlockType.BeginFinallyBlock">
+ <summary>The beginning of a finally block</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ExceptionBlockType.EndExceptionBlock">
+ <summary>The end of an exception block</summary>
+
+ </member>
+ <member name="T:HarmonyLib.ExceptionBlock">
+ <summary>An exception block</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ExceptionBlock.blockType">
+ <summary>Block type</summary>
+
+ </member>
+ <member name="F:HarmonyLib.ExceptionBlock.catchType">
+ <summary>Catch type</summary>
+
+ </member>
+ <member name="M:HarmonyLib.ExceptionBlock.#ctor(HarmonyLib.ExceptionBlockType,System.Type)">
+ <summary>Creates an exception block</summary>
+ <param name="blockType">The <see cref="T:HarmonyLib.ExceptionBlockType"/></param>
+ <param name="catchType">The catch type</param>
+
+ </member>
+ <member name="T:HarmonyLib.Harmony">
+ <summary>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</summary>
+
+ </member>
+ <member name="P:HarmonyLib.Harmony.Id">
+ <summary>The unique identifier</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Harmony.DEBUG">
+ <summary>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 &amp;&amp; game.exe"</summary>
+ <remarks>This is for full debugging. To debug only specific patches, use the <see cref="T:HarmonyLib.HarmonyDebug"/> attribute</remarks>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.#ctor(System.String)">
+ <summary>Creates a new Harmony instance</summary>
+ <param name="id">A unique identifier (you choose your own)</param>
+ <returns>A Harmony instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.PatchAll">
+ <summary>Searches the current assembly for Harmony annotations and uses them to create patches</summary>
+ <remarks>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 <code>PatchAll(Assembly.GetExecutingAssembly())</code> instead.</remarks>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.CreateProcessor(System.Reflection.MethodBase)">
+ <summary>Creates a empty patch processor for an original method</summary>
+ <param name="original">The original method/constructor</param>
+ <returns>A new <see cref="T:HarmonyLib.PatchProcessor"/> instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.CreateClassProcessor(System.Type)">
+ <summary>Creates a patch class processor from an annotated class</summary>
+ <param name="type">The class/type</param>
+ <returns>A new <see cref="T:HarmonyLib.PatchClassProcessor"/> instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.CreateReversePatcher(System.Reflection.MethodBase,HarmonyLib.HarmonyMethod)">
+ <summary>Creates a reverse patcher for one of your stub methods</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="standin">The stand-in stub method as <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+ <returns>A new <see cref="T:HarmonyLib.ReversePatcher"/> instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.PatchAll(System.Reflection.Assembly)">
+ <summary>Searches an assembly for Harmony annotations and uses them to create patches</summary>
+ <param name="assembly">The assembly</param>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.Patch(System.Reflection.MethodBase,HarmonyLib.HarmonyMethod,HarmonyLib.HarmonyMethod,HarmonyLib.HarmonyMethod,HarmonyLib.HarmonyMethod)">
+ <summary>Creates patches by manually specifying the methods</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="prefix">An optional prefix method wrapped in a <see cref="T:HarmonyLib.HarmonyMethod"/> object</param>
+ <param name="postfix">An optional postfix method wrapped in a <see cref="T:HarmonyLib.HarmonyMethod"/> object</param>
+ <param name="transpiler">An optional transpiler method wrapped in a <see cref="T:HarmonyLib.HarmonyMethod"/> object</param>
+ <param name="finalizer">An optional finalizer method wrapped in a <see cref="T:HarmonyLib.HarmonyMethod"/> object</param>
+ <returns>The replacement method that was created to patch the original method</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.ReversePatch(System.Reflection.MethodBase,HarmonyLib.HarmonyMethod,System.Reflection.MethodInfo)">
+ <summary>Patches a foreign method onto a stub method of yours and optionally applies transpilers during the process</summary>
+ <param name="original">The original method/constructor you want to duplicate</param>
+ <param name="standin">Your stub method as <see cref="T:HarmonyLib.HarmonyMethod"/> that will become the original. Needs to have the correct signature (either original or whatever your transpilers generates)</param>
+ <param name="transpiler">An optional transpiler as method that will be applied during the process</param>
+ <returns>The replacement method that was created to patch the stub method</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.UnpatchAll(System.String)">
+ <summary>Unpatches methods by patching them with zero patches. Fully unpatching is not supported. Be careful, unpatching is global</summary>
+ <param name="harmonyID">The optional Harmony ID to restrict unpatching to a specific Harmony instance</param>
+ <remarks>This method could be static if it wasn't for the fact that unpatching creates a new replacement method that contains your harmony ID</remarks>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.Unpatch(System.Reflection.MethodBase,HarmonyLib.HarmonyPatchType,System.String)">
+ <summary>Unpatches a method by patching it with zero patches. Fully unpatching is not supported. Be careful, unpatching is global</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="type">The <see cref="T:HarmonyLib.HarmonyPatchType"/></param>
+ <param name="harmonyID">The optional Harmony ID to restrict unpatching to a specific Harmony instance</param>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.Unpatch(System.Reflection.MethodBase,System.Reflection.MethodInfo)">
+ <summary>Unpatches a method by patching it with zero patches. Fully unpatching is not supported. Be careful, unpatching is global</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="patch">The patch method as method to remove</param>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.HasAnyPatches(System.String)">
+ <summary>Test for patches from a specific Harmony ID</summary>
+ <param name="harmonyID">The Harmony ID</param>
+ <returns>True if patches for this ID exist</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.GetPatchInfo(System.Reflection.MethodBase)">
+ <summary>Gets patch information for a given original method</summary>
+ <param name="method">The original method/constructor</param>
+ <returns>The patch information as <see cref="T:HarmonyLib.Patches"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.GetPatchedMethods">
+ <summary>Gets the methods this instance has patched</summary>
+ <returns>An enumeration of original methods/constructors</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.GetAllPatchedMethods">
+ <summary>Gets all patched original methods in the appdomain</summary>
+ <returns>An enumeration of patched original methods/constructors</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.GetOriginalMethod(System.Reflection.MethodInfo)">
+ <summary>Gets the original method from a given replacement method</summary>
+ <param name="replacement">A replacement method, for example from a stacktrace</param>
+ <returns>The original method/constructor or <c>null</c> if not found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.GetMethodFromStackframe(System.Diagnostics.StackFrame)">
+ <summary>Tries to get the method from a stackframe including dynamic replacement methods</summary>
+ <param name="frame">The <see cref="T:System.Diagnostics.StackFrame"/></param>
+ <returns>For normal frames, <c>frame.GetMethod()</c> is returned. For frames containing patched methods, the replacement method is returned or <c>null</c> if no method can be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Harmony.VersionInfo(System.Version@)">
+ <summary>Gets Harmony version for all active Harmony instances</summary>
+ <param name="currentVersion">[out] The current Harmony version</param>
+ <returns>A dictionary containing assembly versions keyed by Harmony IDs</returns>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyException">
+ <summary>Under Mono, HarmonyException wraps IL compile errors with detailed information about the failure</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)">
+ <summary>Default serialization constructor (not implemented)</summary>
+ <param name="serializationInfo">The info</param>
+ <param name="streamingContext">The context</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyException.GetInstructionsWithOffsets">
+ <summary>Get a list of IL instructions in pairs of offset+code</summary>
+ <returns>A list of key/value pairs which represent an offset and the code at that offset</returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyException.GetInstructions">
+ <summary>Get a list of IL instructions without offsets</summary>
+ <returns>A list of <see cref="T:HarmonyLib.CodeInstruction"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyException.GetErrorOffset">
+ <summary>Get the error offset of the errornous IL instruction</summary>
+ <returns>The offset</returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyException.GetErrorIndex">
+ <summary>Get the index of the errornous IL instruction</summary>
+ <returns>The index into the list of instructions or -1 if not found</returns>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyMethod">
+ <summary>A wrapper around a method to use it as a patch (for example a Prefix)</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.method">
+ <summary>The original method</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.declaringType">
+ <summary>Class/type declaring this patch</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.methodName">
+ <summary>Patch method name</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.methodType">
+ <summary>Optional patch <see cref="T:HarmonyLib.MethodType"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.argumentTypes">
+ <summary>Array of argument types of the patch method</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.priority">
+ <summary><see cref="T:HarmonyLib.Priority"/> of the patch</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.before">
+ <summary>Install this patch before patches with these Harmony IDs</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.after">
+ <summary>Install this patch after patches with these Harmony IDs</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.reversePatchType">
+ <summary>Reverse patch type, see <see cref="T:HarmonyLib.HarmonyReversePatchType"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.debug">
+ <summary>Create debug output for this patch</summary>
+
+ </member>
+ <member name="F:HarmonyLib.HarmonyMethod.nonVirtualDelegate">
+ <summary>Whether to use <see cref="F:HarmonyLib.MethodDispatchType.Call"/> (<c>true</c>) or <see cref="F:HarmonyLib.MethodDispatchType.VirtualCall"/> (<c>false</c>) mechanics
+ for <see cref="T:HarmonyLib.HarmonyDelegate"/>-attributed delegate</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethod.#ctor">
+ <summary>Default constructor</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethod.#ctor(System.Reflection.MethodInfo)">
+ <summary>Creates a patch from a given method</summary>
+ <param name="method">The original method</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethod.#ctor(System.Reflection.MethodInfo,System.Int32,System.String[],System.String[],System.Nullable{System.Boolean})">
+ <summary>Creates a patch from a given method</summary>
+ <param name="method">The original method</param>
+ <param name="priority">The patch <see cref="T:HarmonyLib.Priority"/></param>
+ <param name="before">A list of harmony IDs that should come after this patch</param>
+ <param name="after">A list of harmony IDs that should come before this patch</param>
+ <param name="debug">Set to true to generate debug output</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethod.#ctor(System.Type,System.String,System.Type[])">
+ <summary>Creates a patch from a given method</summary>
+ <param name="methodType">The patch class/type</param>
+ <param name="methodName">The patch method name</param>
+ <param name="argumentTypes">The optional argument types of the patch method (for overloaded methods)</param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethod.HarmonyFields">
+ <summary>Gets the names of all internal patch info fields</summary>
+ <returns>A list of field names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethod.Merge(System.Collections.Generic.List{HarmonyLib.HarmonyMethod})">
+ <summary>Merges annotations</summary>
+ <param name="attributes">The list of <see cref="T:HarmonyLib.HarmonyMethod"/> to merge</param>
+ <returns>The merged <see cref="T:HarmonyLib.HarmonyMethod"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethod.ToString">
+ <summary>Returns a string that represents the annotation</summary>
+ <returns>A string representation</returns>
+
+ </member>
+ <member name="T:HarmonyLib.HarmonyMethodExtensions">
+ <summary>Annotation extensions</summary>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethodExtensions.CopyTo(HarmonyLib.HarmonyMethod,HarmonyLib.HarmonyMethod)">
+ <summary>Copies annotation information</summary>
+ <param name="from">The source <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+ <param name="to">The destination <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethodExtensions.Clone(HarmonyLib.HarmonyMethod)">
+ <summary>Clones an annotation</summary>
+ <param name="original">The <see cref="T:HarmonyLib.HarmonyMethod"/> to clone</param>
+ <returns>A copied <see cref="T:HarmonyLib.HarmonyMethod"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethodExtensions.Merge(HarmonyLib.HarmonyMethod,HarmonyLib.HarmonyMethod)">
+ <summary>Merges annotations</summary>
+ <param name="master">The master <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+ <param name="detail">The detail <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+ <returns>A new, merged <see cref="T:HarmonyLib.HarmonyMethod"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethodExtensions.GetFromType(System.Type)">
+ <summary>Gets all annotations on a class/type</summary>
+ <param name="type">The class/type</param>
+ <returns>A list of all <see cref="T:HarmonyLib.HarmonyMethod"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethodExtensions.GetMergedFromType(System.Type)">
+ <summary>Gets merged annotations on a class/type</summary>
+ <param name="type">The class/type</param>
+ <returns>The merged <see cref="T:HarmonyLib.HarmonyMethod"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethodExtensions.GetFromMethod(System.Reflection.MethodBase)">
+ <summary>Gets all annotations on a method</summary>
+ <param name="method">The method/constructor</param>
+ <returns>A list of <see cref="T:HarmonyLib.HarmonyMethod"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.HarmonyMethodExtensions.GetMergedFromMethod(System.Reflection.MethodBase)">
+ <summary>Gets merged annotations on a method</summary>
+ <param name="method">The method/constructor</param>
+ <returns>The merged <see cref="T:HarmonyLib.HarmonyMethod"/></returns>
+
+ </member>
+ <member name="T:HarmonyLib.InlineSignature">
+ <summary>
+ A mutable representation of an inline signature, similar to Mono.Cecil's CallSite.
+ Used by the calli instruction, can be used by transpilers
+ </summary>
+
+ </member>
+ <member name="P:HarmonyLib.InlineSignature.HasThis">
+ <summary>See <see cref="F:System.Reflection.CallingConventions.HasThis"/></summary>
+
+ </member>
+ <member name="P:HarmonyLib.InlineSignature.ExplicitThis">
+ <summary>See <see cref="F:System.Reflection.CallingConventions.ExplicitThis"/></summary>
+
+ </member>
+ <member name="P:HarmonyLib.InlineSignature.CallingConvention">
+ <summary>See <see cref="T:System.Runtime.InteropServices.CallingConvention"/></summary>
+
+ </member>
+ <member name="P:HarmonyLib.InlineSignature.Parameters">
+ <summary>The list of all parameter types or function pointer signatures received by the call site</summary>
+
+ </member>
+ <member name="P:HarmonyLib.InlineSignature.ReturnType">
+ <summary>The return type or function pointer signature returned by the call site</summary>
+
+ </member>
+ <member name="M:HarmonyLib.InlineSignature.ToString">
+ <summary>Returns a string representation of the inline signature</summary>
+ <returns>A string representation of the inline signature</returns>
+
+ </member>
+ <member name="T:HarmonyLib.InlineSignature.ModifierType">
+ <summary>
+ A mutable representation of a parameter type with an attached type modifier,
+ similar to Mono.Cecil's OptionalModifierType / RequiredModifierType and C#'s modopt / modreq
+ </summary>
+
+ </member>
+ <member name="F:HarmonyLib.InlineSignature.ModifierType.IsOptional">
+ <summary>Whether this is a modopt (optional modifier type) or a modreq (required modifier type)</summary>
+
+ </member>
+ <member name="F:HarmonyLib.InlineSignature.ModifierType.Modifier">
+ <summary>The modifier type attached to the parameter type</summary>
+
+ </member>
+ <member name="F:HarmonyLib.InlineSignature.ModifierType.Type">
+ <summary>The modified parameter type</summary>
+
+ </member>
+ <member name="M:HarmonyLib.InlineSignature.ModifierType.ToString">
+ <summary>Returns a string representation of the modifier type</summary>
+ <returns>A string representation of the modifier type</returns>
+
+ </member>
+ <member name="T:HarmonyLib.PatchInfoSerialization">
+ <summary>Patch serialization</summary>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfoSerialization.Binder.BindToType(System.String,System.String)">
+ <summary>Control the binding of a serialized object to a type</summary>
+ <param name="assemblyName">Specifies the assembly name of the serialized object</param>
+ <param name="typeName">Specifies the type name of the serialized object</param>
+ <returns>The type of the object the formatter creates a new instance of</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfoSerialization.Serialize(HarmonyLib.PatchInfo)">
+ <summary>Serializes a patch info</summary>
+ <param name="patchInfo">The <see cref="T:HarmonyLib.PatchInfo"/></param>
+ <returns>The serialized data</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfoSerialization.Deserialize(System.Byte[])">
+ <summary>Deserialize a patch info</summary>
+ <param name="bytes">The serialized data</param>
+ <returns>A <see cref="T:HarmonyLib.PatchInfo"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfoSerialization.PriorityComparer(System.Object,System.Int32,System.Int32)">
+ <summary>Compare function to sort patch priorities</summary>
+ <param name="obj">The patch</param>
+ <param name="index">Zero-based index</param>
+ <param name="priority">The priority</param>
+ <returns>A standard sort integer (-1, 0, 1)</returns>
+
+ </member>
+ <member name="T:HarmonyLib.PatchInfo">
+ <summary>Serializable patch information</summary>
+
+ </member>
+ <member name="F:HarmonyLib.PatchInfo.prefixes">
+ <summary>Prefixes as an array of <see cref="T:HarmonyLib.Patch"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.PatchInfo.postfixes">
+ <summary>Postfixes as an array of <see cref="T:HarmonyLib.Patch"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.PatchInfo.transpilers">
+ <summary>Transpilers as an array of <see cref="T:HarmonyLib.Patch"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.PatchInfo.finalizers">
+ <summary>Finalizers as an array of <see cref="T:HarmonyLib.Patch"/></summary>
+
+ </member>
+ <member name="P:HarmonyLib.PatchInfo.Debugging">
+ <summary>Returns if any of the patches wants debugging turned on</summary>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.AddPrefixes(System.String,HarmonyLib.HarmonyMethod[])">
+ <summary>Adds prefixes</summary>
+ <param name="owner">An owner (Harmony ID)</param>
+ <param name="methods">The patch methods</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.AddPrefix(System.Reflection.MethodInfo,System.String,System.Int32,System.String[],System.String[],System.Boolean)">
+ <summary>Adds a prefix</summary>
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.RemovePrefix(System.String)">
+ <summary>Removes prefixes</summary>
+ <param name="owner">The owner of the prefixes, or <c>*</c> for all</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.AddPostfixes(System.String,HarmonyLib.HarmonyMethod[])">
+ <summary>Adds postfixes</summary>
+ <param name="owner">An owner (Harmony ID)</param>
+ <param name="methods">The patch methods</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.AddPostfix(System.Reflection.MethodInfo,System.String,System.Int32,System.String[],System.String[],System.Boolean)">
+ <summary>Adds a postfix</summary>
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.RemovePostfix(System.String)">
+ <summary>Removes postfixes</summary>
+ <param name="owner">The owner of the postfixes, or <c>*</c> for all</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.AddTranspilers(System.String,HarmonyLib.HarmonyMethod[])">
+ <summary>Adds transpilers</summary>
+ <param name="owner">An owner (Harmony ID)</param>
+ <param name="methods">The patch methods</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.AddTranspiler(System.Reflection.MethodInfo,System.String,System.Int32,System.String[],System.String[],System.Boolean)">
+ <summary>Adds a transpiler</summary>
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.RemoveTranspiler(System.String)">
+ <summary>Removes transpilers</summary>
+ <param name="owner">The owner of the transpilers, or <c>*</c> for all</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.AddFinalizers(System.String,HarmonyLib.HarmonyMethod[])">
+ <summary>Adds finalizers</summary>
+ <param name="owner">An owner (Harmony ID)</param>
+ <param name="methods">The patch methods</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.AddFinalizer(System.Reflection.MethodInfo,System.String,System.Int32,System.String[],System.String[],System.Boolean)">
+ <summary>Adds a finalizer</summary>
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.RemoveFinalizer(System.String)">
+ <summary>Removes finalizers</summary>
+ <param name="owner">The owner of the finalizers, or <c>*</c> for all</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.RemovePatch(System.Reflection.MethodInfo)">
+ <summary>Removes a patch using its method</summary>
+ <param name="patch">The method of the patch to remove</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.Add(System.String,HarmonyLib.HarmonyMethod[],HarmonyLib.Patch[])">
+ <summary>Gets a concatenated list of patches</summary>
+ <param name="owner">The Harmony instance ID adding the new patches</param>
+ <param name="add">The patches to add</param>
+ <param name="current">The current patches</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchInfo.Remove(System.String,HarmonyLib.Patch[])">
+ <summary>Gets a list of patches with any from the given owner removed</summary>
+ <param name="owner">The owner of the methods, or <c>*</c> for all</param>
+ <param name="current">The current patches</param>
+
+ </member>
+ <member name="T:HarmonyLib.Patch">
+ <summary>A serializable patch</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patch.index">
+ <summary>Zero-based index</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patch.owner">
+ <summary>The owner (Harmony ID)</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patch.priority">
+ <summary>The priority, see <see cref="T:HarmonyLib.Priority"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patch.before">
+ <summary>Keep this patch before the patches indicated in the list of Harmony IDs</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patch.after">
+ <summary>Keep this patch after the patches indicated in the list of Harmony IDs</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patch.debug">
+ <summary>A flag that will log the replacement method via <see cref="T:HarmonyLib.FileLog"/> every time this patch is used to build the replacement, even in the future</summary>
+
+ </member>
+ <member name="P:HarmonyLib.Patch.PatchMethod">
+ <summary>The method of the static patch method</summary>
+
+ </member>
+ <member name="M:HarmonyLib.Patch.#ctor(System.Reflection.MethodInfo,System.Int32,System.String,System.Int32,System.String[],System.String[],System.Boolean)">
+ <summary>Creates a patch</summary>
+ <param name="patch">The method of the patch</param>
+ <param name="index">Zero-based index</param>
+ <param name="owner">An owner (Harmony ID)</param>
+ <param name="priority">The priority, see <see cref="T:HarmonyLib.Priority"/></param>
+ <param name="before">A list of Harmony IDs for patches that should run after this patch</param>
+ <param name="after">A list of Harmony IDs for patches that should run before this patch</param>
+ <param name="debug">A flag that will log the replacement method via <see cref="T:HarmonyLib.FileLog"/> every time this patch is used to build the replacement, even in the future</param>
+
+ </member>
+ <member name="M:HarmonyLib.Patch.#ctor(HarmonyLib.HarmonyMethod,System.Int32,System.String)">
+ <summary>Creates a patch</summary>
+ <param name="method">The method of the patch</param>
+ <param name="index">Zero-based index</param>
+ <param name="owner">An owner (Harmony ID)</param>
+ </member>
+ <member name="M:HarmonyLib.Patch.GetMethod(System.Reflection.MethodBase)">
+ <summary>Get the patch method or a DynamicMethod if original patch method is a patch factory</summary>
+ <param name="original">The original method/constructor</param>
+ <returns>The method of the patch</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Patch.Equals(System.Object)">
+ <summary>Determines whether patches are equal</summary>
+ <param name="obj">The other patch</param>
+ <returns>true if equal</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Patch.CompareTo(System.Object)">
+ <summary>Determines how patches sort</summary>
+ <param name="obj">The other patch</param>
+ <returns>integer to define sort order (-1, 0, 1)</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Patch.GetHashCode">
+ <summary>Hash function</summary>
+ <returns>A hash code</returns>
+
+ </member>
+ <member name="T:HarmonyLib.PatchClassProcessor">
+ <summary>A PatchClassProcessor used to turn <see cref="T:HarmonyLib.HarmonyAttribute"/> on a class/type into patches</summary>
+
+ </member>
+ <member name="M:HarmonyLib.PatchClassProcessor.#ctor(HarmonyLib.Harmony,System.Type)">
+ <summary>Creates a patch class processor by pointing out a class. Similar to PatchAll() but without searching through all classes.</summary>
+ <param name="instance">The Harmony instance</param>
+ <param name="type">The class to process (need to have at least a [HarmonyPatch] attribute)</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchClassProcessor.Patch">
+ <summary>Applies the patches</summary>
+ <returns>A list of all created replacement methods or null if patch class is not annotated</returns>
+
+ </member>
+ <member name="T:HarmonyLib.Patches">
+ <summary>A group of patches</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patches.Prefixes">
+ <summary>A collection of prefix <see cref="T:HarmonyLib.Patch"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patches.Postfixes">
+ <summary>A collection of postfix <see cref="T:HarmonyLib.Patch"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patches.Transpilers">
+ <summary>A collection of transpiler <see cref="T:HarmonyLib.Patch"/></summary>
+
+ </member>
+ <member name="F:HarmonyLib.Patches.Finalizers">
+ <summary>A collection of finalizer <see cref="T:HarmonyLib.Patch"/></summary>
+
+ </member>
+ <member name="P:HarmonyLib.Patches.Owners">
+ <summary>Gets all owners (Harmony IDs) or all known patches</summary>
+ <value>The patch owners</value>
+
+ </member>
+ <member name="M:HarmonyLib.Patches.#ctor(HarmonyLib.Patch[],HarmonyLib.Patch[],HarmonyLib.Patch[],HarmonyLib.Patch[])">
+ <summary>Creates a group of patches</summary>
+ <param name="prefixes">An array of prefixes as <see cref="T:HarmonyLib.Patch"/></param>
+ <param name="postfixes">An array of postfixes as <see cref="T:HarmonyLib.Patch"/></param>
+ <param name="transpilers">An array of transpileres as <see cref="T:HarmonyLib.Patch"/></param>
+ <param name="finalizers">An array of finalizeres as <see cref="T:HarmonyLib.Patch"/></param>
+
+ </member>
+ <member name="T:HarmonyLib.PatchProcessor">
+ <summary>A PatchProcessor handles patches on a method/constructor</summary>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.#ctor(HarmonyLib.Harmony,System.Reflection.MethodBase)">
+ <summary>Creates an empty patch processor</summary>
+ <param name="instance">The Harmony instance</param>
+ <param name="original">The original method/constructor</param>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.AddPrefix(HarmonyLib.HarmonyMethod)">
+ <summary>Adds a prefix</summary>
+ <param name="prefix">The prefix as a <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.AddPrefix(System.Reflection.MethodInfo)">
+ <summary>Adds a prefix</summary>
+ <param name="fixMethod">The prefix method</param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.AddPostfix(HarmonyLib.HarmonyMethod)">
+ <summary>Adds a postfix</summary>
+ <param name="postfix">The postfix as a <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.AddPostfix(System.Reflection.MethodInfo)">
+ <summary>Adds a postfix</summary>
+ <param name="fixMethod">The postfix method</param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.AddTranspiler(HarmonyLib.HarmonyMethod)">
+ <summary>Adds a transpiler</summary>
+ <param name="transpiler">The transpiler as a <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.AddTranspiler(System.Reflection.MethodInfo)">
+ <summary>Adds a transpiler</summary>
+ <param name="fixMethod">The transpiler method</param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.AddFinalizer(HarmonyLib.HarmonyMethod)">
+ <summary>Adds a finalizer</summary>
+ <param name="finalizer">The finalizer as a <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.AddFinalizer(System.Reflection.MethodInfo)">
+ <summary>Adds a finalizer</summary>
+ <param name="fixMethod">The finalizer method</param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.GetAllPatchedMethods">
+ <summary>Gets all patched original methods in the appdomain</summary>
+ <returns>An enumeration of patched method/constructor</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.Patch">
+ <summary>Applies all registered patches</summary>
+ <returns>The generated replacement method</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.Unpatch(HarmonyLib.HarmonyPatchType,System.String)">
+ <summary>Unpatches patches of a given type and/or Harmony ID</summary>
+ <param name="type">The <see cref="T:HarmonyLib.HarmonyPatchType"/> patch type</param>
+ <param name="harmonyID">Harmony ID or <c>*</c> for any</param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.Unpatch(System.Reflection.MethodInfo)">
+ <summary>Unpatches a specific patch</summary>
+ <param name="patch">The method of the patch</param>
+ <returns>A <see cref="T:HarmonyLib.PatchProcessor"/> for chaining calls</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.GetPatchInfo(System.Reflection.MethodBase)">
+ <summary>Gets patch information on an original</summary>
+ <param name="method">The original method/constructor</param>
+ <returns>The patch information as <see cref="T:HarmonyLib.Patches"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.GetSortedPatchMethods(System.Reflection.MethodBase,HarmonyLib.Patch[])">
+ <summary>Sort patch methods by their priority rules</summary>
+ <param name="original">The original method</param>
+ <param name="patches">Patches to sort</param>
+ <returns>The sorted patch methods</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.VersionInfo(System.Version@)">
+ <summary>Gets Harmony version for all active Harmony instances</summary>
+ <param name="currentVersion">[out] The current Harmony version</param>
+ <returns>A dictionary containing assembly version keyed by Harmony ID</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.CreateILGenerator">
+ <summary>Creates a new empty <see cref="T:System.Reflection.Emit.ILGenerator">generator</see> to use when reading method bodies</summary>
+ <returns>A new <see cref="T:System.Reflection.Emit.ILGenerator"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.CreateILGenerator(System.Reflection.MethodBase)">
+ <summary>Creates a new <see cref="T:System.Reflection.Emit.ILGenerator">generator</see> matching the method/constructor to use when reading method bodies</summary>
+ <param name="original">The original method/constructor to copy method information from</param>
+ <returns>A new <see cref="T:System.Reflection.Emit.ILGenerator"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.GetOriginalInstructions(System.Reflection.MethodBase,System.Reflection.Emit.ILGenerator)">
+ <summary>Returns the methods unmodified list of code instructions</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="generator">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)</param>
+ <returns>A list containing all the original <see cref="T:HarmonyLib.CodeInstruction"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.GetOriginalInstructions(System.Reflection.MethodBase,System.Reflection.Emit.ILGenerator@)">
+ <summary>Returns the methods unmodified list of code instructions</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="generator">A new generator that now contains all local variables and labels contained in the result</param>
+ <returns>A list containing all the original <see cref="T:HarmonyLib.CodeInstruction"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.GetCurrentInstructions(System.Reflection.MethodBase,System.Int32,System.Reflection.Emit.ILGenerator)">
+ <summary>Returns the methods current list of code instructions after all existing transpilers have been applied</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="maxTranspilers">Apply only the first count of transpilers</param>
+ <param name="generator">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)</param>
+ <returns>A list of <see cref="T:HarmonyLib.CodeInstruction"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.GetCurrentInstructions(System.Reflection.MethodBase,System.Reflection.Emit.ILGenerator@,System.Int32)">
+ <summary>Returns the methods current list of code instructions after all existing transpilers have been applied</summary>
+ <param name="original">The original method/constructor</param>
+ <param name="generator">A new generator that now contains all local variables and labels contained in the result</param>
+ <param name="maxTranspilers">Apply only the first count of transpilers</param>
+ <returns>A list of <see cref="T:HarmonyLib.CodeInstruction"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.ReadMethodBody(System.Reflection.MethodBase)">
+ <summary>A low level way to read the body of a method. Used for quick searching in methods</summary>
+ <param name="method">The original method</param>
+ <returns>All instructions as opcode/operand pairs</returns>
+
+ </member>
+ <member name="M:HarmonyLib.PatchProcessor.ReadMethodBody(System.Reflection.MethodBase,System.Reflection.Emit.ILGenerator)">
+ <summary>A low level way to read the body of a method. Used for quick searching in methods</summary>
+ <param name="method">The original method</param>
+ <param name="generator">An existing generator that will be used to create all local variables and labels contained in the result</param>
+ <returns>All instructions as opcode/operand pairs</returns>
+
+ </member>
+ <member name="T:HarmonyLib.Priority">
+ <summary>A patch priority</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.Last">
+ <summary>Patch last</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.VeryLow">
+ <summary>Patch with very low priority</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.Low">
+ <summary>Patch with low priority</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.LowerThanNormal">
+ <summary>Patch with lower than normal priority</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.Normal">
+ <summary>Patch with normal priority</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.HigherThanNormal">
+ <summary>Patch with higher than normal priority</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.High">
+ <summary>Patch with high priority</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.VeryHigh">
+ <summary>Patch with very high priority</summary>
+
+ </member>
+ <member name="F:HarmonyLib.Priority.First">
+ <summary>Patch first</summary>
+
+ </member>
+ <member name="T:HarmonyLib.ReversePatcher">
+ <summary>A reverse patcher</summary>
+
+ </member>
+ <member name="M:HarmonyLib.ReversePatcher.#ctor(HarmonyLib.Harmony,System.Reflection.MethodBase,HarmonyLib.HarmonyMethod)">
+ <summary>Creates a reverse patcher</summary>
+ <param name="instance">The Harmony instance</param>
+ <param name="original">The original method/constructor</param>
+ <param name="standin">Your stand-in stub method as <see cref="T:HarmonyLib.HarmonyMethod"/></param>
+
+ </member>
+ <member name="M:HarmonyLib.ReversePatcher.Patch(HarmonyLib.HarmonyReversePatchType)">
+ <summary>Applies the patch</summary>
+ <param name="type">The type of patch, see <see cref="T:HarmonyLib.HarmonyReversePatchType"/></param>
+ <returns>The generated replacement method</returns>
+
+ </member>
+ <member name="T:HarmonyLib.Transpilers">
+ <summary>A collection of commonly used transpilers</summary>
+
+ </member>
+ <member name="M:HarmonyLib.Transpilers.MethodReplacer(System.Collections.Generic.IEnumerable{HarmonyLib.CodeInstruction},System.Reflection.MethodBase,System.Reflection.MethodBase)">
+ <summary>A transpiler that replaces all occurrences of a given method with another one using the same signature</summary>
+ <param name="instructions">The enumeration of <see cref="T:HarmonyLib.CodeInstruction"/> to act on</param>
+ <param name="from">Method or constructor to search for</param>
+ <param name="to">Method or constructor to replace with</param>
+ <returns>Modified enumeration of <see cref="T:HarmonyLib.CodeInstruction"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.Transpilers.Manipulator(System.Collections.Generic.IEnumerable{HarmonyLib.CodeInstruction},System.Func{HarmonyLib.CodeInstruction,System.Boolean},System.Action{HarmonyLib.CodeInstruction})">
+ <summary>A transpiler that alters instructions that match a predicate by calling an action</summary>
+ <param name="instructions">The enumeration of <see cref="T:HarmonyLib.CodeInstruction"/> to act on</param>
+ <param name="predicate">A predicate selecting the instructions to change</param>
+ <param name="action">An action to apply to matching instructions</param>
+ <returns>Modified enumeration of <see cref="T:HarmonyLib.CodeInstruction"/></returns>
+
+ </member>
+ <member name="M:HarmonyLib.Transpilers.DebugLogger(System.Collections.Generic.IEnumerable{HarmonyLib.CodeInstruction},System.String)">
+ <summary>A transpiler that logs a text at the beginning of the method</summary>
+ <param name="instructions">The instructions to act on</param>
+ <param name="text">The log text</param>
+ <returns>Modified enumeration of <see cref="T:HarmonyLib.CodeInstruction"/></returns>
+
+ </member>
+ <member name="T:HarmonyLib.AccessTools">
+ <summary>A helper class for reflection related functions</summary>
+
+ </member>
+ <member name="F:HarmonyLib.AccessTools.all">
+ <summary>Shortcut for <see cref="T:System.Reflection.BindingFlags"/> to simplify the use of reflections and make it work for any access level</summary>
+
+ </member>
+ <member name="F:HarmonyLib.AccessTools.allDeclared">
+ <summary>Shortcut for <see cref="T:System.Reflection.BindingFlags"/> to simplify the use of reflections and make it work for any access level but only within the current type</summary>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.AllAssemblies">
+ <summary>Enumerates all assemblies in the current app domain, excluding visual studio assemblies</summary>
+ <returns>An enumeration of <see cref="T:System.Reflection.Assembly"/></returns>
+ </member>
+ <member name="M:HarmonyLib.AccessTools.TypeByName(System.String)">
+ <summary>Gets a type by name. Prefers a full name with namespace but falls back to the first type matching the name otherwise</summary>
+ <param name="name">The name</param>
+ <returns>A type or null if not found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetTypesFromAssembly(System.Reflection.Assembly)">
+ <summary>Gets all successfully loaded types from a given assembly</summary>
+ <param name="assembly">The assembly</param>
+ <returns>An array of types</returns>
+ <remarks>
+ This calls and returns <see cref="M:System.Reflection.Assembly.GetTypes"/>, while catching any thrown <see cref="T:System.Reflection.ReflectionTypeLoadException"/>.
+ If such an exception is thrown, returns the successfully loaded types (<see cref="P:System.Reflection.ReflectionTypeLoadException.Types"/>,
+ filtered for non-null values).
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.AllTypes">
+ <summary>Enumerates all successfully loaded types in the current app domain, excluding visual studio assemblies</summary>
+ <returns>An enumeration of all <see cref="T:System.Type"/> in all assemblies, excluding visual studio assemblies</returns>
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FindIncludingBaseTypes``1(System.Type,System.Func{System.Type,``0})">
+ <summary>Applies a function going up the type hierarchy and stops at the first non-<c>null</c> result</summary>
+ <typeparam name="T">Result type of func()</typeparam>
+ <param name="type">The class/type to start with</param>
+ <param name="func">The evaluation function returning T</param>
+ <returns>The first non-<c>null</c> result, or <c>null</c> if no match</returns>
+ <remarks>
+ 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 <see cref="T:System.Object"/>.
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FindIncludingInnerTypes``1(System.Type,System.Func{System.Type,``0})">
+ <summary>Applies a function going into inner types and stops at the first non-<c>null</c> result</summary>
+ <typeparam name="T">Generic type parameter</typeparam>
+ <param name="type">The class/type to start with</param>
+ <param name="func">The evaluation function returning T</param>
+ <returns>The first non-<c>null</c> result, or <c>null</c> if no match</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.DeclaredField(System.Type,System.String)">
+ <summary>Gets the reflection information for a directly declared field</summary>
+ <param name="type">The class/type where the field is defined</param>
+ <param name="name">The name of the field</param>
+ <returns>A field or null when type/name is null or when the field cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.Field(System.Type,System.String)">
+ <summary>Gets the reflection information for a field by searching the type and all its super types</summary>
+ <param name="type">The class/type where the field is defined</param>
+ <param name="name">The name of the field (case sensitive)</param>
+ <returns>A field or null when type/name is null or when the field cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.DeclaredField(System.Type,System.Int32)">
+ <summary>Gets the reflection information for a field</summary>
+ <param name="type">The class/type where the field is declared</param>
+ <param name="idx">The zero-based index of the field inside the class definition</param>
+ <returns>A field or null when type is null or when the field cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.DeclaredProperty(System.Type,System.String)">
+ <summary>Gets the reflection information for a directly declared property</summary>
+ <param name="type">The class/type where the property is declared</param>
+ <param name="name">The name of the property (case sensitive)</param>
+ <returns>A property or null when type/name is null or when the property cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.DeclaredPropertyGetter(System.Type,System.String)">
+ <summary>Gets the reflection information for the getter method of a directly declared property</summary>
+ <param name="type">The class/type where the property is declared</param>
+ <param name="name">The name of the property (case sensitive)</param>
+ <returns>A method or null when type/name is null or when the property cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.DeclaredPropertySetter(System.Type,System.String)">
+ <summary>Gets the reflection information for the setter method of a directly declared property</summary>
+ <param name="type">The class/type where the property is declared</param>
+ <param name="name">The name of the property (case sensitive)</param>
+ <returns>A method or null when type/name is null or when the property cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.Property(System.Type,System.String)">
+ <summary>Gets the reflection information for a property by searching the type and all its super types</summary>
+ <param name="type">The class/type</param>
+ <param name="name">The name</param>
+ <returns>A property or null when type/name is null or when the property cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.PropertyGetter(System.Type,System.String)">
+ <summary>Gets the reflection information for the getter method of a property by searching the type and all its super types</summary>
+ <param name="type">The class/type</param>
+ <param name="name">The name</param>
+ <returns>A method or null when type/name is null or when the property cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.PropertySetter(System.Type,System.String)">
+ <summary>Gets the reflection information for the setter method of a property by searching the type and all its super types</summary>
+ <param name="type">The class/type</param>
+ <param name="name">The name</param>
+ <returns>A method or null when type/name is null or when the property cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.DeclaredMethod(System.Type,System.String,System.Type[],System.Type[])">
+ <summary>Gets the reflection information for a directly declared method</summary>
+ <param name="type">The class/type where the method is declared</param>
+ <param name="name">The name of the method (case sensitive)</param>
+ <param name="parameters">Optional parameters to target a specific overload of the method</param>
+ <param name="generics">Optional list of types that define the generic version of the method</param>
+ <returns>A method or null when type/name is null or when the method cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.Method(System.Type,System.String,System.Type[],System.Type[])">
+ <summary>Gets the reflection information for a method by searching the type and all its super types</summary>
+ <param name="type">The class/type where the method is declared</param>
+ <param name="name">The name of the method (case sensitive)</param>
+ <param name="parameters">Optional parameters to target a specific overload of the method</param>
+ <param name="generics">Optional list of types that define the generic version of the method</param>
+ <returns>A method or null when type/name is null or when the method cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.Method(System.String,System.Type[],System.Type[])">
+ <summary>Gets the reflection information for a method by searching the type and all its super types</summary>
+ <param name="typeColonMethodname">The target method in the form <c>TypeFullName:MethodName</c>, where the type name matches a form recognized by <a href="https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype">Type.GetType</a> like <c>Some.Namespace.Type</c>.</param>
+ <param name="parameters">Optional parameters to target a specific overload of the method</param>
+ <param name="generics">Optional list of types that define the generic version of the method</param>
+ <returns>A method or null when type/name is null or when the method cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetMethodNames(System.Type)">
+ <summary>Gets the names of all method that are declared in a type</summary>
+ <param name="type">The declaring class/type</param>
+ <returns>A list of method names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetMethodNames(System.Object)">
+ <summary>Gets the names of all method that are declared in the type of the instance</summary>
+ <param name="instance">An instance of the type to search in</param>
+ <returns>A list of method names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetFieldNames(System.Type)">
+ <summary>Gets the names of all fields that are declared in a type</summary>
+ <param name="type">The declaring class/type</param>
+ <returns>A list of field names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetFieldNames(System.Object)">
+ <summary>Gets the names of all fields that are declared in the type of the instance</summary>
+ <param name="instance">An instance of the type to search in</param>
+ <returns>A list of field names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetPropertyNames(System.Type)">
+ <summary>Gets the names of all properties that are declared in a type</summary>
+ <param name="type">The declaring class/type</param>
+ <returns>A list of property names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetPropertyNames(System.Object)">
+ <summary>Gets the names of all properties that are declared in the type of the instance</summary>
+ <param name="instance">An instance of the type to search in</param>
+ <returns>A list of property names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetUnderlyingType(System.Reflection.MemberInfo)">
+ <summary>Gets the type of any class member of</summary>
+ <param name="member">A member</param>
+ <returns>The class/type of this member</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsDeclaredMember``1(``0)">
+ <summary>Test if a class member is actually an concrete implementation</summary>
+ <param name="member">A member</param>
+ <returns>True if the member is a declared</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetDeclaredMember``1(``0)">
+ <summary>Gets the real implementation of a class member</summary>
+ <param name="member">A member</param>
+ <returns>The member itself if its declared. Otherwise the member that is actually implemented in some base type</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.DeclaredConstructor(System.Type,System.Type[],System.Boolean)">
+ <summary>Gets the reflection information for a directly declared constructor</summary>
+ <param name="type">The class/type where the constructor is declared</param>
+ <param name="parameters">Optional parameters to target a specific overload of the constructor</param>
+ <param name="searchForStatic">Optional parameters to only consider static constructors</param>
+ <returns>A constructor info or null when type is null or when the constructor cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.Constructor(System.Type,System.Type[],System.Boolean)">
+ <summary>Gets the reflection information for a constructor by searching the type and all its super types</summary>
+ <param name="type">The class/type where the constructor is declared</param>
+ <param name="parameters">Optional parameters to target a specific overload of the method</param>
+ <param name="searchForStatic">Optional parameters to only consider static constructors</param>
+ <returns>A constructor info or null when type is null or when the method cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetDeclaredConstructors(System.Type,System.Nullable{System.Boolean})">
+ <summary>Gets reflection information for all declared constructors</summary>
+ <param name="type">The class/type where the constructors are declared</param>
+ <param name="searchForStatic">Optional parameters to only consider static constructors</param>
+ <returns>A list of constructor infos</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetDeclaredMethods(System.Type)">
+ <summary>Gets reflection information for all declared methods</summary>
+ <param name="type">The class/type where the methods are declared</param>
+ <returns>A list of methods</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetDeclaredProperties(System.Type)">
+ <summary>Gets reflection information for all declared properties</summary>
+ <param name="type">The class/type where the properties are declared</param>
+ <returns>A list of properties</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetDeclaredFields(System.Type)">
+ <summary>Gets reflection information for all declared fields</summary>
+ <param name="type">The class/type where the fields are declared</param>
+ <returns>A list of fields</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetReturnedType(System.Reflection.MethodBase)">
+ <summary>Gets the return type of a method or constructor</summary>
+ <param name="methodOrConstructor">The method/constructor</param>
+ <returns>The return type</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.Inner(System.Type,System.String)">
+ <summary>Given a type, returns the first inner type matching a recursive search by name</summary>
+ <param name="type">The class/type to start searching at</param>
+ <param name="name">The name of the inner type (case sensitive)</param>
+ <returns>The inner type or null if type/name is null or if a type with that name cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FirstInner(System.Type,System.Func{System.Type,System.Boolean})">
+ <summary>Given a type, returns the first inner type matching a recursive search with a predicate</summary>
+ <param name="type">The class/type to start searching at</param>
+ <param name="predicate">The predicate to search with</param>
+ <returns>The inner type or null if type/predicate is null or if a type with that name cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FirstMethod(System.Type,System.Func{System.Reflection.MethodInfo,System.Boolean})">
+ <summary>Given a type, returns the first method matching a predicate</summary>
+ <param name="type">The class/type to start searching at</param>
+ <param name="predicate">The predicate to search with</param>
+ <returns>The method or null if type/predicate is null or if a type with that name cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FirstConstructor(System.Type,System.Func{System.Reflection.ConstructorInfo,System.Boolean})">
+ <summary>Given a type, returns the first constructor matching a predicate</summary>
+ <param name="type">The class/type to start searching at</param>
+ <param name="predicate">The predicate to search with</param>
+ <returns>The constructor info or null if type/predicate is null or if a type with that name cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FirstProperty(System.Type,System.Func{System.Reflection.PropertyInfo,System.Boolean})">
+ <summary>Given a type, returns the first property matching a predicate</summary>
+ <param name="type">The class/type to start searching at</param>
+ <param name="predicate">The predicate to search with</param>
+ <returns>The property or null if type/predicate is null or if a type with that name cannot be found</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetTypes(System.Object[])">
+ <summary>Returns an array containing the type of each object in the given array</summary>
+ <param name="parameters">An array of objects</param>
+ <returns>An array of types or an empty array if parameters is null (if an object is null, the type for it will be object)</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.ActualParameters(System.Reflection.MethodBase,System.Object[])">
+ <summary>Creates an array of input parameters for a given method and a given set of potential inputs</summary>
+ <param name="method">The method/constructor you are planing to call</param>
+ <param name="inputs"> The possible input parameters in any order</param>
+ <returns>An object array matching the method signature</returns>
+
+ </member>
+ <member name="T:HarmonyLib.AccessTools.FieldRef`2">
+ <summary>A readable/assignable reference delegate to an instance field of a class or static field (NOT an instance field of a struct)</summary>
+ <typeparam name="T">
+ An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including <see cref="T:System.Object"/>),
+ implemented interface, or derived class of this type
+ </typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="instance">The runtime instance to access the field (ignored and can be omitted for static fields)</param>
+ <returns>A readable/assignable reference to the field</returns>
+ <exception cref="T:System.NullReferenceException">Null instance passed to a non-static field ref delegate</exception>
+ <exception cref="T:System.InvalidCastException">
+ Instance of invalid type passed to a non-static field ref delegate
+ (this can happen if <typeparamref name="T"/> is a parent class or interface of the field's declaring type)
+ </exception>
+ <remarks>
+ <para>
+ 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 <see cref="T:HarmonyLib.AccessTools.StructFieldRef`2"/> instead.
+ </para>
+ <para>
+ Note that <typeparamref name="T"/> is not required to be the field's declaring type. It can be a parent class (including <see cref="T:System.Object"/>),
+ implemented interface, or a derived class of the field's declaring type ("<c>instanceOfT is FieldDeclaringType</c>" must be possible).
+ Specifically, <typeparamref name="F"/> must be <see cref="M:System.Type.IsAssignableFrom(System.Type)">assignable from</see> OR to the field's declaring type.
+ Technically, this allows <c>Nullable</c>, although <c>Nullable</c> 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.
+ </para>
+ <para>
+ Similarly, <typeparamref name="F"/> 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 <c>object</c>) 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: <typeparamref name="F"/> can also be the underlying integral type of the enum type.
+ Specifically, for reference types, <typeparamref name="F"/> must be <see cref="M:System.Type.IsAssignableFrom(System.Type)">assignable from</see>
+ the field's field type; for non-enum value types, <typeparamref name="F"/> must be exactly the field's field type; for enum types,
+ <typeparamref name="F"/> must be either the field's field type or the underyling integral type of that field type.
+ </para>
+ <para>
+ This delegate supports static fields, even those defined in structs, for legacy reasons.
+ For such static fields, <typeparamref name="T"/> is effectively ignored.
+ Consider using <see cref="T:HarmonyLib.AccessTools.FieldRef`1"/> (and <c>StaticFieldRefAccess</c> methods that return it) instead for static fields.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FieldRefAccess``2(System.String)">
+ <summary>Creates a field reference delegate for an instance field of a class</summary>
+ <typeparam name="T">The class that defines the instance field, or derived class of this type</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="fieldName">The name of the field</param>
+ <returns>A readable/assignable <see cref="T:HarmonyLib.AccessTools.FieldRef`2"/> delegate</returns>
+ <remarks>
+ <para>
+ For backwards compatibility, there is no class constraint on <typeparamref name="T"/>.
+ Instead, the non-value-type check is done at runtime within the method.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FieldRefAccess``2(``0,System.String)">
+ <summary>Creates an instance field reference for a specific instance of a class</summary>
+ <typeparam name="T">The class that defines the instance field, or derived class of this type</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="instance">The instance</param>
+ <param name="fieldName">The name of the field</param>
+ <returns>A readable/assignable reference to the field</returns>
+ <remarks>
+ <para>
+ 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 <see cref="M:HarmonyLib.AccessTools.FieldRefAccess``2(System.String)"/> instead.
+ <c>FieldRefAccess&lt;T, F&gt;(instance, fieldName)</c> is functionally equivalent to <c>FieldRefAccess&lt;T, F&gt;(fieldName)(instance)</c>.
+ </para>
+ <para>
+ For backwards compatibility, there is no class constraint on <typeparamref name="T"/>.
+ Instead, the non-value-type check is done at runtime within the method.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FieldRefAccess``1(System.Type,System.String)">
+ <summary>Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct)</summary>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="type">
+ The type that defines the field, or derived class of this type; must not be a struct type unless the field is static
+ </param>
+ <param name="fieldName">The name of the field</param>
+ <returns>
+ A readable/assignable <see cref="T:HarmonyLib.AccessTools.FieldRef`2"/> delegate with <c>T=object</c>
+ (for static fields, the <c>instance</c> delegate parameter is ignored)
+ </returns>
+ <remarks>
+ <para>
+ 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 <c>T</c>
+ in e.g. <see cref="M:HarmonyLib.AccessTools.FieldRefAccess``2(System.String)"/>.
+ </para>
+ <para>
+ This method supports static fields, even those defined in structs, for legacy reasons.
+ Consider using <see cref="M:HarmonyLib.AccessTools.StaticFieldRefAccess``1(System.Type,System.String)"/> (and other overloads) instead for static fields.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FieldRefAccess``2(System.Reflection.FieldInfo)">
+ <summary>Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct)</summary>
+ <typeparam name="T">
+ An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including <see cref="T:System.Object"/>),
+ implemented interface, or derived class of this type ("<c>instanceOfT is FieldDeclaringType</c>" must be possible)
+ </typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="fieldInfo">The field</param>
+ <returns>A readable/assignable <see cref="T:HarmonyLib.AccessTools.FieldRef`2"/> delegate</returns>
+ <remarks>
+ <para>
+ This method is meant for cases where the field has already been obtained, avoiding the field searching cost in
+ e.g. <see cref="M:HarmonyLib.AccessTools.FieldRefAccess``2(System.String)"/>.
+ </para>
+ <para>
+ This method supports static fields, even those defined in structs, for legacy reasons.
+ For such static fields, <typeparamref name="T"/> is effectively ignored.
+ Consider using <see cref="M:HarmonyLib.AccessTools.StaticFieldRefAccess``2(System.Reflection.FieldInfo)"/> (and other overloads) instead for static fields.
+ </para>
+ <para>
+ For backwards compatibility, there is no class constraint on <typeparamref name="T"/>.
+ Instead, the non-value-type check is done at runtime within the method.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.FieldRefAccess``2(``0,System.Reflection.FieldInfo)">
+ <summary>Creates a field reference for an instance field of a class</summary>
+ <typeparam name="T">
+ The type that defines the field; or a parent class (including <see cref="T:System.Object"/>), implemented interface, or derived class of this type
+ ("<c>instanceOfT is FieldDeclaringType</c>" must be possible)
+ </typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="instance">The instance</param>
+ <param name="fieldInfo">The field</param>
+ <returns>A readable/assignable reference to the field</returns>
+ <remarks>
+ <para>
+ 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 <see cref="M:HarmonyLib.AccessTools.FieldRefAccess``2(System.Reflection.FieldInfo)"/> instead.
+ <c>FieldRefAccess&lt;T, F&gt;(instance, fieldInfo)</c> is functionally equivalent to <c>FieldRefAccess&lt;T, F&gt;(fieldInfo)(instance)</c>.
+ </para>
+ <para>
+ For backwards compatibility, there is no class constraint on <typeparamref name="T"/>.
+ Instead, the non-value-type check is done at runtime within the method.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="T:HarmonyLib.AccessTools.StructFieldRef`2">
+ <summary>A readable/assignable reference delegate to an instance field of a struct</summary>
+ <typeparam name="T">The struct that defines the instance field</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="instance">A reference to the runtime instance to access the field</param>
+ <returns>A readable/assignable reference to the field</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.StructFieldRefAccess``2(System.String)">
+ <summary>Creates a field reference delegate for an instance field of a struct</summary>
+ <typeparam name="T">The struct that defines the instance field</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="fieldName">The name of the field</param>
+ <returns>A readable/assignable <see cref="T:HarmonyLib.AccessTools.StructFieldRef`2"/> delegate</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.StructFieldRefAccess``2(``0@,System.String)">
+ <summary>Creates an instance field reference for a specific instance of a struct</summary>
+ <typeparam name="T">The struct that defines the instance field</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="instance">The instance</param>
+ <param name="fieldName">The name of the field</param>
+ <returns>A readable/assignable reference to the field</returns>
+ <remarks>
+ <para>
+ 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 <see cref="M:HarmonyLib.AccessTools.StructFieldRefAccess``2(System.String)"/> instead.
+ <c>StructFieldRefAccess&lt;T, F&gt;(ref instance, fieldName)</c> is functionally equivalent to <c>StructFieldRefAccess&lt;T, F&gt;(fieldName)(ref instance)</c>.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.StructFieldRefAccess``2(System.Reflection.FieldInfo)">
+ <summary>Creates a field reference delegate for an instance field of a struct</summary>
+ <typeparam name="T">The struct that defines the instance field</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="fieldInfo">The field</param>
+ <returns>A readable/assignable <see cref="T:HarmonyLib.AccessTools.StructFieldRef`2"/> delegate</returns>
+ <remarks>
+ <para>
+ This method is meant for cases where the field has already been obtained, avoiding the field searching cost in
+ e.g. <see cref="M:HarmonyLib.AccessTools.StructFieldRefAccess``2(System.String)"/>.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.StructFieldRefAccess``2(``0@,System.Reflection.FieldInfo)">
+ <summary>Creates a field reference for an instance field of a struct</summary>
+ <typeparam name="T">The struct that defines the instance field</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="instance">The instance</param>
+ <param name="fieldInfo">The field</param>
+ <returns>A readable/assignable reference to the field</returns>
+ <remarks>
+ <para>
+ 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 <see cref="M:HarmonyLib.AccessTools.StructFieldRefAccess``2(System.Reflection.FieldInfo)"/> instead.
+ <c>StructFieldRefAccess&lt;T, F&gt;(ref instance, fieldInfo)</c> is functionally equivalent to <c>StructFieldRefAccess&lt;T, F&gt;(fieldInfo)(ref instance)</c>.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="T:HarmonyLib.AccessTools.FieldRef`1">
+ <summary>A readable/assignable reference delegate to a static field</summary>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <returns>A readable/assignable reference to the field</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.StaticFieldRefAccess``2(System.String)">
+ <summary>Creates a static field reference</summary>
+ <typeparam name="T">The type (can be class or struct) the field is defined in</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="fieldName">The name of the field</param>
+ <returns>A readable/assignable reference to the field</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.StaticFieldRefAccess``1(System.Type,System.String)">
+ <summary>Creates a static field reference</summary>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="type">The type (can be class or struct) the field is defined in</param>
+ <param name="fieldName">The name of the field</param>
+ <returns>A readable/assignable reference to the field</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.StaticFieldRefAccess``2(System.Reflection.FieldInfo)">
+ <summary>Creates a static field reference</summary>
+ <typeparam name="T">An arbitrary type (by convention, the type the field is defined in)</typeparam>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="fieldInfo">The field</param>
+ <returns>A readable/assignable reference to the field</returns>
+ <remarks>
+ The type parameter <typeparamref name="T"/> is only used in exception messaging and to distinguish between this method overload
+ and the <see cref="M:HarmonyLib.AccessTools.StaticFieldRefAccess``1(System.Reflection.FieldInfo)"/> overload (which returns a <see cref="T:HarmonyLib.AccessTools.FieldRef`1"/> rather than a reference).
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.StaticFieldRefAccess``1(System.Reflection.FieldInfo)">
+ <summary>Creates a static field reference delegate</summary>
+ <typeparam name="F">
+ 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 <see cref="M:System.Type.IsAssignableFrom(System.Type)">is assignable from</see> that type; or if the field's type is an enum type,
+ either that type or the underlying integral type of that enum type
+ </typeparam>
+ <param name="fieldInfo">The field</param>
+ <returns>A readable/assignable <see cref="T:HarmonyLib.AccessTools.FieldRef`1"/> delegate</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.MethodDelegate``1(System.Reflection.MethodInfo,System.Object,System.Boolean)">
+ <summary>Creates a delegate to a given method</summary>
+ <typeparam name="DelegateType">The delegate Type</typeparam>
+ <param name="method">The method to create a delegate from.</param>
+ <param name="instance">
+ Only applies for instance methods. If <c>null</c> (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 <paramref name="instance"/>.
+ </param>
+ <param name="virtualCall">
+ Only applies for instance methods. If <c>true</c> (default) and <paramref name="method"/> 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 <paramref name="method"/> (this is useful for calling base class methods)
+ Note: if <c>false</c> and <paramref name="method"/> is an interface method, an ArgumentException is thrown.
+ </param>
+ <returns>A delegate of given <typeparamref name="DelegateType"/> to given <paramref name="method"/></returns>
+ <remarks>
+ <para>
+ Delegate invocation is more performant and more convenient to use than <see cref="M:System.Reflection.MethodBase.Invoke(System.Object,System.Object[])"/>
+ at a one-time setup cost.
+ </para>
+ <para>
+ 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.
+ </para>
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.HarmonyDelegate``1(System.Object)">
+ <summary>Creates a delegate for a given delegate definition, attributed with [<see cref="T:HarmonyLib.HarmonyDelegate"/>]</summary>
+ <typeparam name="DelegateType">The delegate Type, attributed with [<see cref="T:HarmonyLib.HarmonyDelegate"/>]</typeparam>
+ <param name="instance">
+ Only applies for instance methods. If <c>null</c> (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 <paramref name="instance"/>.
+ </param>
+ <returns>A delegate of given <typeparamref name="DelegateType"/> to the method specified via [<see cref="T:HarmonyLib.HarmonyDelegate"/>]
+ attributes on <typeparamref name="DelegateType"/></returns>
+ <remarks>
+ This calls <see cref="M:HarmonyLib.AccessTools.MethodDelegate``1(System.Reflection.MethodInfo,System.Object,System.Boolean)"/> with the <c>method</c> and <c>virtualCall</c> arguments
+ determined from the [<see cref="T:HarmonyLib.HarmonyDelegate"/>] attributes on <typeparamref name="DelegateType"/>,
+ and the given <paramref name="instance"/> (for closed instance delegates).
+ </remarks>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetOutsideCaller">
+ <summary>Returns who called the current method</summary>
+ <returns>The calling method/constructor (excluding the caller)</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.RethrowException(System.Exception)">
+ <summary>Rethrows an exception while preserving its stack trace (throw statement typically clobbers existing stack traces)</summary>
+ <param name="exception">The exception to rethrow</param>
+
+ </member>
+ <member name="P:HarmonyLib.AccessTools.IsMonoRuntime">
+ <summary>True if the current runtime is based on Mono, false otherwise (.NET)</summary>
+
+ </member>
+ <member name="P:HarmonyLib.AccessTools.IsNetFrameworkRuntime">
+ <summary>True if the current runtime is .NET Framework, false otherwise (.NET Core or Mono, although latter isn't guaranteed)</summary>
+
+ </member>
+ <member name="P:HarmonyLib.AccessTools.IsNetCoreRuntime">
+ <summary>True if the current runtime is .NET Core, false otherwise (Mono or .NET Framework)</summary>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.ThrowMissingMemberException(System.Type,System.String[])">
+ <summary>Throws a missing member runtime exception</summary>
+ <param name="type">The type that is involved</param>
+ <param name="names">A list of names</param>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.GetDefaultValue(System.Type)">
+ <summary>Gets default value for a specific type</summary>
+ <param name="type">The class/type</param>
+ <returns>The default value</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.CreateInstance(System.Type)">
+ <summary>Creates an (possibly uninitialized) instance of a given type</summary>
+ <param name="type">The class/type</param>
+ <returns>The new instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.CreateInstance``1">
+ <summary>Creates an (possibly uninitialized) instance of a given type</summary>
+ <typeparam name="T">The class/type</typeparam>
+ <returns>The new instance</returns>
+
+ </member>
+ <member name="F:HarmonyLib.AccessTools.addHandlerCache">
+ <summary>
+ A cache for the <see cref="M:System.Collections.Generic.ICollection`1.Add(`0)"/> or similar Add methods for different types.
+ </summary>
+ </member>
+ <member name="M:HarmonyLib.AccessTools.MakeDeepCopy``1(System.Object)">
+ <summary>Makes a deep copy of any object</summary>
+ <typeparam name="T">The type of the instance that should be created; for legacy reasons, this must be a class or interface</typeparam>
+ <param name="source">The original object</param>
+ <returns>A copy of the original object but of type T</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.MakeDeepCopy``1(System.Object,``0@,System.Func{System.String,HarmonyLib.Traverse,HarmonyLib.Traverse,System.Object},System.String)">
+ <summary>Makes a deep copy of any object</summary>
+ <typeparam name="T">The type of the instance that should be created</typeparam>
+ <param name="source">The original object</param>
+ <param name="result">[out] The copy of the original object</param>
+ <param name="processor">Optional value transformation function (taking a field name and src/dst <see cref="T:HarmonyLib.Traverse"/> instances)</param>
+ <param name="pathRoot">The optional path root to start with</param>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.MakeDeepCopy(System.Object,System.Type,System.Func{System.String,HarmonyLib.Traverse,HarmonyLib.Traverse,System.Object},System.String)">
+ <summary>Makes a deep copy of any object</summary>
+ <param name="source">The original object</param>
+ <param name="resultType">The type of the instance that should be created</param>
+ <param name="processor">Optional value transformation function (taking a field name and src/dst <see cref="T:HarmonyLib.Traverse"/> instances)</param>
+ <param name="pathRoot">The optional path root to start with</param>
+ <returns>The copy of the original object</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsStruct(System.Type)">
+ <summary>Tests if a type is a struct</summary>
+ <param name="type">The type</param>
+ <returns>True if the type is a struct</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsClass(System.Type)">
+ <summary>Tests if a type is a class</summary>
+ <param name="type">The type</param>
+ <returns>True if the type is a class</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsValue(System.Type)">
+ <summary>Tests if a type is a value type</summary>
+ <param name="type">The type</param>
+ <returns>True if the type is a value type</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsInteger(System.Type)">
+ <summary>Tests if a type is an integer type</summary>
+ <param name="type">The type</param>
+ <returns>True if the type represents some integer</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsFloatingPoint(System.Type)">
+ <summary>Tests if a type is a floating point type</summary>
+ <param name="type">The type</param>
+ <returns>True if the type represents some floating point</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsNumber(System.Type)">
+ <summary>Tests if a type is a numerical type</summary>
+ <param name="type">The type</param>
+ <returns>True if the type represents some number</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsVoid(System.Type)">
+ <summary>Tests if a type is void</summary>
+ <param name="type">The type</param>
+ <returns>True if the type is void</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsOfNullableType``1(``0)">
+ <summary>Test whether an instance is of a nullable type</summary>
+ <typeparam name="T">Type of instance</typeparam>
+ <param name="instance">An instance to test</param>
+ <returns>True if instance is of nullable type, false if not</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsStatic(System.Reflection.MemberInfo)">
+ <summary>Tests whether a type or member is static, as defined in C#</summary>
+ <param name="member">The type or member</param>
+ <returns>True if the type or member is static</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsStatic(System.Type)">
+ <summary>Tests whether a type is static, as defined in C#</summary>
+ <param name="type">The type</param>
+ <returns>True if the type is static</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsStatic(System.Reflection.PropertyInfo)">
+ <summary>Tests whether a property is static, as defined in C#</summary>
+ <param name="propertyInfo">The property</param>
+ <returns>True if the property is static</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.IsStatic(System.Reflection.EventInfo)">
+ <summary>Tests whether an event is static, as defined in C#</summary>
+ <param name="eventInfo">The event</param>
+ <returns>True if the event is static</returns>
+
+ </member>
+ <member name="M:HarmonyLib.AccessTools.CombinedHashCode(System.Collections.Generic.IEnumerable{System.Object})">
+ <summary>Calculates a combined hash code for an enumeration of objects</summary>
+ <param name="objects">The objects</param>
+ <returns>The hash code</returns>
+
+ </member>
+ <member name="T:HarmonyLib.GeneralExtensions">
+ <summary>General extensions for common cases</summary>
+
+ </member>
+ <member name="M:HarmonyLib.GeneralExtensions.Join``1(System.Collections.Generic.IEnumerable{``0},System.Func{``0,System.String},System.String)">
+ <summary>Joins an enumeration with a value converter and a delimiter to a string</summary>
+ <typeparam name="T">The inner type of the enumeration</typeparam>
+ <param name="enumeration">The enumeration</param>
+ <param name="converter">An optional value converter (from T to string)</param>
+ <param name="delimiter">An optional delimiter</param>
+ <returns>The values joined into a string</returns>
+
+ </member>
+ <member name="M:HarmonyLib.GeneralExtensions.Description(System.Type[])">
+ <summary>Converts an array of types (for example methods arguments) into a human readable form</summary>
+ <param name="parameters">The array of types</param>
+ <returns>A human readable description including brackets</returns>
+
+ </member>
+ <member name="M:HarmonyLib.GeneralExtensions.FullDescription(System.Type)">
+ <summary>A full description of a type</summary>
+ <param name="type">The type</param>
+ <returns>A human readable description</returns>
+
+ </member>
+ <member name="M:HarmonyLib.GeneralExtensions.FullDescription(System.Reflection.MethodBase)">
+ <summary>A a full description of a method or a constructor without assembly details but with generics</summary>
+ <param name="member">The method/constructor</param>
+ <returns>A human readable description</returns>
+
+ </member>
+ <member name="M:HarmonyLib.GeneralExtensions.Types(System.Reflection.ParameterInfo[])">
+ <summary>A helper converting parameter infos to types</summary>
+ <param name="pinfo">The array of parameter infos</param>
+ <returns>An array of types</returns>
+
+ </member>
+ <member name="M:HarmonyLib.GeneralExtensions.GetValueSafe``2(System.Collections.Generic.Dictionary{``0,``1},``0)">
+ <summary>A helper to access a value via key from a dictionary</summary>
+ <typeparam name="S">The key type</typeparam>
+ <typeparam name="T">The value type</typeparam>
+ <param name="dictionary">The dictionary</param>
+ <param name="key">The key</param>
+ <returns>The value for the key or the default value (of T) if that key does not exist</returns>
+
+ </member>
+ <member name="M:HarmonyLib.GeneralExtensions.GetTypedValue``1(System.Collections.Generic.Dictionary{System.String,System.Object},System.String)">
+ <summary>A helper to access a value via key from a dictionary with extra casting</summary>
+ <typeparam name="T">The value type</typeparam>
+ <param name="dictionary">The dictionary</param>
+ <param name="key">The key</param>
+ <returns>The value for the key or the default value (of T) if that key does not exist or cannot be cast to T</returns>
+
+ </member>
+ <member name="M:HarmonyLib.GeneralExtensions.ToLiteral(System.String,System.String)">
+ <summary>Escapes Unicode and ASCII non printable characters</summary>
+ <param name="input">The string to convert</param>
+ <param name="quoteChar">The string to convert</param>
+ <returns>A string literal surrounded by <paramref name="quoteChar"/></returns>
+
+ </member>
+ <member name="T:HarmonyLib.CodeInstructionExtensions">
+ <summary>Extensions for <see cref="T:HarmonyLib.CodeInstruction"/></summary>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.OperandIs(HarmonyLib.CodeInstruction,System.Object)">
+ <summary>Shortcut for testing whether the operand is equal to a non-null value</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="value">The value</param>
+ <returns>True if the operand has the same type and is equal to the value</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.OperandIs(HarmonyLib.CodeInstruction,System.Reflection.MemberInfo)">
+ <summary>Shortcut for testing whether the operand is equal to a non-null value</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="value">The <see cref="T:System.Reflection.MemberInfo"/> value</param>
+ <returns>True if the operand is equal to the value</returns>
+ <remarks>This is an optimized version of <see cref="M:HarmonyLib.CodeInstructionExtensions.OperandIs(HarmonyLib.CodeInstruction,System.Object)"/> for <see cref="T:System.Reflection.MemberInfo"/></remarks>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.Is(HarmonyLib.CodeInstruction,System.Reflection.Emit.OpCode,System.Object)">
+ <summary>Shortcut for <code>code.opcode == opcode &amp;&amp; code.OperandIs(operand)</code></summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="opcode">The <see cref="T:System.Reflection.Emit.OpCode"/></param>
+ <param name="operand">The operand value</param>
+ <returns>True if the opcode is equal to the given opcode and the operand has the same type and is equal to the given operand</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.Is(HarmonyLib.CodeInstruction,System.Reflection.Emit.OpCode,System.Reflection.MemberInfo)">
+ <summary>Shortcut for <code>code.opcode == opcode &amp;&amp; code.OperandIs(operand)</code></summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="opcode">The <see cref="T:System.Reflection.Emit.OpCode"/></param>
+ <param name="operand">The <see cref="T:System.Reflection.MemberInfo"/> operand value</param>
+ <returns>True if the opcode is equal to the given opcode and the operand is equal to the given operand</returns>
+ <remarks>This is an optimized version of <see cref="M:HarmonyLib.CodeInstructionExtensions.Is(HarmonyLib.CodeInstruction,System.Reflection.Emit.OpCode,System.Object)"/> for <see cref="T:System.Reflection.MemberInfo"/></remarks>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.IsLdarg(HarmonyLib.CodeInstruction,System.Nullable{System.Int32})">
+ <summary>Tests for any form of Ldarg*</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="n">The (optional) index</param>
+ <returns>True if it matches one of the variations</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.IsLdarga(HarmonyLib.CodeInstruction,System.Nullable{System.Int32})">
+ <summary>Tests for Ldarga/Ldarga_S</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="n">The (optional) index</param>
+ <returns>True if it matches one of the variations</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.IsStarg(HarmonyLib.CodeInstruction,System.Nullable{System.Int32})">
+ <summary>Tests for Starg/Starg_S</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="n">The (optional) index</param>
+ <returns>True if it matches one of the variations</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.IsLdloc(HarmonyLib.CodeInstruction,System.Reflection.Emit.LocalBuilder)">
+ <summary>Tests for any form of Ldloc*</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="variable">The optional local variable</param>
+ <returns>True if it matches one of the variations</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.IsStloc(HarmonyLib.CodeInstruction,System.Reflection.Emit.LocalBuilder)">
+ <summary>Tests for any form of Stloc*</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="variable">The optional local variable</param>
+ <returns>True if it matches one of the variations</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.Branches(HarmonyLib.CodeInstruction,System.Nullable{System.Reflection.Emit.Label}@)">
+ <summary>Tests if the code instruction branches</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="label">The label if the instruction is a branch operation or <see langword="null"/> if not</param>
+ <returns>True if the instruction branches</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.Calls(HarmonyLib.CodeInstruction,System.Reflection.MethodInfo)">
+ <summary>Tests if the code instruction calls the method/constructor</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="method">The method</param>
+ <returns>True if the instruction calls the method or constructor</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.LoadsConstant(HarmonyLib.CodeInstruction)">
+ <summary>Tests if the code instruction loads a constant</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <returns>True if the instruction loads a constant</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.LoadsConstant(HarmonyLib.CodeInstruction,System.Int64)">
+ <summary>Tests if the code instruction loads an integer constant</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="number">The integer constant</param>
+ <returns>True if the instruction loads the constant</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.LoadsConstant(HarmonyLib.CodeInstruction,System.Double)">
+ <summary>Tests if the code instruction loads a floating point constant</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="number">The floating point constant</param>
+ <returns>True if the instruction loads the constant</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.LoadsConstant(HarmonyLib.CodeInstruction,System.Enum)">
+ <summary>Tests if the code instruction loads an enum constant</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="e">The enum</param>
+ <returns>True if the instruction loads the constant</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.LoadsField(HarmonyLib.CodeInstruction,System.Reflection.FieldInfo,System.Boolean)">
+ <summary>Tests if the code instruction loads a field</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="field">The field</param>
+ <param name="byAddress">Set to true if the address of the field is loaded</param>
+ <returns>True if the instruction loads the field</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.StoresField(HarmonyLib.CodeInstruction,System.Reflection.FieldInfo)">
+ <summary>Tests if the code instruction stores a field</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="field">The field</param>
+ <returns>True if the instruction stores this field</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.WithLabels(HarmonyLib.CodeInstruction,System.Reflection.Emit.Label[])">
+ <summary>Adds labels to the code instruction and return it</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="labels">One or several <see cref="T:System.Reflection.Emit.Label"/> to add</param>
+ <returns>The same code instruction</returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.WithLabels(HarmonyLib.CodeInstruction,System.Collections.Generic.IEnumerable{System.Reflection.Emit.Label})">
+ <summary>Adds labels to the code instruction and return it</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="labels">An enumeration of <see cref="T:System.Reflection.Emit.Label"/></param>
+ <returns>The same code instruction</returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.ExtractLabels(HarmonyLib.CodeInstruction)">
+ <summary>Extracts all labels from the code instruction and returns them</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <returns>A list of <see cref="T:System.Reflection.Emit.Label"/></returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.MoveLabelsTo(HarmonyLib.CodeInstruction,HarmonyLib.CodeInstruction)">
+ <summary>Moves all labels from the code instruction to a different one</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the labels from</param>
+ <param name="other">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the labels to</param>
+ <returns>The code instruction labels were moved from (now empty)</returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.MoveLabelsFrom(HarmonyLib.CodeInstruction,HarmonyLib.CodeInstruction)">
+ <summary>Moves all labels from a different code instruction to the current one</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the labels from</param>
+ <param name="other">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the labels to</param>
+ <returns>The code instruction that received the labels</returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.WithBlocks(HarmonyLib.CodeInstruction,HarmonyLib.ExceptionBlock[])">
+ <summary>Adds ExceptionBlocks to the code instruction and return it</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="blocks">One or several <see cref="T:HarmonyLib.ExceptionBlock"/> to add</param>
+ <returns>The same code instruction</returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.WithBlocks(HarmonyLib.CodeInstruction,System.Collections.Generic.IEnumerable{HarmonyLib.ExceptionBlock})">
+ <summary>Adds ExceptionBlocks to the code instruction and return it</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <param name="blocks">An enumeration of <see cref="T:HarmonyLib.ExceptionBlock"/></param>
+ <returns>The same code instruction</returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.ExtractBlocks(HarmonyLib.CodeInstruction)">
+ <summary>Extracts all ExceptionBlocks from the code instruction and returns them</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/></param>
+ <returns>A list of <see cref="T:HarmonyLib.ExceptionBlock"/></returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.MoveBlocksTo(HarmonyLib.CodeInstruction,HarmonyLib.CodeInstruction)">
+ <summary>Moves all ExceptionBlocks from the code instruction to a different one</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the ExceptionBlocks from</param>
+ <param name="other">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the ExceptionBlocks to</param>
+ <returns>The code instruction blocks were moved from (now empty)</returns>
+ </member>
+ <member name="M:HarmonyLib.CodeInstructionExtensions.MoveBlocksFrom(HarmonyLib.CodeInstruction,HarmonyLib.CodeInstruction)">
+ <summary>Moves all ExceptionBlocks from a different code instruction to the current one</summary>
+ <param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the ExceptionBlocks from</param>
+ <param name="other">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the ExceptionBlocks to</param>
+ <returns>The code instruction that received the blocks</returns>
+ </member>
+ <member name="T:HarmonyLib.CollectionExtensions">
+ <summary>General extensions for collections</summary>
+
+ </member>
+ <member name="M:HarmonyLib.CollectionExtensions.Do``1(System.Collections.Generic.IEnumerable{``0},System.Action{``0})">
+ <summary>A simple way to execute code for every element in a collection</summary>
+ <typeparam name="T">The inner type of the collection</typeparam>
+ <param name="sequence">The collection</param>
+ <param name="action">The action to execute</param>
+
+ </member>
+ <member name="M:HarmonyLib.CollectionExtensions.DoIf``1(System.Collections.Generic.IEnumerable{``0},System.Func{``0,System.Boolean},System.Action{``0})">
+ <summary>A simple way to execute code for elements in a collection matching a condition</summary>
+ <typeparam name="T">The inner type of the collection</typeparam>
+ <param name="sequence">The collection</param>
+ <param name="condition">The predicate</param>
+ <param name="action">The action to execute</param>
+
+ </member>
+ <member name="M:HarmonyLib.CollectionExtensions.AddItem``1(System.Collections.Generic.IEnumerable{``0},``0)">
+ <summary>A helper to add an item to a collection</summary>
+ <typeparam name="T">The inner type of the collection</typeparam>
+ <param name="sequence">The collection</param>
+ <param name="item">The item to add</param>
+ <returns>The collection containing the item</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CollectionExtensions.AddToArray``1(``0[],``0)">
+ <summary>A helper to add an item to an array</summary>
+ <typeparam name="T">The inner type of the collection</typeparam>
+ <param name="sequence">The array</param>
+ <param name="item">The item to add</param>
+ <returns>The array containing the item</returns>
+
+ </member>
+ <member name="M:HarmonyLib.CollectionExtensions.AddRangeToArray``1(``0[],``0[])">
+ <summary>A helper to add items to an array</summary>
+ <typeparam name="T">The inner type of the collection</typeparam>
+ <param name="sequence">The array</param>
+ <param name="items">The items to add</param>
+ <returns>The array containing the items</returns>
+
+ </member>
+ <member name="T:HarmonyLib.MethodBaseExtensions">
+ <summary>General extensions for collections</summary>
+
+ </member>
+ <member name="M:HarmonyLib.MethodBaseExtensions.HasMethodBody(System.Reflection.MethodBase)">
+ <summary>Tests a class member if it has an IL method body (external methods for example don't have a body)</summary>
+ <param name="member">The member to test</param>
+ <returns>Returns true if the member has an IL body or false if not</returns>
+ </member>
+ <member name="T:HarmonyLib.FileLog">
+ <summary>A file log for debugging</summary>
+
+ </member>
+ <member name="F:HarmonyLib.FileLog.logPath">
+ <summary>Full pathname of the log file, defaults to a file called <c>harmony.log.txt</c> on your Desktop</summary>
+
+ </member>
+ <member name="F:HarmonyLib.FileLog.indentChar">
+ <summary>The indent character. The default is <c>tab</c></summary>
+
+ </member>
+ <member name="F:HarmonyLib.FileLog.indentLevel">
+ <summary>The current indent level</summary>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.ChangeIndent(System.Int32)">
+ <summary>Changes the indentation level</summary>
+ <param name="delta">The value to add to the indentation level</param>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.LogBuffered(System.String)">
+ <summary>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</summary>
+ <param name="str">The string to log</param>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.LogBuffered(System.Collections.Generic.List{System.String})">
+ <summary>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</summary>
+ <param name="strings">A list of strings to log (they will not be re-indented)</param>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.GetBuffer(System.Boolean)">
+ <summary>Returns the log buffer and optionally empties it</summary>
+ <param name="clear">True to empty the buffer</param>
+ <returns>The buffer.</returns>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.SetBuffer(System.Collections.Generic.List{System.String})">
+ <summary>Replaces the buffer with new lines</summary>
+ <param name="buffer">The lines to store</param>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.FlushBuffer">
+ <summary>Flushes the log buffer to disk (use in combination with LogBuffered)</summary>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.Log(System.String)">
+ <summary>Log a string directly to disk. Slower method that prevents missing information in case of a crash</summary>
+ <param name="str">The string to log.</param>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.Reset">
+ <summary>Resets and deletes the log</summary>
+
+ </member>
+ <member name="M:HarmonyLib.FileLog.LogBytes(System.Int64,System.Int32)">
+ <summary>Logs some bytes as hex values</summary>
+ <param name="ptr">The pointer to some memory</param>
+ <param name="len">The length of bytes to log</param>
+
+ </member>
+ <member name="T:HarmonyLib.SymbolExtensions">
+ <summary>A helper class to retrieve reflection info for non-private methods</summary>
+
+ </member>
+ <member name="M:HarmonyLib.SymbolExtensions.GetMethodInfo(System.Linq.Expressions.Expression{System.Action})">
+ <summary>Given a lambda expression that calls a method, returns the method info</summary>
+ <param name="expression">The lambda expression using the method</param>
+ <returns>The method in the lambda expression</returns>
+
+ </member>
+ <member name="M:HarmonyLib.SymbolExtensions.GetMethodInfo``1(System.Linq.Expressions.Expression{System.Action{``0}})">
+ <summary>Given a lambda expression that calls a method, returns the method info</summary>
+ <typeparam name="T">The generic type</typeparam>
+ <param name="expression">The lambda expression using the method</param>
+ <returns>The method in the lambda expression</returns>
+
+ </member>
+ <member name="M:HarmonyLib.SymbolExtensions.GetMethodInfo``2(System.Linq.Expressions.Expression{System.Func{``0,``1}})">
+ <summary>Given a lambda expression that calls a method, returns the method info</summary>
+ <typeparam name="T">The generic type</typeparam>
+ <typeparam name="TResult">The generic result type</typeparam>
+ <param name="expression">The lambda expression using the method</param>
+ <returns>The method in the lambda expression</returns>
+
+ </member>
+ <member name="M:HarmonyLib.SymbolExtensions.GetMethodInfo(System.Linq.Expressions.LambdaExpression)">
+ <summary>Given a lambda expression that calls a method, returns the method info</summary>
+ <param name="expression">The lambda expression using the method</param>
+ <returns>The method in the lambda expression</returns>
+
+ </member>
+ <member name="T:HarmonyLib.Traverse`1">
+ <summary>A reflection helper to read and write private elements</summary>
+ <typeparam name="T">The result type defined by GetValue()</typeparam>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse`1.#ctor(HarmonyLib.Traverse)">
+ <summary>Creates a traverse instance from an existing instance</summary>
+ <param name="traverse">The existing <see cref="T:HarmonyLib.Traverse"/> instance</param>
+
+ </member>
+ <member name="P:HarmonyLib.Traverse`1.Value">
+ <summary>Gets/Sets the current value</summary>
+ <value>The value to read or write</value>
+
+ </member>
+ <member name="T:HarmonyLib.Traverse">
+ <summary>A reflection helper to read and write private elements</summary>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Create(System.Type)">
+ <summary>Creates a new traverse instance from a class/type</summary>
+ <param name="type">The class/type</param>
+ <returns>A <see cref="T:HarmonyLib.Traverse"/> instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Create``1">
+ <summary>Creates a new traverse instance from a class T</summary>
+ <typeparam name="T">The class</typeparam>
+ <returns>A <see cref="T:HarmonyLib.Traverse"/> instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Create(System.Object)">
+ <summary>Creates a new traverse instance from an instance</summary>
+ <param name="root">The object</param>
+ <returns>A <see cref="T:HarmonyLib.Traverse"/> instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.CreateWithType(System.String)">
+ <summary>Creates a new traverse instance from a named type</summary>
+ <param name="name">The type name, for format see <see cref="M:HarmonyLib.AccessTools.TypeByName(System.String)"/></param>
+ <returns>A <see cref="T:HarmonyLib.Traverse"/> instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.#ctor">
+ <summary>Creates a new and empty traverse instance</summary>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.#ctor(System.Type)">
+ <summary>Creates a new traverse instance from a class/type</summary>
+ <param name="type">The class/type</param>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.#ctor(System.Object)">
+ <summary>Creates a new traverse instance from an instance</summary>
+ <param name="root">The object</param>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.GetValue">
+ <summary>Gets the current value</summary>
+ <value>The value</value>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.GetValue``1">
+ <summary>Gets the current value</summary>
+ <typeparam name="T">The type of the value</typeparam>
+ <value>The value</value>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.GetValue(System.Object[])">
+ <summary>Invokes the current method with arguments and returns the result</summary>
+ <param name="arguments">The method arguments</param>
+ <value>The value returned by the method</value>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.GetValue``1(System.Object[])">
+ <summary>Invokes the current method with arguments and returns the result</summary>
+ <typeparam name="T">The type of the value</typeparam>
+ <param name="arguments">The method arguments</param>
+ <value>The value returned by the method</value>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.SetValue(System.Object)">
+ <summary>Sets a value of the current field or property</summary>
+ <param name="value">The value</param>
+ <returns>The same traverse instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.GetValueType">
+ <summary>Gets the type of the current field or property</summary>
+ <returns>The type</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Type(System.String)">
+ <summary>Moves the current traverse instance to a inner type</summary>
+ <param name="name">The type name</param>
+ <returns>A traverse instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Field(System.String)">
+ <summary>Moves the current traverse instance to a field</summary>
+ <param name="name">The type name</param>
+ <returns>A traverse instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Field``1(System.String)">
+ <summary>Moves the current traverse instance to a field</summary>
+ <typeparam name="T">The type of the field</typeparam>
+ <param name="name">The type name</param>
+ <returns>A traverse instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Fields">
+ <summary>Gets all fields of the current type</summary>
+ <returns>A list of field names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Property(System.String,System.Object[])">
+ <summary>Moves the current traverse instance to a property</summary>
+ <param name="name">The type name</param>
+ <param name="index">Optional property index</param>
+ <returns>A traverse instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Property``1(System.String,System.Object[])">
+ <summary>Moves the current traverse instance to a field</summary>
+ <typeparam name="T">The type of the property</typeparam>
+ <param name="name">The type name</param>
+ <param name="index">Optional property index</param>
+ <returns>A traverse instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Properties">
+ <summary>Gets all properties of the current type</summary>
+ <returns>A list of property names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Method(System.String,System.Object[])">
+ <summary>Moves the current traverse instance to a method</summary>
+ <param name="name">The name of the method</param>
+ <param name="arguments">The arguments defining the argument types of the method overload</param>
+ <returns>A traverse instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Method(System.String,System.Type[],System.Object[])">
+ <summary>Moves the current traverse instance to a method</summary>
+ <param name="name">The name of the method</param>
+ <param name="paramTypes">The argument types of the method</param>
+ <param name="arguments">The arguments for the method</param>
+ <returns>A traverse instance</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.Methods">
+ <summary>Gets all methods of the current type</summary>
+ <returns>A list of method names</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.FieldExists">
+ <summary>Checks if the current traverse instance is for a field</summary>
+ <returns>True if its a field</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.PropertyExists">
+ <summary>Checks if the current traverse instance is for a property</summary>
+ <returns>True if its a property</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.MethodExists">
+ <summary>Checks if the current traverse instance is for a method</summary>
+ <returns>True if its a method</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.TypeExists">
+ <summary>Checks if the current traverse instance is for a type</summary>
+ <returns>True if its a type</returns>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.IterateFields(System.Object,System.Action{HarmonyLib.Traverse})">
+ <summary>Iterates over all fields of the current type and executes a traverse action</summary>
+ <param name="source">Original object</param>
+ <param name="action">The action receiving a <see cref="T:HarmonyLib.Traverse"/> instance for each field</param>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.IterateFields(System.Object,System.Object,System.Action{HarmonyLib.Traverse,HarmonyLib.Traverse})">
+ <summary>Iterates over all fields of the current type and executes a traverse action</summary>
+ <param name="source">Original object</param>
+ <param name="target">Target object</param>
+ <param name="action">The action receiving a pair of <see cref="T:HarmonyLib.Traverse"/> instances for each field pair</param>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.IterateFields(System.Object,System.Object,System.Action{System.String,HarmonyLib.Traverse,HarmonyLib.Traverse})">
+ <summary>Iterates over all fields of the current type and executes a traverse action</summary>
+ <param name="source">Original object</param>
+ <param name="target">Target object</param>
+ <param name="action">The action receiving a dot path representing the field pair and the <see cref="T:HarmonyLib.Traverse"/> instances</param>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.IterateProperties(System.Object,System.Action{HarmonyLib.Traverse})">
+ <summary>Iterates over all properties of the current type and executes a traverse action</summary>
+ <param name="source">Original object</param>
+ <param name="action">The action receiving a <see cref="T:HarmonyLib.Traverse"/> instance for each property</param>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.IterateProperties(System.Object,System.Object,System.Action{HarmonyLib.Traverse,HarmonyLib.Traverse})">
+ <summary>Iterates over all properties of the current type and executes a traverse action</summary>
+ <param name="source">Original object</param>
+ <param name="target">Target object</param>
+ <param name="action">The action receiving a pair of <see cref="T:HarmonyLib.Traverse"/> instances for each property pair</param>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.IterateProperties(System.Object,System.Object,System.Action{System.String,HarmonyLib.Traverse,HarmonyLib.Traverse})">
+ <summary>Iterates over all properties of the current type and executes a traverse action</summary>
+ <param name="source">Original object</param>
+ <param name="target">Target object</param>
+ <param name="action">The action receiving a dot path representing the property pair and the <see cref="T:HarmonyLib.Traverse"/> instances</param>
+
+ </member>
+ <member name="F:HarmonyLib.Traverse.CopyFields">
+ <summary>A default field action that copies fields to fields</summary>
+
+ </member>
+ <member name="M:HarmonyLib.Traverse.ToString">
+ <summary>Returns a string that represents the current traverse</summary>
+ <returns>A string representation</returns>
+
+ </member>
+ </members>
+</doc>
diff --git a/build/common.targets b/build/common.targets
index ce805402..d526a2bb 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
- <Version>3.11.0</Version>
+ <Version>3.12.0</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
@@ -38,7 +38,11 @@
<Copy SourceFiles="$(TargetDir)\SMAPI.config.json" DestinationFiles="$(GamePath)\smapi-internal\config.json" />
<Copy SourceFiles="$(TargetDir)\SMAPI.metadata.json" DestinationFiles="$(GamePath)\smapi-internal\metadata.json" />
<Copy SourceFiles="$(TargetDir)\0Harmony.dll" DestinationFolder="$(GamePath)\smapi-internal" />
+ <Copy SourceFiles="$(TargetDir)\0Harmony.xml" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)\smapi-internal" />
+ <Copy SourceFiles="$(TargetDir)\Mono.Cecil.Mdb.dll" DestinationFolder="$(GamePath)\smapi-internal" />
+ <Copy SourceFiles="$(TargetDir)\Mono.Cecil.Pdb.dll" DestinationFolder="$(GamePath)\smapi-internal" />
+ <Copy SourceFiles="$(TargetDir)\MonoMod.Common.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\TMXTile.dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\smapi-internal\i18n" />
diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets
index 408230a9..88e565f9 100644
--- a/build/prepare-install-package.targets
+++ b/build/prepare-install-package.targets
@@ -45,7 +45,11 @@
<Copy SourceFiles="$(SmapiBin)\StardewModdingAPI.xml" DestinationFolder="$(PackagePath)\bundle" />
<Copy SourceFiles="$(SmapiBin)\steam_appid.txt" DestinationFolder="$(PackagePath)\bundle" />
<Copy SourceFiles="$(SmapiBin)\0Harmony.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(SmapiBin)\0Harmony.xml" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(SmapiBin)\Mono.Cecil.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(SmapiBin)\Mono.Cecil.Mdb.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(SmapiBin)\Mono.Cecil.Pdb.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
+ <Copy SourceFiles="$(SmapiBin)\MonoMod.Common.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(SmapiBin)\Newtonsoft.Json.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(SmapiBin)\TMXTile.dll" DestinationFolder="$(PackagePath)\bundle\smapi-internal" />
<Copy SourceFiles="$(SmapiBin)\SMAPI.config.json" DestinationFiles="$(PackagePath)\bundle\smapi-internal\config.json" />
diff --git a/docs/release-notes.md b/docs/release-notes.md
index d0aa2fbf..36f07129 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,11 +1,23 @@
← [README](README.md)
# Release notes
-<!--
-## Future 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 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 (in _Console Commands_, thanks to bladeoflight16!).
+ * Fixed handling of Unicode characters in console commands.
+ * 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:
- * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
--->
+ * **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
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.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
/// <param name="executablePath">The absolute path to the executable file.</param>
private bool Is64Bit(string executablePath)
{
- return AssemblyName.GetAssemblyName(executablePath).ProcessorArchitecture != ProcessorArchitecture.X86;
+ return LowLevelEnvironmentUtility.Is64BitAssembly(executablePath);
}
/// <summary>Get the display text for a color scheme.</summary>
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
+{
+ /// <summary>Provides base implementation logic for <see cref="IPatcher"/> instances.</summary>
+ internal abstract class BasePatcher : IPatcher
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <inheritdoc />
+ public abstract void Apply(Harmony harmony, IMonitor monitor);
+
+
+ /*********
+ ** Protected methods
+ *********/
+ /// <summary>Get a method and assert that it was found.</summary>
+ /// <typeparam name="TTarget">The type containing the method.</typeparam>
+ /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
+ protected ConstructorInfo RequireConstructor<TTarget>(params Type[] parameters)
+ {
+ return PatchHelper.RequireConstructor<TTarget>(parameters);
+ }
+
+ /// <summary>Get a method and assert that it was found.</summary>
+ /// <typeparam name="TTarget">The type containing the method.</typeparam>
+ /// <param name="name">The method name.</param>
+ /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
+ /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
+ protected MethodInfo RequireMethod<TTarget>(string name, Type[] parameters = null, Type[] generics = null)
+ {
+ return PatchHelper.RequireMethod<TTarget>(name, parameters, generics);
+ }
+
+ /// <summary>Get a Harmony patch method on the current patcher instance.</summary>
+ /// <param name="name">The method name.</param>
+ /// <param name="priority">The patch priority to apply, usually specified using Harmony's <see cref="Priority"/> enum, or <c>null</c> to keep the default value.</param>
+ 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
+{
+ /// <summary>Simplifies applying <see cref="IPatcher"/> instances to the game.</summary>
+ internal static class HarmonyPatcher
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Apply the given Harmony patchers.</summary>
+ /// <param name="id">The mod ID applying the patchers.</param>
+ /// <param name="monitor">The monitor with which to log any errors.</param>
+ /// <param name="patchers">The patchers to apply.</param>
+ 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
+{
+ /// <summary>A set of Harmony patches to apply.</summary>
+ internal interface IPatcher
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Apply the Harmony patches for this instance.</summary>
+ /// <param name="harmony">The Harmony instance.</param>
+ /// <param name="monitor">The monitor with which to log any errors.</param>
+ 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..fc79ddf2
--- /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
+{
+ /// <summary>Provides utility methods for patching game code with Harmony.</summary>
+ internal static class PatchHelper
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get a constructor and assert that it was found.</summary>
+ /// <typeparam name="TTarget">The type containing the method.</typeparam>
+ /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
+ /// <exception cref="InvalidOperationException">The type has no matching constructor.</exception>
+ public static ConstructorInfo RequireConstructor<TTarget>(Type[] parameters = null)
+ {
+ return
+ AccessTools.Constructor(typeof(TTarget), parameters)
+ ?? throw new InvalidOperationException($"Can't find constructor {PatchHelper.GetMethodString(typeof(TTarget), null, parameters)} to patch.");
+ }
+
+ /// <summary>Get a method and assert that it was found.</summary>
+ /// <typeparam name="TTarget">The type containing the method.</typeparam>
+ /// <param name="name">The method name.</param>
+ /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
+ /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
+ /// <exception cref="InvalidOperationException">The type has no matching method.</exception>
+ public static MethodInfo RequireMethod<TTarget>(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.");
+ }
+
+ /// <summary>Get a human-readable representation of a method target.</summary>
+ /// <param name="type">The type containing the method.</param>
+ /// <param name="name">The method name, or <c>null</c> for a constructor.</param>
+ /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
+ /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
+ public static string GetMethodString(Type type, string name, Type[] parameters = null, Type[] generics = null)
+ {
+ StringBuilder str = new StringBuilder();
+
+ // 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+ <HasSharedItems>true</HasSharedItems>
+ <SharedGUID>6c16e948-3e5c-47a7-bf4b-07a7469a87a5</SharedGUID>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration">
+ <Import_RootNamespace>SMAPI.Internal.Patching</Import_RootNamespace>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildThisFileDirectory)BasePatcher.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)HarmonyPatcher.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)IPatcher.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)PatchHelper.cs" />
+ </ItemGroup>
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>6c16e948-3e5c-47a7-bf4b-07a7469a87a5</ProjectGuid>
+ <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+ </PropertyGroup>
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
+ <PropertyGroup />
+ <Import Project="SMAPI.Internal.Patching.projitems" Label="Shared" />
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
+</Project>
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
+{
+ /// <summary>Provides extension methods for handling exceptions.</summary>
+ internal static class ExceptionExtensions
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
+ /// <param name="exception">The error to summarize.</param>
+ 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();
+ }
+ }
+
+ /// <summary>Get the lowest exception in an exception stack.</summary>
+ /// <param name="exception">The exception from which to search.</param>
+ 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 @@
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\IConsoleWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)ExceptionExtensions.cs" />
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs
index 4cfaf242..ceaeb278 100644
--- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs
+++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs
@@ -15,7 +15,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
** Fields
*********/
/// <summary>The valid types that can be cleared.</summary>
- private readonly string[] ValidTypes = { "crops", "debris", "fruit-trees", "furniture", "grass", "trees", "everything" };
+ private readonly string[] ValidTypes = { "crops", "debris", "fruit-trees", "furniture", "grass", "trees", "removable", "everything" };
/// <summary>The resource clump IDs to consider debris.</summary>
private readonly int[] DebrisClumps = { ResourceClump.stumpIndex, ResourceClump.hollowLogIndex, ResourceClump.meteoriteIndex, ResourceClump.boulderIndex };
@@ -30,8 +30,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
name: "world_clear",
description: "Clears in-game entities in a given location.\n\n"
+ "Usage: world_clear <location> <object type>\n"
- + "- location: the location name for which to clear objects (like Farm), or 'current' for the current location.\n"
- + " - object type: the type of object clear. You can specify 'crops', 'debris' (stones/twigs/weeds and dead crops), 'furniture', 'grass', and 'trees' / 'fruit-trees'. You can also specify 'everything', which includes things not removed by the other types (like resource clumps)."
+ + " - location: the location name for which to clear objects (like Farm), or 'current' for the current location.\n"
+ + " - object type: the type of object clear. You can specify 'crops', 'debris' (stones/twigs/weeds and dead crops), 'furniture', 'grass', and 'trees' / 'fruit-trees'. You can also specify 'removable' (remove everything that can be removed or destroyed during normal gameplay) or 'everything' (remove everything including permanent bushes)."
)
{ }
@@ -133,13 +133,15 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
break;
}
+ case "removable":
case "everything":
{
+ bool everything = type == "everything";
int removed =
this.RemoveFurniture(location, p => true)
+ this.RemoveObjects(location, p => true)
+ this.RemoveTerrainFeatures(location, p => true)
- + this.RemoveLargeTerrainFeatures(location, p => true)
+ + this.RemoveLargeTerrainFeatures(location, p => everything || !(p is Bush bush) || bush.isDestroyable(location, p.currentTileLocation))
+ this.RemoveResourceClumps(location, p => true);
monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info);
break;
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/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
index d9426d75..067f6a8d 100644
--- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
+++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
@@ -1,8 +1,7 @@
+using System;
using System.Reflection;
using StardewModdingAPI.Events;
-using StardewModdingAPI.Framework;
-using StardewModdingAPI.Framework.Logging;
-using StardewModdingAPI.Framework.Patching;
+using StardewModdingAPI.Internal.Patching;
using StardewModdingAPI.Mods.ErrorHandler.Patches;
using StardewValley;
@@ -29,15 +28,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler
IMonitor monitorForGame = this.GetMonitorForGame();
// apply patches
- new GamePatcher(this.Monitor).Apply(
- new DialogueErrorPatch(monitorForGame, this.Helper.Reflection),
- new EventPatches(monitorForGame),
- new GameLocationPatches(monitorForGame),
- new ObjectErrorPatch(),
- new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved),
- new ScheduleErrorPatch(monitorForGame),
- new SpriteBatchValidationPatches(),
- new UtilityErrorPatches()
+ HarmonyPatcher.Apply(this.ModManifest.UniqueID, this.Monitor,
+ 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
@@ -70,12 +71,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler
/// <summary>Get the monitor with which to log game errors.</summary>
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/DialogueErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs
deleted file mode 100644
index cce13064..00000000
--- a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-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
-
-namespace StardewModdingAPI.Mods.ErrorHandler.Patches
-{
- /// <summary>A Harmony patch for the <see cref="Dialogue"/> constructor which intercepts invalid dialogue lines and logs an error instead of crashing.</summary>
- /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
- [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
- *********/
- /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
- private static IMonitor MonitorForGame;
-
- /// <summary>Simplifies access to private code.</summary>
- private static IReflectionHelper Reflection;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
- /// <param name="reflector">Simplifies access to private code.</param>
- public DialogueErrorPatch(IMonitor monitorForGame, IReflectionHelper reflector)
- {
- DialogueErrorPatch.MonitorForGame = monitorForGame;
- DialogueErrorPatch.Reflection = reflector;
- }
-
-
- /// <inheritdoc />
-#if HARMONY_2
- 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))
- );
- harmony.Patch(
- original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod,
- finalizer: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Finalize_NPC_CurrentDialogue))
- );
- }
-#else
- public void Apply(HarmonyInstance harmony)
- {
- harmony.Patch(
- original: AccessTools.Constructor(typeof(Dialogue), new[] { typeof(string), typeof(NPC) }),
- prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_Dialogue_Constructor))
- );
- harmony.Patch(
- original: AccessTools.Property(typeof(NPC), nameof(NPC.CurrentDialogue)).GetMethod,
- prefix: new HarmonyMethod(this.GetType(), nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue))
- );
- }
-#endif
-
-
- /*********
- ** Private methods
- *********/
-#if HARMONY_2
- /// <summary>The method to call after the Dialogue constructor.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="masterDialogue">The dialogue being parsed.</param>
- /// <param name="speaker">The NPC for which the dialogue is being parsed.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- 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;
- }
-
- /// <summary>The method to call after <see cref="NPC.CurrentDialogue"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- private static Exception Finalize_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __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<Dialogue>();
-
- return null;
- }
-#else
-
- /// <summary>The method to call instead of the Dialogue constructor.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="masterDialogue">The dialogue being parsed.</param>
- /// <param name="speaker">The NPC for which the dialogue is being parsed.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker)
- {
- // get private members
- bool nameArraysTranslated = DialogueErrorPatch.Reflection.GetField<bool>(typeof(Dialogue), "nameArraysTranslated").GetValue();
- IReflectedMethod translateArraysOfStrings = DialogueErrorPatch.Reflection.GetMethod(typeof(Dialogue), "TranslateArraysOfStrings");
- IReflectedMethod parseDialogueString = DialogueErrorPatch.Reflection.GetMethod(__instance, "parseDialogueString");
- IReflectedMethod checkForSpecialDialogueAttributes = DialogueErrorPatch.Reflection.GetMethod(__instance, "checkForSpecialDialogueAttributes");
-
- // replicate base constructor
- __instance.dialogues ??= new List<string>();
-
- // duplicate code with try..catch
- try
- {
- if (!nameArraysTranslated)
- translateArraysOfStrings.Invoke();
- __instance.speaker = speaker;
- parseDialogueString.Invoke(masterDialogue);
- checkForSpecialDialogueAttributes.Invoke();
- }
- catch (Exception baseEx) when (baseEx.InnerException is TargetInvocationException invocationEx && invocationEx.InnerException is Exception ex)
- {
- string name = !string.IsNullOrWhiteSpace(speaker?.Name) ? speaker.Name : null;
- DialogueErrorPatch.MonitorForGame.Log($"Failed parsing dialogue string{(name != null ? $" for {name}" : "")}:\n{masterDialogue}\n{ex}", LogLevel.Error);
-
- parseDialogueString.Invoke("...");
- checkForSpecialDialogueAttributes.Invoke();
- }
-
- return false;
- }
-
- /// <summary>The method to call instead of <see cref="NPC.CurrentDialogue"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="__originalMethod">The method being wrapped.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod)
- {
- const string key = nameof(DialogueErrorPatch.Before_NPC_CurrentDialogue);
- if (!PatchHelper.StartIntercept(key))
- return true;
-
- try
- {
- __result = (Stack<Dialogue>)__originalMethod.Invoke(__instance, new object[0]);
- return false;
- }
- catch (TargetInvocationException ex)
- {
- DialogueErrorPatch.MonitorForGame.Log($"Failed loading current dialogue for NPC {__instance.Name}:\n{ex.InnerException ?? ex}", LogLevel.Error);
- __result = new Stack<Dialogue>();
- return false;
- }
- finally
- {
- PatchHelper.StopIntercept(key);
- }
- }
-#endif
- }
-}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs
new file mode 100644
index 00000000..7a3af39c
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Internal;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="Dialogue"/> which intercept invalid dialogue lines and logs an error instead of crashing.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
+ private static IMonitor MonitorForGame;
+
+ /// <summary>Simplifies access to private code.</summary>
+ private static IReflectionHelper Reflection;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
+ /// <param name="reflector">Simplifies access to private code.</param>
+ public DialoguePatcher(IMonitor monitorForGame, IReflectionHelper reflector)
+ {
+ DialoguePatcher.MonitorForGame = monitorForGame;
+ DialoguePatcher.Reflection = reflector;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireConstructor<Dialogue>(typeof(string), typeof(NPC)),
+ finalizer: this.GetHarmonyMethod(nameof(DialoguePatcher.Finalize_Constructor))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call when the Dialogue constructor throws an exception.</summary>
+ /// <param name="__instance">The instance being patched.</param>
+ /// <param name="masterDialogue">The dialogue being parsed.</param>
+ /// <param name="speaker">The NPC for which the dialogue is being parsed.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_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..6ad64e16
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley.GameData;
+using StardewValley.GameData.HomeRenovations;
+using StardewValley.GameData.Movies;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="Dictionary{TKey,TValue}"/> which add the accessed key to <see cref="KeyNotFoundException"/> exceptions.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Simplifies access to private code.</summary>
+ private static IReflectionHelper Reflection;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="reflector">Simplifies access to private code.</param>
+ public DictionaryPatcher(IReflectionHelper reflector)
+ {
+ DictionaryPatcher.Reflection = reflector;
+ }
+
+ /// <inheritdoc />
+ 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) };
+
+ 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") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "get_Item")} to patch."),
+ finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_GetItem))
+ );
+ }
+ }
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call after the dictionary indexer throws an exception.</summary>
+ /// <param name="key">The dictionary key being fetched.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_GetItem(object key, Exception __exception)
+ {
+ if (__exception is KeyNotFoundException)
+ {
+ DictionaryPatcher.Reflection
+ .GetField<string>(__exception, "_message")
+ .SetValue($"{__exception.Message}\nkey: '{key}'");
+ }
+
+ return __exception;
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs
index 72863d17..1b706147 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs
@@ -1,11 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
-#if HARMONY_2
using HarmonyLib;
-#else
-using Harmony;
-#endif
-using StardewModdingAPI.Framework.Patching;
+using StardewModdingAPI.Internal.Patching;
using StardewValley;
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
@@ -14,7 +10,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
[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
+ internal class EventPatcher : BasePatcher
{
/*********
** Fields
@@ -28,21 +24,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
*********/
/// <summary>Construct an instance.</summary>
/// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
- public EventPatches(IMonitor monitorForGame)
+ public EventPatcher(IMonitor monitorForGame)
{
- EventPatches.MonitorForGame = monitorForGame;
+ EventPatcher.MonitorForGame = monitorForGame;
}
/// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
-#else
- public void Apply(HarmonyInstance harmony)
-#endif
+ public override void Apply(Harmony harmony, IMonitor monitor)
{
harmony.Patch(
- original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)),
- postfix: new HarmonyMethod(this.GetType(), nameof(EventPatches.After_Event_LogErrorAndHalt))
+ original: this.RequireMethod<Event>(nameof(Event.LogErrorAndHalt)),
+ postfix: this.GetHarmonyMethod(nameof(EventPatcher.After_LogErrorAndHalt))
);
}
@@ -52,9 +44,9 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
*********/
/// <summary>The method to call after <see cref="Event.LogErrorAndHalt"/>.</summary>
/// <param name="e">The exception being logged.</param>
- private static void After_Event_LogErrorAndHalt(Exception e)
+ private static void After_LogErrorAndHalt(Exception e)
{
- EventPatches.MonitorForGame.Log(e.ToString(), LogLevel.Error);
+ 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
new file mode 100644
index 00000000..7df6b0a2
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley;
+using xTile;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="GameLocation"/> which intercept errors instead of crashing.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
+ private static IMonitor MonitorForGame;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
+ public GameLocationPatcher(IMonitor monitorForGame)
+ {
+ GameLocationPatcher.MonitorForGame = monitorForGame;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireMethod<GameLocation>(nameof(GameLocation.checkEventPrecondition)),
+ finalizer: this.GetHarmonyMethod(nameof(GameLocationPatcher.Finalize_CheckEventPrecondition))
+ );
+ harmony.Patch(
+ original: this.RequireMethod<GameLocation>(nameof(GameLocation.updateSeasonalTileSheets)),
+ finalizer: this.GetHarmonyMethod(nameof(GameLocationPatcher.Finalize_UpdateSeasonalTileSheets))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call when <see cref="GameLocation.checkEventPrecondition"/> throws an exception.</summary>
+ /// <param name="__result">The return value of the original method.</param>
+ /// <param name="precondition">The precondition to be parsed.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_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;
+ }
+
+ /// <summary>The method to call when <see cref="GameLocation.updateSeasonalTileSheets"/> throws an exception.</summary>
+ /// <param name="__instance">The instance being patched.</param>
+ /// <param name="map">The map whose tilesheets to update.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ 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);
+
+ 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 7a48133e..00000000
--- a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs
+++ /dev/null
@@ -1,158 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-#if HARMONY_2
-using System;
-using HarmonyLib;
-#else
-using System.Reflection;
-using Harmony;
-#endif
-using StardewModdingAPI.Framework.Patching;
-using StardewValley;
-using xTile;
-
-namespace StardewModdingAPI.Mods.ErrorHandler.Patches
-{
- /// <summary>Harmony patches for <see cref="GameLocation.checkEventPrecondition"/> and <see cref="GameLocation.updateSeasonalTileSheets"/> which intercept errors instead of crashing.</summary>
- /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
- [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
- *********/
- /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
- private static IMonitor MonitorForGame;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
- public GameLocationPatches(IMonitor monitorForGame)
- {
- GameLocationPatches.MonitorForGame = monitorForGame;
- }
-
- /// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
- {
- harmony.Patch(
- original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)),
- finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Finalize_GameLocation_CheckEventPrecondition))
- );
-harmony.Patch(
- original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)),
- finalizer: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_UpdateSeasonalTileSheets))
- );
- }
-#else
- public void Apply(HarmonyInstance harmony)
- {
- harmony.Patch(
- original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.checkEventPrecondition)),
- prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition))
- );
- harmony.Patch(
- original: AccessTools.Method(typeof(GameLocation), nameof(GameLocation.updateSeasonalTileSheets)),
- prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets))
- );
- }
-#endif
-
-
- /*********
- ** Private methods
- *********/
-#if HARMONY_2
- /// <summary>The method to call instead of GameLocation.checkEventPrecondition.</summary>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="precondition">The precondition to be parsed.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- private static Exception Finalize_GameLocation_CheckEventPrecondition(ref int __result, string precondition, Exception __exception)
- {
- if (__exception != null)
- {
- __result = -1;
- EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{__exception.InnerException}", LogLevel.Error);
- }
-
- return null;
- }
-#else
- /// <summary>The method to call instead of <see cref="GameLocation.checkEventPrecondition"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="precondition">The precondition to be parsed.</param>
- /// <param name="__originalMethod">The method being wrapped.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod)
- {
- const string key = nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition);
- if (!PatchHelper.StartIntercept(key))
- return true;
-
- try
- {
- __result = (int)__originalMethod.Invoke(__instance, new object[] { precondition });
- return false;
- }
- catch (TargetInvocationException ex)
- {
- __result = -1;
- GameLocationPatches.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error);
- return false;
- }
- finally
- {
- PatchHelper.StopIntercept(key);
- }
- }
-#endif
-
-#if HARMONY_2
- /// <summary>The method to call instead of <see cref="GameLocation.updateSeasonalTileSheets"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="map">The map whose tilesheets to update.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- 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;
- }
-#else
- /// <summary>The method to call instead of <see cref="GameLocation.updateSeasonalTileSheets"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="map">The map whose tilesheets to update.</param>
- /// <param name="__originalMethod">The method being wrapped.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_GameLocation_UpdateSeasonalTileSheets(GameLocation __instance, Map map, MethodInfo __originalMethod)
- {
- const string key = nameof(GameLocationPatches.Before_GameLocation_UpdateSeasonalTileSheets);
- if (!PatchHelper.StartIntercept(key))
- return true;
-
- try
- {
- __originalMethod.Invoke(__instance, new object[] { map });
- return false;
- }
- catch (TargetInvocationException ex)
- {
- GameLocationPatches.MonitorForGame.Log($"Failed updating seasonal tilesheets for location '{__instance.NameOrUniqueName}'. Technical details:\n{ex.InnerException}", LogLevel.Error);
- return false;
- }
- finally
- {
- PatchHelper.StopIntercept(key);
- }
- }
-#endif
- }
-}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs
new file mode 100644
index 00000000..b65a695a
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs
@@ -0,0 +1,44 @@
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley;
+using StardewValley.Menus;
+using SObject = StardewValley.Object;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="IClickableMenu"/> which intercept crashes due to invalid items.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireMethod<IClickableMenu>(nameof(IClickableMenu.drawToolTip)),
+ prefix: this.GetHarmonyMethod(nameof(IClickableMenuPatcher.Before_DrawTooltip))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call instead of <see cref="IClickableMenu.drawToolTip"/>.</summary>
+ /// <param name="hoveredItem">The item for which to draw a tooltip.</param>
+ /// <returns>Returns whether to execute the original method.</returns>
+ 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))
+ return false;
+
+ return true;
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs
deleted file mode 100644
index 52d5f5a1..00000000
--- a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-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;
-using StardewValley.Buildings;
-using StardewValley.Locations;
-
-namespace StardewModdingAPI.Mods.ErrorHandler.Patches
-{
- /// <summary>A Harmony patch for <see cref="SaveGame"/> which prevents some errors due to broken save data.</summary>
- /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
- [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
- *********/
- /// <summary>Writes messages to the console and log file.</summary>
- private static IMonitor Monitor;
-
- /// <summary>A callback invoked when custom content is removed from the save data to avoid a crash.</summary>
- private static Action OnContentRemoved;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="monitor">Writes messages to the console and log file.</param>
- /// <param name="onContentRemoved">A callback invoked when custom content is removed from the save data to avoid a crash.</param>
- public LoadErrorPatch(IMonitor monitor, Action onContentRemoved)
- {
- LoadErrorPatch.Monitor = monitor;
- LoadErrorPatch.OnContentRemoved = onContentRemoved;
- }
-
-
- /// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
-#else
- public void Apply(HarmonyInstance harmony)
-#endif
- {
- harmony.Patch(
- original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)),
- prefix: new HarmonyMethod(this.GetType(), nameof(LoadErrorPatch.Before_SaveGame_LoadDataToLocations))
- );
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>The method to call instead of <see cref="SaveGame.loadDataToLocations"/>.</summary>
- /// <param name="gamelocations">The game locations being loaded.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_SaveGame_LoadDataToLocations(List<GameLocation> gamelocations)
- {
- bool removedAny =
- LoadErrorPatch.RemoveBrokenBuildings(gamelocations)
- | LoadErrorPatch.RemoveInvalidNpcs(gamelocations);
-
- if (removedAny)
- LoadErrorPatch.OnContentRemoved();
-
- return true;
- }
-
- /// <summary>Remove buildings which don't exist in the game data.</summary>
- /// <param name="locations">The current game locations.</param>
- private static bool RemoveBrokenBuildings(IEnumerable<GameLocation> locations)
- {
- bool removedAny = false;
-
- foreach (BuildableGameLocation location in locations.OfType<BuildableGameLocation>())
- {
- foreach (Building building in location.buildings.ToArray())
- {
- try
- {
- BluePrint _ = new BluePrint(building.buildingType.Value);
- }
- catch (SContentLoadException)
- {
- 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;
- }
-
- /// <summary>Remove NPCs which don't exist in the game data.</summary>
- /// <param name="locations">The current game locations.</param>
- private static bool RemoveInvalidNpcs(IEnumerable<GameLocation> locations)
- {
- bool removedAny = false;
-
- IDictionary<string, string> data = Game1.content.Load<Dictionary<string, string>>("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;
- }
-
- /// <summary>Get all locations, including building interiors.</summary>
- /// <param name="locations">The main game locations.</param>
- private static IEnumerable<GameLocation> GetAllLocations(IEnumerable<GameLocation> 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..275bb5bf
--- /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.Internal;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="NPC"/> which intercept crashes due to invalid schedule data.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
+ private static IMonitor MonitorForGame;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
+ public NpcPatcher(IMonitor monitorForGame)
+ {
+ NpcPatcher.MonitorForGame = monitorForGame;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireMethod<NPC>($"get_{nameof(NPC.CurrentDialogue)}"),
+ finalizer: this.GetHarmonyMethod(nameof(NpcPatcher.Finalize_CurrentDialogue))
+ );
+
+ harmony.Patch(
+ original: this.RequireMethod<NPC>(nameof(NPC.parseMasterSchedule)),
+ finalizer: this.GetHarmonyMethod(nameof(NpcPatcher.Finalize_ParseMasterSchedule))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call when <see cref="NPC.CurrentDialogue"/> throws an exception.</summary>
+ /// <param name="__instance">The instance being patched.</param>
+ /// <param name="__result">The return value of the original method.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __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<Dialogue>();
+
+ return null;
+ }
+
+ /// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary>
+ /// <param name="rawData">The raw schedule data to parse.</param>
+ /// <param name="__instance">The instance being patched.</param>
+ /// <param name="__result">The patched method's return value.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_ParseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __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<int, SchedulePathDescription>();
+ }
+
+ 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 9f8a98cd..00000000
--- a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-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
-{
- /// <summary>A Harmony patch for <see cref="SObject.getDescription"/> which intercepts crashes due to the item no longer existing.</summary>
- /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
- [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
- *********/
- /// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
-#else
- public void Apply(HarmonyInstance harmony)
-#endif
- {
- // 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"),
-#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
- harmony.Patch(
- original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawToolTip)),
- prefix: new HarmonyMethod(this.GetType(), nameof(ObjectErrorPatch.Before_IClickableMenu_DrawTooltip))
- );
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>The method to call instead of <see cref="StardewValley.Object.getDescription"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The patched method's return value.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- 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;
- }
-
-#if HARMONY_2
- /// <summary>The method to call after <see cref="StardewValley.Object.loadDisplayName"/>.</summary>
- /// <param name="__result">The patched method's return value.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- private static Exception Finalize_Object_loadDisplayName(ref string __result, Exception __exception)
- {
- if (__exception is KeyNotFoundException)
- {
- __result = "???";
- return null;
- }
-
- return __exception;
- }
-#else
- /// <summary>The method to call instead of <see cref="StardewValley.Object.loadDisplayName"/>.</summary>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The patched method's return value.</param>
- /// <param name="__originalMethod">The method being wrapped.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_Object_loadDisplayName(SObject __instance, ref string __result, MethodInfo __originalMethod)
- {
- const string key = nameof(ObjectErrorPatch.Before_Object_loadDisplayName);
- if (!PatchHelper.StartIntercept(key))
- return true;
-
- try
- {
- __result = (string)__originalMethod.Invoke(__instance, new object[0]);
- return false;
- }
- catch (TargetInvocationException ex) when (ex.InnerException is KeyNotFoundException)
- {
- __result = "???";
- return false;
- }
- catch
- {
- return true;
- }
- finally
- {
- PatchHelper.StopIntercept(key);
- }
- }
-#endif
-
- /// <summary>The method to call instead of <see cref="IClickableMenu.drawToolTip"/>.</summary>
- /// <param name="hoveredItem">The item for which to draw a tooltip.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- 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/ObjectPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs
new file mode 100644
index 00000000..fd4ea35c
--- /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.Internal.Patching;
+using StardewValley;
+using SObject = StardewValley.Object;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="SObject"/> which intercept crashes due to invalid items.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ // object.getDescription
+ harmony.Patch(
+ original: this.RequireMethod<SObject>(nameof(SObject.getDescription)),
+ prefix: this.GetHarmonyMethod(nameof(ObjectPatcher.Before_Object_GetDescription))
+ );
+
+ // object.getDisplayName
+ harmony.Patch(
+ original: this.RequireMethod<SObject>("loadDisplayName"),
+ finalizer: this.GetHarmonyMethod(nameof(ObjectPatcher.Finalize_Object_loadDisplayName))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call instead of <see cref="StardewValley.Object.getDescription"/>.</summary>
+ /// <param name="__instance">The instance being patched.</param>
+ /// <param name="__result">The patched method's return value.</param>
+ /// <returns>Returns whether to execute the original method.</returns>
+ 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;
+ }
+
+ /// <summary>The method to call after <see cref="StardewValley.Object.loadDisplayName"/>.</summary>
+ /// <param name="__result">The patched method's return value.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ 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..2a43cb10
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using HarmonyLib;
+using Microsoft.Xna.Framework.Content;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley;
+using StardewValley.Buildings;
+using StardewValley.Locations;
+using SObject = StardewValley.Object;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="SaveGame"/> which prevent some errors due to broken save data.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Writes messages to the console and log file.</summary>
+ private static IMonitor Monitor;
+
+ /// <summary>A callback invoked when custom content is removed from the save data to avoid a crash.</summary>
+ private static Action OnContentRemoved;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitor">Writes messages to the console and log file.</param>
+ /// <param name="onContentRemoved">A callback invoked when custom content is removed from the save data to avoid a crash.</param>
+ public SaveGamePatcher(IMonitor monitor, Action onContentRemoved)
+ {
+ SaveGamePatcher.Monitor = monitor;
+ SaveGamePatcher.OnContentRemoved = onContentRemoved;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireMethod<SaveGame>(nameof(SaveGame.loadDataToLocations)),
+ prefix: this.GetHarmonyMethod(nameof(SaveGamePatcher.Before_LoadDataToLocations))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call instead of <see cref="SaveGame.loadDataToLocations"/>.</summary>
+ /// <param name="gamelocations">The game locations being loaded.</param>
+ /// <returns>Returns whether to execute the original method.</returns>
+ private static bool Before_LoadDataToLocations(List<GameLocation> gamelocations)
+ {
+ IDictionary<string, string> npcs = Game1.content.Load<Dictionary<string, string>>("Data\\NPCDispositions");
+
+ if (SaveGamePatcher.RemoveBrokenContent(gamelocations, npcs))
+ SaveGamePatcher.OnContentRemoved();
+
+ return true;
+ }
+
+ /// <summary>Remove content which no longer exists in the game data.</summary>
+ /// <param name="locations">The current game locations.</param>
+ /// <param name="npcs">The NPC data.</param>
+ private static bool RemoveBrokenContent(IEnumerable<GameLocation> locations, IDictionary<string, string> npcs)
+ {
+ bool removedAny = false;
+
+ foreach (GameLocation location in locations)
+ removedAny |= SaveGamePatcher.RemoveBrokenContent(location, npcs);
+
+ return removedAny;
+ }
+
+ /// <summary>Remove content which no longer exists in the game data.</summary>
+ /// <param name="location">The current game location.</param>
+ /// <param name="npcs">The NPC data.</param>
+ private static bool RemoveBrokenContent(GameLocation location, IDictionary<string, string> npcs)
+ {
+ bool removedAny = false;
+ if (location == null)
+ return false;
+
+ // check buildings
+ if (location is BuildableGameLocation buildableLocation)
+ {
+ foreach (Building building in buildableLocation.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);
+ buildableLocation.buildings.Remove(building);
+ removedAny = true;
+ continue;
+ }
+
+ SaveGamePatcher.RemoveBrokenContent(building.indoors.Value, npcs);
+ }
+ }
+
+ // check NPCs
+ foreach (NPC npc in location.characters.ToArray())
+ {
+ if (npc.isVillager() && !npcs.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;
+ }
+ }
+ }
+
+ // check objects
+ foreach (var pair in location.objects.Pairs.ToArray())
+ {
+ // SpaceCore can leave null values when removing its custom content
+ if (pair.Value == null)
+ {
+ location.Objects.Remove(pair.Key);
+ SaveGamePatcher.Monitor.Log($"Removed invalid null object in {location.Name} ({pair.Key}) to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom item mod?)", LogLevel.Warn);
+ removedAny = true;
+ }
+ }
+
+ return removedAny;
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs
deleted file mode 100644
index d2a5e988..00000000
--- a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-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
-
-namespace StardewModdingAPI.Mods.ErrorHandler.Patches
-{
- /// <summary>A Harmony patch for <see cref="NPC.parseMasterSchedule"/> which intercepts crashes due to invalid schedule data.</summary>
- /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
- [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
- *********/
- /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
- private static IMonitor MonitorForGame;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
- public ScheduleErrorPatch(IMonitor monitorForGame)
- {
- ScheduleErrorPatch.MonitorForGame = monitorForGame;
- }
-
- /// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
-#else
- public void Apply(HarmonyInstance harmony)
-#endif
- {
- harmony.Patch(
- original: AccessTools.Method(typeof(NPC), nameof(NPC.parseMasterSchedule)),
-#if HARMONY_2
- finalizer: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Finalize_NPC_parseMasterSchedule))
-#else
- prefix: new HarmonyMethod(this.GetType(), nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule))
-#endif
- );
- }
-
-
- /*********
- ** Private methods
- *********/
-#if HARMONY_2
- /// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary>
- /// <param name="rawData">The raw schedule data to parse.</param>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The patched method's return value.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- private static Exception Finalize_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __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<int, SchedulePathDescription>();
- }
-
- return null;
- }
-#else
- /// <summary>The method to call instead of <see cref="NPC.parseMasterSchedule"/>.</summary>
- /// <param name="rawData">The raw schedule data to parse.</param>
- /// <param name="__instance">The instance being patched.</param>
- /// <param name="__result">The patched method's return value.</param>
- /// <param name="__originalMethod">The method being wrapped.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_NPC_parseMasterSchedule(string rawData, NPC __instance, ref Dictionary<int, SchedulePathDescription> __result, MethodInfo __originalMethod)
- {
- const string key = nameof(ScheduleErrorPatch.Before_NPC_parseMasterSchedule);
- if (!PatchHelper.StartIntercept(key))
- return true;
-
- try
- {
- __result = (Dictionary<int, SchedulePathDescription>)__originalMethod.Invoke(__instance, new object[] { rawData });
- return false;
- }
- catch (TargetInvocationException ex)
- {
- ScheduleErrorPatch.MonitorForGame.Log($"Failed parsing schedule for NPC {__instance.Name}:\n{rawData}\n{ex.InnerException ?? ex}", LogLevel.Error);
- __result = new Dictionary<int, SchedulePathDescription>();
- return false;
- }
- finally
- {
- PatchHelper.StopIntercept(key);
- }
- }
-#endif
- }
-}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs
index 8056fd71..6860a4ec 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs
@@ -1,36 +1,28 @@
-#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;
+using StardewModdingAPI.Internal.Patching;
namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{
- /// <summary>Harmony patch for <see cref="SpriteBatch"/> to validate textures earlier.</summary>
+ /// <summary>Harmony patches for <see cref="SpriteBatch"/> which validate textures earlier.</summary>
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
[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
+ internal class SpriteBatchPatcher : BasePatcher
{
/*********
** Public methods
*********/
/// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
-#else
- public void Apply(HarmonyInstance harmony)
-#endif
+ 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(SpriteBatchValidationPatches.After_SpriteBatch_CheckValid))
+ ? this.RequireMethod<SpriteBatch>("InternalDraw")
+ : this.RequireMethod<SpriteBatch>("CheckValid", new[] { typeof(Texture2D) }),
+ postfix: this.GetHarmonyMethod(nameof(SpriteBatchPatcher.After_CheckValid))
);
}
@@ -39,13 +31,13 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
** Private methods
*********/
#if SMAPI_FOR_XNA
- /// <summary>The method to call instead of <see cref="SpriteBatch.InternalDraw"/>.</summary>
+ /// <summary>The method to call after <see cref="SpriteBatch.InternalDraw"/>.</summary>
/// <param name="texture">The texture to validate.</param>
#else
- /// <summary>The method to call instead of <see cref="SpriteBatch.CheckValid"/>.</summary>
+ /// <summary>The method to call after <see cref="SpriteBatch.CheckValid"/>.</summary>
/// <param name="texture">The texture to validate.</param>
#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/UtilityErrorPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs
deleted file mode 100644
index 1ddd407c..00000000
--- a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-#if HARMONY_2
-using System;
-using HarmonyLib;
-#else
-using Harmony;
-#endif
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
-using StardewModdingAPI.Framework.Patching;
-using StardewValley;
-
-namespace StardewModdingAPI.Mods.ErrorHandler.Patches
-{
- /// <summary>A Harmony patch for <see cref="Utility"/> methods to log more detailed errors.</summary>
- /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
- [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
- *********/
- /// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
- {
- harmony.Patch(
- original: AccessTools.Method(typeof(Utility), nameof(Utility.getItemFromStandardTextDescription)),
- finalizer: new HarmonyMethod(this.GetType(), nameof(UtilityErrorPatches.Finalize_Utility_GetItemFromStandardTextDescription))
- );
- }
-#else
- public void Apply(HarmonyInstance harmony)
- {
- harmony.Patch(
- original: AccessTools.Method(typeof(Utility), nameof(Utility.getItemFromStandardTextDescription)),
- prefix: new HarmonyMethod(this.GetType(), nameof(UtilityErrorPatches.Before_Utility_GetItemFromStandardTextDescription))
- );
- }
-#endif
-
-
- /*********
- ** Private methods
- *********/
-#if HARMONY_2
- /// <summary>The method to call instead of <see cref="Utility.getItemFromStandardTextDescription"/>.</summary>
- /// <param name="description">The item text description to parse.</param>
- /// <param name="delimiter">The delimiter by which to split the text description.</param>
- /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
- /// <returns>Returns the exception to throw, if any.</returns>
- private static Exception Finalize_Utility_GetItemFromStandardTextDescription(string description, char delimiter, ref Exception __exception)
- {
- return __exception != null
- ? new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", __exception)
- : null;
- }
-#else
- /// <summary>The method to call instead of <see cref="Utility.getItemFromStandardTextDescription"/>.</summary>
- /// <param name="__result">The return value of the original method.</param>
- /// <param name="description">The item text description to parse.</param>
- /// <param name="who">The player for which the item is being parsed.</param>
- /// <param name="delimiter">The delimiter by which to split the text description.</param>
- /// <param name="__originalMethod">The method being wrapped.</param>
- /// <returns>Returns whether to execute the original method.</returns>
- private static bool Before_Utility_GetItemFromStandardTextDescription(ref Item __result, string description, Farmer who, char delimiter, MethodInfo __originalMethod)
- {
- const string key = nameof(UtilityErrorPatches.Before_Utility_GetItemFromStandardTextDescription);
- if (!PatchHelper.StartIntercept(key))
- return true;
-
- try
- {
- __result = (Item)__originalMethod.Invoke(null, new object[] { description, who, delimiter });
- return false;
- }
- catch (TargetInvocationException ex)
- {
- throw new FormatException($"Failed to parse item text description \"{description}\" with delimiter \"{delimiter}\".", ex.InnerException);
- }
- finally
- {
- PatchHelper.StopIntercept(key);
- }
- }
-#endif
- }
-}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs
new file mode 100644
index 00000000..ce85d0c2
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>A Harmony patch for <see cref="Utility"/> methods to log more detailed errors.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireMethod<Utility>(nameof(Utility.getItemFromStandardTextDescription)),
+ finalizer: this.GetHarmonyMethod(nameof(UtilityPatcher.Finalize_GetItemFromStandardTextDescription))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call when <see cref="Utility.getItemFromStandardTextDescription"/> throws an exception.</summary>
+ /// <param name="description">The item text description to parse.</param>
+ /// <param name="delimiter">The delimiter by which to split the text description.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ 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)
+ : null;
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
index 006a09ca..ffda5f89 100644
--- a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
+++ b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
@@ -15,6 +15,7 @@
<ItemGroup>
<Reference Include="$(GameExecutableName)" HintPath="$(GamePath)\$(GameExecutableName).exe" Private="False" />
+ <Reference Include="StardewValley.GameData" HintPath="$(GamePath)\StardewValley.GameData.dll" Private="False" />
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
</ItemGroup>
@@ -46,4 +47,5 @@
</ItemGroup>
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
+ <Import Project="..\SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems" Label="Shared" />
</Project>
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.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
}
}
-
/// <summary>Get the human-readable OS name and version.</summary>
/// <param name="platform">The current platform.</param>
[SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")]
@@ -89,6 +89,13 @@ namespace StardewModdingAPI.Toolkit.Framework
: "StardewValley.exe";
}
+ /// <summary>Get whether an executable is 64-bit.</summary>
+ /// <param name="executablePath">The absolute path to the executable file.</param>
+ public static bool Is64BitAssembly(string executablePath)
+ {
+ return AssemblyName.GetAssemblyName(executablePath).ProcessorArchitecture != ProcessorArchitecture.X86;
+ }
+
/*********
** Private methods
diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
index 8dc4d559..0e051c84 100644
--- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
+++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj
@@ -19,6 +19,4 @@
<ItemGroup>
<ProjectReference Include="..\SMAPI.Toolkit.CoreInterfaces\SMAPI.Toolkit.CoreInterfaces.csproj" />
</ItemGroup>
-
- <Import Project="..\..\build\common.targets" />
</Project>
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.sln.DotSettings b/src/SMAPI.sln.DotSettings
index 29d4ade5..9a6cad37 100644
--- a/src/SMAPI.sln.DotSettings
+++ b/src/SMAPI.sln.DotSettings
@@ -36,6 +36,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=fallbacks/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=filenames/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gamepad/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/UserDictionary/Words/=gameplay/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hangfire/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=initializers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Junimo/@EntryIndexedValue">True</s:Boolean>
@@ -48,6 +49,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Netcode/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=overworld/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pastebin/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/UserDictionary/Words/=pathfinding/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Pathoschild/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=premultiplied/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=premultiply/@EntryIndexedValue">True</s:Boolean>
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 9e93551c..3877e17a 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -61,7 +61,7 @@ namespace StardewModdingAPI
internal static int? LogScreenId { get; set; }
/// <summary>SMAPI's current raw semantic version.</summary>
- internal static string RawApiVersion = "3.11.0";
+ internal static string RawApiVersion = "3.12.0";
}
/// <summary>Contains SMAPI's constants and assumptions.</summary>
@@ -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
diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
index f3731d16..45b34556 100644
--- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
+++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
@@ -3,25 +3,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
-#if HARMONY_2
using HarmonyLib;
-#else
-using Harmony;
-#endif
namespace StardewModdingAPI.Framework.Commands
{
/// <summary>The 'harmony_summary' SMAPI console command.</summary>
internal class HarmonySummaryCommand : IInternalCommand
{
-#if !HARMONY_2
- /*********
- ** Fields
- *********/
- /// <summary>The Harmony instance through which to fetch patch info.</summary>
- private readonly HarmonyInstance HarmonyInstance = HarmonyInstance.Create($"SMAPI.{nameof(HarmonySummaryCommand)}");
-#endif
-
/*********
** Accessors
*********/
@@ -60,9 +48,7 @@ namespace StardewModdingAPI.Framework.Commands
{
PatchType.Prefix => 0,
PatchType.Postfix => 1,
-#if HARMONY_2
PatchType.Finalizer => 2,
-#endif
PatchType.Transpiler => 3,
_ => 4
});
@@ -111,26 +97,16 @@ namespace StardewModdingAPI.Framework.Commands
/// <summary>Get all current Harmony patches.</summary>
private IEnumerable<SearchResult> GetAllPatches()
{
-#if HARMONY_2
foreach (MethodBase method in Harmony.GetAllPatchedMethods().ToArray())
-#else
- foreach (MethodBase method in this.HarmonyInstance.GetPatchedMethods().ToArray())
-#endif
{
// get metadata for method
-#if HARMONY_2
HarmonyLib.Patches patchInfo = Harmony.GetPatchInfo(method);
-#else
- Harmony.Patches patchInfo = this.HarmonyInstance.GetPatchInfo(method);
-#endif
IDictionary<PatchType, IReadOnlyCollection<Patch>> patchGroups = new Dictionary<PatchType, IReadOnlyCollection<Patch>>
{
[PatchType.Prefix] = patchInfo.Prefixes,
[PatchType.Postfix] = patchInfo.Postfixes,
-#if HARMONY_2
[PatchType.Finalizer] = patchInfo.Finalizers,
-#endif
[PatchType.Transpiler] = patchInfo.Transpilers
};
@@ -160,10 +136,8 @@ namespace StardewModdingAPI.Framework.Commands
/// <summary>A postfix patch.</summary>
Postfix,
-#if HARMONY_2
/// <summary>A finalizer patch.</summary>
Finalizer,
-#endif
/// <summary>A transpiler patch.</summary>
Transpiler
diff --git a/src/SMAPI/Framework/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 4f6aa775..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;
@@ -77,6 +78,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <inheritdoc />
public override T Load<T>(string assetName, LanguageCode language, bool useCache)
{
+ // normalize key
+ bool isXnbFile = Path.GetExtension(assetName).ToLower() == ".xnb";
assetName = this.AssertAndNormalizeAssetName(assetName);
// disable caching
@@ -108,7 +111,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.");
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
/// <summary>Provides extension methods for SMAPI's internal use.</summary>
internal static class InternalExtensions
{
+ /*********
+ ** Public methods
+ *********/
/****
** IMonitor
****/
@@ -55,38 +58,6 @@ namespace StardewModdingAPI.Framework
}
/****
- ** Exceptions
- ****/
- /// <summary>Get a string representation of an exception suitable for writing to the error log.</summary>
- /// <param name="exception">The error to summarize.</param>
- 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();
- }
- }
-
- /// <summary>Get the lowest exception in an exception stack.</summary>
- /// <param name="exception">The exception from which to search.</param>
- public static Exception GetInnermostException(this Exception exception)
- {
- while (exception.InnerException != null)
- exception = exception.InnerException;
- return exception;
- }
-
- /****
** ReaderWriterLockSlim
****/
/// <summary>Run code within a read lock.</summary>
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index a4df3c18..a3d4f23d 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -3,11 +3,13 @@ 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;
using StardewModdingAPI.Framework.Models;
using StardewModdingAPI.Framework.ModLoading;
+using StardewModdingAPI.Internal;
using StardewModdingAPI.Internal.ConsoleWriting;
using StardewModdingAPI.Toolkit.Framework.ModData;
using StardewModdingAPI.Toolkit.Utilities;
@@ -106,6 +108,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;
}
/// <summary>Get a monitor instance derived from SMAPI's current settings.</summary>
@@ -258,7 +264,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()
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 4b3675bc..7a3b428d 100644
--- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
@@ -1,4 +1,3 @@
-#if HARMONY_2
using System;
using HarmonyLib;
using Mono.Cecil;
@@ -29,11 +28,11 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
public override bool Handle(ModuleDefinition module, TypeReference type, Action<TypeReference> 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));
- this.MarkRewritten();
+ this.OnChanged();
this.ReplacedTypes = true;
return true;
}
@@ -42,19 +41,25 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
}
/// <inheritdoc />
- public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith)
+ public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
{
// rewrite Harmony 1.x methods to Harmony 2.0
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
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;
@@ -64,6 +69,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
/*********
** Private methods
*********/
+ /// <summary>Update the mod metadata when any Harmony 1.x code is migrated.</summary>
+ private void OnChanged()
+ {
+ this.MarkRewritten();
+ this.MarkFlag(InstructionHandleResult.DetectedGamePatch);
+ }
+
/// <summary>Rewrite methods to use Harmony facades if needed.</summary>
/// <param name="module">The assembly module containing the method reference.</param>
/// <param name="methodRef">The method reference to map.</param>
@@ -73,24 +85,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))
@@ -103,18 +106,24 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
}
/// <summary>Get an equivalent Harmony 2.x type.</summary>
- /// <param name="type">The Harmony 1.x method.</param>
+ /// <param name="type">The Harmony 1.x type.</param>
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
+ /// <summary>Get an equivalent Harmony 2.x type using the convention expected for most types.</summary>
+ /// <param name="type">The Harmony 1.x type.</param>
+ 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);
}
}
}
-#endif
diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs
deleted file mode 100644
index ddecda08..00000000
--- a/src/SMAPI/Framework/Patching/GamePatcher.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-#if HARMONY_2
-using HarmonyLib;
-#else
-using Harmony;
-#endif
-
-namespace StardewModdingAPI.Framework.Patching
-{
- /// <summary>Encapsulates applying Harmony patches to the game.</summary>
- internal class GamePatcher
- {
- /*********
- ** Fields
- *********/
- /// <summary>Encapsulates monitoring and logging.</summary>
- private readonly IMonitor Monitor;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="monitor">Encapsulates monitoring and logging.</param>
- public GamePatcher(IMonitor monitor)
- {
- this.Monitor = monitor;
- }
-
- /// <summary>Apply all loaded patches to the game.</summary>
- /// <param name="patches">The patches to apply.</param>
- public void Apply(params IHarmonyPatch[] patches)
- {
-#if HARMONY_2
- Harmony harmony = new Harmony("SMAPI");
-#else
- HarmonyInstance harmony = HarmonyInstance.Create("SMAPI");
-#endif
- foreach (IHarmonyPatch patch in patches)
- {
- try
- {
- 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 38d30ab2..00000000
--- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-#if HARMONY_2
-using HarmonyLib;
-#else
-using Harmony;
-#endif
-
-namespace StardewModdingAPI.Framework.Patching
-{
- /// <summary>A Harmony patch to apply.</summary>
- internal interface IHarmonyPatch
- {
- /*********
- ** Methods
- *********/
- /// <summary>Apply the Harmony patch.</summary>
- /// <param name="harmony">The Harmony instance.</param>
-#if HARMONY_2
- void Apply(Harmony harmony);
-#else
- void Apply(HarmonyInstance harmony);
-#endif
- }
-}
diff --git a/src/SMAPI/Framework/Patching/PatchHelper.cs b/src/SMAPI/Framework/Patching/PatchHelper.cs
deleted file mode 100644
index d1aa0185..00000000
--- a/src/SMAPI/Framework/Patching/PatchHelper.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-#if !HARMONY_2
-using System;
-using System.Collections.Generic;
-
-namespace StardewModdingAPI.Framework.Patching
-{
- /// <summary>Provides generic methods for implementing Harmony patches.</summary>
- internal class PatchHelper
- {
- /*********
- ** Fields
- *********/
- /// <summary>The interception keys currently being intercepted.</summary>
- private static readonly HashSet<string> InterceptingKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Track a method that will be intercepted.</summary>
- /// <param name="key">The intercept key.</param>
- /// <returns>Returns false if the method was already marked for interception, else true.</returns>
- public static bool StartIntercept(string key)
- {
- return PatchHelper.InterceptingKeys.Add(key);
- }
-
- /// <summary>Track a method as no longer being intercepted.</summary>
- /// <param name="key">The intercept key.</param>
- public static void StopIntercept(string key)
- {
- PatchHelper.InterceptingKeys.Remove(key);
- }
- }
-}
-#endif
diff --git a/src/SMAPI/Framework/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<TInterface>(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);
+ }
}
}
}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index c3285979..a34b3eff 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;
@@ -30,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;
@@ -46,6 +48,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,8 +254,10 @@ namespace StardewModdingAPI.Framework
StardewValley.GameRunner.instance = this.Game;
// apply game patches
- new GamePatcher(this.Monitor).Apply(
- new LoadContextPatch(this.Reflection, this.OnLoadStageChanged)
+ MiniMonoModHotfix.Apply();
+ HarmonyPatcher.Apply("SMAPI", this.Monitor,
+ new Game1Patcher(this.Reflection, this.OnLoadStageChanged),
+ new TitleMenuPatcher(this.OnLoadStageChanged)
);
// add exit handler
@@ -308,6 +313,14 @@ namespace StardewModdingAPI.Framework
}
}
+ /// <summary>Get the core logger and monitor on behalf of the game.</summary>
+ /// <remarks>This method is called using reflection by the ErrorHandler mod to log game errors.</remarks>
+ [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via reflection")]
+ public IMonitor GetMonitorForGame()
+ {
+ return this.LogManager.MonitorForGame;
+ }
+
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
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/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs
new file mode 100644
index 00000000..9d63ab2c
--- /dev/null
+++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs
@@ -0,0 +1,239 @@
+// 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.Reflection;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using HarmonyLib;
+using System.Reflection.Emit;
+
+// 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 object[] _NoArgs = new object[0];
+ private static object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null };
+
+ private static Type t_RuntimeModule =
+ typeof(Module).Assembly
+ .GetType("System.Reflection.RuntimeModule");
+
+ private static PropertyInfo p_RuntimeModule_RuntimeType =
+ typeof(Module).Assembly
+ .GetType("System.Reflection.RuntimeModule")
+ ?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ private static Type t_RuntimeType =
+ typeof(Type).Assembly
+ .GetType("System.RuntimeType");
+
+ private static PropertyInfo p_RuntimeType_Cache =
+ typeof(Type).Assembly
+ .GetType("System.RuntimeType")
+ ?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ private static MethodInfo m_RuntimeTypeCache_GetFieldList =
+ typeof(Type).Assembly
+ .GetType("System.RuntimeType+RuntimeTypeCache")
+ ?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ 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<Type, CacheFixEntry> _CacheFixed = new ConditionalWeakTable<Type, CacheFixEntry>();
+
+ 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(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))
+ );
+
+ }
+
+ private static IEnumerable<CodeInstruction> ResolveTokenFix(IEnumerable<CodeInstruction> instrs)
+ {
+ MethodInfo getdecl = typeof(MiniMonoModHotfix).GetMethod(nameof(GetRealDeclaringType));
+ MethodInfo fixup = typeof(MiniMonoModHotfix).GetMethod(nameof(FixReflectionCache));
+
+ foreach (CodeInstruction instr in instrs)
+ {
+ yield return instr;
+
+ if (instr.operand is MethodInfo called)
+ {
+ switch (called.Name)
+ {
+ case "ResolveType":
+ // type.FixReflectionCache();
+ yield return new CodeInstruction(OpCodes.Dup);
+ yield return new CodeInstruction(OpCodes.Call, fixup);
+ break;
+
+ case "ResolveMember":
+ case "ResolveMethod":
+ case "ResolveField":
+ // member.GetRealDeclaringType().FixReflectionCache();
+ yield return new CodeInstruction(OpCodes.Dup);
+ yield return new CodeInstruction(OpCodes.Call, getdecl);
+ yield return new CodeInstruction(OpCodes.Call, fixup);
+ break;
+ }
+ }
+ }
+ }
+
+ public static Type GetModuleType(this Module module)
+ {
+ // Sadly we can't blindly resolve type 0x02000001 as the runtime throws ArgumentException.
+
+ if (module == null || t_RuntimeModule == null || !t_RuntimeModule.IsInstanceOfType(module))
+ return null;
+
+ // .NET
+ if (p_RuntimeModule_RuntimeType != null)
+ return (Type)p_RuntimeModule_RuntimeType.GetValue(module, _NoArgs);
+
+ // The hotfix doesn't apply to Mono anyway, thus that's not copied over.
+
+ return null;
+ }
+
+ public static Type GetRealDeclaringType(this MemberInfo member)
+ => member.DeclaringType ?? member.Module?.GetModuleType();
+
+ public static void FixReflectionCache(this Type type)
+ {
+ if (t_RuntimeType == null ||
+ p_RuntimeType_Cache == null ||
+ m_RuntimeTypeCache_GetFieldList == null ||
+ m_RuntimeTypeCache_GetPropertyList == null)
+ return;
+
+ for (; type != null; type = type.DeclaringType)
+ {
+ // All types SHOULD inherit RuntimeType, including those built at runtime.
+ // One might never know what awaits us in the depths of reflection hell though.
+ if (!t_RuntimeType.IsInstanceOfType(type))
+ continue;
+
+ 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);
+
+ _FixReflectionCacheOrder<PropertyInfo>(properties);
+ _FixReflectionCacheOrder<FieldInfo>(fields);
+
+ entryNew.NeedsVerify = false;
+ return entryNew;
+ });
+
+ if (entry.NeedsVerify && !_Verify(entry, type))
+ {
+ lock (entry)
+ {
+ _FixReflectionCacheOrder<PropertyInfo>(entry.Properties);
+ _FixReflectionCacheOrder<FieldInfo>(entry.Fields);
+ }
+ }
+
+ entry.NeedsVerify = true;
+ }
+ }
+
+ 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);
+ return (Array)getter.Invoke(cache, _CacheGetterArgs);
+ }
+
+ private static void _FixReflectionCacheOrder<T>(Array orig) where T : MemberInfo
+ {
+ // Sort using a short-lived list.
+ List<T> list = new List<T>(orig.Length);
+ for (int i = 0; i < orig.Length; i++)
+ list.Add((T)orig.GetValue(i));
+
+ list.Sort((a, b) => a.MetadataToken - b.MetadataToken);
+
+ for (int i = orig.Length - 1; i >= 0; --i)
+ orig.SetValue(list[i], i);
+ }
+
+ private class CacheFixEntry
+ {
+ public object Cache;
+ public Array Properties;
+ public Array Fields;
+ public bool NeedsVerify;
+ }
+ }
+}
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs
index 5641f90f..a8686ca4 100644
--- a/src/SMAPI/Metadata/CoreAssetPropagator.cs
+++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs
@@ -5,9 +5,9 @@ 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;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
using StardewValley.BellsAndWhistles;
@@ -938,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();
}
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/Game1Patcher.cs
index c43d7071..173a2055 100644
--- a/src/SMAPI/Patches/LoadContextPatch.cs
+++ b/src/SMAPI/Patches/Game1Patcher.cs
@@ -1,24 +1,20 @@
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;
+using StardewModdingAPI.Internal.Patching;
using StardewValley;
using StardewValley.Menus;
using StardewValley.Minigames;
namespace StardewModdingAPI.Patches
{
- /// <summary>Harmony patches which notify SMAPI for save creation load stages.</summary>
+ /// <summary>Harmony patches for <see cref="Game1"/> which notify SMAPI for save load stages.</summary>
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
[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
+ internal class Game1Patcher : BasePatcher
{
/*********
** Fields
@@ -39,42 +35,32 @@ namespace StardewModdingAPI.Patches
/// <summary>Construct an instance.</summary>
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="onStageChanged">A callback to invoke when the load stage changes.</param>
- public LoadContextPatch(Reflector reflection, Action<LoadStage> onStageChanged)
+ public Game1Patcher(Reflector reflection, Action<LoadStage> onStageChanged)
{
- LoadContextPatch.Reflection = reflection;
- LoadContextPatch.OnStageChanged = onStageChanged;
+ Game1Patcher.Reflection = reflection;
+ Game1Patcher.OnStageChanged = onStageChanged;
}
/// <inheritdoc />
-#if HARMONY_2
- public void Apply(Harmony harmony)
-#else
- public void Apply(HarmonyInstance harmony)
-#endif
+ 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(LoadContextPatch.Before_TitleMenu_CreatedNewCharacter))
- );
-
// detect CreatedInitialLocations and SaveAddedLocations
harmony.Patch(
- original: AccessTools.Method(typeof(Game1), nameof(Game1.AddModNPCs)),
- prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_AddModNPCs))
+ original: this.RequireMethod<Game1>(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(LoadContextPatch.Before_Game1_LoadForNewGame)),
- postfix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.After_Game1_LoadForNewGame))
+ original: this.RequireMethod<Game1>(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(LoadContextPatch.Before_Game1_CleanupReturningToTitle))
+ original: this.RequireMethod<Game1>(nameof(Game1.CleanupReturningToTitle)),
+ prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_CleanupReturningToTitle))
);
}
@@ -82,25 +68,16 @@ namespace StardewModdingAPI.Patches
/*********
** Private methods
*********/
- /// <summary>Called before <see cref="TitleMenu.createdNewCharacter"/>.</summary>
- /// <returns>Returns whether to execute the original method.</returns>
- /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
- private static bool Before_TitleMenu_CreatedNewCharacter()
- {
- LoadContextPatch.OnStageChanged(LoadStage.CreatedBasicInfo);
- return true;
- }
-
- /// <summary>Called before <see cref="Game1.AddModNPCs"/>.</summary>
+ /// <summary>The method to call before <see cref="Game1.AddModNPCs"/>.</summary>
/// <returns>Returns whether to execute the original method.</returns>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
- private static bool 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.
- if (LoadContextPatch.IsInLoadForNewGame)
+ if (Game1Patcher.IsInLoadForNewGame)
{
- LoadContextPatch.OnStageChanged(LoadContextPatch.IsCreating()
+ Game1Patcher.OnStageChanged(Game1Patcher.IsCreating()
? LoadStage.CreatedInitialLocations
: LoadStage.SaveAddedLocations
);
@@ -109,32 +86,32 @@ namespace StardewModdingAPI.Patches
return true;
}
- /// <summary>Called before <see cref="Game1.CleanupReturningToTitle"/>.</summary>
+ /// <summary>The method to call before <see cref="Game1.CleanupReturningToTitle"/>.</summary>
/// <returns>Returns whether to execute the original method.</returns>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
- private static bool Before_Game1_CleanupReturningToTitle()
+ private static bool Before_CleanupReturningToTitle()
{
- LoadContextPatch.OnStageChanged(LoadStage.ReturningToTitle);
+ Game1Patcher.OnStageChanged(LoadStage.ReturningToTitle);
return true;
}
- /// <summary>Called before <see cref="Game1.loadForNewGame"/>.</summary>
+ /// <summary>The method to call before <see cref="Game1.loadForNewGame"/>.</summary>
/// <returns>Returns whether to execute the original method.</returns>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
- private static bool Before_Game1_LoadForNewGame()
+ private static bool Before_LoadForNewGame()
{
- LoadContextPatch.IsInLoadForNewGame = true;
+ Game1Patcher.IsInLoadForNewGame = true;
return true;
}
- /// <summary>Called after <see cref="Game1.loadForNewGame"/>.</summary>
+ /// <summary>The method to call after <see cref="Game1.loadForNewGame"/>.</summary>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
- private static void After_Game1_LoadForNewGame()
+ private static void After_LoadForNewGame()
{
- LoadContextPatch.IsInLoadForNewGame = false;
+ Game1Patcher.IsInLoadForNewGame = false;
- if (LoadContextPatch.IsCreating())
- LoadContextPatch.OnStageChanged(LoadStage.CreatedLocations);
+ if (Game1Patcher.IsCreating())
+ Game1Patcher.OnStageChanged(LoadStage.CreatedLocations);
}
/// <summary>Get whether the save file is currently being created.</summary>
@@ -142,7 +119,7 @@ namespace StardewModdingAPI.Patches
{
return
(Game1.currentMinigame is Intro) // creating save with intro
- || (Game1.activeClickableMenu is TitleMenu menu && LoadContextPatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro
+ || (Game1.activeClickableMenu is TitleMenu menu && Game1Patcher.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro
}
}
}
diff --git a/src/SMAPI/Patches/TitleMenuPatcher.cs b/src/SMAPI/Patches/TitleMenuPatcher.cs
new file mode 100644
index 00000000..b4320ce0
--- /dev/null
+++ b/src/SMAPI/Patches/TitleMenuPatcher.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using HarmonyLib;
+using StardewModdingAPI.Enums;
+using StardewModdingAPI.Internal.Patching;
+using StardewValley.Menus;
+
+namespace StardewModdingAPI.Patches
+{
+ /// <summary>Harmony patches for <see cref="TitleMenu"/> which notify SMAPI when a new character was created.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [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 : BasePatcher
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>A callback to invoke when the load stage changes.</summary>
+ private static Action<LoadStage> OnStageChanged;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="onStageChanged">A callback to invoke when the load stage changes.</param>
+ public TitleMenuPatcher(Action<LoadStage> onStageChanged)
+ {
+ TitleMenuPatcher.OnStageChanged = onStageChanged;
+ }
+
+ /// <inheritdoc />
+ public override void Apply(Harmony harmony, IMonitor monitor)
+ {
+ harmony.Patch(
+ original: this.RequireMethod<TitleMenu>(nameof(TitleMenu.createdNewCharacter)),
+ prefix: this.GetHarmonyMethod(nameof(TitleMenuPatcher.Before_CreatedNewCharacter))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call before <see cref="TitleMenu.createdNewCharacter"/>.</summary>
+ /// <returns>Returns whether to execute the original method.</returns>
+ /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
+ private static bool Before_CreatedNewCharacter()
+ {
+ TitleMenuPatcher.OnStageChanged(LoadStage.CreatedBasicInfo);
+ return true;
+ }
+ }
+}
diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs
index e830f799..3249e02f 100644
--- a/src/SMAPI/Program.cs
+++ b/src/SMAPI/Program.cs
@@ -4,6 +4,8 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using StardewModdingAPI.Framework;
+using StardewModdingAPI.Toolkit.Framework;
+using StardewModdingAPI.Toolkit.Serialization.Models;
namespace StardewModdingAPI
{
@@ -31,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
@@ -107,8 +110,35 @@ 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 (!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)
+ 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
+ }
+
+ /// <summary>Assert that the versions of all SMAPI components are correct.</summary>
+ /// <remarks>Players sometimes have mismatched versions (particularly when installed through Vortex), which can cause some very confusing bugs without this check.</remarks>
+ private static void AssertSmapiVersions()
+ {
+ // 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) })
+ {
+ 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.");
+ }
}
/// <summary>Initialize SMAPI and launch the game.</summary>
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 413d9f33..7d5e7ef9 100644
--- a/src/SMAPI/SMAPI.csproj
+++ b/src/SMAPI/SMAPI.csproj
@@ -20,7 +20,8 @@
<ItemGroup>
<PackageReference Include="LargeAddressAware" Version="1.0.5" />
- <PackageReference Include="Mono.Cecil" Version="0.11.3" />
+ <PackageReference Include="Mono.Cecil" Version="0.11.4" />
+ <PackageReference Include="MonoMod.Common" Version="21.6.21.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Platonymous.TMXTile" Version="1.5.8" />
</ItemGroup>
@@ -75,4 +76,5 @@
</ItemGroup>
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
+ <Import Project="..\SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems" Label="Shared" />
</Project>