Miscellaneous utilities used by the injector.
        /// <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 #####");
        }
Example #2
0
        /// <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 #####");
        }