public void Inject(Instruction startCode, int token = 0, 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(
InjectMethod.Name + "_return",
InjectTarget.ReturnType);
targetBody.Variables.Add(returnDef);
}
if (flags.PassTag)
{
Logger.LogLine(LogMask.Inject, $"Passing custom token value: {token}");
il.InsertBefore(startInstruction, il.Create(OpCodes.Ldc_I4, token));
}
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 #####");
}
}