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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
#nullable disable
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 not 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);
}
}
}
|