summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-09-03 18:36:39 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-09-03 18:36:39 -0400
commitc5b8cd626489dad6210fe629658314dfc85f4d08 (patch)
tree3e30c3172e6c0bb3e422036581684593156fad22 /src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs
parent4ee96a20bb6c74bc7ff6176a03e7f15d47cddfa8 (diff)
parent6d4ea7f0bd584602632e6e308d52bb369b30006f (diff)
downloadSMAPI-c5b8cd626489dad6210fe629658314dfc85f4d08.tar.gz
SMAPI-c5b8cd626489dad6210fe629658314dfc85f4d08.tar.bz2
SMAPI-c5b8cd626489dad6210fe629658314dfc85f4d08.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs')
-rw-r--r--src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs143
1 files changed, 143 insertions, 0 deletions
diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs
new file mode 100644
index 00000000..922d4bc4
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs
@@ -0,0 +1,143 @@
+using System;
+using HarmonyLib;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using StardewModdingAPI.Framework.ModLoading.Framework;
+using StardewModdingAPI.Framework.ModLoading.RewriteFacades;
+
+namespace StardewModdingAPI.Framework.ModLoading.Rewriters
+{
+ /// <summary>Detects Harmony references, and rewrites Harmony 1.x assembly references to work with Harmony 2.x.</summary>
+ internal class HarmonyRewriter : BaseInstructionHandler
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Whether any Harmony 1.x types were replaced.</summary>
+ private bool ReplacedTypes;
+
+ /// <summary>Whether to rewrite Harmony 1.x code.</summary>
+ private readonly bool ShouldRewrite;
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ public HarmonyRewriter(bool shouldRewrite = true)
+ : base(defaultPhrase: "Harmony 1.x")
+ {
+ this.ShouldRewrite = shouldRewrite;
+ }
+
+ /// <inheritdoc />
+ public override bool Handle(ModuleDefinition module, TypeReference type, Action<TypeReference> replaceWith)
+ {
+ // detect Harmony
+ if (!(type.Scope is AssemblyNameReference scope) || scope.Name != "0Harmony")
+ return false;
+
+ // rewrite Harmony 1.x type to Harmony 2.0 type
+ if (this.ShouldRewrite && scope.Version.Major == 1)
+ {
+ Type targetType = this.GetMappedType(type);
+ replaceWith(module.ImportReference(targetType));
+ this.OnChanged();
+ this.ReplacedTypes = true;
+ return true;
+ }
+
+ this.MarkFlag(InstructionHandleResult.DetectedGamePatch);
+ return false;
+ }
+
+ /// <inheritdoc />
+ public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
+ {
+ if (this.ShouldRewrite)
+ {
+ // rewrite Harmony 1.x methods to Harmony 2.0
+ MethodReference methodRef = RewriteHelper.AsMethodReference(instruction);
+ if (this.TryRewriteMethodsToFacade(module, methodRef))
+ {
+ this.OnChanged();
+ return true;
+ }
+
+ // rewrite renamed fields
+ FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction);
+ if (fieldRef != null)
+ {
+ if (fieldRef.DeclaringType.FullName == "HarmonyLib.HarmonyMethod" && fieldRef.Name == "prioritiy")
+ {
+ fieldRef.Name = nameof(HarmonyMethod.priority);
+ this.OnChanged();
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Update the mod metadata when any Harmony 1.x code is migrated.</summary>
+ private void OnChanged()
+ {
+ this.MarkRewritten();
+ this.MarkFlag(InstructionHandleResult.DetectedGamePatch);
+ }
+
+ /// <summary>Rewrite methods to use Harmony facades if needed.</summary>
+ /// <param name="module">The assembly module containing the method reference.</param>
+ /// <param name="methodRef">The method reference to map.</param>
+ private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference methodRef)
+ {
+ if (!this.ReplacedTypes)
+ return false; // not Harmony (or already using Harmony 2.0)
+
+ // get facade type
+ Type toType = methodRef?.DeclaringType.FullName switch
+ {
+ "HarmonyLib.Harmony" => typeof(HarmonyInstanceFacade),
+ "HarmonyLib.AccessTools" => typeof(AccessToolsFacade),
+ "HarmonyLib.HarmonyMethod" => typeof(HarmonyMethodFacade),
+ _ => null
+ };
+ if (toType == null)
+ return false;
+
+ // map if there's a matching method
+ if (RewriteHelper.HasMatchingSignature(toType, methodRef))
+ {
+ methodRef.DeclaringType = module.ImportReference(toType);
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>Get an equivalent Harmony 2.x type.</summary>
+ /// <param name="type">The Harmony 1.x type.</param>
+ private Type GetMappedType(TypeReference type)
+ {
+ return type.FullName switch
+ {
+ "Harmony.HarmonyInstance" => typeof(Harmony),
+ "Harmony.ILCopying.ExceptionBlock" => typeof(ExceptionBlock),
+ _ => this.GetMappedTypeByConvention(type)
+ };
+ }
+
+ /// <summary>Get an equivalent Harmony 2.x type using the convention expected for most types.</summary>
+ /// <param name="type">The Harmony 1.x type.</param>
+ private Type GetMappedTypeByConvention(TypeReference type)
+ {
+ string fullName = type.FullName.Replace("Harmony.", "HarmonyLib.");
+ string targetName = typeof(Harmony).AssemblyQualifiedName!.Replace(typeof(Harmony).FullName!, fullName);
+ return Type.GetType(targetName, throwOnError: true);
+ }
+ }
+}