Skip to content

Commit 4f22807

Browse files
committed
Working hooks
1 parent 4ba93a5 commit 4f22807

4 files changed

Lines changed: 99 additions & 54 deletions

File tree

ModFramework/Emitters/EventEmitter.cs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@ 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;
21-
using System;
22+
using MonoMod;
2223
using System.Linq;
2324

2425
namespace ModFramework;
2526

2627
[MonoMod.MonoModIgnore]
2728
public static class EventEmitter
2829
{
30+
public static TypeReference GetEventHandlerReference(MonoModder modder)
31+
=> modder.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.EventHandler`1"));
32+
2933
/// <summary>
3034
/// Creates a new event based upon the source definition
3135
/// </summary>
@@ -34,14 +38,16 @@ public static class EventEmitter
3438
/// <param name="eventArgsType">The event args to use</param>
3539
/// <param name="name">Optional desired name for the event</param>
3640
/// <returns>A tuple of the field and event definitions</returns>
37-
public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition) CreateEvent(this MethodDefinition sourceDefinition, TypeDefinition containingType, TypeDefinition eventArgsType, string? name = null)
41+
public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition) CreateEvent(this MethodDefinition sourceDefinition, TypeDefinition containingType, TypeDefinition eventArgsType, MonoModder modder, string? name = null)
3842
{
43+
var eventHandlerType = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.EventHandler`1"));
44+
3945
// Define the event backing field
4046
var fieldName = name ?? $"{sourceDefinition.Name}Event";
4147
FieldDefinition eventField = new(
4248
fieldName,
4349
FieldAttributes.Private | FieldAttributes.Static,
44-
new GenericInstanceType(sourceDefinition.Module.ImportReference(typeof(EventHandler<>)))
50+
new GenericInstanceType(eventHandlerType)
4551
{
4652
GenericArguments = { eventArgsType }
4753
}
@@ -65,9 +71,11 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
6571
ParameterDefinition parameter = new("value", ParameterAttributes.None, eventField.FieldType);
6672
addMethod.Parameters.Add(parameter);
6773
var ilAdd = addMethod.Body.GetILProcessor();
68-
GenericInstanceMethod methodInterlockedCompareExchange = new(containingType.Module.ImportReference(typeof(System.Threading.Interlocked)
69-
.GetMethods()
70-
.Single(m => m.Name == "CompareExchange" && m.IsGenericMethodDefinition)));
74+
75+
var compareExchange = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.Threading.Interlocked")
76+
.Methods.Single(m => m.Name == "CompareExchange" && m.HasGenericParameters && m.IsStatic));
77+
78+
GenericInstanceMethod methodInterlockedCompareExchange = new(compareExchange);
7179
methodInterlockedCompareExchange.GenericArguments.Add(eventField.FieldType);
7280

7381

@@ -86,7 +94,10 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
8694
ilAdd.Emit(OpCodes.Stloc_1); // Store into local v1
8795
ilAdd.Emit(OpCodes.Ldloc_1); // Load local v1
8896
ilAdd.Emit(OpCodes.Ldarg_0); // Load the parameter value
89-
ilAdd.Emit(OpCodes.Call, containingType.Module.ImportReference(typeof(Delegate).GetMethods().Single(x => x.Name == "Combine" && x.GetParameters().Length == 2)));
97+
98+
var combine = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.Delegate")
99+
.Methods.Single(m => m.Name == "Combine" && m.IsStatic && m.Parameters.Count == 2));
100+
ilAdd.Emit(OpCodes.Call, combine);
90101
ilAdd.Emit(OpCodes.Castclass, eventField.FieldType);
91102
ilAdd.Emit(OpCodes.Stloc_2); // Store into local v2
92103
ilAdd.Emit(OpCodes.Ldsflda, eventField);
@@ -124,7 +135,9 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
124135
ilRemove.Emit(OpCodes.Stloc_1); // Store into local v1
125136
ilRemove.Emit(OpCodes.Ldloc_1); // Load local v1
126137
ilRemove.Emit(OpCodes.Ldarg_0); // Load the parameter value
127-
ilRemove.Emit(OpCodes.Call, containingType.Module.ImportReference(typeof(Delegate).GetMethod("Remove")));
138+
var remove = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.Delegate")
139+
.Methods.Single(m => m.Name == "Remove" && m.IsStatic));
140+
ilRemove.Emit(OpCodes.Call, remove);
128141
ilRemove.Emit(OpCodes.Castclass, eventField.FieldType);
129142
ilRemove.Emit(OpCodes.Stloc_2); // Store into local v2
130143
ilRemove.Emit(OpCodes.Ldsflda, eventField);
@@ -139,9 +152,11 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
139152
containingType.Methods.Add(removeMethod);
140153

141154
// add compiler generated attribute
142-
addMethod.CustomAttributes.Add(new(containingType.Module.ImportReference(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes))));
143-
removeMethod.CustomAttributes.Add(new(containingType.Module.ImportReference(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes))));
144-
eventField.CustomAttributes.Add(new(containingType.Module.ImportReference(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).GetConstructor(Type.EmptyTypes))));
155+
var ctor = containingType.Module.ImportReference(CoreLibRelinker.ResolveFirstFrameworkType(modder, "System.Runtime.CompilerServices.CompilerGeneratedAttribute")
156+
.Methods.Single(m => m.Name == ".ctor" && m.IsConstructor && m.Parameters.Count == 0));
157+
addMethod.CustomAttributes.Add(new(ctor));
158+
removeMethod.CustomAttributes.Add(new(ctor));
159+
eventField.CustomAttributes.Add(new(ctor));
145160

146161
// Link the add/remove methods to the event
147162
eventDefinition.AddMethod = addMethod;

ModFramework/Emitters/HookEmitter.cs

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License
1818
*/
1919
using Mono.Cecil;
2020
using Mono.Cecil.Cil;
21+
using MonoMod;
2122
using System;
2223
using System.Linq;
2324

@@ -55,22 +56,28 @@ static string GetUniqueName(MethodDefinition method)
5556
if (method.DeclaringType.Methods.Count(x => x.Name == name) > 1 && method.Parameters.Count > 0)
5657
{
5758
// overloads are detected, append parameter types to the name
58-
name += "_" + string.Join("_", method.Parameters.Select(y => y.ParameterType.Name));
59+
name += "_" + string.Join("_", method.Parameters.Select(y =>
60+
{
61+
var name = y.ParameterType.Name;
62+
if (y.ParameterType.IsByReference)
63+
name = $"{y.ParameterType.GetElementType().Name}ByRef";
64+
return name;
65+
}));
5966
}
6067
return name;
6168
}
6269

6370
const String HookReturnValueName = "HookReturnValue";
6471
const String ContinueExecutionName = "ContinueExecution";
6572

66-
static TypeDefinition CreateHookEventArgs(TypeDefinition hookType, MethodDefinition hookDefinition, string? name = null)
73+
static TypeDefinition CreateHookEventArgs(TypeDefinition hookType, MethodDefinition hookDefinition, string? name = null, MonoModder? modder = null)
6774
{
6875
var hookEventName = name ?? (hookDefinition.Name + "EventArgs");
6976
TypeDefinition hookEvent = new(
7077
"", //hookType.Namespace,
7178
hookEventName,
7279
TypeAttributes.Class | TypeAttributes.BeforeFieldInit | TypeAttributes.NestedPublic | TypeAttributes.Sealed,
73-
hookType.Module.ImportReference(typeof(Object))
80+
hookType.Module.TypeSystem.Object
7481
);
7582
hookType.NestedTypes.Add(hookEvent);
7683

@@ -95,14 +102,12 @@ static TypeDefinition CreateHookEventArgs(TypeDefinition hookType, MethodDefinit
95102

96103
// create ctor, calling base ctor
97104
MethodDefinition ctor = new(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, hookDefinition.Module.TypeSystem.Void);
98-
//ctor.Body = new(ctor)
99-
//{
100-
// InitLocals = true
101-
//};
102105
var il = ctor.Body.GetILProcessor();
103106

104107
il.Emit(OpCodes.Ldarg_0);
105-
il.Emit(OpCodes.Call, hookDefinition.Module.ImportReference(typeof(object).GetConstructors().Single()));
108+
109+
var objCtor = hookDefinition.Module.TypeSystem.Object.Resolve().Methods.Single(x => x.Name == ".ctor");
110+
il.Emit(OpCodes.Call, hookDefinition.Module.ImportReference(objCtor));
106111

107112
// Set ContinueExecution to true
108113
il.Emit(OpCodes.Ldarg_0);
@@ -165,7 +170,7 @@ public static Instruction CreateStoreIndirectFunction(TypeReference type)
165170
};
166171
}
167172

168-
static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinition eventField, TypeDefinition hookEventArgsType, string? name = null)
173+
static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinition eventField, TypeDefinition hookEventArgsType, MonoModder modder, string? name = null)
169174
{
170175
var methodName = name ?? $"Invoke{eventField.Name.TrimStart('_')}";
171176

@@ -200,7 +205,8 @@ static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinit
200205
}
201206

202207
// Create a GenericInstanceType for EventHandler<HookEventArgsType>
203-
var eventHandlerGenericType = hookType.Module.ImportReference(typeof(EventHandler<>));
208+
var eventHandlerGenericType = EventEmitter.GetEventHandlerReference(modder);
209+
204210
GenericInstanceType genericEventHandlerType = new(eventHandlerGenericType)
205211
{
206212
GenericArguments = { hookEventArgsType }
@@ -241,7 +247,7 @@ static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinit
241247

242248
// Check if the event is not null
243249
il.Emit(OpCodes.Ldsfld, eventField); // Load the static event field
244-
il.Emit(OpCodes.Brfalse_S, returnLabel); // If null, skip invocation
250+
il.Emit(OpCodes.Brfalse, returnLabel); // If null, skip invocation
245251

246252
// Invoke the event delegate
247253
il.Emit(OpCodes.Ldsfld, eventField); // Load the static event field
@@ -292,10 +298,8 @@ static MethodDefinition CreateReplacement(MethodDefinition original, MethodDefin
292298
il.Emit(OpCodes.Ldarg_S, param);
293299
var defaultValue = CreateDefaultValueInstruction(type);
294300
il.Append(defaultValue);
295-
//il.Emit(OpCodes.Stind_Ref);
296301
if (defaultValue.OpCode != OpCodes.Initobj)
297302
il.Append(CreateStoreIndirectFunction(type));
298-
//il.Emit(OpCodes.Stind_I1);
299303
}
300304

301305
il.Emit(original.IsStatic ? OpCodes.Ldnull : OpCodes.Ldarg_0);
@@ -319,7 +323,6 @@ static MethodDefinition CreateReplacement(MethodDefinition original, MethodDefin
319323
il.Emit(OpCodes.Ldarg_S, param);
320324
il.Emit(OpCodes.Ldloc, eventArgsVariable);
321325
il.Emit(OpCodes.Ldfld, field);
322-
//il.Emit(OpCodes.Stind_Ref);
323326
il.Append(CreateStoreIndirectFunction(field.FieldType.GetElementType()));
324327
}
325328

@@ -329,7 +332,7 @@ static MethodDefinition CreateReplacement(MethodDefinition original, MethodDefin
329332

330333
// the event invoke is a boolen, if false, return else invoke the original method
331334
var returnLabel = hookReturnValueField is not null ? il.Create(OpCodes.Ldloc, eventArgsVariable) : il.Create(OpCodes.Ret);
332-
il.Emit(OpCodes.Brfalse_S, returnLabel);
335+
il.Emit(OpCodes.Brfalse, returnLabel);
333336

334337
if (!original.IsStatic)
335338
il.Emit(OpCodes.Ldarg_0);
@@ -349,7 +352,8 @@ static MethodDefinition CreateReplacement(MethodDefinition original, MethodDefin
349352
}
350353
}
351354
il.Emit(OpCodes.Call, original.Module.ImportReference(original));
352-
il.Emit(OpCodes.Ret);
355+
if (hookReturnValueField is not null)
356+
il.Emit(OpCodes.Ret);
353357

354358
il.Append(returnLabel);
355359

@@ -362,6 +366,12 @@ static MethodDefinition CreateReplacement(MethodDefinition original, MethodDefin
362366
return methodDefinition;
363367
}
364368

369+
/// <summary>
370+
/// The name to place in front of the preserved hook method
371+
/// </summary>
372+
/// <remarks>ModFramework Hook</remarks>
373+
public const String HookMethodNamePrefix = "mfwh_";
374+
365375
/// <summary>
366376
/// Creates a hook for a single method.
367377
/// </summary>
@@ -379,26 +389,20 @@ public static void CreateHook(this MethodDefinition definition, ModFwModder modd
379389
var uniqueName = GetUniqueName(definition);
380390
var hookType = GetOrCreateHookType(definition.DeclaringType);
381391

382-
var hookEventArgs = CreateHookEventArgs(hookType, definition, name: $"{uniqueName}EventArgs");
383-
var (hookField, _) = definition.CreateEvent(hookType, hookEventArgs, name: uniqueName);
384-
var newMethod = CreateInvokeMethod(hookType, hookField, hookEventArgs, name: $"Invoke{uniqueName}");
392+
var hookEventArgs = CreateHookEventArgs(hookType, definition, name: $"{uniqueName}EventArgs", modder: modder);
393+
var (hookField, _) = definition.CreateEvent(hookType, hookEventArgs, modder, name: uniqueName);
394+
var newMethod = CreateInvokeMethod(hookType, hookField, hookEventArgs, modder, name: $"Invoke{uniqueName}");
385395

386-
//var originalName = definition.Name;
387-
//definition.Name = $"hooked_{definition.Name}";
388-
389-
var replacement = CreateReplacement(definition, newMethod, name: $"hooked_{definition.Name}");
396+
var replacement = CreateReplacement(definition, newMethod, name: $"{HookMethodNamePrefix}{definition.Name}");
390397

391398
definition.DeclaringType.Methods.Add(replacement);
392399

393-
//// rename the original method
394-
//definition.Name = $"hooked_{definition.Name}";
395-
396400
// remove any overrides etc
397401
replacement.Attributes &= ~MethodAttributes.Virtual;
398402
replacement.Attributes &= ~MethodAttributes.NewSlot;
399403
replacement.Attributes &= ~MethodAttributes.SpecialName;
400404

401-
// swap bodies instead
405+
// swap bodies, much easier this way...
402406

403407
// swap the method references
404408
foreach (var instr in replacement.Body.Instructions)
@@ -411,8 +415,6 @@ public static void CreateHook(this MethodDefinition definition, ModFwModder modd
411415
var temp = definition.Body;
412416
definition.Body = replacement.Body;
413417
replacement.Body = temp;
414-
415-
//
416418
}
417419

418420
/// <summary>
@@ -428,7 +430,9 @@ public static void CreateHooks(this TypeDefinition definition, ModFwModder modde
428430
// not a constructor
429431
!x.IsConstructor &&
430432
// not an event
431-
!(definition.Events.Any(evt => evt.AddMethod == x || evt.RemoveMethod == x || evt.InvokeMethod == x))
433+
!(definition.Events.Any(evt => evt.AddMethod == x || evt.RemoveMethod == x || evt.InvokeMethod == x)) &&
434+
// not generic instance (TODO)
435+
!x.HasGenericParameters
432436
).ToList())
433437
method.CreateHook(modder);
434438
}

ModFramework/Extensions/CecilHelpers.Extensions.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public static class CecilHelpersExtensions
3232
public static ILCursor GetILCursor(this MonoMod.MonoModder modder, Expression<Action> reference, bool followRedirect = true)
3333
=> new ILCursor(new ILContext(modder.Module.GetDefinition<MethodDefinition>(reference, followRedirect)) { ReferenceBag = RuntimeILReferenceBag.Instance });
3434

35-
public static ILCursor GetILCursor(this MonoMod.MonoModder modder, MethodDefinition method, bool followRedirect = true)
35+
public static ILCursor GetILCursor(this MonoMod.MonoModder modder, MethodDefinition method)
3636
=> new ILCursor(new ILContext(method) { ReferenceBag = RuntimeILReferenceBag.Instance });
3737

3838
public static MethodDefinition GetMethodDefinition(this MonoMod.MonoModder modder, Expression<Action> reference, bool followRedirect = true)
@@ -97,9 +97,17 @@ public static TReturn GetDefinition<TReturn>(this IMetadataTokenProvider token,
9797
if (followRedirect)
9898
{
9999
var methods = methodReference.DeclaringType.Resolve().Methods;
100-
var redirected =
101-
methods.SingleOrDefault(m => m.Name == "orig_" + methodReference.Name) ??
102-
methods.SingleOrDefault(m => m.Name == "hooked_" + methodReference.Name);
100+
var monomod = methods.SingleOrDefault(m => m.Name == "orig_" + methodReference.Name);
101+
var modfw = methods.SingleOrDefault(m => m.Name == HookEmitter.HookMethodNamePrefix + methodReference.Name);
102+
103+
var redirected = monomod ?? modfw;
104+
if (monomod != null && modfw != null)
105+
{
106+
// if one references the other, use the one that doesnt as the redirected target
107+
var monoModReferencesModFw = monomod.Body.Instructions.Any(x => x.Operand is MethodReference mr && mr.FullName == modfw.FullName);
108+
if (monoModReferencesModFw)
109+
redirected = modfw;
110+
}
103111
if (redirected != null)
104112
{
105113
return (TReturn)(object)redirected;

0 commit comments

Comments
 (0)