summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ModLoading/Rewriters/Harmony1AssemblyRewriter.cs
blob: 7a3b428d024db0e443e39b3c1b1429f89f487267 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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>Rewrites Harmony 1.x assembly references to work with Harmony 2.x.</summary>
    internal class Harmony1AssemblyRewriter : BaseInstructionHandler
    {
        /*********
        ** Fields
        *********/
        /// <summary>Whether any Harmony 1.x types were replaced.</summary>
        private bool ReplacedTypes;


        /*********
        ** Public methods
        *********/
        /// <summary>Construct an instance.</summary>
        public Harmony1AssemblyRewriter()
            : base(defaultPhrase: "Harmony 1.x") { }

        /// <inheritdoc />
        public override bool Handle(ModuleDefinition module, TypeReference type, Action<TypeReference> replaceWith)
        {
            // rewrite Harmony 1.x type to Harmony 2.0 type
            if (type.Scope is AssemblyNameReference { Name: "0Harmony" } scope && scope.Version.Major == 1)
            {
                Type targetType = this.GetMappedType(type);
                replaceWith(module.ImportReference(targetType));
                this.OnChanged();
                this.ReplacedTypes = true;
                return true;
            }

            return false;
        }

        /// <inheritdoc />
        public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction)
        {
            // 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);
        }
    }
}