/// <summary>
/// Inject the call of the injection method into the target.
/// </summary>
/// <param name="startCode">The instruction from which to start injecting.</param>
/// <param name="token">
/// If <see cref="InjectFlags.PassTag" /> is specified, the value of this parameter will be passed as a
/// parameter to the injection method.
/// </param>
/// <param name="direction">The direction in which to insert the call: either above the start code or below it.</param>
public void Inject(Instruction startCode,
object token = null,
InjectDirection direction = InjectDirection.Before)
{
InjectValues flags = Flags.ToValues();
#if DEBUG
Logger.LogLine(LogMask.Inject, "##### INJECTION START #####");
Logger.LogLine(
LogMask.Inject,
$"Injecting a call to {InjectMethod.Module.Name}.{InjectMethod.Name} into {InjectTarget.Module.Name}.{InjectTarget.Name}.");
Logger.LogLine(LogMask.Inject, "Patch parameters:");
Logger.LogLine(LogMask.Inject, $"Pass tag: {flags.PassTag}");
Logger.LogLine(LogMask.Inject, $"Modify return value: {flags.ModifyReturn}");
Logger.LogLine(LogMask.Inject, $"Pass THIS: {flags.PassInvokingInstance}");
Logger.LogLine(LogMask.Inject, $"Pass method locals: {flags.PassLocals}");
Logger.LogLine(LogMask.Inject, $"Pass member fields: {flags.PassFields}");
Logger.LogLine(LogMask.Inject, $"Pass member parameters: {flags.PassParameters}");
if (flags.PassParameters)
{
Logger.LogLine(
LogMask.Inject,
$"Member parameters are passed by {(flags.PassParametersByRef ? "reference" : "value")}");
}
#endif
bool isVoid = InjectTarget.ReturnType.FullName == "System.Void";
MethodReference hookRef = InjectTarget.Module.Import(InjectMethod);
// If the hook is generic but not instantiated fully, attempt to fill in the generic arguments with the ones specified in the target method/class
if (hookRef.HasGenericParameters &&
(!hookRef.IsGenericInstance ||
hookRef.IsGenericInstance &&
((GenericInstanceMethod)hookRef).GenericArguments.Count < hookRef.GenericParameters.Count))
{
GenericInstanceMethod genericInjectMethod = new GenericInstanceMethod(hookRef);
foreach (GenericParameter genericParameter in InjectMethod.GenericParameters)
{
List <GenericParameter> @params = new List <GenericParameter>();
@params.AddRange(InjectTarget.GenericParameters);
@params.AddRange(InjectTarget.DeclaringType.GenericParameters);
GenericParameter param = @params.FirstOrDefault(p => p.Name == genericParameter.Name);
if (param == null)
{
throw new
Exception("Could not find a suitable type to bind to the generic injection method. Try to manually instantiate the generic injection method before injecting.");
}
genericInjectMethod.GenericArguments.Add(param);
}
hookRef = genericInjectMethod;
}
MethodBody targetBody = InjectTarget.Body;
ILProcessor il = targetBody.GetILProcessor();
int startIndex = targetBody.Instructions.IndexOf(startCode);
if (startIndex == -1)
{
throw new ArgumentOutOfRangeException(nameof(startCode));
}
Instruction startInstruction = startCode;
if (direction == InjectDirection.Before && startIndex != 0)
{
Instruction oldIns = ILUtils.CopyInstruction(startCode);
ILUtils.ReplaceInstruction(startCode, il.Create(OpCodes.Nop));
Instruction ins = targetBody.Instructions[startIndex];
il.InsertAfter(ins, oldIns);
startInstruction = targetBody.Instructions[startIndex + 1];
}
else if (direction == InjectDirection.After)
{
il.InsertAfter(startCode, il.Create(OpCodes.Nop));
startInstruction = targetBody.Instructions[startIndex + 1];
}
VariableDefinition returnDef = null;
if (flags.ModifyReturn && !isVoid)
{
targetBody.InitLocals = true;
returnDef = new VariableDefinition(InjectTarget.ReturnType);
targetBody.Variables.Add(returnDef);
}
if (flags.PassTag)
{
Logger.LogLine(LogMask.Inject, $"Passing custom token value: {token}");
switch (flags.TagType)
{
case InjectValues.PassTagType.Int32:
{
int tag = token as int? ?? 0;
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldc_I4, tag));
}
break;
case InjectValues.PassTagType.String:
{
string tag = token as string;
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldstr, tag));
}
break;
case InjectValues.PassTagType.None: break;
case InjectValues.PassTagType.Max: break;
default: throw new ArgumentOutOfRangeException();
}
}
if (flags.PassInvokingInstance)
{
Logger.LogLine(LogMask.Inject, "Passing THIS argument");
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldarg_0));
}
if (flags.ModifyReturn && !isVoid)
{
Logger.LogLine(LogMask.Inject, "Passing return reference");
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldloca_S, returnDef));
}
if (flags.PassLocals)
{
Logger.LogLine(LogMask.Inject, "Passing local variable references");
foreach (int i in LocalVarIDs)
{
Logger.LogLine(LogMask.Inject, $"Passing local variable index: {i}");
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldloca_S, (byte)i));
}
}
if (flags.PassFields)
{
Logger.LogLine(LogMask.Inject, "Passing member field references");
IEnumerable <FieldReference> memberRefs = MemberReferences.Select(t => t.Module.Import(t));
foreach (FieldReference t in memberRefs)
{
Logger.LogLine(LogMask.Inject, $"Passing member field {t.FullName}");
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldarg_0));
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldflda, t));
}
}
if (flags.PassParameters)
{
Logger.LogLine(LogMask.Inject,
$"Passing member parameters by {(flags.PassParametersByRef ? "reference" : "value")}");
int icr = Convert.ToInt32(!InjectTarget.IsStatic);
for (int i = 0; i < ParameterCount; i++)
{
Logger.LogLine(LogMask.Inject, $"Passing parameter of index {i + icr}");
il.InsertBefore(startInstruction,
flags.PassParametersByRef
? il.Create(OpCodes.Ldarga_S, (byte)(i + icr))
: il.Create(OpCodes.Ldarg_S, (byte)(i + icr)));
}
}
Logger.LogLine(LogMask.Inject, "Injecting the call to the method");
il.InsertBefore(startInstruction, il.Create(OpCodes.Call, hookRef));
if (flags.ModifyReturn)
{
Logger.LogLine(LogMask.Inject, "Inserting return check");
il.InsertBefore(startInstruction, il.Create(OpCodes.Brfalse_S, startInstruction));
if (!isVoid)
{
Logger.LogLine(LogMask.Inject, "Inserting return value");
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldloc_S, returnDef));
}
Logger.LogLine(LogMask.Inject, "Inserting return command");
il.InsertBefore(startInstruction, il.Create(OpCodes.Ret));
}
// If we don't use the return value of InjectMethod, pop it from the ES
else if (InjectMethod.ReturnType.FullName != "System.Void")
{
il.InsertBefore(startInstruction, il.Create(OpCodes.Pop));
}
if (direction == InjectDirection.After)
{
il.Remove(startInstruction);
}
Logger.LogLine(LogMask.Inject, "Injection complete");
Logger.LogLine(LogMask.Inject, "##### INJECTION END #####");
}