Merge branch 'develop' into stable
This commit is contained in:
commit
8e9237bdd7
Binary file not shown.
|
@ -265,6 +265,9 @@
|
|||
<member name="F:HarmonyLib.MethodType.StaticConstructor">
|
||||
<summary>This is a static constructor</summary>
|
||||
</member>
|
||||
<member name="F:HarmonyLib.MethodType.Enumerator">
|
||||
<summary>This targets the MoveNext method of the enumerator result</summary>
|
||||
</member>
|
||||
<member name="T:HarmonyLib.ArgumentType">
|
||||
<summary>Specifies the type of argument</summary>
|
||||
|
||||
|
@ -475,6 +478,13 @@
|
|||
<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,System.String,HarmonyLib.MethodType)">
|
||||
<summary>An annotation that specifies a method, property or constructor to patch</summary>
|
||||
<param name="typeName">The full name of 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="T:HarmonyLib.HarmonyDelegate">
|
||||
<summary>Annotation to define the original method for delegate injection</summary>
|
||||
|
@ -796,6 +806,13 @@
|
|||
<param name="expression">The lambda expression using the method</param>
|
||||
<returns></returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeInstruction.CallClosure``1(``0)">
|
||||
<summary>Returns an instruction to call the specified closure</summary>
|
||||
<typeparam name="T">The delegate type to emit</typeparam>
|
||||
<param name="closure">The closure that defines the method to call</param>
|
||||
<returns>A <see cref="T:HarmonyLib.CodeInstruction"/> that calls the closure as a method</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>
|
||||
|
@ -980,6 +997,11 @@
|
|||
<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.GetOriginalMethodFromStackframe(System.Diagnostics.StackFrame)">
|
||||
<summary>Gets the original method from the stackframe and uses original if method is a dynamic replacement</summary>
|
||||
<param name="frame">The <see cref="T:System.Diagnostics.StackFrame"/></param>
|
||||
<returns>The original method from that stackframe</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>
|
||||
|
@ -1210,7 +1232,7 @@
|
|||
|
||||
</member>
|
||||
<member name="T:HarmonyLib.PatchInfoSerialization">
|
||||
<summary>Patch serialization</summary>
|
||||
<summary>Patch serialization</summary>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.PatchInfoSerialization.Binder.BindToType(System.String,System.String)">
|
||||
|
@ -1241,27 +1263,27 @@
|
|||
|
||||
</member>
|
||||
<member name="T:HarmonyLib.PatchInfo">
|
||||
<summary>Serializable patch information</summary>
|
||||
<summary>Serializable patch information</summary>
|
||||
|
||||
</member>
|
||||
<member name="F:HarmonyLib.PatchInfo.prefixes">
|
||||
<summary>Prefixes as an array of <see cref="T:HarmonyLib.Patch"/></summary>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<summary>Returns if any of the patches wants debugging turned on</summary>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.PatchInfo.AddPrefixes(System.String,HarmonyLib.HarmonyMethod[])">
|
||||
|
@ -1339,35 +1361,35 @@
|
|||
|
||||
</member>
|
||||
<member name="T:HarmonyLib.Patch">
|
||||
<summary>A serializable patch</summary>
|
||||
<summary>A serializable patch</summary>
|
||||
|
||||
</member>
|
||||
<member name="F:HarmonyLib.Patch.index">
|
||||
<summary>Zero-based index</summary>
|
||||
<summary>Zero-based index</summary>
|
||||
|
||||
</member>
|
||||
<member name="F:HarmonyLib.Patch.owner">
|
||||
<summary>The owner (Harmony ID)</summary>
|
||||
<summary>The owner (Harmony ID)</summary>
|
||||
|
||||
</member>
|
||||
<member name="F:HarmonyLib.Patch.priority">
|
||||
<summary>The priority, see <see cref="T:HarmonyLib.Priority"/></summary>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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)">
|
||||
|
@ -1760,6 +1782,12 @@
|
|||
<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.DeclaredField(System.String)">
|
||||
<summary>Gets the reflection information for a directly declared field</summary>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<returns>A field or null 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>
|
||||
|
@ -1767,6 +1795,12 @@
|
|||
<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.Field(System.String)">
|
||||
<summary>Gets the reflection information for a field by searching the type and all its super types</summary>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<returns>A field or null 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>
|
||||
|
@ -1781,6 +1815,12 @@
|
|||
<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.DeclaredProperty(System.String)">
|
||||
<summary>Gets the reflection information for a directly declared property</summary>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<returns>A property or null 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>
|
||||
|
@ -1788,6 +1828,12 @@
|
|||
<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.DeclaredPropertyGetter(System.String)">
|
||||
<summary>Gets the reflection information for the getter method of a directly declared property</summary>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<returns>A method or null 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>
|
||||
|
@ -1795,6 +1841,12 @@
|
|||
<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.String)">
|
||||
<summary>Gets the reflection information for the Setter method of a directly declared property</summary>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<returns>A method or null 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>
|
||||
|
@ -1802,6 +1854,12 @@
|
|||
<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.Property(System.String)">
|
||||
<summary>Gets the reflection information for a property by searching the type and all its super types</summary>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<returns>A property or null 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>
|
||||
|
@ -1809,6 +1867,12 @@
|
|||
<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.PropertyGetter(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="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<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>
|
||||
|
@ -1816,6 +1880,12 @@
|
|||
<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.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="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<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>
|
||||
|
@ -1825,6 +1895,14 @@
|
|||
<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.DeclaredMethod(System.String,System.Type[],System.Type[])">
|
||||
<summary>Gets the reflection information for a directly declared method</summary>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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 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>
|
||||
|
@ -1837,12 +1915,17 @@
|
|||
</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="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<returns>A method or null when the method cannot be found</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.AccessTools.EnumeratorMoveNext(System.Reflection.MethodBase)">
|
||||
<summary>Gets the <see cref="M:System.Collections.IEnumerator.MoveNext" /> method of an enumerator method</summary>
|
||||
<param name="method">Enumerator method that creates the enumerator <see cref="T:System.Collections.IEnumerator" /></param>
|
||||
<returns>The internal <see cref="M:System.Collections.IEnumerator.MoveNext" /> method of the enumerator or <b>null</b> if no valid enumerator is detected</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>
|
||||
|
@ -2109,6 +2192,12 @@
|
|||
</remarks>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.AccessTools.FieldRefAccess``1(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"> type of the field</typeparam>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<returns>A readable/assignable <see cref="T:HarmonyLib.AccessTools.FieldRef`2"/> delegate with <c>T=object</c></returns>
|
||||
</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">
|
||||
|
@ -2281,6 +2370,13 @@
|
|||
<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.String)">
|
||||
<summary>Creates a static field reference</summary>
|
||||
<typeparam name="F">The type of the field</typeparam>
|
||||
<param name="typeColonName">The member in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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>
|
||||
<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>
|
||||
|
@ -2336,6 +2432,34 @@
|
|||
</para>
|
||||
</remarks>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.AccessTools.MethodDelegate``1(System.String,System.Object,System.Boolean)">
|
||||
<summary>Creates a delegate to a given method</summary>
|
||||
<typeparam name="DelegateType">The delegate Type</typeparam>
|
||||
<param name="typeColonName">The method in the form <c>TypeFullName:MemberName</c>, where TypeFullName matches the 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="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="typeColonName"/> 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="typeColonName"/> (this is useful for calling base class methods)
|
||||
Note: if <c>false</c> and <paramref name="typeColonName"/> is an interface method, an ArgumentException is thrown.
|
||||
</param>
|
||||
<returns>A delegate of given <typeparamref name="DelegateType"/> to given <paramref name="typeColonName"/></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>
|
||||
|
@ -2508,6 +2632,412 @@
|
|||
<param name="objects">The objects</param>
|
||||
<returns>The hash code</returns>
|
||||
|
||||
</member>
|
||||
<member name="T:HarmonyLib.CodeMatch">
|
||||
<summary>A CodeInstruction match</summary>
|
||||
</member>
|
||||
<member name="F:HarmonyLib.CodeMatch.name">
|
||||
<summary>The name of the match</summary>
|
||||
</member>
|
||||
<member name="F:HarmonyLib.CodeMatch.opcodes">
|
||||
<summary>The matched opcodes</summary>
|
||||
</member>
|
||||
<member name="F:HarmonyLib.CodeMatch.operands">
|
||||
<summary>The matched operands</summary>
|
||||
</member>
|
||||
<member name="F:HarmonyLib.CodeMatch.jumpsFrom">
|
||||
<summary>The jumps from the match</summary>
|
||||
</member>
|
||||
<member name="F:HarmonyLib.CodeMatch.jumpsTo">
|
||||
<summary>The jumps to the match</summary>
|
||||
</member>
|
||||
<member name="F:HarmonyLib.CodeMatch.predicate">
|
||||
<summary>The match predicate</summary>
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatch.#ctor(System.Nullable{System.Reflection.Emit.OpCode},System.Object,System.String)">
|
||||
<summary>Creates a code match</summary>
|
||||
<param name="opcode">The optional opcode</param>
|
||||
<param name="operand">The optional operand</param>
|
||||
<param name="name">The optional name</param>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatch.#ctor(HarmonyLib.CodeInstruction,System.String)">
|
||||
<summary>Creates a code match</summary>
|
||||
<param name="instruction">The CodeInstruction</param>
|
||||
<param name="name">An optional name</param>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatch.#ctor(System.Func{HarmonyLib.CodeInstruction,System.Boolean},System.String)">
|
||||
<summary>Creates a code match</summary>
|
||||
<param name="predicate">The predicate</param>
|
||||
<param name="name">An optional name</param>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatch.ToString">
|
||||
<summary>Returns a string that represents the match</summary>
|
||||
<returns>A string representation</returns>
|
||||
|
||||
</member>
|
||||
<member name="T:HarmonyLib.CodeMatcher">
|
||||
<summary>A CodeInstruction matcher</summary>
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.Pos">
|
||||
<summary>The current position</summary>
|
||||
<value>The index or -1 if out of bounds</value>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.Length">
|
||||
<summary>Gets the number of code instructions in this matcher</summary>
|
||||
<value>The count</value>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.IsValid">
|
||||
<summary>Checks whether the position of this CodeMatcher is within bounds</summary>
|
||||
<value>True if this CodeMatcher is valid</value>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.IsInvalid">
|
||||
<summary>Checks whether the position of this CodeMatcher is outside its bounds</summary>
|
||||
<value>True if this CodeMatcher is invalid</value>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.Remaining">
|
||||
<summary>Gets the remaining code instructions</summary>
|
||||
<value>The remaining count</value>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.Opcode">
|
||||
<summary>Gets the opcode at the current position</summary>
|
||||
<value>The opcode</value>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.Operand">
|
||||
<summary>Gets the operand at the current position</summary>
|
||||
<value>The operand</value>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.Labels">
|
||||
<summary>Gets the labels at the current position</summary>
|
||||
<value>The labels</value>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.Blocks">
|
||||
<summary>Gets the exception blocks at the current position</summary>
|
||||
<value>The blocks</value>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.#ctor">
|
||||
<summary>Creates an empty code matcher</summary>
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.#ctor(System.Collections.Generic.IEnumerable{HarmonyLib.CodeInstruction},System.Reflection.Emit.ILGenerator)">
|
||||
<summary>Creates a code matcher from an enumeration of instructions</summary>
|
||||
<param name="instructions">The instructions (transpiler argument)</param>
|
||||
<param name="generator">An optional IL generator</param>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Clone">
|
||||
<summary>Makes a clone of this instruction matcher</summary>
|
||||
<returns>A copy of this matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="P:HarmonyLib.CodeMatcher.Instruction">
|
||||
<summary>Gets instructions at the current position</summary>
|
||||
<value>The instruction</value>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.InstructionAt(System.Int32)">
|
||||
<summary>Gets instructions at the current position with offset</summary>
|
||||
<param name="offset">The offset</param>
|
||||
<returns>The instruction</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Instructions">
|
||||
<summary>Gets all instructions</summary>
|
||||
<returns>A list of instructions</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.InstructionEnumeration">
|
||||
<summary>Gets all instructions as an enumeration</summary>
|
||||
<returns>A list of instructions</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Instructions(System.Int32)">
|
||||
<summary>Gets some instructions counting from current position</summary>
|
||||
<param name="count">Number of instructions</param>
|
||||
<returns>A list of instructions</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.InstructionsInRange(System.Int32,System.Int32)">
|
||||
<summary>Gets all instructions within a range</summary>
|
||||
<param name="start">The start index</param>
|
||||
<param name="end">The end index</param>
|
||||
<returns>A list of instructions</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.InstructionsWithOffsets(System.Int32,System.Int32)">
|
||||
<summary>Gets all instructions within a range (relative to current position)</summary>
|
||||
<param name="startOffset">The start offset</param>
|
||||
<param name="endOffset">The end offset</param>
|
||||
<returns>A list of instructions</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.DistinctLabels(System.Collections.Generic.IEnumerable{HarmonyLib.CodeInstruction})">
|
||||
<summary>Gets a list of all distinct labels</summary>
|
||||
<param name="instructions">The instructions (transpiler argument)</param>
|
||||
<returns>A list of Labels</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.ReportFailure(System.Reflection.MethodBase,System.Action{System.String})">
|
||||
<summary>Reports a failure</summary>
|
||||
<param name="method">The method involved</param>
|
||||
<param name="logger">The logger</param>
|
||||
<returns>True if current position is invalid and error was logged</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.ThrowIfInvalid(System.String)">
|
||||
<summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed)</summary>
|
||||
<param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.ThrowIfNotMatch(System.String,HarmonyLib.CodeMatch[])">
|
||||
<summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed),
|
||||
or if the matches do not match at current position</summary>
|
||||
<param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
|
||||
<param name="matches">Some code matches</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.ThrowIfNotMatchForward(System.String,HarmonyLib.CodeMatch[])">
|
||||
<summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed),
|
||||
or if the matches do not match at any point between current position and the end</summary>
|
||||
<param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
|
||||
<param name="matches">Some code matches</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.ThrowIfNotMatchBack(System.String,HarmonyLib.CodeMatch[])">
|
||||
<summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed),
|
||||
or if the matches do not match at any point between current position and the start</summary>
|
||||
<param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
|
||||
<param name="matches">Some code matches</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.ThrowIfFalse(System.String,System.Func{HarmonyLib.CodeMatcher,System.Boolean})">
|
||||
<summary>Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed),
|
||||
or if the check function returns false</summary>
|
||||
<param name="explanation">Explanation of where/why the exception was thrown that will be added to the exception message</param>
|
||||
<param name="stateCheckFunc">Function that checks validity of current state. If it returns false, an exception is thrown</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.SetInstruction(HarmonyLib.CodeInstruction)">
|
||||
<summary>Sets an instruction at current position</summary>
|
||||
<param name="instruction">The instruction to set</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.SetInstructionAndAdvance(HarmonyLib.CodeInstruction)">
|
||||
<summary>Sets instruction at current position and advances</summary>
|
||||
<param name="instruction">The instruction</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Set(System.Reflection.Emit.OpCode,System.Object)">
|
||||
<summary>Sets opcode and operand at current position</summary>
|
||||
<param name="opcode">The opcode</param>
|
||||
<param name="operand">The operand</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.SetAndAdvance(System.Reflection.Emit.OpCode,System.Object)">
|
||||
<summary>Sets opcode and operand at current position and advances</summary>
|
||||
<param name="opcode">The opcode</param>
|
||||
<param name="operand">The operand</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.SetOpcodeAndAdvance(System.Reflection.Emit.OpCode)">
|
||||
<summary>Sets opcode at current position and advances</summary>
|
||||
<param name="opcode">The opcode</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.SetOperandAndAdvance(System.Object)">
|
||||
<summary>Sets operand at current position and advances</summary>
|
||||
<param name="operand">The operand</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.CreateLabel(System.Reflection.Emit.Label@)">
|
||||
<summary>Creates a label at current position</summary>
|
||||
<param name="label">[out] The label</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.CreateLabelAt(System.Int32,System.Reflection.Emit.Label@)">
|
||||
<summary>Creates a label at a position</summary>
|
||||
<param name="position">The position</param>
|
||||
<param name="label">[out] The new label</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.CreateLabelWithOffsets(System.Int32,System.Reflection.Emit.Label@)">
|
||||
<summary>Creates a label at a position</summary>
|
||||
<param name="offset">The offset</param>
|
||||
<param name="label">[out] The new label</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.AddLabels(System.Collections.Generic.IEnumerable{System.Reflection.Emit.Label})">
|
||||
<summary>Adds an enumeration of labels to current position</summary>
|
||||
<param name="labels">The labels</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.AddLabelsAt(System.Int32,System.Collections.Generic.IEnumerable{System.Reflection.Emit.Label})">
|
||||
<summary>Adds an enumeration of labels at a position</summary>
|
||||
<param name="position">The position</param>
|
||||
<param name="labels">The labels</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.SetJumpTo(System.Reflection.Emit.OpCode,System.Int32,System.Reflection.Emit.Label@)">
|
||||
<summary>Sets jump to</summary>
|
||||
<param name="opcode">Branch instruction</param>
|
||||
<param name="destination">Destination for the jump</param>
|
||||
<param name="label">[out] The created label</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Insert(HarmonyLib.CodeInstruction[])">
|
||||
<summary>Inserts some instructions</summary>
|
||||
<param name="instructions">The instructions</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Insert(System.Collections.Generic.IEnumerable{HarmonyLib.CodeInstruction})">
|
||||
<summary>Inserts an enumeration of instructions</summary>
|
||||
<param name="instructions">The instructions</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.InsertBranch(System.Reflection.Emit.OpCode,System.Int32)">
|
||||
<summary>Inserts a branch</summary>
|
||||
<param name="opcode">The branch opcode</param>
|
||||
<param name="destination">Branch destination</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.InsertAndAdvance(HarmonyLib.CodeInstruction[])">
|
||||
<summary>Inserts some instructions and advances the position</summary>
|
||||
<param name="instructions">The instructions</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.InsertAndAdvance(System.Collections.Generic.IEnumerable{HarmonyLib.CodeInstruction})">
|
||||
<summary>Inserts an enumeration of instructions and advances the position</summary>
|
||||
<param name="instructions">The instructions</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.InsertBranchAndAdvance(System.Reflection.Emit.OpCode,System.Int32)">
|
||||
<summary>Inserts a branch and advances the position</summary>
|
||||
<param name="opcode">The branch opcode</param>
|
||||
<param name="destination">Branch destination</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.RemoveInstruction">
|
||||
<summary>Removes current instruction</summary>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.RemoveInstructions(System.Int32)">
|
||||
<summary>Removes some instruction from current position by count</summary>
|
||||
<param name="count">Number of instructions</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.RemoveInstructionsInRange(System.Int32,System.Int32)">
|
||||
<summary>Removes the instructions in a range</summary>
|
||||
<param name="start">The start</param>
|
||||
<param name="end">The end</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.RemoveInstructionsWithOffsets(System.Int32,System.Int32)">
|
||||
<summary>Removes the instructions in a offset range</summary>
|
||||
<param name="startOffset">The start offset</param>
|
||||
<param name="endOffset">The end offset</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Advance(System.Int32)">
|
||||
<summary>Advances the current position</summary>
|
||||
<param name="offset">The offset</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Start">
|
||||
<summary>Moves the current position to the start</summary>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.End">
|
||||
<summary>Moves the current position to the end</summary>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.SearchForward(System.Func{HarmonyLib.CodeInstruction,System.Boolean})">
|
||||
<summary>Searches forward with a predicate and advances position</summary>
|
||||
<param name="predicate">The predicate</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.SearchBackwards(System.Func{HarmonyLib.CodeInstruction,System.Boolean})">
|
||||
<summary>Searches backwards with a predicate and reverses position</summary>
|
||||
<param name="predicate">The predicate</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.MatchStartForward(HarmonyLib.CodeMatch[])">
|
||||
<summary>Matches forward and advances position to beginning of matching sequence</summary>
|
||||
<param name="matches">Some code matches</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.MatchEndForward(HarmonyLib.CodeMatch[])">
|
||||
<summary>Matches forward and advances position to ending of matching sequence</summary>
|
||||
<param name="matches">Some code matches</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.MatchStartBackwards(HarmonyLib.CodeMatch[])">
|
||||
<summary>Matches backwards and reverses position to beginning of matching sequence</summary>
|
||||
<param name="matches">Some code matches</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.MatchEndBackwards(HarmonyLib.CodeMatch[])">
|
||||
<summary>Matches backwards and reverses position to ending of matching sequence</summary>
|
||||
<param name="matches">Some code matches</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.Repeat(System.Action{HarmonyLib.CodeMatcher},System.Action{System.String})">
|
||||
<summary>Repeats a match action until boundaries are met</summary>
|
||||
<param name="matchAction">The match action</param>
|
||||
<param name="notFoundAction">An optional action that is executed when no match is found</param>
|
||||
<returns>The same code matcher</returns>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeMatcher.NamedMatch(System.String)">
|
||||
<summary>Gets a match by its name</summary>
|
||||
<param name="name">The match name</param>
|
||||
<returns>An instruction</returns>
|
||||
|
||||
</member>
|
||||
<member name="T:HarmonyLib.GeneralExtensions">
|
||||
<summary>General extensions for common cases</summary>
|
||||
|
@ -2574,6 +3104,11 @@
|
|||
<summary>Extensions for <see cref="T:HarmonyLib.CodeInstruction"/></summary>
|
||||
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeInstructionExtensions.IsValid(System.Reflection.Emit.OpCode)">
|
||||
<summary>Returns if an <see cref="T:System.Reflection.Emit.OpCode"/> is initialized and valid</summary>
|
||||
<param name="code">The <see cref="T:System.Reflection.Emit.OpCode"/></param>
|
||||
<returns></returns>
|
||||
</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>
|
||||
|
@ -2715,15 +3250,15 @@
|
|||
<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>
|
||||
<summary>Moves all labels from the code instruction to another 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>
|
||||
<param name="other">The other <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>
|
||||
<summary>Moves all labels from another code instruction to the current one</summary>
|
||||
<param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the labels to</param>
|
||||
<param name="other">The other <see cref="T:HarmonyLib.CodeInstruction"/> to move the labels from</param>
|
||||
<returns>The code instruction that received the labels</returns>
|
||||
</member>
|
||||
<member name="M:HarmonyLib.CodeInstructionExtensions.WithBlocks(HarmonyLib.CodeInstruction,HarmonyLib.ExceptionBlock[])">
|
||||
|
@ -2744,15 +3279,15 @@
|
|||
<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>
|
||||
<summary>Moves all ExceptionBlocks from the code instruction to another 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>
|
||||
<param name="other">The other <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>
|
||||
<summary>Moves all ExceptionBlocks from another code instruction to the current one</summary>
|
||||
<param name="code">The <see cref="T:HarmonyLib.CodeInstruction"/> to move the ExceptionBlocks to</param>
|
||||
<param name="other">The other <see cref="T:HarmonyLib.CodeInstruction"/> to move the ExceptionBlocks from</param>
|
||||
<returns>The code instruction that received the blocks</returns>
|
||||
</member>
|
||||
<member name="T:HarmonyLib.CollectionExtensions">
|
||||
|
@ -2859,6 +3394,11 @@
|
|||
<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.Debug(System.String)">
|
||||
<summary>Log a string directly to disk if Harmony.DEBUG is true. 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>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<!--set general build properties -->
|
||||
<Version>3.14.7</Version>
|
||||
<Version>3.15.0</Version>
|
||||
<Product>SMAPI</Product>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||
|
|
|
@ -1,11 +1,31 @@
|
|||
← [README](README.md)
|
||||
|
||||
# Release notes
|
||||
## 3.15.0
|
||||
Released 17 June 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/67877219).
|
||||
|
||||
* For players:
|
||||
* Optimized mod image file loading.
|
||||
* Minor optimizations (thanks to Michael Kuklinski / Ameisen!).
|
||||
* Updated compatibility list.
|
||||
|
||||
* For mod authors:
|
||||
* Added an [`IRawTextureData` asset type](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0#Raw_texture_data), to avoid creating full `Texture2D` instances in many cases.
|
||||
* In `smapi-internal/config.json`, you can now enable verbose logging for specific mods (instead of all or nothing).
|
||||
* Updated dependencies:
|
||||
* Harmony 2.2.1 (see changes in [2.2.0](https://github.com/pardeike/Harmony/releases/tag/v2.2.0.0) and [2.2.1](https://github.com/pardeike/Harmony/releases/tag/v2.2.1.0));
|
||||
* Newtonsoft.Json 13.0.1 (see [changes](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/13.0.1));
|
||||
* Pintail 2.2.0.
|
||||
* Removed transitional `UsePintail` option added in 3.14.0 (now always enabled).
|
||||
* Fixed `onBehalfOf` arguments in the new content API being case-sensitive.
|
||||
* Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players.
|
||||
|
||||
## 3.14.7
|
||||
Released 01 June 2022 for Stardew Valley 1.5.6 or later.
|
||||
|
||||
* For players:
|
||||
* Optimized reflection cache to reduce frame skips for some players.
|
||||
|
||||
* For mod authors:
|
||||
* Removed `runtimeconfig.json` setting which impacted hot reload support.
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
|
||||
<!--
|
||||
This is imported through Microsoft.Build.Utilities.Core. When installed by a mod, NuGet
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Console Commands",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.14.7",
|
||||
"Version": "3.15.0",
|
||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||
"UniqueID": "SMAPI.ConsoleCommands",
|
||||
"EntryDll": "ConsoleCommands.dll",
|
||||
"MinimumApiVersion": "3.14.7"
|
||||
"MinimumApiVersion": "3.15.0"
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Reflection;
|
||||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
using StardewModdingAPI.Mods.ErrorHandler.ModPatches;
|
||||
using StardewModdingAPI.Mods.ErrorHandler.Patches;
|
||||
using StardewValley;
|
||||
|
||||
|
@ -29,6 +30,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler
|
|||
|
||||
// apply patches
|
||||
HarmonyPatcher.Apply(this.ModManifest.UniqueID, this.Monitor,
|
||||
// game patches
|
||||
new DialoguePatcher(monitorForGame, this.Helper.Reflection),
|
||||
new EventPatcher(monitorForGame),
|
||||
new GameLocationPatcher(monitorForGame),
|
||||
|
@ -37,7 +39,10 @@ namespace StardewModdingAPI.Mods.ErrorHandler
|
|||
new ObjectPatcher(),
|
||||
new SaveGamePatcher(this.Monitor, this.OnSaveContentRemoved),
|
||||
new SpriteBatchPatcher(),
|
||||
new UtilityPatcher()
|
||||
new UtilityPatcher(),
|
||||
|
||||
// mod patches
|
||||
new PyTkPatcher(helper.ModRegistry)
|
||||
);
|
||||
|
||||
// hook events
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using StardewModdingAPI.Framework;
|
||||
using StardewModdingAPI.Framework.Content;
|
||||
using StardewModdingAPI.Internal;
|
||||
using StardewModdingAPI.Internal.Patching;
|
||||
|
||||
//
|
||||
// This is part of a three-part fix for PyTK 1.23.0 and earlier. When removing this, search
|
||||
// 'Platonymous.Toolkit' to find the other part in SMAPI and Content Patcher.
|
||||
//
|
||||
|
||||
namespace StardewModdingAPI.Mods.ErrorHandler.ModPatches
|
||||
{
|
||||
/// <summary>Harmony patches for the PyTK mod for compatibility with newer SMAPI versions.</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.")]
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "'Platonymous' is part of the mod ID.")]
|
||||
internal class PyTkPatcher : BasePatcher
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The PyTK mod metadata, if it's installed.</summary>
|
||||
private static IModMetadata? PyTk;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="modRegistry">The mod registry from which to read PyTK metadata.</param>
|
||||
public PyTkPatcher(IModRegistry modRegistry)
|
||||
{
|
||||
IModMetadata? pyTk = (IModMetadata?)modRegistry.Get(@"Platonymous.Toolkit");
|
||||
if (pyTk is not null && !pyTk.Manifest.Version.IsNewerThan("1.23.0"))
|
||||
PyTkPatcher.PyTk = pyTk;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply(Harmony harmony, IMonitor monitor)
|
||||
{
|
||||
try
|
||||
{
|
||||
// get mod info
|
||||
IModMetadata? pyTk = PyTkPatcher.PyTk;
|
||||
if (pyTk is null)
|
||||
return;
|
||||
|
||||
// get patch method
|
||||
const string patchMethodName = "PatchImage";
|
||||
MethodInfo? patch = AccessTools.Method(pyTk.Mod!.GetType(), patchMethodName);
|
||||
if (patch is null)
|
||||
{
|
||||
monitor.Log("Failed applying compatibility patch for PyTK. Its image scaling feature may not work correctly.", LogLevel.Warn);
|
||||
monitor.Log($"Couldn't find patch method '{pyTk.Mod.GetType().FullName}.{patchMethodName}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
// apply patch
|
||||
harmony = new($"{harmony.Id}.compatibility-patches.PyTK");
|
||||
harmony.Patch(
|
||||
original: AccessTools.Method(typeof(AssetDataForImage), nameof(AssetDataForImage.PatchImage), new[] { typeof(Texture2D), typeof(Rectangle), typeof(Rectangle), typeof(PatchMode) }),
|
||||
prefix: new HarmonyMethod(patch)
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
monitor.Log("Failed applying compatibility patch for PyTK. Its image scaling feature may not work correctly.", LogLevel.Warn);
|
||||
monitor.Log(ex.GetLogSummary());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,4 @@
|
|||
<None Update="i18n\*.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
<None Update="manifest.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\SMAPI.Internal\SMAPI.Internal.projitems" Label="Shared" />
|
||||
<Import Project="..\SMAPI.Internal.Patching\SMAPI.Internal.Patching.projitems" Label="Shared" />
|
||||
</Project>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Error Handler",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.14.7",
|
||||
"Version": "3.15.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.14.7"
|
||||
"MinimumApiVersion": "3.15.0"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"Name": "Save Backup",
|
||||
"Author": "SMAPI",
|
||||
"Version": "3.14.7",
|
||||
"Version": "3.15.0",
|
||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||
"UniqueID": "SMAPI.SaveBackup",
|
||||
"EntryDll": "SaveBackup.dll",
|
||||
"MinimumApiVersion": "3.14.7"
|
||||
"MinimumApiVersion": "3.15.0"
|
||||
}
|
||||
|
|
|
@ -29,11 +29,8 @@ namespace SMAPI.Tests.Core
|
|||
/// <summary>The random number generator with which to create sample values.</summary>
|
||||
private readonly Random Random = new();
|
||||
|
||||
/// <summary>Sample user inputs for season names.</summary>
|
||||
private static readonly IInterfaceProxyFactory[] ProxyFactories = {
|
||||
new InterfaceProxyFactory(),
|
||||
new OriginalInterfaceProxyFactory()
|
||||
};
|
||||
/// <summary>The proxy factory to use in unit tests.</summary>
|
||||
private static readonly IInterfaceProxyFactory[] ProxyFactories = { new InterfaceProxyFactory() };
|
||||
|
||||
|
||||
/*********
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.5.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
<Import Project="..\..\build\common.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
|
||||
<PackageReference Include="System.Management" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
|
|
|
@ -15,14 +15,14 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.27" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.12.0" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.29" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.13.14" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" />
|
||||
<PackageReference Include="Markdig" Version="0.26.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
||||
<PackageReference Include="Markdig" Version="0.30.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.5" />
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Pathoschild.FluentNexus" Version="1.0.5" />
|
||||
<PackageReference Include="Pathoschild.Http.FluentClient" Version="4.1.0" />
|
||||
|
|
|
@ -170,6 +170,11 @@
|
|||
/*********
|
||||
** Broke in SMAPI 3.14.0
|
||||
*********/
|
||||
"CFAutomate": {
|
||||
"ID": "Platonymous.CFAutomate",
|
||||
"~2.12.9 | Status": "AssumeBroken",
|
||||
"~2.12.9 | StatusReasonDetails": "causes runtime errors in newer versions of Automate"
|
||||
},
|
||||
"Dynamic Game Assets": {
|
||||
"ID": "spacechase0.DynamicGameAssets",
|
||||
"~1.4.1 | Status": "AssumeBroken",
|
||||
|
|
|
@ -103,14 +103,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{3B5BF14D-F61
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Tests.ModApiProvider", "SMAPI.Tests.ModApiProvider\SMAPI.Tests.ModApiProvider.csproj", "{239AEEAC-07D1-4A3F-AA99-8C74F5038F50}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMAPI.Tests.ModApiConsumer", "SMAPI.Tests.ModApiConsumer\SMAPI.Tests.ModApiConsumer.csproj", "{2A4DF030-E8B1-4BBD-AA93-D4DE68CB9D85}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMAPI.Tests.ModApiConsumer", "SMAPI.Tests.ModApiConsumer\SMAPI.Tests.ModApiConsumer.csproj", "{2A4DF030-E8B1-4BBD-AA93-D4DE68CB9D85}"
|
||||
EndProject
|
||||
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
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace StardewModdingAPI
|
|||
internal static int? LogScreenId { get; set; }
|
||||
|
||||
/// <summary>SMAPI's current raw semantic version.</summary>
|
||||
internal static string RawApiVersion = "3.14.7";
|
||||
internal static string RawApiVersion = "3.15.0";
|
||||
}
|
||||
|
||||
/// <summary>Contains SMAPI's constants and assumptions.</summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using StardewValley;
|
||||
|
@ -28,74 +29,62 @@ namespace StardewModdingAPI.Framework.Content
|
|||
public AssetDataForImage(string? locale, IAssetName assetName, Texture2D data, Func<string, string> getNormalizedPath, Action<Texture2D> onDataReplaced)
|
||||
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
|
||||
{
|
||||
this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
|
||||
|
||||
// validate source data
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source), "Can't patch from null source data.");
|
||||
|
||||
// get the pixels for the source area
|
||||
Color[] sourceData;
|
||||
{
|
||||
int areaX = sourceArea.Value.X;
|
||||
int areaY = sourceArea.Value.Y;
|
||||
int areaWidth = sourceArea.Value.Width;
|
||||
int areaHeight = sourceArea.Value.Height;
|
||||
|
||||
if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight == source.Height)
|
||||
sourceData = source.Data;
|
||||
else
|
||||
{
|
||||
sourceData = new Color[areaWidth * areaHeight];
|
||||
int i = 0;
|
||||
for (int y = areaY, maxY = areaY + areaHeight - 1; y <= maxY; y++)
|
||||
{
|
||||
for (int x = areaX, maxX = areaX + areaWidth - 1; x <= maxX; x++)
|
||||
{
|
||||
int targetIndex = (y * source.Width) + x;
|
||||
sourceData[i++] = source.Data[targetIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply
|
||||
this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace)
|
||||
{
|
||||
// get texture
|
||||
this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height);
|
||||
|
||||
// validate source texture
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture.");
|
||||
Texture2D target = this.Data;
|
||||
|
||||
// get areas
|
||||
sourceArea ??= new Rectangle(0, 0, source.Width, source.Height);
|
||||
targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height));
|
||||
|
||||
// validate
|
||||
if (!source.Bounds.Contains(sourceArea.Value))
|
||||
throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
|
||||
if (!target.Bounds.Contains(targetArea.Value))
|
||||
throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture.");
|
||||
if (sourceArea.Value.Size != targetArea.Value.Size)
|
||||
throw new InvalidOperationException("The source and target areas must be the same size.");
|
||||
|
||||
// get source data
|
||||
int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height;
|
||||
Color[] sourceData = GC.AllocateUninitializedArray<Color>(pixelCount);
|
||||
source.GetData(0, sourceArea, sourceData, 0, pixelCount);
|
||||
|
||||
// merge data in overlay mode
|
||||
if (patchMode == PatchMode.Overlay)
|
||||
{
|
||||
// get target data
|
||||
Color[] targetData = GC.AllocateUninitializedArray<Color>(pixelCount);
|
||||
target.GetData(0, targetArea, targetData, 0, pixelCount);
|
||||
|
||||
// merge pixels
|
||||
for (int i = 0; i < sourceData.Length; i++)
|
||||
{
|
||||
Color above = sourceData[i];
|
||||
Color below = targetData[i];
|
||||
|
||||
// shortcut transparency
|
||||
if (above.A < MinOpacity)
|
||||
{
|
||||
sourceData[i] = below;
|
||||
continue;
|
||||
}
|
||||
if (below.A < MinOpacity)
|
||||
{
|
||||
sourceData[i] = above;
|
||||
continue;
|
||||
}
|
||||
|
||||
// merge pixels
|
||||
// This performs a conventional alpha blend for the pixels, which are already
|
||||
// premultiplied by the content pipeline. The formula is derived from
|
||||
// https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
|
||||
// Note: don't use named arguments here since they're different between
|
||||
// Linux/macOS and Windows.
|
||||
float alphaBelow = 1 - (above.A / 255f);
|
||||
sourceData[i] = new Color(
|
||||
(int)(above.R + (below.R * alphaBelow)), // r
|
||||
(int)(above.G + (below.G * alphaBelow)), // g
|
||||
(int)(above.B + (below.B * alphaBelow)), // b
|
||||
Math.Max(above.A, below.A) // a
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// patch target texture
|
||||
target.SetData(0, targetArea, sourceData, 0, pixelCount);
|
||||
// apply
|
||||
this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -110,5 +99,85 @@ namespace StardewModdingAPI.Framework.Content
|
|||
this.PatchImage(original);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Get the bounds for an image patch.</summary>
|
||||
/// <param name="sourceArea">The source area to set if needed.</param>
|
||||
/// <param name="targetArea">The target area to set if needed.</param>
|
||||
/// <param name="sourceWidth">The width of the full source image.</param>
|
||||
/// <param name="sourceHeight">The height of the full source image.</param>
|
||||
private void GetPatchBounds([NotNull] ref Rectangle? sourceArea, [NotNull] ref Rectangle? targetArea, int sourceWidth, int sourceHeight)
|
||||
{
|
||||
sourceArea ??= new Rectangle(0, 0, sourceWidth, sourceHeight);
|
||||
targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, this.Data.Width), Math.Min(sourceArea.Value.Height, this.Data.Height));
|
||||
}
|
||||
|
||||
/// <summary>Overwrite part of the image.</summary>
|
||||
/// <param name="sourceData">The image data to patch into the content.</param>
|
||||
/// <param name="sourceWidth">The pixel width of the source image.</param>
|
||||
/// <param name="sourceHeight">The pixel height of the source image.</param>
|
||||
/// <param name="sourceArea">The part of the <paramref name="sourceData"/> to copy (or <c>null</c> to take the whole texture). This must be within the bounds of the <paramref name="sourceData"/> texture.</param>
|
||||
/// <param name="targetArea">The part of the content to patch (or <c>null</c> to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet.</param>
|
||||
/// <param name="patchMode">Indicates how an image should be patched.</param>
|
||||
/// <exception cref="ArgumentNullException">One of the arguments is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="targetArea"/> is outside the bounds of the spritesheet.</exception>
|
||||
/// <exception cref="InvalidOperationException">The content being read isn't an image.</exception>
|
||||
private void PatchImageImpl(Color[] sourceData, int sourceWidth, int sourceHeight, Rectangle sourceArea, Rectangle targetArea, PatchMode patchMode)
|
||||
{
|
||||
// get texture
|
||||
Texture2D target = this.Data;
|
||||
int pixelCount = sourceArea.Width * sourceArea.Height;
|
||||
|
||||
// validate
|
||||
if (sourceArea.X < 0 || sourceArea.Y < 0 || sourceArea.Right > sourceWidth || sourceArea.Bottom > sourceHeight)
|
||||
throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture.");
|
||||
if (!target.Bounds.Contains(targetArea))
|
||||
throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture.");
|
||||
if (sourceArea.Size != targetArea.Size)
|
||||
throw new InvalidOperationException("The source and target areas must be the same size.");
|
||||
|
||||
// merge data
|
||||
if (patchMode == PatchMode.Overlay)
|
||||
{
|
||||
// get target data
|
||||
Color[] mergedData = GC.AllocateUninitializedArray<Color>(pixelCount);
|
||||
target.GetData(0, targetArea, mergedData, 0, pixelCount);
|
||||
|
||||
// merge pixels
|
||||
for (int i = 0; i < pixelCount; i++)
|
||||
{
|
||||
Color above = sourceData[i];
|
||||
Color below = mergedData[i];
|
||||
|
||||
// shortcut transparency
|
||||
if (above.A < MinOpacity)
|
||||
continue;
|
||||
if (below.A < MinOpacity)
|
||||
mergedData[i] = above;
|
||||
|
||||
// merge pixels
|
||||
else
|
||||
{
|
||||
// This performs a conventional alpha blend for the pixels, which are already
|
||||
// premultiplied by the content pipeline. The formula is derived from
|
||||
// https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
|
||||
float alphaBelow = 1 - (above.A / 255f);
|
||||
mergedData[i] = new Color(
|
||||
r: (int)(above.R + (below.R * alphaBelow)),
|
||||
g: (int)(above.G + (below.G * alphaBelow)),
|
||||
b: (int)(above.B + (below.B * alphaBelow)),
|
||||
alpha: Math.Max(above.A, below.A)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
target.SetData(0, targetArea, mergedData, 0, pixelCount);
|
||||
}
|
||||
else
|
||||
target.SetData(0, targetArea, sourceData, 0, pixelCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
** Fields
|
||||
*********/
|
||||
/// <summary>The underlying asset cache.</summary>
|
||||
private readonly IDictionary<string, object> Cache;
|
||||
private readonly Dictionary<string, object> Cache;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -29,7 +29,7 @@ namespace StardewModdingAPI.Framework.Content
|
|||
}
|
||||
|
||||
/// <summary>The current cache keys.</summary>
|
||||
public IEnumerable<string> Keys => this.Cache.Keys;
|
||||
public Dictionary<string, object>.KeyCollection Keys => this.Cache.Keys;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -89,33 +89,36 @@ namespace StardewModdingAPI.Framework.Content
|
|||
/// <returns>Returns the removed key (if any).</returns>
|
||||
public bool Remove(string key, bool dispose)
|
||||
{
|
||||
// get entry
|
||||
if (!this.Cache.TryGetValue(key, out object? value))
|
||||
// remove and get entry
|
||||
if (!this.Cache.Remove(key, out object? value))
|
||||
return false;
|
||||
|
||||
// dispose & remove entry
|
||||
if (dispose && value is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
|
||||
return this.Cache.Remove(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Purge matched assets from the cache.</summary>
|
||||
/// <summary>Purge assets matching <paramref name="predicate"/> from the cache.</summary>
|
||||
/// <param name="predicate">Matches the asset keys to invalidate.</param>
|
||||
/// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param>
|
||||
/// <returns>Returns the removed keys (if any).</returns>
|
||||
/// <param name="dispose">Whether to dispose invalidated assets. This should only be <see langword="true"/> when they're being invalidated as part of a <see cref="IDisposable.Dispose"/>, to avoid crashing the game.</param>
|
||||
/// <returns>Returns any removed keys.</returns>
|
||||
public IEnumerable<string> Remove(Func<string, object, bool> predicate, bool dispose)
|
||||
{
|
||||
List<string> removed = new List<string>();
|
||||
foreach (string key in this.Cache.Keys.ToArray())
|
||||
List<string> removed = new();
|
||||
foreach ((string key, object value) in this.Cache)
|
||||
{
|
||||
if (predicate(key, this.Cache[key]))
|
||||
{
|
||||
this.Remove(key, dispose);
|
||||
if (predicate(key, value))
|
||||
removed.Add(key);
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
|
||||
foreach (string key in removed)
|
||||
this.Remove(key, dispose);
|
||||
|
||||
return removed.Count == 0
|
||||
? Enumerable.Empty<string>() // let GC collect the list in gen0 instead of potentially living longer
|
||||
: removed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Content
|
||||
{
|
||||
/// <summary>The raw data for an image read from the filesystem.</summary>
|
||||
/// <param name="Width">The image width.</param>
|
||||
/// <param name="Height">The image height.</param>
|
||||
/// <param name="Data">The loaded image data.</param>
|
||||
internal record RawTextureData(int Width, int Height, Color[] Data) : IRawTextureData;
|
||||
}
|
|
@ -32,6 +32,9 @@ namespace StardewModdingAPI.Framework
|
|||
/// <summary>An asset key prefix for assets from SMAPI mod folders.</summary>
|
||||
private readonly string ManagedPrefix = "SMAPI";
|
||||
|
||||
/// <summary>Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU.</summary>
|
||||
private readonly bool UseRawImageLoading;
|
||||
|
||||
/// <summary>Get a file lookup for the given directory.</summary>
|
||||
private readonly Func<string, IFileLookup> GetFileLookup;
|
||||
|
||||
|
@ -130,7 +133,8 @@ namespace StardewModdingAPI.Framework
|
|||
/// <param name="getFileLookup">Get a file lookup for the given directory.</param>
|
||||
/// <param name="onAssetsInvalidated">A callback to invoke when any asset names have been invalidated from the cache.</param>
|
||||
/// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param>
|
||||
public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, Func<string, IFileLookup> getFileLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, AssetOperationGroup?> requestAssetOperations)
|
||||
/// <param name="useRawImageLoading">Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU.</param>
|
||||
public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action<BaseContentManager, IAssetName> onAssetLoaded, Func<string, IFileLookup> getFileLookup, Action<IList<IAssetName>> onAssetsInvalidated, Func<IAssetInfo, AssetOperationGroup?> requestAssetOperations, bool useRawImageLoading)
|
||||
{
|
||||
this.GetFileLookup = getFileLookup;
|
||||
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
|
||||
|
@ -141,6 +145,7 @@ namespace StardewModdingAPI.Framework
|
|||
this.OnAssetsInvalidated = onAssetsInvalidated;
|
||||
this.RequestAssetOperations = requestAssetOperations;
|
||||
this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory);
|
||||
this.UseRawImageLoading = useRawImageLoading;
|
||||
this.ContentManagers.Add(
|
||||
this.MainContentManager = new GameContentManager(
|
||||
name: "Game1.content",
|
||||
|
@ -219,7 +224,8 @@ namespace StardewModdingAPI.Framework
|
|||
reflection: this.Reflection,
|
||||
jsonHelper: this.JsonHelper,
|
||||
onDisposing: this.OnDisposing,
|
||||
fileLookup: this.GetFileLookup(rootDirectory)
|
||||
fileLookup: this.GetFileLookup(rootDirectory),
|
||||
useRawImageLoading: this.UseRawImageLoading
|
||||
);
|
||||
this.ContentManagers.Add(manager);
|
||||
return manager;
|
||||
|
@ -465,7 +471,7 @@ namespace StardewModdingAPI.Framework
|
|||
assets: invalidatedAssets.ToDictionary(p => p.Key, p => p.Value),
|
||||
ignoreWorld: Context.IsWorldFullyUnloaded,
|
||||
out IDictionary<IAssetName, bool> propagated,
|
||||
out bool updatedNpcWarps
|
||||
out bool updatedWarpRoutes
|
||||
);
|
||||
|
||||
// log summary
|
||||
|
@ -481,8 +487,8 @@ namespace StardewModdingAPI.Framework
|
|||
? $"Propagated {propagatedKeys.Length} core assets ({FormatKeyList(propagatedKeys)})."
|
||||
: "Propagated 0 core assets."
|
||||
);
|
||||
if (updatedNpcWarps)
|
||||
report.AppendLine("Updated NPC pathfinding cache.");
|
||||
if (updatedWarpRoutes)
|
||||
report.AppendLine("Updated NPC warp route cache.");
|
||||
}
|
||||
this.Monitor.Log(report.ToString().TrimEnd());
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ using Microsoft.Xna.Framework.Graphics;
|
|||
using StardewModdingAPI.Events;
|
||||
using StardewModdingAPI.Framework.Content;
|
||||
using StardewModdingAPI.Framework.Deprecations;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Framework.Utilities;
|
||||
using StardewModdingAPI.Internal;
|
||||
|
@ -93,6 +94,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <inheritdoc />
|
||||
public override T LoadExact<T>(IAssetName assetName, bool useCache)
|
||||
{
|
||||
if (typeof(IRawTextureData).IsAssignableFrom(typeof(T)))
|
||||
throw new SContentLoadException(ContentLoadErrorType.Other, $"Can't load {nameof(IRawTextureData)} assets from the game content pipeline. This asset type is only available for mod files.");
|
||||
|
||||
// raise first-load callback
|
||||
if (GameContentManager.IsFirstLoad)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -7,6 +9,8 @@ using BmFont;
|
|||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using SkiaSharp;
|
||||
using StardewModdingAPI.Framework.Content;
|
||||
using StardewModdingAPI.Framework.Exceptions;
|
||||
using StardewModdingAPI.Framework.Reflection;
|
||||
using StardewModdingAPI.Toolkit.Serialization;
|
||||
|
@ -20,11 +24,14 @@ using xTile.Tiles;
|
|||
namespace StardewModdingAPI.Framework.ContentManagers
|
||||
{
|
||||
/// <summary>A content manager which handles reading files from a SMAPI mod folder with support for unpacked files.</summary>
|
||||
internal class ModContentManager : BaseContentManager
|
||||
internal sealed class ModContentManager : BaseContentManager
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU.</summary>
|
||||
private readonly bool UseRawImageLoading;
|
||||
|
||||
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
|
||||
private readonly JsonHelper JsonHelper;
|
||||
|
||||
|
@ -38,7 +45,17 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
private readonly IFileLookup FileLookup;
|
||||
|
||||
/// <summary>If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder.</summary>
|
||||
private static readonly HashSet<string> LocalTilesheetExtensions = new(StringComparer.OrdinalIgnoreCase) { ".png", ".xnb" };
|
||||
private static readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" };
|
||||
|
||||
/// <summary>A lookup of image file paths to whether they have PyTK scaling information.</summary>
|
||||
private static readonly Dictionary<string, bool> IsPyTkScaled = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>Whether to enable legacy compatibility mode for PyTK scale-up textures.</summary>
|
||||
internal static bool EnablePyTkLegacyMode;
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -57,13 +74,15 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
|
||||
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
|
||||
/// <param name="fileLookup">A lookup for files within the <paramref name="rootDirectory"/>.</param>
|
||||
public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, IFileLookup fileLookup)
|
||||
/// <param name="useRawImageLoading">Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU.</param>
|
||||
public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, IFileLookup fileLookup, bool useRawImageLoading)
|
||||
: base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true)
|
||||
{
|
||||
this.GameContentManager = gameContentManager;
|
||||
this.FileLookup = fileLookup;
|
||||
this.JsonHelper = jsonHelper;
|
||||
this.ModName = modName;
|
||||
this.UseRawImageLoading = useRawImageLoading;
|
||||
|
||||
this.TryLocalizeKeys = false;
|
||||
}
|
||||
|
@ -119,8 +138,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
_ => this.HandleUnknownFileType<T>(assetName, file)
|
||||
};
|
||||
}
|
||||
catch (Exception ex) when (ex is not SContentLoadException)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is SContentLoadException)
|
||||
throw;
|
||||
|
||||
throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex);
|
||||
}
|
||||
|
||||
|
@ -130,6 +152,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete($"Temporary {nameof(ModContentManager)}s are unsupported")]
|
||||
public override LocalizedContentManager CreateTemporary()
|
||||
{
|
||||
throw new NotSupportedException("Can't create a temporary mod content manager.");
|
||||
|
@ -155,11 +178,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="file">The file to load.</param>
|
||||
private T LoadFont<T>(IAssetName assetName, FileInfo file)
|
||||
{
|
||||
// validate
|
||||
if (!typeof(T).IsAssignableFrom(typeof(XmlSource)))
|
||||
throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(XmlSource)}'.");
|
||||
this.AssertValidType<T>(assetName, file, typeof(XmlSource));
|
||||
|
||||
// load
|
||||
string source = File.ReadAllText(file.FullName);
|
||||
return (T)(object)new XmlSource(source);
|
||||
}
|
||||
|
@ -182,15 +202,89 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="file">The file to load.</param>
|
||||
private T LoadImageFile<T>(IAssetName assetName, FileInfo file)
|
||||
{
|
||||
// validate
|
||||
if (typeof(T) != typeof(Texture2D))
|
||||
throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'.");
|
||||
this.AssertValidType<T>(assetName, file, typeof(Texture2D), typeof(IRawTextureData));
|
||||
bool expectsRawData = typeof(T).IsAssignableTo(typeof(IRawTextureData));
|
||||
bool asRawData = expectsRawData || this.UseRawImageLoading;
|
||||
|
||||
// disable raw data if PyTK will rescale the image (until it supports raw data)
|
||||
if (asRawData && !expectsRawData)
|
||||
{
|
||||
if (ModContentManager.EnablePyTkLegacyMode)
|
||||
{
|
||||
if (!ModContentManager.IsPyTkScaled.TryGetValue(file.FullName, out bool isScaled))
|
||||
{
|
||||
string? dirPath = file.DirectoryName;
|
||||
string fileName = $"{Path.GetFileNameWithoutExtension(file.Name)}.pytk.json";
|
||||
|
||||
string path = dirPath is not null
|
||||
? Path.Combine(dirPath, fileName)
|
||||
: fileName;
|
||||
|
||||
ModContentManager.IsPyTkScaled[file.FullName] = isScaled = File.Exists(path);
|
||||
}
|
||||
|
||||
asRawData = !isScaled;
|
||||
if (!asRawData)
|
||||
this.Monitor.LogOnce("Enabled compatibility mode for PyTK scaled textures. This won't cause any issues, but may impact performance.", LogLevel.Warn);
|
||||
}
|
||||
else
|
||||
asRawData = true;
|
||||
}
|
||||
|
||||
// load
|
||||
using FileStream stream = File.OpenRead(file.FullName);
|
||||
Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
|
||||
texture = this.PremultiplyTransparency(texture);
|
||||
return (T)(object)texture;
|
||||
if (asRawData)
|
||||
{
|
||||
IRawTextureData raw = this.LoadRawImageData(file, expectsRawData);
|
||||
|
||||
if (expectsRawData)
|
||||
return (T)raw;
|
||||
else
|
||||
{
|
||||
Texture2D texture = new(Game1.graphics.GraphicsDevice, raw.Width, raw.Height);
|
||||
texture.SetData(raw.Data);
|
||||
return (T)(object)texture;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using FileStream stream = File.OpenRead(file.FullName);
|
||||
Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
|
||||
texture = this.PremultiplyTransparency(texture);
|
||||
return (T)(object)texture;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Load the raw image data from a file on disk.</summary>
|
||||
/// <param name="file">The file whose data to load.</param>
|
||||
/// <param name="forRawData">Whether the data is being loaded for an <see cref="IRawTextureData"/> (true) or <see cref="Texture2D"/> (false) instance.</param>
|
||||
/// <remarks>This is separate to let framework mods intercept the data before it's loaded, if needed.</remarks>
|
||||
[SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")]
|
||||
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")]
|
||||
private IRawTextureData LoadRawImageData(FileInfo file, bool forRawData)
|
||||
{
|
||||
// load raw data
|
||||
int width;
|
||||
int height;
|
||||
SKPMColor[] rawPixels;
|
||||
{
|
||||
using FileStream stream = File.OpenRead(file.FullName);
|
||||
using SKBitmap bitmap = SKBitmap.Decode(stream);
|
||||
rawPixels = SKPMColor.PreMultiply(bitmap.Pixels);
|
||||
width = bitmap.Width;
|
||||
height = bitmap.Height;
|
||||
}
|
||||
|
||||
// convert to XNA pixel format
|
||||
var pixels = GC.AllocateUninitializedArray<Color>(rawPixels.Length);
|
||||
for (int i = 0; i < pixels.Length; i++)
|
||||
{
|
||||
SKPMColor pixel = rawPixels[i];
|
||||
pixels[i] = pixel.Alpha == 0
|
||||
? Color.Transparent
|
||||
: new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha);
|
||||
}
|
||||
|
||||
return new RawTextureData(width, height, pixels);
|
||||
}
|
||||
|
||||
/// <summary>Load an unpacked image file (<c>.tbin</c> or <c>.tmx</c>).</summary>
|
||||
|
@ -199,11 +293,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="file">The file to load.</param>
|
||||
private T LoadMapFile<T>(IAssetName assetName, FileInfo file)
|
||||
{
|
||||
// validate
|
||||
if (typeof(T) != typeof(Map))
|
||||
throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'.");
|
||||
this.AssertValidType<T>(assetName, file, typeof(Map));
|
||||
|
||||
// load
|
||||
FormatManager formatManager = FormatManager.Instance;
|
||||
Map map = formatManager.LoadMap(file.FullName);
|
||||
map.assetPath = assetName.Name;
|
||||
|
@ -216,6 +307,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <param name="assetName">The asset name relative to the loader root directory.</param>
|
||||
private T LoadXnbFile<T>(IAssetName assetName)
|
||||
{
|
||||
if (typeof(IRawTextureData).IsAssignableFrom(typeof(T)))
|
||||
throw this.GetLoadError(assetName, ContentLoadErrorType.Other, $"can't read XNB file as type {typeof(IRawTextureData)}; that type can only be read from a PNG file.");
|
||||
|
||||
// the underlying content manager adds a .xnb extension implicitly, so
|
||||
// we need to strip it here to avoid trying to load a '.xnb.xnb' file.
|
||||
IAssetName loadName = assetName.Name.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase)
|
||||
|
@ -242,11 +336,24 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'.");
|
||||
}
|
||||
|
||||
/// <summary>Assert that the asset type is compatible with one of the allowed types.</summary>
|
||||
/// <typeparam name="TAsset">The actual asset type.</typeparam>
|
||||
/// <param name="assetName">The asset name relative to the loader root directory.</param>
|
||||
/// <param name="file">The file being loaded.</param>
|
||||
/// <param name="validTypes">The allowed asset types.</param>
|
||||
/// <exception cref="SContentLoadException">The <typeparamref name="TAsset"/> is not compatible with any of the <paramref name="validTypes"/>.</exception>
|
||||
private void AssertValidType<TAsset>(IAssetName assetName, FileInfo file, params Type[] validTypes)
|
||||
{
|
||||
if (!validTypes.Any(validType => validType.IsAssignableFrom(typeof(TAsset))))
|
||||
throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'.");
|
||||
}
|
||||
|
||||
/// <summary>Get an error which indicates that an asset couldn't be loaded.</summary>
|
||||
/// <param name="errorType">Why loading an asset through the content pipeline failed.</param>
|
||||
/// <param name="assetName">The asset name that failed to load.</param>
|
||||
/// <param name="reasonPhrase">The reason the file couldn't be loaded.</param>
|
||||
/// <param name="exception">The underlying exception, if applicable.</param>
|
||||
[DebuggerStepThrough, DebuggerHidden]
|
||||
private SContentLoadException GetLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null)
|
||||
{
|
||||
return new(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception);
|
||||
|
@ -261,7 +368,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
FileInfo file = this.FileLookup.GetFile(path);
|
||||
|
||||
// try with default image extensions
|
||||
if (!file.Exists && typeof(Texture2D).IsAssignableFrom(typeof(T)) && !ModContentManager.LocalTilesheetExtensions.Contains(file.Extension))
|
||||
if (!file.Exists && typeof(Texture2D).IsAssignableFrom(typeof(T)) && !ModContentManager.LocalTilesheetExtensions.Contains(file.Extension, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (string extension in ModContentManager.LocalTilesheetExtensions)
|
||||
{
|
||||
|
@ -284,7 +391,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
private Texture2D PremultiplyTransparency(Texture2D texture)
|
||||
{
|
||||
// premultiply pixels
|
||||
Color[] data = new Color[texture.Width * texture.Height];
|
||||
Color[] data = GC.AllocateUninitializedArray<Color>(texture.Width * texture.Height);
|
||||
texture.GetData(data);
|
||||
bool changed = false;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
|
@ -324,7 +431,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
|
||||
// reverse incorrect eager tilesheet path prefixing
|
||||
if (fixEagerPathPrefixes && relativeMapFolder.Length > 0 && imageSource.StartsWith(relativeMapFolder))
|
||||
imageSource = imageSource.Substring(relativeMapFolder.Length + 1);
|
||||
imageSource = imageSource[(relativeMapFolder.Length + 1)..];
|
||||
|
||||
// validate tilesheet path
|
||||
string errorPrefix = $"{this.ModName} loaded map '{relativeMapPath}' with invalid tilesheet path '{imageSource}'.";
|
||||
|
@ -345,8 +452,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
tilesheet.ImageSource = assetName.Name;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is not SContentLoadException)
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is SContentLoadException)
|
||||
throw;
|
||||
|
||||
throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{errorPrefix} The tilesheet couldn't be loaded.", ex);
|
||||
}
|
||||
}
|
||||
|
@ -361,7 +471,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
/// <remarks>See remarks on <see cref="FixTilesheetPaths"/>.</remarks>
|
||||
private bool TryGetTilesheetAssetName(string modRelativeMapFolder, string relativePath, out IAssetName? assetName, out string? error)
|
||||
{
|
||||
assetName = null;
|
||||
error = null;
|
||||
|
||||
// nothing to do
|
||||
|
@ -376,7 +485,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
// opened in Tiled, while still mapping it to the vanilla 'Maps/spring_town' asset at runtime.
|
||||
{
|
||||
string filename = Path.GetFileName(relativePath);
|
||||
if (filename.StartsWith("."))
|
||||
if (filename.StartsWith('.'))
|
||||
relativePath = Path.Combine(Path.GetDirectoryName(relativePath) ?? "", filename.TrimStart('.'));
|
||||
}
|
||||
|
||||
|
@ -391,7 +500,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
}
|
||||
|
||||
// get from game assets
|
||||
IAssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath), allowLocales: false);
|
||||
AssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath), allowLocales: false);
|
||||
try
|
||||
{
|
||||
this.GameContentManager.LoadLocalized<Texture2D>(contentKey, this.GameContentManager.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset
|
||||
|
@ -412,6 +521,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
}
|
||||
|
||||
// not found
|
||||
assetName = null;
|
||||
error = "The tilesheet couldn't be found relative to either map file or the game's content folder.";
|
||||
return false;
|
||||
}
|
||||
|
@ -422,11 +532,11 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
{
|
||||
// get file path
|
||||
string path = Path.Combine(this.GameContentManager.FullRootDirectory, key);
|
||||
if (!path.EndsWith(".xnb"))
|
||||
if (!path.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase))
|
||||
path += ".xnb";
|
||||
|
||||
// get file
|
||||
return new FileInfo(path).Exists;
|
||||
return File.Exists(path);
|
||||
}
|
||||
|
||||
/// <summary>Get the asset key for a tilesheet in the game's <c>Maps</c> content folder.</summary>
|
||||
|
@ -442,7 +552,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
|||
|
||||
// remove file extension from unpacked file
|
||||
if (key.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
|
||||
key = key.Substring(0, key.Length - 4);
|
||||
key = key[..^4];
|
||||
|
||||
return key;
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
/// <summary>Prefixing a low-level message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</summary>
|
||||
private const char IgnoreChar = InterceptingTextWriter.IgnoreChar;
|
||||
|
||||
/// <summary>Get a named monitor instance.</summary>
|
||||
private readonly Func<string, Monitor> GetMonitorImpl;
|
||||
/// <summary>Create a monitor instance given the ID and name.</summary>
|
||||
private readonly Func<string, string, Monitor> GetMonitorImpl;
|
||||
|
||||
/// <summary>Regex patterns which match console non-error messages to suppress from the console and log.</summary>
|
||||
private readonly Regex[] SuppressConsolePatterns =
|
||||
|
@ -88,23 +88,23 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
/// <param name="logPath">The log file path to write.</param>
|
||||
/// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param>
|
||||
/// <param name="writeToConsole">Whether to output log messages to the console.</param>
|
||||
/// <param name="isVerbose">Whether verbose logging is enabled. This enables more detailed diagnostic messages than are normally needed.</param>
|
||||
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
|
||||
/// <param name="isDeveloperMode">Whether to enable full console output for developers.</param>
|
||||
/// <param name="getScreenIdForLog">Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</param>
|
||||
public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode, Func<int?> getScreenIdForLog)
|
||||
public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, HashSet<string> verboseLogging, bool isDeveloperMode, Func<int?> getScreenIdForLog)
|
||||
{
|
||||
// init log file
|
||||
this.LogFile = new LogFileManager(logPath);
|
||||
|
||||
// init monitor
|
||||
this.GetMonitorImpl = name => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog)
|
||||
this.GetMonitorImpl = (id, name) => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, verboseLogging.Contains("*") || verboseLogging.Contains(id), getScreenIdForLog)
|
||||
{
|
||||
WriteToConsole = writeToConsole,
|
||||
ShowTraceInConsole = isDeveloperMode,
|
||||
ShowFullStampInConsole = isDeveloperMode
|
||||
};
|
||||
this.Monitor = this.GetMonitor("SMAPI");
|
||||
this.MonitorForGame = this.GetMonitor("game");
|
||||
this.Monitor = this.GetMonitor("SMAPI", "SMAPI");
|
||||
this.MonitorForGame = this.GetMonitor("game", "game");
|
||||
|
||||
// redirect direct console output
|
||||
this.ConsoleInterceptor = new InterceptingTextWriter(
|
||||
|
@ -124,10 +124,11 @@ namespace StardewModdingAPI.Framework.Logging
|
|||
}
|
||||
|
||||
/// <summary>Get a monitor instance derived from SMAPI's current settings.</summary>
|
||||
/// <param name="id">The unique ID for the mod context.</param>
|
||||
/// <param name="name">The name of the module which will log messages with this instance.</param>
|
||||
public Monitor GetMonitor(string name)
|
||||
public Monitor GetMonitor(string id, string name)
|
||||
{
|
||||
return this.GetMonitorImpl(name);
|
||||
return this.GetMonitorImpl(id, name);
|
||||
}
|
||||
|
||||
/// <summary>Set the title of the SMAPI console window.</summary>
|
||||
|
|
|
@ -20,10 +20,9 @@ namespace StardewModdingAPI.Framework.Models
|
|||
[nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(),
|
||||
[nameof(GitHubProjectName)] = "Pathoschild/SMAPI",
|
||||
[nameof(WebApiBaseUrl)] = "https://smapi.io/api/",
|
||||
[nameof(VerboseLogging)] = false,
|
||||
[nameof(LogNetworkTraffic)] = false,
|
||||
[nameof(RewriteMods)] = true,
|
||||
[nameof(UsePintail)] = true,
|
||||
[nameof(UseRawImageLoading)] = true,
|
||||
[nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux
|
||||
};
|
||||
|
||||
|
@ -57,14 +56,15 @@ namespace StardewModdingAPI.Framework.Models
|
|||
/// <summary>The base URL for SMAPI's web API, used to perform update checks.</summary>
|
||||
public string WebApiBaseUrl { get; }
|
||||
|
||||
/// <summary>Whether SMAPI should log more information about the game context.</summary>
|
||||
public bool VerboseLogging { get; }
|
||||
/// <summary>The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</summary>
|
||||
/// <remarks>The possible values are "*" (everything is verbose), "SMAPI", (SMAPI itself), or mod IDs.</remarks>
|
||||
public HashSet<string> VerboseLogging { get; }
|
||||
|
||||
/// <summary>Whether SMAPI should rewrite mods for compatibility.</summary>
|
||||
public bool RewriteMods { get; }
|
||||
|
||||
/// <summary>Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</summary>
|
||||
public bool UsePintail { get; }
|
||||
/// <summary>Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU.</summary>
|
||||
public bool UseRawImageLoading { get; }
|
||||
|
||||
/// <summary>Whether to make SMAPI file APIs case-insensitive, even on Linux.</summary>
|
||||
public bool UseCaseInsensitivePaths { get; }
|
||||
|
@ -76,7 +76,7 @@ namespace StardewModdingAPI.Framework.Models
|
|||
public ColorSchemeConfig ConsoleColors { get; }
|
||||
|
||||
/// <summary>The mod IDs SMAPI should ignore when performing update checks or validating update keys.</summary>
|
||||
public string[] SuppressUpdateChecks { get; }
|
||||
public HashSet<string> SuppressUpdateChecks { get; }
|
||||
|
||||
|
||||
/********
|
||||
|
@ -89,14 +89,14 @@ namespace StardewModdingAPI.Framework.Models
|
|||
/// <param name="useBetaChannel">Whether to show beta versions as valid updates.</param>
|
||||
/// <param name="gitHubProjectName">SMAPI's GitHub project name, used to perform update checks.</param>
|
||||
/// <param name="webApiBaseUrl">The base URL for SMAPI's web API, used to perform update checks.</param>
|
||||
/// <param name="verboseLogging">Whether SMAPI should log more information about the game context.</param>
|
||||
/// <param name="verboseLogging">The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting.</param>
|
||||
/// <param name="rewriteMods">Whether SMAPI should rewrite mods for compatibility.</param>
|
||||
/// <param name="usePintail">Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself.</param>
|
||||
/// <param name="useRawImageLoading">Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU.</param>
|
||||
/// <param name="useCaseInsensitivePaths">>Whether to make SMAPI file APIs case-insensitive, even on Linux.</param>
|
||||
/// <param name="logNetworkTraffic">Whether SMAPI should log network traffic.</param>
|
||||
/// <param name="consoleColors">The colors to use for text written to the SMAPI console.</param>
|
||||
/// <param name="suppressUpdateChecks">The mod IDs SMAPI should ignore when performing update checks or validating update keys.</param>
|
||||
public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool? verboseLogging, bool? rewriteMods, bool? usePintail, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
|
||||
public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? usePintail, bool? useRawImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks)
|
||||
{
|
||||
this.DeveloperMode = developerMode;
|
||||
this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)];
|
||||
|
@ -104,13 +104,13 @@ namespace StardewModdingAPI.Framework.Models
|
|||
this.UseBetaChannel = useBetaChannel ?? (bool)SConfig.DefaultValues[nameof(this.UseBetaChannel)];
|
||||
this.GitHubProjectName = gitHubProjectName;
|
||||
this.WebApiBaseUrl = webApiBaseUrl;
|
||||
this.VerboseLogging = verboseLogging ?? (bool)SConfig.DefaultValues[nameof(this.VerboseLogging)];
|
||||
this.VerboseLogging = new HashSet<string>(verboseLogging ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)];
|
||||
this.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(this.UsePintail)];
|
||||
this.UseRawImageLoading = useRawImageLoading ?? (bool)SConfig.DefaultValues[nameof(this.UseRawImageLoading)];
|
||||
this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)];
|
||||
this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)];
|
||||
this.ConsoleColors = consoleColors;
|
||||
this.SuppressUpdateChecks = suppressUpdateChecks ?? Array.Empty<string>();
|
||||
this.SuppressUpdateChecks = new HashSet<string>(suppressUpdateChecks ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Override the value of <see cref="DeveloperMode"/>.</summary>
|
||||
|
@ -132,9 +132,11 @@ namespace StardewModdingAPI.Framework.Models
|
|||
custom[name] = value;
|
||||
}
|
||||
|
||||
HashSet<string> curSuppressUpdateChecks = new(this.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
|
||||
if (SConfig.DefaultSuppressUpdateChecks.Count != curSuppressUpdateChecks.Count || SConfig.DefaultSuppressUpdateChecks.Any(p => !curSuppressUpdateChecks.Contains(p)))
|
||||
custom[nameof(this.SuppressUpdateChecks)] = "[" + string.Join(", ", this.SuppressUpdateChecks) + "]";
|
||||
if (!this.SuppressUpdateChecks.SetEquals(SConfig.DefaultSuppressUpdateChecks))
|
||||
custom[nameof(this.SuppressUpdateChecks)] = $"[{string.Join(", ", this.SuppressUpdateChecks)}]";
|
||||
|
||||
if (this.VerboseLogging.Any())
|
||||
custom[nameof(this.VerboseLogging)] = $"[{string.Join(", ", this.VerboseLogging)}]";
|
||||
|
||||
return custom;
|
||||
}
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Reflection
|
||||
{
|
||||
/// <summary>Generates a proxy class to access a mod API through an arbitrary interface.</summary>
|
||||
internal class OriginalInterfaceProxyBuilder
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The target class type.</summary>
|
||||
private readonly Type TargetType;
|
||||
|
||||
/// <summary>The generated proxy type.</summary>
|
||||
private readonly Type ProxyType;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="name">The type name to generate.</param>
|
||||
/// <param name="moduleBuilder">The CLR module in which to create proxy classes.</param>
|
||||
/// <param name="interfaceType">The interface type to implement.</param>
|
||||
/// <param name="targetType">The target type.</param>
|
||||
public OriginalInterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType)
|
||||
{
|
||||
// validate
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
if (targetType == null)
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
|
||||
// define proxy type
|
||||
TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class);
|
||||
proxyBuilder.AddInterfaceImplementation(interfaceType);
|
||||
|
||||
// create field to store target instance
|
||||
FieldBuilder targetField = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private);
|
||||
|
||||
// create constructor which accepts target instance and sets field
|
||||
{
|
||||
ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType });
|
||||
ILGenerator il = constructor.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0); // this
|
||||
// ReSharper disable once AssignNullToNotNullAttribute -- never null
|
||||
il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)!); // call base constructor
|
||||
il.Emit(OpCodes.Ldarg_0); // this
|
||||
il.Emit(OpCodes.Ldarg_1); // load argument
|
||||
il.Emit(OpCodes.Stfld, targetField); // set field to loaded argument
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
// proxy methods
|
||||
foreach (MethodInfo proxyMethod in interfaceType.GetMethods())
|
||||
{
|
||||
var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray());
|
||||
if (targetMethod == null)
|
||||
throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API.");
|
||||
|
||||
this.ProxyMethod(proxyBuilder, targetMethod, targetField);
|
||||
}
|
||||
|
||||
// save info
|
||||
this.TargetType = targetType;
|
||||
this.ProxyType = proxyBuilder.CreateType()!;
|
||||
}
|
||||
|
||||
/// <summary>Create an instance of the proxy for a target instance.</summary>
|
||||
/// <param name="targetInstance">The target instance.</param>
|
||||
public object CreateInstance(object targetInstance)
|
||||
{
|
||||
ConstructorInfo? constructor = this.ProxyType.GetConstructor(new[] { this.TargetType });
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen
|
||||
return constructor.Invoke(new[] { targetInstance });
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Define a method which proxies access to a method on the target.</summary>
|
||||
/// <param name="proxyBuilder">The proxy type being generated.</param>
|
||||
/// <param name="target">The target method.</param>
|
||||
/// <param name="instanceField">The proxy field containing the API instance.</param>
|
||||
private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField)
|
||||
{
|
||||
Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray();
|
||||
|
||||
// create method
|
||||
MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual);
|
||||
methodBuilder.SetParameters(argTypes);
|
||||
methodBuilder.SetReturnType(target.ReturnType);
|
||||
|
||||
// create method body
|
||||
{
|
||||
ILGenerator il = methodBuilder.GetILGenerator();
|
||||
|
||||
// load target instance
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, instanceField);
|
||||
|
||||
// invoke target method on instance
|
||||
for (int i = 0; i < argTypes.Length; i++)
|
||||
il.Emit(OpCodes.Ldarg, i + 1);
|
||||
il.Emit(OpCodes.Call, target);
|
||||
|
||||
// return result
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace StardewModdingAPI.Framework.Reflection
|
||||
{
|
||||
/// <inheritdoc />
|
||||
internal class OriginalInterfaceProxyFactory : IInterfaceProxyFactory
|
||||
{
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The CLR module in which to create proxy classes.</summary>
|
||||
private readonly ModuleBuilder ModuleBuilder;
|
||||
|
||||
/// <summary>The generated proxy types.</summary>
|
||||
private readonly IDictionary<string, OriginalInterfaceProxyBuilder> Builders = new Dictionary<string, OriginalInterfaceProxyBuilder>();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public OriginalInterfaceProxyFactory()
|
||||
{
|
||||
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run);
|
||||
this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public TInterface CreateProxy<TInterface>(object instance, string sourceModID, string targetModID)
|
||||
where TInterface : class
|
||||
{
|
||||
lock (this.Builders)
|
||||
{
|
||||
// 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 OriginalInterfaceProxyBuilder? builder))
|
||||
{
|
||||
builder = new OriginalInterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType);
|
||||
this.Builders[proxyTypeName] = builder;
|
||||
}
|
||||
|
||||
// create instance
|
||||
return (TInterface)builder.CreateInstance(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -194,7 +194,7 @@ namespace StardewModdingAPI.Framework
|
|||
if (developerMode.HasValue)
|
||||
this.Settings.OverrideDeveloperMode(developerMode.Value);
|
||||
|
||||
this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, isVerbose: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog);
|
||||
this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, verboseLogging: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog);
|
||||
this.CommandManager = new CommandManager(this.Monitor);
|
||||
this.EventManager = new EventManager(this.ModRegistry);
|
||||
SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
|
||||
|
@ -1231,7 +1231,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
|
||||
// make sure it's a content pack for the requesting mod
|
||||
if (!onBehalfOf.IsContentPack || !string.Equals(onBehalfOf.Manifest.ContentPackFor?.UniqueID, mod.Manifest.UniqueID))
|
||||
if (!onBehalfOf.IsContentPack || !string.Equals(onBehalfOf.Manifest.ContentPackFor?.UniqueID, mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mod.LogAsModOnce($"{errorPrefix}: that isn't a content pack for this mod.", LogLevel.Warn);
|
||||
return null;
|
||||
|
@ -1301,7 +1301,8 @@ namespace StardewModdingAPI.Framework
|
|||
onAssetLoaded: this.OnAssetLoaded,
|
||||
onAssetsInvalidated: this.OnAssetsInvalidated,
|
||||
getFileLookup: this.GetFileLookup,
|
||||
requestAssetOperations: this.RequestAssetOperations
|
||||
requestAssetOperations: this.RequestAssetOperations,
|
||||
useRawImageLoading: this.Settings.UseRawImageLoading
|
||||
);
|
||||
if (this.ContentCore.Language != this.Translator.LocaleEnum)
|
||||
this.Translator.SetLocale(this.ContentCore.GetLocale(), this.ContentCore.Language);
|
||||
|
@ -1507,7 +1508,7 @@ namespace StardewModdingAPI.Framework
|
|||
{
|
||||
try
|
||||
{
|
||||
HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
|
||||
HashSet<string> suppressUpdateChecks = this.Settings.SuppressUpdateChecks;
|
||||
|
||||
// prepare search model
|
||||
List<ModSearchEntryModel> searchMods = new List<ModSearchEntryModel>();
|
||||
|
@ -1608,10 +1609,8 @@ namespace StardewModdingAPI.Framework
|
|||
using (AssemblyLoader modAssemblyLoader = new(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings, this.Settings.RewriteMods))
|
||||
{
|
||||
// init
|
||||
HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase);
|
||||
IInterfaceProxyFactory proxyFactory = this.Settings.UsePintail
|
||||
? new InterfaceProxyFactory()
|
||||
: new OriginalInterfaceProxyFactory();
|
||||
HashSet<string> suppressUpdateChecks = this.Settings.SuppressUpdateChecks;
|
||||
IInterfaceProxyFactory proxyFactory = new InterfaceProxyFactory();
|
||||
|
||||
// load mods
|
||||
foreach (IModMetadata mod in mods)
|
||||
|
@ -1637,6 +1636,14 @@ namespace StardewModdingAPI.Framework
|
|||
// initialize translations
|
||||
this.ReloadTranslations(loaded);
|
||||
|
||||
// set temporary PyTK compatibility mode
|
||||
// This is part of a three-part fix for PyTK 1.23.0 and earlier. When removing this,
|
||||
// search 'Platonymous.Toolkit' to find the other part in SMAPI and Content Patcher.
|
||||
{
|
||||
IModInfo? pyTk = this.ModRegistry.Get("Platonymous.Toolkit");
|
||||
ModContentManager.EnablePyTkLegacyMode = pyTk is not null && pyTk.Manifest.Version.IsOlderThan("1.23.1");
|
||||
}
|
||||
|
||||
// initialize loaded non-content-pack mods
|
||||
this.Monitor.Log("Launching mods...", LogLevel.Debug);
|
||||
#pragma warning disable CS0612, CS0618 // deprecated code
|
||||
|
@ -1827,7 +1834,7 @@ namespace StardewModdingAPI.Framework
|
|||
// load as content pack
|
||||
if (mod.IsContentPack)
|
||||
{
|
||||
IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName);
|
||||
IMonitor monitor = this.LogManager.GetMonitor(manifest.UniqueID, mod.DisplayName);
|
||||
IFileLookup fileLookup = this.GetFileLookup(mod.DirectoryPath);
|
||||
GameContentHelper gameContentHelper = new(this.ContentCore, mod, mod.DisplayName, monitor, this.Reflection);
|
||||
IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), this.Reflection);
|
||||
|
@ -1902,7 +1909,7 @@ namespace StardewModdingAPI.Framework
|
|||
}
|
||||
|
||||
// init mod helpers
|
||||
IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName);
|
||||
IMonitor monitor = this.LogManager.GetMonitor(manifest.UniqueID, mod.DisplayName);
|
||||
TranslationHelper translationHelper = new(mod, contentCore.GetLocale(), contentCore.Language);
|
||||
IModHelper modHelper;
|
||||
{
|
||||
|
@ -1965,7 +1972,7 @@ namespace StardewModdingAPI.Framework
|
|||
);
|
||||
|
||||
// create mod helpers
|
||||
IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.Name);
|
||||
IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.UniqueID, packManifest.Name);
|
||||
GameContentHelper gameContentHelper = new(contentCore, fakeMod, packManifest.Name, packMonitor, this.Reflection);
|
||||
IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, fakeMod, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), this.Reflection);
|
||||
TranslationHelper packTranslationHelper = new(fakeMod, contentCore.GetLocale(), contentCore.Language);
|
||||
|
|
|
@ -27,15 +27,6 @@ namespace MonoMod.Utils
|
|||
private static readonly object[] _NoArgs = Array.Empty<object>();
|
||||
private static readonly object?[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null };
|
||||
|
||||
private static readonly Type? t_RuntimeModule =
|
||||
typeof(Module).Assembly
|
||||
.GetType("System.Reflection.RuntimeModule");
|
||||
|
||||
private static readonly PropertyInfo? p_RuntimeModule_RuntimeType =
|
||||
typeof(Module).Assembly
|
||||
.GetType("System.Reflection.RuntimeModule")
|
||||
?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
private static readonly Type? t_RuntimeType =
|
||||
typeof(Type).Assembly
|
||||
.GetType("System.RuntimeType");
|
||||
|
@ -109,22 +100,6 @@ namespace MonoMod.Utils
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return member.DeclaringType ?? member.Module.GetModuleType();
|
||||
|
|
|
@ -10,6 +10,16 @@ namespace StardewModdingAPI
|
|||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Overwrite part of the image.</summary>
|
||||
/// <param name="source">The image to patch into the content.</param>
|
||||
/// <param name="sourceArea">The part of the <paramref name="source"/> to copy (or <c>null</c> to take the whole texture). This must be within the bounds of the <paramref name="source"/> texture.</param>
|
||||
/// <param name="targetArea">The part of the content to patch (or <c>null</c> to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet.</param>
|
||||
/// <param name="patchMode">Indicates how an image should be patched.</param>
|
||||
/// <exception cref="ArgumentNullException">One of the arguments is null.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="targetArea"/> is outside the bounds of the spritesheet.</exception>
|
||||
/// <exception cref="InvalidOperationException">The content being read isn't an image.</exception>
|
||||
void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace);
|
||||
|
||||
/// <summary>Overwrite part of the image.</summary>
|
||||
/// <param name="source">The image to patch into the content.</param>
|
||||
/// <param name="sourceArea">The part of the <paramref name="source"/> to copy (or <c>null</c> to take the whole texture). This must be within the bounds of the <paramref name="source"/> texture.</param>
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace StardewModdingAPI
|
|||
** Public methods
|
||||
*********/
|
||||
/// <summary>Load content from the game folder or mod folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, dictionaries, and lists; other types may be supported by the game's content pipeline.</typeparam>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, <see cref="IRawTextureData"/> (for mod content only), and data structures; other types may be supported by the game's content pipeline.</typeparam>
|
||||
/// <param name="key">The asset key to fetch (if the <paramref name="source"/> is <see cref="ContentSource.GameContent"/>), or the local path to a content file relative to the mod folder.</param>
|
||||
/// <param name="source">Where to search for a matching content asset.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace StardewModdingAPI
|
|||
where TModel : class;
|
||||
|
||||
/// <summary>Load content from the content pack folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, and dictionaries; other types may be supported by the game's content pipeline.</typeparam>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, <see cref="IRawTextureData"/>, and data structures; other types may be supported by the game's content pipeline.</typeparam>
|
||||
/// <param name="key">The relative file path within the content pack (case-insensitive).</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception>
|
||||
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace StardewModdingAPI
|
|||
** Public methods
|
||||
*********/
|
||||
/// <summary>Load content from the mod folder and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, dictionaries, and lists; other types may be supported by the game's content pipeline.</typeparam>
|
||||
/// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, <see cref="IRawTextureData"/>, and data structures; other types may be supported by the game's content pipeline.</typeparam>
|
||||
/// <param name="relativePath">The local path to a content file relative to the mod folder.</param>
|
||||
/// <exception cref="ArgumentException">The <paramref name="relativePath"/> is empty or contains invalid characters.</exception>
|
||||
/// <exception cref="ContentLoadException">The content asset couldn't be loaded (e.g. because it doesn't exist).</exception>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>The raw data for an image read from the filesystem.</summary>
|
||||
public interface IRawTextureData
|
||||
{
|
||||
/// <summary>The image width.</summary>
|
||||
int Width { get; }
|
||||
|
||||
/// <summary>The image height.</summary>
|
||||
int Height { get; }
|
||||
|
||||
/// <summary>The loaded image data.</summary>
|
||||
Color[] Data { get; }
|
||||
}
|
||||
}
|
|
@ -85,8 +85,8 @@ namespace StardewModdingAPI.Metadata
|
|||
/// <param name="assets">The asset keys and types to reload.</param>
|
||||
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
|
||||
/// <param name="propagatedAssets">A lookup of asset names to whether they've been propagated.</param>
|
||||
/// <param name="updatedNpcWarps">Whether the NPC pathfinding cache was reloaded.</param>
|
||||
public void Propagate(IDictionary<IAssetName, Type> assets, bool ignoreWorld, out IDictionary<IAssetName, bool> propagatedAssets, out bool updatedNpcWarps)
|
||||
/// <param name="changedWarpRoutes">Whether the NPC pathfinding warp route cache was reloaded.</param>
|
||||
public void Propagate(IDictionary<IAssetName, Type> assets, bool ignoreWorld, out IDictionary<IAssetName, bool> propagatedAssets, out bool changedWarpRoutes)
|
||||
{
|
||||
// get base name lookup
|
||||
propagatedAssets = assets
|
||||
|
@ -107,7 +107,7 @@ namespace StardewModdingAPI.Metadata
|
|||
});
|
||||
|
||||
// reload assets
|
||||
updatedNpcWarps = false;
|
||||
changedWarpRoutes = false;
|
||||
foreach (var bucket in buckets)
|
||||
{
|
||||
switch (bucket.Key)
|
||||
|
@ -126,10 +126,10 @@ namespace StardewModdingAPI.Metadata
|
|||
foreach (var entry in bucket)
|
||||
{
|
||||
bool changed = false;
|
||||
bool curChangedMapWarps = false;
|
||||
bool curChangedMapRoutes = false;
|
||||
try
|
||||
{
|
||||
changed = this.PropagateOther(entry.Key, entry.Value, ignoreWorld, out curChangedMapWarps);
|
||||
changed = this.PropagateOther(entry.Key, entry.Value, ignoreWorld, out curChangedMapRoutes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -137,14 +137,14 @@ namespace StardewModdingAPI.Metadata
|
|||
}
|
||||
|
||||
propagatedAssets[entry.Key] = changed;
|
||||
updatedNpcWarps = updatedNpcWarps || curChangedMapWarps;
|
||||
changedWarpRoutes = changedWarpRoutes || curChangedMapRoutes;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// reload NPC pathfinding cache if any map changed
|
||||
if (updatedNpcWarps)
|
||||
// reload NPC pathfinding cache if any map routes changed
|
||||
if (changedWarpRoutes)
|
||||
NPC.populateRoutesFromLocationToLocationList();
|
||||
}
|
||||
|
||||
|
@ -156,14 +156,14 @@ namespace StardewModdingAPI.Metadata
|
|||
/// <param name="assetName">The asset name to reload.</param>
|
||||
/// <param name="type">The asset type to reload.</param>
|
||||
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
|
||||
/// <param name="changedWarps">Whether any map warps were changed as part of this propagation.</param>
|
||||
/// <param name="changedWarpRoutes">Whether the locations reachable by warps from this location changed as part of this propagation.</param>
|
||||
/// <returns>Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true.</returns>
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These deliberately match the asset names.")]
|
||||
private bool PropagateOther(IAssetName assetName, Type type, bool ignoreWorld, out bool changedWarps)
|
||||
private bool PropagateOther(IAssetName assetName, Type type, bool ignoreWorld, out bool changedWarpRoutes)
|
||||
{
|
||||
var content = this.MainContentManager;
|
||||
string key = assetName.BaseName;
|
||||
changedWarps = false;
|
||||
changedWarpRoutes = false;
|
||||
|
||||
/****
|
||||
** Special case: current map tilesheet
|
||||
|
@ -197,7 +197,7 @@ namespace StardewModdingAPI.Metadata
|
|||
static ISet<string> GetWarpSet(GameLocation location)
|
||||
{
|
||||
return new HashSet<string>(
|
||||
location.warps.Select(p => $"{p.X} {p.Y} {p.TargetName} {p.TargetX} {p.TargetY}")
|
||||
location.warps.Select(p => p.TargetName)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ namespace StardewModdingAPI.Metadata
|
|||
this.UpdateMap(info);
|
||||
var newWarps = GetWarpSet(location);
|
||||
|
||||
changedWarps = changedWarps || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p));
|
||||
changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p));
|
||||
anyChanged = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,3 +3,4 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("SMAPI.Tests")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq for unit testing
|
||||
[assembly: InternalsVisibleTo("ContentPatcher")]
|
||||
[assembly: InternalsVisibleTo("ErrorHandler")]
|
||||
|
|
|
@ -16,9 +16,17 @@ copy all the settings, or you may cause bugs due to overridden changes in future
|
|||
*/
|
||||
{
|
||||
/**
|
||||
* Whether SMAPI should log more information about the game context.
|
||||
* The logs for which to enable verbose logging, which may show a lot more information to
|
||||
* simplify troubleshooting.
|
||||
*
|
||||
* The possible values are:
|
||||
* - "*" for everything (not recommended);
|
||||
* - "SMAPI" for messages from SMAPI itself;
|
||||
* - mod IDs from their manifest.json files.
|
||||
*
|
||||
* For example: [ "SMAPI", "Pathoschild.ContentPatcher" ]
|
||||
*/
|
||||
"VerboseLogging": false,
|
||||
"VerboseLogging": [],
|
||||
|
||||
/**
|
||||
* Whether SMAPI should check for newer versions of SMAPI and mods when you load the game. If new
|
||||
|
@ -52,6 +60,12 @@ copy all the settings, or you may cause bugs due to overridden changes in future
|
|||
*/
|
||||
"UsePintail": true,
|
||||
|
||||
/**
|
||||
* Whether to use raw image data when possible, instead of initializing an XNA Texture2D
|
||||
* instance through the GPU.
|
||||
*/
|
||||
"UseRawImageLoading": true,
|
||||
|
||||
/**
|
||||
* Whether to add a section to the 'mod issues' list for mods which directly use potentially
|
||||
* sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="LargeAddressAware" Version="1.0.5" />
|
||||
<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="Pintail" Version="2.1.0" />
|
||||
<PackageReference Include="MonoMod.Common" Version="22.3.5.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Pintail" Version="2.2.0" />
|
||||
<PackageReference Include="Platonymous.TMXTile" Version="1.5.9" />
|
||||
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
|||
<Reference Include="GalaxyCSharp" HintPath="$(GamePath)\GalaxyCSharp.dll" Private="False" />
|
||||
<Reference Include="Lidgren.Network" HintPath="$(GamePath)\Lidgren.Network.dll" Private="False" />
|
||||
<Reference Include="MonoGame.Framework" HintPath="$(GamePath)\MonoGame.Framework.dll" Private="False" />
|
||||
<Reference Include="SkiaSharp" HintPath="$(GamePath)\SkiaSharp.dll" Private="False" />
|
||||
<Reference Include="xTile" HintPath="$(GamePath)\xTile.dll" Private="False" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
Loading…
Reference in New Issue