-
Notifications
You must be signed in to change notification settings - Fork 104
Expand file tree
/
Copy pathNetFrameworkModder.cs
More file actions
143 lines (110 loc) · 6.43 KB
/
Copy pathNetFrameworkModder.cs
File metadata and controls
143 lines (110 loc) · 6.43 KB
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
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod;
using MonoMod.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MethodBody = Mono.Cecil.Cil.MethodBody;
using MethodImplAttributes = Mono.Cecil.MethodImplAttributes;
namespace NETCoreifier {
public class NetFrameworkModder : MonoModder {
// Patching RNG doesn't seem to be required (yet), as .NET Framework and .NET Core share their RNG implementation
private static readonly HashSet<string> _PrivateSystemLibs = new HashSet<string>() { "System.Private.CoreLib" };
public Action<string> LogCallback, LogVerboseCallback;
public bool SharedAssemblyResolver, SharedDependencies;
public bool PreventInlining = true;
private ModuleDefinition _CoreifierModule;
public override void Dispose() {
// Don't dispose the main module
Module = null;
// Don't dispose the assembly resolver if it's shared
if (SharedAssemblyResolver)
AssemblyResolver = null;
// Don't dispose the dependency modules if they're shared
if (SharedDependencies) {
DependencyMap.Clear();
DependencyCache.Clear();
}
_CoreifierModule?.Dispose();
base.Dispose();
}
public override void Log(string text)
=> LogCallback?.Invoke(text);
public override void LogVerbose(string text)
=> LogVerboseCallback?.Invoke(text);
public void AddReferenceIfMissing(AssemblyName asmName) {
if (!Module.AssemblyReferences.Any(asmRef => asmRef.Name == asmName.Name)) {
Module.AssemblyReferences.Add(AssemblyNameReference.Parse(asmName.FullName));
}
}
public void AddReferenceIfMissing(string name) => AddReferenceIfMissing(Assembly.GetExecutingAssembly().GetReferencedAssemblies().First(asmName => asmName.Name == name));
public override void MapDependencies() {
// Ensure critical references are present
AddReferenceIfMissing("System.Runtime");
AddReferenceIfMissing(Assembly.GetExecutingAssembly().GetName());
// We have to load our own module again every time because MonoMod messes with it ._.
_CoreifierModule ??= ModuleDefinition.ReadModule(Assembly.GetExecutingAssembly().Location) ?? throw new Exception("Failed to load .NET Coreifier assembly");
DependencyCache[Assembly.GetExecutingAssembly().FullName] = _CoreifierModule;
base.MapDependencies();
}
public override void AutoPatch() {
// Parse our own patching rules
ParseRules(DependencyMap[Module].First(dep => dep.Assembly.Name.Name == "NETCoreifier"));
base.AutoPatch();
}
public override void PatchRefs(ModuleDefinition mod) {
base.PatchRefs(mod);
// Remove references to private system libraries
for (int i = 0; i < mod.AssemblyReferences.Count; i++) {
if (_PrivateSystemLibs.Contains(mod.AssemblyReferences[i].Name))
mod.AssemblyReferences.RemoveAt(i--);
}
}
public override IMetadataTokenProvider Relinker(IMetadataTokenProvider mtp, IGenericParameterProvider context) {
IMetadataTokenProvider relinkedMtp = base.Relinker(mtp, context);
if (relinkedMtp is TypeReference typeRef && _PrivateSystemLibs.Contains(typeRef.Scope.Name)) {
// Don't reference System.Private.CoreLib directly
return Module.ImportReference(FindType(typeRef.FullName));
}
return relinkedMtp;
}
public override void PatchRefsInMethod(MethodDefinition method) {
base.PatchRefsInMethod(method);
// The CoreCLR JIT is much more aggressive about inlining, so explicitly force it to not inline in some cases
// The performance penalty isn't that bad, and it makes modding easier
if (PreventInlining && (method.ImplAttributes & MethodImplAttributes.AggressiveInlining) == 0 && method.Body is MethodBody body && !CanInlineLegacyCode(body))
method.ImplAttributes |= MethodImplAttributes.NoInlining;
// Resolve uninstantiated generic typeref/def tokens inside of member methods by replacing them with generic type instances
// CoreCLR seems to be more strict on this, because the faulty IL worked fine on .NET Framwork / Mono
if (method.DeclaringType.HasGenericParameters && method.Body != null) {
for (int i = 0; i < method.Body.Instructions.Count; i++) {
Instruction instr = method.Body.Instructions[i];
if (instr.OpCode == OpCodes.Ldtoken)
//ldtoken doesn't have the strict metadata checking
continue;
if (instr.Operand is TypeReference typeRef && typeRef.SafeResolve() == method.DeclaringType && !typeRef.IsGenericInstance) {
GenericInstanceType typeInst = new GenericInstanceType(typeRef);
typeInst.GenericArguments.AddRange(method.DeclaringType.GenericParameters);
instr.Operand = typeInst;
} else if (instr.Operand is MemberReference memberRef && instr.Operand is not TypeReference && memberRef.DeclaringType.SafeResolve() == method.DeclaringType && !memberRef.DeclaringType.IsGenericInstance) {
GenericInstanceType typeInst = new GenericInstanceType(memberRef.DeclaringType);
typeInst.GenericArguments.AddRange(method.DeclaringType.GenericParameters);
memberRef.DeclaringType = typeInst;
}
}
}
}
// Use the mono criteria for this, as those are known (see mono_method_check_inlining)
private bool CanInlineLegacyCode(MethodBody body) {
const int INLINE_LENGTH_LIMIT = 20; // mono/mini/method-to-ir.c
// Methods exceeding a certain size aren't inlined
if (body.CodeSize >= INLINE_LENGTH_LIMIT)
return false;
// There are other checks (..ctor, profiling, method attributes, etc.), but those aren't relevant for us
// The method might be inlined by mono, so consider it safe to inline for the modern runtime
return true;
}
}
}