Skip to content

Commit 8d531be

Browse files
committed
WIP custom event handler delegate
1 parent bb3479d commit 8d531be

File tree

4 files changed

+156
-57
lines changed

4 files changed

+156
-57
lines changed

ModFramework/Emitters/EventEmitter.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,17 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1616
You should have received a copy of the GNU General Public License
1717
along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
*/
19-
using ModFramework.Relinker;
2019
using Mono.Cecil;
2120
using Mono.Cecil.Cil;
2221
using MonoMod;
22+
using System;
2323
using System.Linq;
2424

2525
namespace ModFramework;
2626

2727
[MonoMod.MonoModIgnore]
2828
public static class EventEmitter
2929
{
30-
public static TypeReference GetEventHandlerReference(MonoModder modder)
31-
=> modder.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.EventHandler`1"));
32-
3330
/// <summary>
3431
/// Creates a new event based upon the source definition
3532
/// </summary>
@@ -40,7 +37,7 @@ public static TypeReference GetEventHandlerReference(MonoModder modder)
4037
/// <returns>A tuple of the field and event definitions</returns>
4138
public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition) CreateEvent(this MethodDefinition sourceDefinition, TypeDefinition containingType, TypeDefinition eventArgsType, MonoModder modder, string? name = null)
4239
{
43-
var eventHandlerType = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.EventHandler`1"));
40+
var eventHandlerType = modder.ResolveTypeReference(typeof(EventHandler<>));
4441

4542
// Define the event backing field
4643
var fieldName = name ?? $"{sourceDefinition.Name}Event";
@@ -72,7 +69,7 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
7269
addMethod.Parameters.Add(parameter);
7370
var ilAdd = addMethod.Body.GetILProcessor();
7471

75-
var compareExchange = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.Threading.Interlocked")
72+
var compareExchange = containingType.Module.ImportReference(modder.ResolveFirstFrameworkType("System.Threading.Interlocked")
7673
.Methods.Single(m => m.Name == "CompareExchange" && m.HasGenericParameters && m.IsStatic));
7774

7875
GenericInstanceMethod methodInterlockedCompareExchange = new(compareExchange);
@@ -95,7 +92,8 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
9592
ilAdd.Emit(OpCodes.Ldloc_1); // Load local v1
9693
ilAdd.Emit(OpCodes.Ldarg_0); // Load the parameter value
9794

98-
var combine = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.Delegate")
95+
var delegateType = modder.ResolveFirstFrameworkType<System.Delegate>();
96+
var combine = containingType.Module.ImportReference(delegateType
9997
.Methods.Single(m => m.Name == "Combine" && m.IsStatic && m.Parameters.Count == 2));
10098
ilAdd.Emit(OpCodes.Call, combine);
10199
ilAdd.Emit(OpCodes.Castclass, eventField.FieldType);
@@ -135,7 +133,7 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
135133
ilRemove.Emit(OpCodes.Stloc_1); // Store into local v1
136134
ilRemove.Emit(OpCodes.Ldloc_1); // Load local v1
137135
ilRemove.Emit(OpCodes.Ldarg_0); // Load the parameter value
138-
var remove = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.Delegate")
136+
var remove = containingType.Module.ImportReference(delegateType
139137
.Methods.Single(m => m.Name == "Remove" && m.IsStatic));
140138
ilRemove.Emit(OpCodes.Call, remove);
141139
ilRemove.Emit(OpCodes.Castclass, eventField.FieldType);
@@ -152,7 +150,7 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
152150
containingType.Methods.Add(removeMethod);
153151

154152
// add compiler generated attribute
155-
var ctor = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.Runtime.CompilerServices.CompilerGeneratedAttribute")
153+
var ctor = containingType.Module.ImportReference(modder.ResolveFirstFrameworkType<System.Runtime.CompilerServices.CompilerGeneratedAttribute>()
156154
.Methods.Single(m => m.Name == ".ctor" && m.IsConstructor && m.Parameters.Count == 0));
157155
addMethod.CustomAttributes.Add(new(ctor));
158156
removeMethod.CustomAttributes.Add(new(ctor));

ModFramework/Emitters/HookEmitter.cs

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1616
You should have received a copy of the GNU General Public License
1717
along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
*/
19+
using ModFramework.Relinker;
1920
using Mono.Cecil;
2021
using Mono.Cecil.Cil;
2122
using MonoMod;
23+
using MonoMod.Utils;
2224
using System;
2325
using System.Linq;
2426

@@ -171,6 +173,68 @@ public static Instruction CreateStoreIndirectFunction(TypeReference type)
171173
};
172174
}
173175

176+
public static TypeDefinition CreateHookDelegate(MonoModder modder)
177+
{
178+
var test = modder.Module.ImportReference(typeof(HookDelegate<,>));
179+
var hookDelegate = modder.Module.Types.SingleOrDefault(x => x.Name == "HookDelegate");
180+
if (hookDelegate is not null)
181+
return hookDelegate;
182+
183+
var multicast = modder.ResolveTypeReference<MulticastDelegate>();
184+
185+
hookDelegate = new(
186+
"HookEvents",
187+
"HookDelegate",
188+
TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed
189+
);
190+
hookDelegate.BaseType = multicast;
191+
hookDelegate.GenericParameters.Add(new GenericParameter("TSender", hookDelegate));
192+
hookDelegate.GenericParameters.Add(new GenericParameter("TArgs", hookDelegate));
193+
194+
modder.Module.Types.Add(hookDelegate);
195+
196+
// create ctor, Invoke, BeginInvoke, EndInvoke (no body)
197+
198+
var ctor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, hookDelegate.Module.TypeSystem.Void)
199+
{
200+
IsRuntime = true
201+
};
202+
ctor.Parameters.Add(new ParameterDefinition("object", ParameterAttributes.None, hookDelegate.Module.TypeSystem.Object));
203+
ctor.Parameters.Add(new ParameterDefinition("method", ParameterAttributes.None, hookDelegate.Module.TypeSystem.IntPtr));
204+
hookDelegate.Methods.Add(ctor);
205+
206+
var invoke = new MethodDefinition("Invoke", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, hookDelegate.Module.TypeSystem.Void)
207+
{
208+
IsRuntime = true
209+
};
210+
invoke.Parameters.Add(new ParameterDefinition("sender", ParameterAttributes.None, hookDelegate.GenericParameters[0]));
211+
invoke.Parameters.Add(new ParameterDefinition("args", ParameterAttributes.None, hookDelegate.GenericParameters[1]));
212+
hookDelegate.Methods.Add(invoke);
213+
214+
var iAsyncResult = modder.ResolveTypeReference<IAsyncResult>();
215+
var iAsyncCallback = modder.ResolveTypeReference<AsyncCallback>();
216+
217+
var beginInvoke = new MethodDefinition("BeginInvoke", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, iAsyncResult)
218+
{
219+
IsRuntime = true
220+
};
221+
beginInvoke.Parameters.Add(new ParameterDefinition("sender", ParameterAttributes.None, hookDelegate.GenericParameters[0]));
222+
beginInvoke.Parameters.Add(new ParameterDefinition("args", ParameterAttributes.None, hookDelegate.GenericParameters[1]));
223+
beginInvoke.Parameters.Add(new ParameterDefinition("callback", ParameterAttributes.None, iAsyncCallback));
224+
beginInvoke.Parameters.Add(new ParameterDefinition("object", ParameterAttributes.None, hookDelegate.Module.TypeSystem.Object));
225+
hookDelegate.Methods.Add(beginInvoke);
226+
227+
var endInvoke = new MethodDefinition("EndInvoke", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, hookDelegate.Module.TypeSystem.Void)
228+
{
229+
IsRuntime = true
230+
};
231+
endInvoke.Parameters.Add(new ParameterDefinition("result", ParameterAttributes.None, iAsyncResult));
232+
hookDelegate.Methods.Add(endInvoke);
233+
234+
235+
return hookDelegate;
236+
}
237+
174238
static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinition eventField, TypeDefinition hookEventArgsType, MonoModder modder, string? name = null)
175239
{
176240
var methodName = name ?? $"Invoke{eventField.Name.TrimStart('_')}";
@@ -206,15 +270,15 @@ static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinit
206270
}
207271

208272
// Create a GenericInstanceType for EventHandler<HookEventArgsType>
209-
var eventHandlerGenericType = EventEmitter.GetEventHandlerReference(modder);
210-
211-
GenericInstanceType genericEventHandlerType = new(eventHandlerGenericType)
273+
//var eventHandlerGenericType = EventEmitter.GetEventHandlerReference(modder);
274+
var eventHandlerType = modder.ResolveTypeReference(typeof(EventHandler<>));
275+
GenericInstanceType genericEventHandlerType = new(eventHandlerType)
212276
{
213277
GenericArguments = { hookEventArgsType }
214278
};
215279

216280
// Import the "Invoke" method of EventHandler<HookEventArgsType>
217-
var eventHandlerInvokeMethod = eventHandlerGenericType.Resolve().Methods.First(m => m.Name == "Invoke");
281+
var eventHandlerInvokeMethod = eventHandlerType.Resolve().Methods.First(m => m.Name == "Invoke");
218282
MethodReference invokeMethodReference = new(
219283
eventHandlerInvokeMethod.Name,
220284
hookType.Module.TypeSystem.Void,
@@ -237,7 +301,7 @@ static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinit
237301
invokeMethod.Body.Variables.Add(vrb);
238302
invokeMethod.Body.InitLocals = true;
239303
il.Emit(OpCodes.Newobj, hookEventArgsType.Methods.Single(x => x.Name == ".ctor")); // Create a new instance of the event args
240-
// Set the fields of the event args instance
304+
// Set the fields of the event args instance
241305
foreach (var prm in invokeMethod.Parameters.Skip(1 /*sender*/))
242306
{
243307
il.Emit(OpCodes.Dup); // Load the event args instance
@@ -387,6 +451,8 @@ public static void CreateHook(this MethodDefinition definition, ModFwModder modd
387451
// call an event
388452
// check whether to continue or not, using a simple bool flag
389453

454+
CreateHookDelegate(modder);
455+
390456
var uniqueName = GetUniqueName(definition);
391457
var hookType = GetOrCreateHookType(definition.DeclaringType);
392458

ModFramework/Extensions/CecilHelpers.Extensions.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1616
You should have received a copy of the GNU General Public License
1717
along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
*/
19+
using ModFramework.Relinker;
1920
using Mono.Cecil;
2021
using Mono.Cecil.Cil;
22+
using MonoMod;
2123
using MonoMod.Cil;
2224
using System;
2325
using System.Collections.Generic;
@@ -568,4 +570,79 @@ public static void SetAnyCPU(this ModuleDefinition module)
568570
}
569571

570572
public static void SetAnyCPU(this ModFwModder modder) => modder.Module.SetAnyCPU();
573+
574+
public static (ModuleDefinition module, IReadOnlyCollection<TypeDefinition> types)? ResolveFrameworkType(this MonoModder modder, string typeFullName)
575+
{
576+
if (modder is null) throw new ArgumentNullException(nameof(modder));
577+
var depds = modder.DependencyCache.Values
578+
.Select(m => new
579+
{
580+
Module = m,
581+
Types = m.Types.Where(x => x.FullName == typeFullName
582+
&& m.Assembly.Name.Name != "mscorlib"
583+
&& m.Assembly.Name.Name != "System.Private.CoreLib"
584+
&& x.IsPublic
585+
)
586+
})
587+
.Where(x => x.Types.Any())
588+
// pick the assembly with the highest version.
589+
// TODO: consider if this will ever need to target other fw's
590+
.OrderByDescending(x => x.Module.Assembly.Name.Version);
591+
592+
var type = depds.FirstOrDefault();
593+
if (type is not null)
594+
{
595+
return (type.Module, type.Types.ToArray());
596+
}
597+
return null;
598+
}
599+
600+
public static TypeDefinition? TryResolveFirstFrameworkType(this MonoModder modder, string typeFullName)
601+
{
602+
var res = modder.ResolveFrameworkType(typeFullName);
603+
return res?.types?.FirstOrDefault();
604+
}
605+
606+
public static TypeDefinition? TryResolveFirstFrameworkType(this MonoModder modder, Type type)
607+
=> modder.TryResolveFirstFrameworkType(type.FullName ?? throw new ArgumentNullException(nameof(type.FullName)));
608+
609+
public static TypeDefinition? TryResolveFirstFrameworkType<TType>(this MonoModder modder)
610+
=> modder.TryResolveFirstFrameworkType(typeof(TType));
611+
612+
public static TypeDefinition ResolveFirstFrameworkType(this MonoModder modder, string typeFullName)
613+
{
614+
return TryResolveFirstFrameworkType(modder, typeFullName) ?? throw new InvalidOperationException($"Could not resolve type {typeFullName}");
615+
}
616+
617+
public static TypeDefinition ResolveFirstFrameworkType(this MonoModder modder, Type type)
618+
=> modder.ResolveFirstFrameworkType(type.FullName ?? throw new ArgumentNullException(nameof(type.FullName)));
619+
620+
public static TypeDefinition ResolveFirstFrameworkType<TType>(this MonoModder modder)
621+
=> modder.ResolveFirstFrameworkType(typeof(TType));
622+
623+
public static AssemblyNameReference? ResolveFrameworkAssembly(this MonoModder modder, string typeFullName)
624+
{
625+
if (modder is null) throw new ArgumentNullException(nameof(modder));
626+
var data = modder.ResolveFrameworkType(typeFullName);
627+
return data?.module?.Assembly?.AsNameReference();
628+
}
629+
630+
public static AssemblyNameReference? ResolveDependency(this MonoModder modder, TypeReference type)
631+
=> ResolveFrameworkAssembly(modder, type.FullName);
632+
633+
public static TypeReference ResolveTypeReference(this MonoModder modder, Type type)
634+
{
635+
// try the method the clr relinker uses first
636+
var clr = modder.TryResolveFirstFrameworkType(type.FullName ?? throw new ArgumentNullException(nameof(type.FullName)));
637+
if (clr is not null)
638+
return modder.Module.ImportReference(clr);
639+
640+
// if we're here, the clr relinker failed, and typically this is because monomod hasn't executed and populated the dependencies
641+
// this will often yield references to System.Private.CoreLib, which is not what we want - but typically is cleaned up by
642+
// the clr relinker after this stage.
643+
return modder.Module.ImportReference(type);
644+
}
645+
646+
public static TypeReference ResolveTypeReference<TType>(this MonoModder modder)
647+
=> modder.ResolveTypeReference(typeof(TType));
571648
}

ModFramework/Relinker/CoreLibRelinker.cs

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -142,56 +142,14 @@ public override void PreWrite()
142142
base.PreWrite();
143143
}
144144

145-
public static (ModuleDefinition module, IReadOnlyCollection<TypeDefinition> types)? ResolveFrameworkType(MonoModder modder, string typeFullName)
146-
{
147-
if (modder is null) throw new ArgumentNullException(nameof(Modder));
148-
var depds = modder.DependencyCache.Values
149-
.Select(m => new
150-
{
151-
Module = m,
152-
Types = m.Types.Where(x => x.FullName == typeFullName
153-
&& m.Assembly.Name.Name != "mscorlib"
154-
&& m.Assembly.Name.Name != "System.Private.CoreLib"
155-
&& x.IsPublic
156-
)
157-
})
158-
.Where(x => x.Types.Any())
159-
// pick the assembly with the highest version.
160-
// TODO: consider if this will ever need to target other fw's
161-
.OrderByDescending(x => x.Module.Assembly.Name.Version);
162-
163-
var type = depds.FirstOrDefault();
164-
if (type is not null)
165-
{
166-
return (type.Module, type.Types.ToArray());
167-
}
168-
return null;
169-
}
170-
171-
public static TypeDefinition ResolveFirstFrameworkType(MonoModder modder, string typeFullName)
172-
{
173-
var res = ResolveFrameworkType(modder, typeFullName);
174-
return res?.types?.FirstOrDefault() ?? throw new InvalidOperationException($"Could not resolve type {typeFullName}");
175-
}
176-
177-
public static AssemblyNameReference? ResolveFrameworkAssembly(MonoModder modder, string typeFullName)
178-
{
179-
if (modder is null) throw new ArgumentNullException(nameof(Modder));
180-
var data = ResolveFrameworkType(modder, typeFullName);
181-
return data?.module?.Assembly?.AsNameReference();
182-
}
183-
184-
public static AssemblyNameReference? ResolveDependency(MonoModder modder, TypeReference type)
185-
=> ResolveFrameworkAssembly(modder, type.FullName);
186-
187145
AssemblyNameReference? ResolveAssembly(TypeReference type)
188146
{
189147
var res = Resolve?.Invoke(type);
190148
if (res is null)
191149
{
192150
if (type.Scope is AssemblyNameReference anr)
193151
{
194-
var dependencyMatch = ResolveDependency(Modder, type);
152+
var dependencyMatch = Modder.ResolveDependency(type);
195153
if (dependencyMatch is not null)
196154
return dependencyMatch;
197155

0 commit comments

Comments
 (0)