summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs26
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs4
-rw-r--r--src/SMAPI/Framework/Patching/GamePatcher.cs8
-rw-r--r--src/SMAPI/Framework/Patching/IHarmonyPatch.cs8
-rw-r--r--src/SMAPI/Framework/Patching/PatchHelper.cs36
-rw-r--r--src/SMAPI/Framework/SCore.cs2
-rw-r--r--src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs176
10 files changed, 179 insertions, 87 deletions
diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
index f3731d16..45b34556 100644
--- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
+++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs
@@ -3,25 +3,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
-#if HARMONY_2
using HarmonyLib;
-#else
-using Harmony;
-#endif
namespace StardewModdingAPI.Framework.Commands
{
/// <summary>The 'harmony_summary' SMAPI console command.</summary>
internal class HarmonySummaryCommand : IInternalCommand
{
-#if !HARMONY_2
- /*********
- ** Fields
- *********/
- /// <summary>The Harmony instance through which to fetch patch info.</summary>
- private readonly HarmonyInstance HarmonyInstance = HarmonyInstance.Create($"SMAPI.{nameof(HarmonySummaryCommand)}");
-#endif
-
/*********
** Accessors
*********/
@@ -60,9 +48,7 @@ namespace StardewModdingAPI.Framework.Commands
{
PatchType.Prefix => 0,
PatchType.Postfix => 1,
-#if HARMONY_2
PatchType.Finalizer => 2,
-#endif
PatchType.Transpiler => 3,
_ => 4
});
@@ -111,26 +97,16 @@ namespace StardewModdingAPI.Framework.Commands
/// <summary>Get all current Harmony patches.</summary>
private IEnumerable<SearchResult> GetAllPatches()
{
-#if HARMONY_2
foreach (MethodBase method in Harmony.GetAllPatchedMethods().ToArray())
-#else
- foreach (MethodBase method in this.HarmonyInstance.GetPatchedMethods().ToArray())
-#endif
{
// get metadata for method
-#if HARMONY_2
HarmonyLib.Patches patchInfo = Harmony.GetPatchInfo(method);
-#else
- Harmony.Patches patchInfo = this.HarmonyInstance.GetPatchInfo(method);
-#endif
IDictionary<PatchType, IReadOnlyCollection<Patch>> patchGroups = new Dictionary<PatchType, IReadOnlyCollection<Patch>>
{
[PatchType.Prefix] = patchInfo.Prefixes,
[PatchType.Postfix] = patchInfo.Postfixes,
-#if HARMONY_2
[PatchType.Finalizer] = patchInfo.Finalizers,
-#endif
[PatchType.Transpiler] = patchInfo.Transpilers
};
@@ -160,10 +136,8 @@ namespace StardewModdingAPI.Framework.Commands
/// <summary>A postfix patch.</summary>
Postfix,
-#if HARMONY_2
/// <summary>A finalizer patch.</summary>
Finalizer,
-#endif
/// <summary>A transpiler patch.</summary>
Transpiler
diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs
index 102f3364..8e4320b3 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs
@@ -1,4 +1,3 @@
-#if HARMONY_2
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -41,4 +40,3 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
}
}
}
-#endif
diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs
index ad6d5e4f..54b91679 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs
@@ -1,4 +1,3 @@
-#if HARMONY_2
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -81,4 +80,3 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
}
}
}
-#endif
diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs
index f3975558..44c97401 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs
@@ -1,4 +1,3 @@
-#if HARMONY_2
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
@@ -44,4 +43,3 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades
}
}
}
-#endif
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
index 147a12f9..5a84a16a 100644
--- a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
@@ -1,4 +1,3 @@
-#if HARMONY_2
extern alias MonoCecilPackage;
using System;
@@ -44,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
}
/// <inheritdoc />
- public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, Action<Instruction> replaceWith)
+ public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
{
// rewrite Harmony 1.x methods to Harmony 2.0
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
@@ -119,4 +118,3 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
}
}
}
-#endif
diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs
index ddecda08..3ce22ee9 100644
--- a/src/SMAPI/Framework/Patching/GamePatcher.cs
+++ b/src/SMAPI/Framework/Patching/GamePatcher.cs
@@ -1,9 +1,5 @@
using System;
-#if HARMONY_2
using HarmonyLib;
-#else
-using Harmony;
-#endif
namespace StardewModdingAPI.Framework.Patching
{
@@ -31,11 +27,7 @@ namespace StardewModdingAPI.Framework.Patching
/// <param name="patches">The patches to apply.</param>
public void Apply(params IHarmonyPatch[] patches)
{
-#if HARMONY_2
Harmony harmony = new Harmony("SMAPI");
-#else
- HarmonyInstance harmony = HarmonyInstance.Create("SMAPI");
-#endif
foreach (IHarmonyPatch patch in patches)
{
try
diff --git a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
index 38d30ab2..c1ff3040 100644
--- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
+++ b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
@@ -1,8 +1,4 @@
-#if HARMONY_2
using HarmonyLib;
-#else
-using Harmony;
-#endif
namespace StardewModdingAPI.Framework.Patching
{
@@ -14,10 +10,6 @@ namespace StardewModdingAPI.Framework.Patching
*********/
/// <summary>Apply the Harmony patch.</summary>
/// <param name="harmony">The Harmony instance.</param>
-#if HARMONY_2
void Apply(Harmony harmony);
-#else
- void Apply(HarmonyInstance harmony);
-#endif
}
}
diff --git a/src/SMAPI/Framework/Patching/PatchHelper.cs b/src/SMAPI/Framework/Patching/PatchHelper.cs
deleted file mode 100644
index d1aa0185..00000000
--- a/src/SMAPI/Framework/Patching/PatchHelper.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-#if !HARMONY_2
-using System;
-using System.Collections.Generic;
-
-namespace StardewModdingAPI.Framework.Patching
-{
- /// <summary>Provides generic methods for implementing Harmony patches.</summary>
- internal class PatchHelper
- {
- /*********
- ** Fields
- *********/
- /// <summary>The interception keys currently being intercepted.</summary>
- private static readonly HashSet<string> InterceptingKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Track a method that will be intercepted.</summary>
- /// <param name="key">The intercept key.</param>
- /// <returns>Returns false if the method was already marked for interception, else true.</returns>
- public static bool StartIntercept(string key)
- {
- return PatchHelper.InterceptingKeys.Add(key);
- }
-
- /// <summary>Track a method as no longer being intercepted.</summary>
- /// <param name="key">The intercept key.</param>
- public static void StopIntercept(string key)
- {
- PatchHelper.InterceptingKeys.Remove(key);
- }
- }
-}
-#endif
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index c3285979..4211abc5 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -46,6 +46,7 @@ using StardewModdingAPI.Toolkit.Utilities;
using StardewModdingAPI.Utilities;
using StardewValley;
using xTile.Display;
+using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix;
using SObject = StardewValley.Object;
namespace StardewModdingAPI.Framework
@@ -251,6 +252,7 @@ namespace StardewModdingAPI.Framework
StardewValley.GameRunner.instance = this.Game;
// apply game patches
+ MiniMonoModHotfix.Apply();
new GamePatcher(this.Monitor).Apply(
new LoadContextPatch(this.Reflection, this.OnLoadStageChanged)
);
diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs
new file mode 100644
index 00000000..6814abea
--- /dev/null
+++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs
@@ -0,0 +1,176 @@
+// This temporary utility fixes an esoteric issue in XNA Framework where deserialization depends on
+// the order of fields returned by Type.GetFields, but that order changes after Harmony/MonoMod use
+// reflection to access the fields due to an issue in .NET Framework.
+// https://twitter.com/0x0ade/status/1414992316964687873
+//
+// This will be removed when Harmony/MonoMod are updated to incorporate the fix.
+//
+// Special thanks to 0x0ade for submitting this worokaround! Copy/pasted and adapted from MonoMod.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using HarmonyLib;
+
+// ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author.
+namespace MonoMod.Utils
+{
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Temporary hotfix submitted by the MonoMod author.")]
+ [SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "Temporary hotfix submitted by the MonoMod author.")]
+ static class MiniMonoModHotfix
+ {
+ // .NET Framework can break member ordering if using Module.Resolve* on certain members.
+
+ private static readonly object[] _NoArgs = new object[0];
+ private static readonly object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null };
+
+ private static readonly Type t_RuntimeModule =
+ typeof(Module).Assembly
+ .GetType("System.Reflection.RuntimeModule");
+
+ private static readonly PropertyInfo p_RuntimeModule_RuntimeType =
+ typeof(Module).Assembly
+ .GetType("System.Reflection.RuntimeModule")
+ ?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ private static readonly Type t_RuntimeType =
+ typeof(Type).Assembly
+ .GetType("System.RuntimeType");
+
+ private static readonly PropertyInfo p_RuntimeType_Cache =
+ typeof(Type).Assembly
+ .GetType("System.RuntimeType")
+ ?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ private static readonly MethodInfo m_RuntimeTypeCache_GetFieldList =
+ typeof(Type).Assembly
+ .GetType("System.RuntimeType+RuntimeTypeCache")
+ ?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ private static readonly MethodInfo m_RuntimeTypeCache_GetPropertyList =
+ typeof(Type).Assembly
+ .GetType("System.RuntimeType+RuntimeTypeCache")
+ ?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+
+ private static readonly ConditionalWeakTable<Type, object> _CacheFixed = new ConditionalWeakTable<Type, object>();
+
+ public static void Apply()
+ {
+ var harmony = new Harmony("MiniMonoModHotfix");
+
+ harmony.Patch(
+ original: typeof(Harmony).Assembly
+ .GetType("HarmonyLib.MethodBodyReader")
+ .GetMethod("ReadOperand", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
+ transpiler: new HarmonyMethod(typeof(MiniMonoModHotfix), nameof(ResolveTokenFix))
+ );
+
+ harmony.Patch(
+ original: typeof(Harmony).Assembly
+ .GetType("MonoMod.Utils.DynamicMethodDefinition+<>c__DisplayClass3_0")
+ .GetMethod("<_CopyMethodToDefinition>g__ResolveTokenAs|1", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
+ transpiler: new HarmonyMethod(typeof(MiniMonoModHotfix), nameof(ResolveTokenFix))
+ );
+
+ }
+
+ private static IEnumerable<CodeInstruction> ResolveTokenFix(IEnumerable<CodeInstruction> instrs)
+ {
+ MethodInfo getdecl = typeof(MiniMonoModHotfix).GetMethod(nameof(GetRealDeclaringType));
+ MethodInfo fixup = typeof(MiniMonoModHotfix).GetMethod(nameof(FixReflectionCache));
+
+ foreach (CodeInstruction instr in instrs)
+ {
+ yield return instr;
+
+ if (instr.operand is MethodInfo called)
+ {
+ switch (called.Name)
+ {
+ case "ResolveType":
+ // type.FixReflectionCache();
+ yield return new CodeInstruction(OpCodes.Dup);
+ yield return new CodeInstruction(OpCodes.Call, fixup);
+ break;
+
+ case "ResolveMember":
+ case "ResolveMethod":
+ case "ResolveField":
+ // member.GetRealDeclaringType().FixReflectionCache();
+ yield return new CodeInstruction(OpCodes.Dup);
+ yield return new CodeInstruction(OpCodes.Call, getdecl);
+ yield return new CodeInstruction(OpCodes.Call, fixup);
+ break;
+ }
+ }
+ }
+ }
+
+ public static Type GetModuleType(this Module module)
+ {
+ // Sadly we can't blindly resolve type 0x02000001 as the runtime throws ArgumentException.
+
+ if (module == null || t_RuntimeModule == null || !t_RuntimeModule.IsInstanceOfType(module))
+ return null;
+
+ // .NET
+ if (p_RuntimeModule_RuntimeType != null)
+ return (Type)p_RuntimeModule_RuntimeType.GetValue(module, _NoArgs);
+
+ // The hotfix doesn't apply to Mono anyway, thus that's not copied over.
+
+ return null;
+ }
+
+ public static Type GetRealDeclaringType(this MemberInfo member)
+ => member.DeclaringType ?? member.Module?.GetModuleType();
+
+ public static void FixReflectionCache(this Type type)
+ {
+ if (t_RuntimeType == null ||
+ p_RuntimeType_Cache == null ||
+ m_RuntimeTypeCache_GetFieldList == null ||
+ m_RuntimeTypeCache_GetPropertyList == null)
+ return;
+
+ for (; type != null; type = type.DeclaringType)
+ {
+ // All types SHOULD inherit RuntimeType, including those built at runtime.
+ // One might never know what awaits us in the depths of reflection hell though.
+ if (!t_RuntimeType.IsInstanceOfType(type))
+ continue;
+
+ _CacheFixed.GetValue(type, rt =>
+ {
+
+ object cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs);
+ _FixReflectionCacheOrder<PropertyInfo>(cache, m_RuntimeTypeCache_GetPropertyList);
+ _FixReflectionCacheOrder<FieldInfo>(cache, m_RuntimeTypeCache_GetFieldList);
+
+ return new object();
+ });
+ }
+ }
+
+ private static void _FixReflectionCacheOrder<T>(object cache, MethodInfo getter) where T : MemberInfo
+ {
+ // Get and discard once, otherwise we might not be getting the actual backing array.
+ getter.Invoke(cache, _CacheGetterArgs);
+ Array orig = (Array)getter.Invoke(cache, _CacheGetterArgs);
+
+ // Sort using a short-lived list.
+ List<T> list = new List<T>(orig.Length);
+ for (int i = 0; i < orig.Length; i++)
+ list.Add((T)orig.GetValue(i));
+
+ list.Sort((a, b) => a.MetadataToken - b.MetadataToken);
+
+ for (int i = orig.Length - 1; i >= 0; --i)
+ orig.SetValue(list[i], i);
+ }
+
+ }
+}