summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ModLoading/Rewriters
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-05-05 20:53:02 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2020-05-05 21:02:33 -0400
commitf4192663d78c7a45418f07f0bf4acb67b11291fe (patch)
tree6b5108ce0162123d2c71ee2798204feb0eb75a42 /src/SMAPI/Framework/ModLoading/Rewriters
parent2d37fe6819dd15a6e995ea55d625179106c22cd7 (diff)
downloadSMAPI-f4192663d78c7a45418f07f0bf4acb67b11291fe.tar.gz
SMAPI-f4192663d78c7a45418f07f0bf4acb67b11291fe.tar.bz2
SMAPI-f4192663d78c7a45418f07f0bf4acb67b11291fe.zip
add Harmony 2.0 rewriters (#711)
Diffstat (limited to 'src/SMAPI/Framework/ModLoading/Rewriters')
-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
3 files changed, 158 insertions, 5 deletions
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;
+ }
+ }
+}