Skip to content

Commit 4ba93a5

Browse files
committed
Improve event and hook emitting
1 parent 1db5de5 commit 4ba93a5

3 files changed

Lines changed: 158 additions & 39 deletions

File tree

ModFramework/Emitters/EventEmitter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
7474
VariableDefinition v0 = new(eventField.FieldType);
7575
VariableDefinition v1 = new(eventField.FieldType);
7676
VariableDefinition v2 = new(eventField.FieldType);
77+
addMethod.Body.InitLocals = true;
7778
addMethod.Body.Variables.Add(v0);
7879
addMethod.Body.Variables.Add(v1);
7980
addMethod.Body.Variables.Add(v2);
@@ -110,6 +111,7 @@ public static (FieldDefinition fieldDefinition, EventDefinition eventDefinition)
110111
v0 = new(eventField.FieldType);
111112
v1 = new(eventField.FieldType);
112113
v2 = new(eventField.FieldType);
114+
removeMethod.Body.InitLocals = true;
113115
removeMethod.Body.Variables.Add(v0);
114116
removeMethod.Body.Variables.Add(v1);
115117
removeMethod.Body.Variables.Add(v2);

ModFramework/Emitters/HookEmitter.cs

Lines changed: 152 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ static TypeDefinition CreateHookEventArgs(TypeDefinition hookType, MethodDefinit
7070
"", //hookType.Namespace,
7171
hookEventName,
7272
TypeAttributes.Class | TypeAttributes.BeforeFieldInit | TypeAttributes.NestedPublic | TypeAttributes.Sealed,
73-
hookType.Module.ImportReference(typeof(EventArgs))
73+
hookType.Module.ImportReference(typeof(Object))
7474
);
7575
hookType.NestedTypes.Add(hookEvent);
7676

@@ -88,28 +88,83 @@ static TypeDefinition CreateHookEventArgs(TypeDefinition hookType, MethodDefinit
8888
// for each parameter in the method, create a field
8989
foreach (var param in hookDefinition.Parameters)
9090
{
91-
var paramType = param.ParameterType;
91+
var paramType = param.ParameterType.IsByReference ? param.ParameterType.GetElementType() : param.ParameterType;
9292
FieldDefinition paramField = new(param.Name, FieldAttributes.Public, paramType);
9393
hookEvent.Fields.Add(paramField);
9494
}
9595

9696
// create ctor, calling base ctor
97-
MethodDefinition ctor = new (".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, hookDefinition.Module.TypeSystem.Void);
98-
ctor.Body = new MethodBody(ctor);
97+
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+
//};
99102
var il = ctor.Body.GetILProcessor();
103+
100104
il.Emit(OpCodes.Ldarg_0);
101105
il.Emit(OpCodes.Call, hookDefinition.Module.ImportReference(typeof(object).GetConstructors().Single()));
106+
102107
// Set ContinueExecution to true
103108
il.Emit(OpCodes.Ldarg_0);
104109
il.Emit(OpCodes.Ldc_I4_1);
105110
il.Emit(OpCodes.Stfld, resultField);
111+
112+
//// Set the defaults for HookReturnValue, e.g. var = default(var)
113+
//if (hasReturnValue)
114+
//{
115+
// il.Emit(OpCodes.Ldarg_0);
116+
// il.Append(CreateDefaultValueInstruction(hookDefinition.ReturnType));
117+
// il.Emit(OpCodes.Stfld, hookEvent.Fields.Single(x => x.Name == HookReturnValueName));
118+
//}
119+
106120
il.Emit(OpCodes.Ret);
107121
hookEvent.Methods.Add(ctor);
108122

109123
hookEvent.Fields.Add(resultField);
110124
return hookEvent;
111125
}
112126

127+
public static Instruction CreateDefaultValueInstruction(TypeReference type)
128+
{
129+
return type.MetadataType switch
130+
{
131+
MetadataType.Boolean or MetadataType.Byte or MetadataType.SByte or MetadataType.Int16 or MetadataType.UInt16 or MetadataType.Int32 or MetadataType.UInt32 => Instruction.Create(OpCodes.Ldc_I4_0),
132+
MetadataType.Int64 or MetadataType.UInt64 => Instruction.Create(OpCodes.Ldc_I8, 0L),
133+
MetadataType.Single => Instruction.Create(OpCodes.Ldc_R4, 0f),
134+
MetadataType.Double => Instruction.Create(OpCodes.Ldc_R8, 0d),
135+
MetadataType.ValueType => Instruction.Create(OpCodes.Initobj, type),
136+
_ => Instruction.Create(OpCodes.Ldnull)
137+
};
138+
}
139+
140+
public static Instruction CreateLoadIndirectInstruction(TypeReference type)
141+
{
142+
return type.MetadataType switch
143+
{
144+
MetadataType.Boolean => Instruction.Create(OpCodes.Ldind_U1),
145+
MetadataType.Byte or MetadataType.SByte or MetadataType.Int16 or MetadataType.UInt16 or MetadataType.Int32 or MetadataType.UInt32 => Instruction.Create(OpCodes.Ldind_I4),
146+
MetadataType.Int64 or MetadataType.UInt64 => Instruction.Create(OpCodes.Ldind_I8),
147+
MetadataType.Single => Instruction.Create(OpCodes.Ldind_R4),
148+
MetadataType.Double => Instruction.Create(OpCodes.Ldind_R8),
149+
MetadataType.ValueType => Instruction.Create(OpCodes.Ldobj, type),
150+
_ => Instruction.Create(OpCodes.Ldind_Ref)
151+
};
152+
}
153+
154+
public static Instruction CreateStoreIndirectFunction(TypeReference type)
155+
{
156+
return type.MetadataType switch
157+
{
158+
MetadataType.Boolean => Instruction.Create(OpCodes.Stind_I1),
159+
MetadataType.Byte or MetadataType.SByte or MetadataType.Int16 or MetadataType.UInt16 or MetadataType.Int32 or MetadataType.UInt32 => Instruction.Create(OpCodes.Stind_I4),
160+
MetadataType.Int64 or MetadataType.UInt64 => Instruction.Create(OpCodes.Stind_I8),
161+
MetadataType.Single => Instruction.Create(OpCodes.Stind_R4),
162+
MetadataType.Double => Instruction.Create(OpCodes.Stind_R8),
163+
MetadataType.ValueType => Instruction.Create(OpCodes.Stobj, type),
164+
_ => Instruction.Create(OpCodes.Stind_Ref)
165+
};
166+
}
167+
113168
static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinition eventField, TypeDefinition hookEventArgsType, string? name = null)
114169
{
115170
var methodName = name ?? $"Invoke{eventField.Name.TrimStart('_')}";
@@ -139,7 +194,8 @@ static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinit
139194
// instead of an event args, intake the parameters so each call doesnt need to new up itself.
140195
foreach (var field in hookEventArgsType.Fields.Where(x => x.Name != ContinueExecutionName && x.Name != HookReturnValueName))
141196
{
142-
ParameterDefinition prm = new(field.Name, ParameterAttributes.None, field.FieldType);
197+
var fieldType = field.FieldType is ByReferenceType byRef ? byRef.ElementType : field.FieldType;
198+
ParameterDefinition prm = new(field.Name, ParameterAttributes.None, fieldType);
143199
invokeMethod.Parameters.Add(prm);
144200
}
145201

@@ -162,42 +218,39 @@ static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinit
162218
};
163219

164220
// Add parameters to the invokeMethodReference
165-
invokeMethodReference.Parameters.Add(new (hookType.Module.TypeSystem.Object)); // sender
166-
invokeMethodReference.Parameters.Add(new (eventHandlerInvokeMethod.Parameters[1].ParameterType)); // args - see EventHandler<>.Invoke, il is !0
221+
invokeMethodReference.Parameters.Add(new(hookType.Module.TypeSystem.Object)); // sender
222+
invokeMethodReference.Parameters.Add(new(eventHandlerInvokeMethod.Parameters[1].ParameterType)); // args - see EventHandler<>.Invoke, il is !0
167223

168224
// Generate IL for the Invoke method
169225
var il = invokeMethod.Body.GetILProcessor();
170226
var returnLabel = il.Create(OpCodes.Ldloc_0);
171227

172228
// Create the event args instance from the method parameters
173-
VariableDefinition vrb = new (hookEventArgsType);
229+
VariableDefinition vrb = new(hookEventArgsType);
174230
invokeMethod.Body.Variables.Add(vrb);
231+
invokeMethod.Body.InitLocals = true;
175232
il.Emit(OpCodes.Newobj, hookEventArgsType.Methods.Single(x => x.Name == ".ctor")); // Create a new instance of the event args
176233
// Set the fields of the event args instance
177234
foreach (var prm in invokeMethod.Parameters.Skip(1 /*sender*/))
178235
{
179-
il.Emit(OpCodes.Dup); // Load the event args instance
180-
il.Emit(OpCodes.Ldarg, prm); // Load the parameter
236+
il.Emit(OpCodes.Dup); // Load the event args instance
237+
il.Emit(OpCodes.Ldarg, prm); // Load the parameter
181238
il.Emit(OpCodes.Stfld, hookEventArgsType.Fields.Single(x => x.Name == prm.Name)); // Set the field
182239
}
183-
il.Emit(OpCodes.Stloc_0); // Store the event args instance
240+
il.Emit(OpCodes.Stloc_0); // Store the event args instance
184241

185242
// Check if the event is not null
186-
il.Emit(OpCodes.Ldsfld, eventField); // Load the static event field
187-
il.Emit(OpCodes.Brfalse_S, returnLabel); // If null, skip invocation
243+
il.Emit(OpCodes.Ldsfld, eventField); // Load the static event field
244+
il.Emit(OpCodes.Brfalse_S, returnLabel); // If null, skip invocation
188245

189246
// Invoke the event delegate
190-
il.Emit(OpCodes.Ldsfld, eventField); // Load the static event field
191-
il.Emit(OpCodes.Ldarg_0); // Load the sender (first parameter)
192-
//il.Emit(OpCodes.Ldarg_1); // Load the args (second parameter)
193-
il.Emit(OpCodes.Ldloc_0); // Load the event args instance
194-
il.Emit(OpCodes.Callvirt, invokeMethodReference); // Call the Invoke method on the delegate
247+
il.Emit(OpCodes.Ldsfld, eventField); // Load the static event field
248+
il.Emit(OpCodes.Ldarg_0); // Load the sender
249+
il.Emit(OpCodes.Ldloc_0); // Load the event args instance
250+
il.Emit(OpCodes.Callvirt, invokeMethodReference); // Call the Invoke method on the delegate
195251

196-
// Return args.Result
197-
il.Append(returnLabel);
198-
//il.Emit(OpCodes.Ldfld, hookEventArgsType.Fields.Single(x => x.Name == "ContinueExecution")); // Load the Result field
199-
// Return the event args variable
200-
il.Emit(OpCodes.Ldloc_0);
252+
// Return args
253+
il.Append(returnLabel); // Return the event args (Ldloc_0 from earlier)
201254
il.Emit(OpCodes.Ret);
202255

203256
// Add the Invoke method to the type
@@ -211,14 +264,15 @@ static MethodDefinition CreateInvokeMethod(TypeDefinition hookType, FieldDefinit
211264
/// </summary>
212265
/// <param name="original"></param>
213266
/// <param name="eventInvoke"></param>
267+
/// <param name="name"></param>
214268
/// <returns></returns>
215-
static MethodDefinition CreateReplacement(MethodDefinition original, MethodDefinition eventInvoke)
269+
static MethodDefinition CreateReplacement(MethodDefinition original, MethodDefinition eventInvoke, string? name = null)
216270
{
217271
var eventArgs = eventInvoke.ReturnType.Resolve();
218272
var hookReturnValueField = eventArgs.Fields.SingleOrDefault(x => x.Name == HookReturnValueName);
219273

220274
MethodDefinition methodDefinition = new(
221-
original.Name,
275+
name ?? original.Name,
222276
original.Attributes,
223277
original.ReturnType
224278
);
@@ -231,29 +285,68 @@ static MethodDefinition CreateReplacement(MethodDefinition original, MethodDefin
231285
VariableDefinition eventArgsVariable = new(eventInvoke.ReturnType);
232286
methodDefinition.Body.Variables.Add(eventArgsVariable);
233287

288+
// if any out parameters, initialise them with default values
289+
foreach (var param in methodDefinition.Parameters.Where(x => x.IsOut))
290+
{
291+
var type = param.ParameterType.GetElementType();
292+
il.Emit(OpCodes.Ldarg_S, param);
293+
var defaultValue = CreateDefaultValueInstruction(type);
294+
il.Append(defaultValue);
295+
//il.Emit(OpCodes.Stind_Ref);
296+
if (defaultValue.OpCode != OpCodes.Initobj)
297+
il.Append(CreateStoreIndirectFunction(type));
298+
//il.Emit(OpCodes.Stind_I1);
299+
}
300+
234301
il.Emit(original.IsStatic ? OpCodes.Ldnull : OpCodes.Ldarg_0);
235302
for (int i = 0; i < methodDefinition.Parameters.Count; i++)
236-
il.Emit(OpCodes.Ldarg, methodDefinition.Parameters[i]);
303+
{
304+
var isByRef = methodDefinition.Parameters[i].ParameterType.IsByReference;
305+
var opCode = isByRef ? OpCodes.Ldarg_S : OpCodes.Ldarg;
306+
il.Emit(opCode, methodDefinition.Parameters[i]);
307+
if (isByRef)
308+
il.Append(CreateLoadIndirectInstruction(methodDefinition.Parameters[i].ParameterType.GetElementType()));
309+
}
237310
il.Emit(OpCodes.Call, eventInvoke);
238311

239312
// store the event args in a local variable
240313
il.Emit(OpCodes.Stloc, eventArgsVariable);
241314

242-
// use ContinueExecutionName to determine whether to continue or not
315+
// if any out parameters, set them from the args
316+
foreach (var param in methodDefinition.Parameters.Where(x => x.ParameterType.IsByReference))
317+
{
318+
var field = eventArgs.Fields.Single(x => x.Name == param.Name);
319+
il.Emit(OpCodes.Ldarg_S, param);
243320
il.Emit(OpCodes.Ldloc, eventArgsVariable);
321+
il.Emit(OpCodes.Ldfld, field);
322+
//il.Emit(OpCodes.Stind_Ref);
323+
il.Append(CreateStoreIndirectFunction(field.FieldType.GetElementType()));
324+
}
325+
326+
// use ContinueExecutionName to determine whether to continue or not
327+
il.Emit(OpCodes.Ldloc, eventArgsVariable);
244328
il.Emit(OpCodes.Ldfld, eventInvoke.ReturnType.Resolve().Fields.Single(x => x.Name == ContinueExecutionName));
245329

246330
// the event invoke is a boolen, if false, return else invoke the original method
247331
var returnLabel = hookReturnValueField is not null ? il.Create(OpCodes.Ldloc, eventArgsVariable) : il.Create(OpCodes.Ret);
248-
249332
il.Emit(OpCodes.Brfalse_S, returnLabel);
250-
251-
il.Emit(OpCodes.Ldarg_0);
333+
334+
if (!original.IsStatic)
335+
il.Emit(OpCodes.Ldarg_0);
252336
// for each event arg field, load it onto the stack to the original method
253337
foreach (var field in eventArgs.Fields.Where(x => x.Name != ContinueExecutionName && x.Name != HookReturnValueName))
254338
{
255-
il.Emit(OpCodes.Ldloc, eventArgsVariable);
256-
il.Emit(OpCodes.Ldfld, field);
339+
var parameter = methodDefinition.Parameters.Single(x => x.Name == field.Name);
340+
// if byref, pass through original args which are updated by the event
341+
if (parameter.ParameterType.IsByReference)
342+
{
343+
il.Emit(OpCodes.Ldarg_S, parameter);
344+
}
345+
else
346+
{
347+
il.Emit(OpCodes.Ldloc, eventArgsVariable);
348+
il.Emit(OpCodes.Ldfld, field);
349+
}
257350
}
258351
il.Emit(OpCodes.Call, original.Module.ImportReference(original));
259352
il.Emit(OpCodes.Ret);
@@ -290,15 +383,36 @@ public static void CreateHook(this MethodDefinition definition, ModFwModder modd
290383
var (hookField, _) = definition.CreateEvent(hookType, hookEventArgs, name: uniqueName);
291384
var newMethod = CreateInvokeMethod(hookType, hookField, hookEventArgs, name: $"Invoke{uniqueName}");
292385

293-
var replacement = CreateReplacement(definition, newMethod);
386+
//var originalName = definition.Name;
387+
//definition.Name = $"hooked_{definition.Name}";
388+
389+
var replacement = CreateReplacement(definition, newMethod, name: $"hooked_{definition.Name}");
390+
294391
definition.DeclaringType.Methods.Add(replacement);
295392

296-
// rename the original method
297-
definition.Name = $"hooked_{definition.Name}";
393+
//// rename the original method
394+
//definition.Name = $"hooked_{definition.Name}";
298395

299396
// remove any overrides etc
300-
definition.Attributes &= ~MethodAttributes.Virtual;
301-
definition.Attributes &= ~MethodAttributes.NewSlot;
397+
replacement.Attributes &= ~MethodAttributes.Virtual;
398+
replacement.Attributes &= ~MethodAttributes.NewSlot;
399+
replacement.Attributes &= ~MethodAttributes.SpecialName;
400+
401+
// swap bodies instead
402+
403+
// swap the method references
404+
foreach (var instr in replacement.Body.Instructions)
405+
{
406+
if (instr.Operand is MethodReference mr && mr.Name == definition.Name)
407+
instr.Operand = replacement;
408+
//mr.Name = replacement.Name;
409+
}
410+
411+
var temp = definition.Body;
412+
definition.Body = replacement.Body;
413+
replacement.Body = temp;
414+
415+
//
302416
}
303417

304418
/// <summary>
@@ -314,7 +428,7 @@ public static void CreateHooks(this TypeDefinition definition, ModFwModder modde
314428
// not a constructor
315429
!x.IsConstructor &&
316430
// not an event
317-
!(definition.Events.Any(evt => evt.AddMethod == x || evt.RemoveMethod == x))
431+
!(definition.Events.Any(evt => evt.AddMethod == x || evt.RemoveMethod == x || evt.InvokeMethod == x))
318432
).ToList())
319433
method.CreateHook(modder);
320434
}

ModFramework/Extensions/CecilHelpers.Extensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ public static TReturn GetDefinition<TReturn>(this IMetadataTokenProvider token,
9696

9797
if (followRedirect)
9898
{
99-
var redirected = methodReference.DeclaringType.Resolve().Methods.SingleOrDefault(m => m.Name == "orig_" + methodReference.Name);
99+
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);
100103
if (redirected != null)
101104
{
102105
return (TReturn)(object)redirected;

0 commit comments

Comments
 (0)