/// <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 #####"); }
/// <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, 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); 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 #####"); }