summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs25
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs25
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs34
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs97
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs77
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs18
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs68
-rw-r--r--src/SMAPI/Framework/RewriteFacades/HarmonyInstanceMethods.cs33
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs5
10 files changed, 295 insertions, 89 deletions
diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs
new file mode 100644
index 00000000..5301186b
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs
@@ -0,0 +1,25 @@
+using System;
+using Mono.Cecil;
+using StardewModdingAPI.Framework.ModLoading.Framework;
+
+namespace StardewModdingAPI.Framework.ModLoading.Finders
+{
+ /// <summary>Finds incompatible CIL instructions that reference types in a given assembly.</summary>
+ internal class TypeAssemblyFinder : BaseTypeFinder
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="assemblyName">The full assembly name to which to find references.</param>
+ /// <param name="result">The result to return for matching instructions.</param>
+ /// <param name="shouldIgnore">A lambda which overrides a matched type.</param>
+ public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null)
+ : base(
+ isMatch: type => type.Scope.Name == assemblyName && (shouldIgnore == null || !shouldIgnore(type)),
+ result: result,
+ nounPhrase: $"{assemblyName} assembly"
+ )
+ { }
+ }
+}
diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs
new file mode 100644
index 00000000..3adc31c7
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs
@@ -0,0 +1,25 @@
+using System;
+using Mono.Cecil;
+using StardewModdingAPI.Framework.ModLoading.Framework;
+
+namespace StardewModdingAPI.Framework.ModLoading.Finders
+{
+ /// <summary>Finds incompatible CIL instructions that reference a given type.</summary>
+ internal class TypeFinder : BaseTypeFinder
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="fullTypeName">The full type name to match.</param>
+ /// <param name="result">The result to return for matching instructions.</param>
+ /// <param name="shouldIgnore">A lambda which overrides a matched type.</param>
+ public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null)
+ : base(
+ isMatch: type => type.FullName == fullTypeName && (shouldIgnore == null || !shouldIgnore(type)),
+ result: result,
+ nounPhrase: $"{fullTypeName} type"
+ )
+ { }
+ }
+}
diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs
index 170bbb48..b1547334 100644
--- a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs
@@ -5,21 +5,18 @@ using Mono.Cecil.Cil;
namespace StardewModdingAPI.Framework.ModLoading.Framework
{
- /// <summary>Finds incompatible CIL instructions that reference a given type.</summary>
- internal class TypeFinder : IInstructionHandler
+ /// <summary>Finds incompatible CIL type reference instructions.</summary>
+ internal abstract class BaseTypeFinder : IInstructionHandler
{
/*********
** Accessors
*********/
- /// <summary>The full type name for which to find references.</summary>
- private readonly string FullTypeName;
+ /// <summary>Matches the type references to handle.</summary>
+ private readonly Func<TypeReference, bool> IsMatchImpl;
/// <summary>The result to return for matching instructions.</summary>
private readonly InstructionHandleResult Result;
- /// <summary>A lambda which overrides a matched type.</summary>
- protected readonly Func<TypeReference, bool> ShouldIgnore;
-
/*********
** Accessors
@@ -32,15 +29,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="fullTypeName">The full type name to match.</param>
+ /// <param name="isMatch">Matches the type references to handle.</param>
/// <param name="result">The result to return for matching instructions.</param>
- /// <param name="shouldIgnore">A lambda which overrides a matched type.</param>
- public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null)
+ /// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches.</param>
+ public BaseTypeFinder(Func<TypeReference, bool> isMatch, InstructionHandleResult result, string nounPhrase)
{
- this.FullTypeName = fullTypeName;
+ this.IsMatchImpl = isMatch;
this.Result = result;
- this.NounPhrase = $"{fullTypeName} type";
- this.ShouldIgnore = shouldIgnore ?? (p => false);
+ this.NounPhrase = nounPhrase;
}
/// <summary>Perform the predefined logic for a method if applicable.</summary>
@@ -68,13 +64,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
: InstructionHandleResult.None;
}
-
- /*********
- ** Protected methods
- *********/
/// <summary>Get whether a CIL instruction matches.</summary>
/// <param name="method">The method definition.</param>
- protected bool IsMatch(MethodDefinition method)
+ public bool IsMatch(MethodDefinition method)
{
if (this.IsMatch(method.ReturnType))
return true;
@@ -90,7 +82,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
/// <summary>Get whether a CIL instruction matches.</summary>
/// <param name="instruction">The IL instruction.</param>
- protected bool IsMatch(Instruction instruction)
+ public bool IsMatch(Instruction instruction)
{
// field reference
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
@@ -116,10 +108,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
/// <summary>Get whether a type reference matches the expected type.</summary>
/// <param name="type">The type to check.</param>
- protected bool IsMatch(TypeReference type)
+ public bool IsMatch(TypeReference type)
{
// root type
- if (type.FullName == this.FullTypeName && !this.ShouldIgnore(type))
+ if (this.IsMatchImpl(type))
return true;
// generic arguments
diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs
index 8c2d11c8..55ce6b5a 100644
--- a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs
@@ -5,30 +5,32 @@ using Mono.Cecil.Cil;
namespace StardewModdingAPI.Framework.ModLoading.Framework
{
/// <summary>Rewrites all references to a type.</summary>
- internal class TypeReferenceRewriter : TypeFinder
+ internal abstract class BaseTypeReferenceRewriter : IInstructionHandler
{
/*********
** Fields
*********/
- /// <summary>The full type name to which to find references.</summary>
- private readonly string FromTypeName;
+ /// <summary>The type finder which matches types to rewrite.</summary>
+ private readonly BaseTypeFinder Finder;
- /// <summary>The new type to reference.</summary>
- private readonly Type ToType;
+
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>A brief noun phrase indicating what the handler matches.</summary>
+ public string NounPhrase { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
- /// <param name="fromTypeFullName">The full type name to which to find references.</param>
- /// <param name="toType">The new type to reference.</param>
- /// <param name="shouldIgnore">A lambda which overrides a matched type.</param>
- public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func<TypeReference, bool> shouldIgnore = null)
- : base(fromTypeFullName, InstructionHandleResult.None, shouldIgnore)
+ /// <param name="finder">The type finder which matches types to rewrite.</param>
+ /// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches.</param>
+ public BaseTypeReferenceRewriter(BaseTypeFinder finder, string nounPhrase)
{
- this.FromTypeName = fromTypeFullName;
- this.ToType = toType;
+ this.Finder = finder;
+ this.NounPhrase = nounPhrase;
}
/// <summary>Perform the predefined logic for a method if applicable.</summary>
@@ -36,46 +38,36 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
/// <param name="method">The method definition containing the instruction.</param>
/// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
/// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public override InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ public InstructionHandleResult Handle(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged)
{
bool rewritten = false;
// return type
- if (this.IsMatch(method.ReturnType))
+ if (this.Finder.IsMatch(method.ReturnType))
{
- this.RewriteIfNeeded(module, method.ReturnType, newType => method.ReturnType = newType);
- rewritten = true;
+ rewritten |= this.RewriteIfNeeded(module, method.ReturnType, newType => method.ReturnType = newType);
}
// parameters
foreach (ParameterDefinition parameter in method.Parameters)
{
- if (this.IsMatch(parameter.ParameterType))
- {
- this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType);
- rewritten = true;
- }
+ if (this.Finder.IsMatch(parameter.ParameterType))
+ rewritten |= this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType);
}
// generic parameters
for (int i = 0; i < method.GenericParameters.Count; i++)
{
var parameter = method.GenericParameters[i];
- if (this.IsMatch(parameter))
- {
- this.RewriteIfNeeded(module, parameter, newType => method.GenericParameters[i] = new GenericParameter(parameter.Name, newType));
- rewritten = true;
- }
+ if (this.Finder.IsMatch(parameter))
+ rewritten |= this.RewriteIfNeeded(module, parameter, newType => method.GenericParameters[i] = new GenericParameter(parameter.Name, newType));
}
// local variables
foreach (VariableDefinition variable in method.Body.Variables)
{
- if (this.IsMatch(variable.VariableType))
- {
- this.RewriteIfNeeded(module, variable.VariableType, newType => variable.VariableType = newType);
- rewritten = true;
- }
+ if (this.Finder.IsMatch(variable.VariableType))
+ rewritten |= this.RewriteIfNeeded(module, variable.VariableType, newType => variable.VariableType = newType);
}
return rewritten
@@ -89,34 +81,37 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
/// <param name="instruction">The instruction to handle.</param>
/// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
/// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
- public override InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ public InstructionHandleResult Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged)
{
- if (!this.IsMatch(instruction))
+ if (!this.Finder.IsMatch(instruction))
return InstructionHandleResult.None;
+ bool rewritten = false;
// field reference
FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
if (fieldRef != null)
{
- this.RewriteIfNeeded(module, fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType);
- this.RewriteIfNeeded(module, fieldRef.FieldType, newType => fieldRef.FieldType = newType);
+ rewritten |= this.RewriteIfNeeded(module, fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType);
+ rewritten |= this.RewriteIfNeeded(module, fieldRef.FieldType, newType => fieldRef.FieldType = newType);
}
// method reference
MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
if (methodRef != null)
{
- this.RewriteIfNeeded(module, methodRef.DeclaringType, newType => methodRef.DeclaringType = newType);
- this.RewriteIfNeeded(module, methodRef.ReturnType, newType => methodRef.ReturnType = newType);
+ rewritten |= this.RewriteIfNeeded(module, methodRef.DeclaringType, newType => methodRef.DeclaringType = newType);
+ rewritten |= this.RewriteIfNeeded(module, methodRef.ReturnType, newType => methodRef.ReturnType = newType);
foreach (var parameter in methodRef.Parameters)
- this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType);
+ rewritten |= this.RewriteIfNeeded(module, parameter.ParameterType, newType => parameter.ParameterType = newType);
}
// type reference
if (instruction.Operand is TypeReference typeRef)
- this.RewriteIfNeeded(module, typeRef, newType => cil.Replace(instruction, cil.Create(instruction.OpCode, newType)));
+ rewritten |= this.RewriteIfNeeded(module, typeRef, newType => cil.Replace(instruction, cil.Create(instruction.OpCode, newType)));
- return InstructionHandleResult.Rewritten;
+ return rewritten
+ ? InstructionHandleResult.Rewritten
+ : InstructionHandleResult.None;
}
/*********
@@ -126,26 +121,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
/// <param name="module">The assembly module containing the instruction.</param>
/// <param name="type">The type to replace if it matches.</param>
/// <param name="set">Assign the new type reference.</param>
- private void RewriteIfNeeded(ModuleDefinition module, TypeReference type, Action<TypeReference> set)
- {
- // current type
- if (type.FullName == this.FromTypeName)
- {
- if (!this.ShouldIgnore(type))
- set(module.ImportReference(this.ToType));
- return;
- }
-
- // recurse into generic arguments
- if (type is GenericInstanceType genericType)
- {
- for (int i = 0; i < genericType.GenericArguments.Count; i++)
- this.RewriteIfNeeded(module, genericType.GenericArguments[i], typeRef => genericType.GenericArguments[i] = typeRef);
- }
-
- // recurse into generic parameters (e.g. constraints)
- for (int i = 0; i < type.GenericParameters.Count; i++)
- this.RewriteIfNeeded(module, type.GenericParameters[i], typeRef => type.GenericParameters[i] = new GenericParameter(typeRef));
- }
+ protected abstract bool RewriteIfNeeded(ModuleDefinition module, TypeReference type, Action<TypeReference> set);
}
}
diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
index f8f10dc4..d9a49cfa 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
@@ -103,7 +103,7 @@ namespace StardewModdingAPI.Framework.ModLoading
public static bool HasMatchingSignature(Type type, MethodReference reference)
{
return type
- .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public)
+ .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public)
.Any(method => RewriteHelper.HasMatchingSignature(method, reference));
}
}
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
new file mode 100644
index 00000000..29e44bfe
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
@@ -0,0 +1,77 @@
+using System;
+using Mono.Cecil;
+using StardewModdingAPI.Framework.ModLoading.Finders;
+using StardewModdingAPI.Framework.ModLoading.Framework;
+
+namespace StardewModdingAPI.Framework.ModLoading.Rewriters
+{
+ /// <summary>Rewrites Harmony 1.x assembly references to work with Harmony 2.x.</summary>
+ internal class Harmony1AssemblyRewriter : BaseTypeReferenceRewriter
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The full assembly name to which to find references.</summary>
+ private const string FromAssemblyName = "0Harmony";
+
+ /// <summary>The main Harmony type.</summary>
+ private readonly Type HarmonyType = typeof(HarmonyLib.Harmony);
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ public Harmony1AssemblyRewriter()
+ : base(new TypeAssemblyFinder(Harmony1AssemblyRewriter.FromAssemblyName, InstructionHandleResult.None), "Harmony 1.x types")
+ { }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Change a type reference if needed.</summary>
+ /// <param name="module">The assembly module containing the instruction.</param>
+ /// <param name="type">The type to replace if it matches.</param>
+ /// <param name="set">Assign the new type reference.</param>
+ protected override bool RewriteIfNeeded(ModuleDefinition module, TypeReference type, Action<TypeReference> set)
+ {
+ bool rewritten = false;
+
+ // current type
+ if (type.Scope.Name == Harmony1AssemblyRewriter.FromAssemblyName && type.Scope is AssemblyNameReference assemblyScope && assemblyScope.Version.Major == 1)
+ {
+ Type targetType = this.GetMappedType(type);
+ set(module.ImportReference(targetType));
+ return true;
+ }
+
+ // recurse into generic arguments
+ if (type is GenericInstanceType genericType)
+ {
+ for (int i = 0; i < genericType.GenericArguments.Count; i++)
+ rewritten |= this.RewriteIfNeeded(module, genericType.GenericArguments[i], typeRef => genericType.GenericArguments[i] = typeRef);
+ }
+
+ // recurse into generic parameters (e.g. constraints)
+ for (int i = 0; i < type.GenericParameters.Count; i++)
+ rewritten |= this.RewriteIfNeeded(module, type.GenericParameters[i], typeRef => type.GenericParameters[i] = new GenericParameter(typeRef));
+
+ return rewritten;
+ }
+
+ /// <summary>Get an equivalent Harmony 2.x type.</summary>
+ /// <param name="type">The Harmony 1.x method.</param>
+ private Type GetMappedType(TypeReference type)
+ {
+ // main Harmony object
+ if (type.FullName == "Harmony.HarmonyInstance")
+ return this.HarmonyType;
+
+ // other objects
+ string fullName = type.FullName.Replace("Harmony.", "HarmonyLib.");
+ string targetName = this.HarmonyType.AssemblyQualifiedName.Replace(this.HarmonyType.FullName, fullName);
+ return Type.GetType(targetName, throwOnError: true);
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs
index 6b8c2de1..0984dc44 100644
--- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
@@ -10,8 +11,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
/*********
** Fields
*********/
- /// <summary>The type whose methods to remap.</summary>
- private readonly Type FromType;
+ /// <summary>The full name of the type whose methods to remap.</summary>
+ private readonly string FromType;
/// <summary>The type with methods to map to.</summary>
private readonly Type ToType;
@@ -34,14 +35,21 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
/// <param name="fromType">The type whose methods to remap.</param>
/// <param name="toType">The type with methods to map to.</param>
/// <param name="onlyIfPlatformChanged">Whether to only rewrite references if loading the assembly on a different platform than it was compiled on.</param>
- public MethodParentRewriter(Type fromType, Type toType, bool onlyIfPlatformChanged = false)
+ public MethodParentRewriter(string fromType, Type toType, bool onlyIfPlatformChanged = false)
{
this.FromType = fromType;
this.ToType = toType;
- this.NounPhrase = $"{fromType.Name} methods";
+ this.NounPhrase = $"{fromType.Split('.').Last()} methods";
this.OnlyIfPlatformChanged = onlyIfPlatformChanged;
}
+ /// <summary>Construct an instance.</summary>
+ /// <param name="fromType">The type whose methods to remap.</param>
+ /// <param name="toType">The type with methods to map to.</param>
+ /// <param name="onlyIfPlatformChanged">Whether to only rewrite references if loading the assembly on a different platform than it was compiled on.</param>
+ public MethodParentRewriter(Type fromType, Type toType, bool onlyIfPlatformChanged = false)
+ : this(fromType.FullName, toType, onlyIfPlatformChanged) { }
+
/// <summary>Perform the predefined logic for a method if applicable.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
/// <param name="method">The method definition containing the instruction.</param>
@@ -81,7 +89,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters
return
methodRef != null
&& (platformChanged || !this.OnlyIfPlatformChanged)
- && methodRef.DeclaringType.FullName == this.FromType.FullName
+ && methodRef.DeclaringType.FullName == this.FromType
&& RewriteHelper.HasMatchingSignature(this.ToType, methodRef);
}
}
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs
new file mode 100644
index 00000000..d95e5ac9
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs
@@ -0,0 +1,68 @@
+using System;
+using Mono.Cecil;
+using StardewModdingAPI.Framework.ModLoading.Finders;
+using StardewModdingAPI.Framework.ModLoading.Framework;
+
+namespace StardewModdingAPI.Framework.ModLoading.Rewriters
+{
+ /// <summary>Rewrites all references to a type.</summary>
+ internal class TypeReferenceRewriter : BaseTypeReferenceRewriter
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>The full type name to which to find references.</summary>
+ private readonly string FromTypeName;
+
+ /// <summary>The new type to reference.</summary>
+ private readonly Type ToType;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="fromTypeFullName">The full type name to which to find references.</param>
+ /// <param name="toType">The new type to reference.</param>
+ /// <param name="shouldIgnore">A lambda which overrides a matched type.</param>
+ public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func<TypeReference, bool> shouldIgnore = null)
+ : base(new TypeFinder(fromTypeFullName, InstructionHandleResult.None, shouldIgnore), $"{fromTypeFullName} type")
+ {
+ this.FromTypeName = fromTypeFullName;
+ this.ToType = toType;
+ }
+
+
+ /*********
+ ** Protected methods
+ *********/
+ /// <summary>Change a type reference if needed.</summary>
+ /// <param name="module">The assembly module containing the instruction.</param>
+ /// <param name="type">The type to replace if it matches.</param>
+ /// <param name="set">Assign the new type reference.</param>
+ protected override bool RewriteIfNeeded(ModuleDefinition module, TypeReference type, Action<TypeReference> set)
+ {
+ bool rewritten = false;
+
+ // current type
+ if (type.FullName == this.FromTypeName)
+ {
+ set(module.ImportReference(this.ToType));
+ return true;
+ }
+
+ // recurse into generic arguments
+ if (type is GenericInstanceType genericType)
+ {
+ for (int i = 0; i < genericType.GenericArguments.Count; i++)
+ rewritten |= this.RewriteIfNeeded(module, genericType.GenericArguments[i], typeRef => genericType.GenericArguments[i] = typeRef);
+ }
+
+ // recurse into generic parameters (e.g. constraints)
+ for (int i = 0; i < type.GenericParameters.Count; i++)
+ rewritten |= this.RewriteIfNeeded(module, type.GenericParameters[i], typeRef => type.GenericParameters[i] = new GenericParameter(typeRef));
+
+ return rewritten;
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/RewriteFacades/HarmonyInstanceMethods.cs b/src/SMAPI/Framework/RewriteFacades/HarmonyInstanceMethods.cs
new file mode 100644
index 00000000..0f906f51
--- /dev/null
+++ b/src/SMAPI/Framework/RewriteFacades/HarmonyInstanceMethods.cs
@@ -0,0 +1,33 @@
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using HarmonyLib;
+
+namespace StardewModdingAPI.Framework.RewriteFacades
+{
+ /// <summary>Maps Harmony 1.x methods to Harmony 2.x to avoid breaking older mods.</summary>
+ /// <remarks>This is public to support SMAPI rewriting and should not be referenced directly by mods.</remarks>
+ public class HarmonyInstanceMethods : Harmony
+ {
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="id">The unique patch identifier.</param>
+ public HarmonyInstanceMethods(string id)
+ : base(id) { }
+
+ /// <summary>Creates a new Harmony instance.</summary>
+ /// <param name="id">A unique identifier for the instance.</param>
+ public static Harmony Create(string id)
+ {
+ return new Harmony(id);
+ }
+
+ public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null)
+ {
+ MethodInfo method = base.Patch(original: original, prefix: prefix, postfix: postfix, transpiler: transpiler);
+ return new DynamicMethod(method.Name, method.Attributes, method.CallingConvention, method.ReturnType, method.GetParameters().Select(p => p.ParameterType).ToArray(), method.Module, true);
+ }
+ }
+}
diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs
index f3d6e6db..fb7141e7 100644
--- a/src/SMAPI/Metadata/InstructionMetadata.cs
+++ b/src/SMAPI/Metadata/InstructionMetadata.cs
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Framework.ModLoading.Finders;
-using StardewModdingAPI.Framework.ModLoading.Framework;
using StardewModdingAPI.Framework.ModLoading.Rewriters;
using StardewModdingAPI.Framework.RewriteFacades;
using StardewValley;
@@ -37,6 +36,10 @@ namespace StardewModdingAPI.Metadata
// rewrite for Stardew Valley 1.3
yield return new StaticFieldToConstantRewriter<int>(typeof(Game1), "tileSize", Game1.tileSize);
+ // rewrite for SMAPI 3.6 (Harmony 1.x => 2.0 update)
+ yield return new Harmony1AssemblyRewriter();
+ yield return new MethodParentRewriter("HarmonyLib.Harmony", typeof(HarmonyInstanceMethods), onlyIfPlatformChanged: false);
+
/****
** detect mod issues
****/