Exemplo n.º 1
0
        /// <summary>
        ///     Processes and writes IL to the provided method body.
        ///     Note that this cleans the existing method body (removes insturctions and exception handlers).
        /// </summary>
        /// <param name="body">Method body to write to.</param>
        /// <param name="original">Original method that transpiler can optionally call into</param>
        /// <exception cref="NotSupportedException">
        ///     One of IL opcodes contains a CallSide (e.g. calli), which is currently not
        ///     fully supported.
        /// </exception>
        /// <exception cref="ArgumentNullException">One of IL opcodes with an operand contains a null operand.</exception>
        public void WriteTo(MethodBody body, MethodBase original = null)
        {
            // Clean up the body of the target method
            body.Instructions.Clear();
            body.ExceptionHandlers.Clear();

            var il  = new CecilILGenerator(body.GetILProcessor());
            var cil = il.GetProxy();

            // Define an "empty" label
            // In Harmony, the first label can point to the end of the method
            // Apparently, some transpilers naively call new Label() to define a label and thus end up
            // using the first label without knowing it
            // By defining the first label we'll ensure label count is correct
            il.DefineLabel();

            // Step 1: Prepare labels for instructions
            Prepare(vDef => il.GetLocal(vDef), il.DefineLabel);

            // Step 2: Run the code instruction through transpilers
            var newInstructions = ApplyTranspilers(cil, original);

            // We don't remove trailing `ret`s because we need to do so only if prefixes/postfixes are present

            // Step 3: Emit code
            foreach (var ins in newInstructions)
            {
                ins.labels.ForEach(l => il.MarkLabel(l));
                ins.blocks.ForEach(b => il.MarkBlockBefore(b));

                // We don't replace `ret`s yet because we might not need to
                // We do that only if we add prefixes/postfixes
                // We also don't need to care for long/short forms thanks to Cecil/MonoMod

                // Temporary fix: CecilILGenerator doesn't properly handle ldarg
                switch (ins.opcode.OperandType)
                {
                case SRE.OperandType.InlineNone:
                    il.Emit(ins.opcode);
                    break;

                case SRE.OperandType.InlineSig:
                    throw new NotSupportedException(
                              "Emitting opcodes with CallSites is currently not fully implemented");

                default:
                    if (ins.operand == null)
                    {
                        throw new ArgumentNullException(nameof(ins.operand), $"Invalid argument for {ins}");
                    }

                    il.Emit(ins.opcode, ins.operand);
                    break;
                }

                ins.blocks.ForEach(b => il.MarkBlockAfter(b));
            }

            // Note: We lose all unassigned labels here along with any way to log them
            // On the contrary, we gain better logging anyway down the line by using Cecil

            // Step 4: Run the code through raw IL manipulators (if any)
            // TODO: IL Manipulators
        }