Esempio n. 1
0
        private void HookNpcLoot()
        {
            //Create an empty method with the same parameters as Terraria.Main.NewItem
            //Then with the method body, add the hooks and the actual call to ""
            //Replace all calls to NewItem in NPCLoot

            var npcLoot = Terraria.NPC.Method("NPCLoot");
            var newItem = Terraria.Item.Method("NewItem");

            //Create the new DropLoot call in the Terraria.NPC class
            var dropLoot = new MethodDefinition("DropLoot", MethodAttributes.Public | MethodAttributes.Static, newItem.ReturnType);
            Terraria.NPC.Methods.Add(dropLoot);

            //Clone the parameters
            foreach (var prm in newItem.Parameters)
                dropLoot.Parameters.Add(prm);
            //            //Add the this call to the end
            //            dropLoot.Parameters.Add(new ParameterDefinition("npcId", ParameterAttributes.HasDefault, Terraria.TypeSystem.Int32));

            //Collect the hooks
            var apiMatch = API.NPCCallback.Methods.Where(x => x.Name.StartsWith("OnDropLoot"));

            if (apiMatch.Count() != 2) throw new InvalidOperationException("There is no matching OnDropLoot Begin/End calls in the API");

            var cbkBegin = apiMatch.Single(x => x.Name.EndsWith("Begin"));
            var cbkEnd = apiMatch.Single(x => x.Name.EndsWith("End"));

            //Create the value to hold the new item id
            var il = dropLoot.Body.GetILProcessor();
            var vrbItemId = new VariableDefinition("otaItem", (cbkBegin.Parameters[0].ParameterType as ByReferenceType).ElementType);
            dropLoot.Body.Variables.Add(vrbItemId);

            il.Emit(OpCodes.Ldloca_S, vrbItemId); //Loads our variable as a reference
            var beginResult = dropLoot.InjectBeginCallback(cbkBegin, false, false);

            var insFirstForMethod = dropLoot.InjectMethodCall(newItem, false, false);
            il.Emit(OpCodes.Stloc, vrbItemId);

            //Set the instruction to be resumed upon not cancelling, if not already
            if (beginResult != null && beginResult.OpCode == OpCodes.Pop)
            {
                beginResult.OpCode = OpCodes.Brtrue_S;
                beginResult.Operand = insFirstForMethod;

                il.InsertAfter(beginResult, il.Create(OpCodes.Ret));
                il.InsertAfter(beginResult, il.Create(OpCodes.Ldloc, vrbItemId));
            }

            dropLoot.InjectEndCallback(cbkEnd, false);

            il.Emit(OpCodes.Ldloc, vrbItemId);
            il.Emit(OpCodes.Ret);

            var itemCalls = npcLoot.Body.Instructions.Where(x => x.OpCode == OpCodes.Call
                                && x.Operand is MethodReference
                                && (x.Operand as MethodReference).Name == "NewItem"
                                && (x.Operand as MethodReference).DeclaringType.Name == "Item").ToArray();

            //            var whoAmI = Terraria.Entity.Field("whoAmI");
            foreach (var call in itemCalls)
            {
                call.Operand = dropLoot;
                //                il.InsertBefore(call, il.Create(OpCodes.Ldarg_0));
                //                il.InsertBefore(call, il.Create(OpCodes.Ldfld, whoAmI));
            }

            //This section will add '&& num40 >= 0' to the statement above "Main.item [num40].color = this.color;"
            var insColour = npcLoot.Body.Instructions.Single(x => x.OpCode == OpCodes.Ldfld && x.Operand == Terraria.NPC.Field("color")); //Grab where the call is located
            var insColorStart = insColour.FindPreviousInstructionByOpCode(OpCodes.Ldsfld); //Find the first instruction for the color call
            var resumeInstruction = insColorStart.Previous.Operand as Instruction; //Find the instruction where it should be transferred to if false is evaludated

            il = npcLoot.Body.GetILProcessor();

            //Insert the num40 variable
            il.InsertBefore(insColorStart, il.Create(OpCodes.Ldloc, (VariableDefinition)insColorStart.Next.Operand));
            //Place 0 on the stack
            il.InsertBefore(insColorStart, il.Create(OpCodes.Ldc_I4_0));
            //Compare the current values on stack, using >=
            il.InsertBefore(insColorStart, il.Create(OpCodes.Blt, resumeInstruction));

            npcLoot.Body.OptimizeMacros();

            //            //Add the instance to DropLoot [still kills the stack]
            //            var dropLootCalls = npcLoot.Body.Instructions.Where(x => x.OpCode == OpCodes.Call
            //                                    && x.Operand is MethodReference
            //                                    && (x.Operand as MethodReference).Name == "DropLoot").ToArray();
            //            dropLoot.Parameters.Add(new ParameterDefinition("npc", ParameterAttributes.None, Terraria.NPC)
            //                {
            //                    HasDefault = true,
            //                    IsOptional = true
            //                });
            //
            //            foreach (var call in dropLootCalls)
            //            {
            //                il.InsertBefore(call, il.Create(OpCodes.Ldarg_0));
            //            }
        }
        /// <summary>
        /// Wraps the method with the specified begin/end calls
        /// </summary>
        /// <param name="method"></param>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        public static MethodDefinition Wrap(this MethodDefinition method, MethodReference begin, MethodReference end = null, bool beginIsCancellable = false)
        {
            if (!method.HasBody) throw new InvalidOperationException("Method must have a body.");
            if (method.ReturnType.Name == "Void")
            {
                //Create the new replacement method
                var wrapped = new MethodDefinition(method.Name, method.Attributes, method.ReturnType);
                var instanceMethod = (method.Attributes & MethodAttributes.Static) == 0;

                //Rename the existing method, and replace it
                method.Name = method.Name + WrappedMethodNameSuffix;
                method.ReplaceWith(wrapped);

                //Copy over parameters
                if (method.HasParameters)
                    foreach (var prm in method.Parameters)
                    {
                        wrapped.Parameters.Add(prm);
                    }

                //Place the new method in the declaring type of the method we are cloning
                method.DeclaringType.Methods.Add(wrapped);

                var beginResult = wrapped.InjectBeginCallback(begin, instanceMethod, beginIsCancellable);

                //Execute the actual code
                var insFirstForMethod = wrapped.InjectMethodCall(method, instanceMethod);
                //Set the instruction to be resumed upon not cancelling, if not already
                if (beginIsCancellable && beginResult != null && beginResult.OpCode == OpCodes.Nop)
                {
                    beginResult.OpCode = OpCodes.Brtrue_S;
                    beginResult.Operand = insFirstForMethod;
                }

                if (end != null)
                {
                    wrapped.InjectEndCallback(end, instanceMethod);
                }

                wrapped.InjectMethodEnd();

                //                var xstFirst = method.Body.Instructions.First();
                ////                var xstLast = method.Body.Instructions.Last(x => x.OpCode == OpCodes.Ret);
                //                var lastInstruction = method.Body.Instructions.Last();
                //
                //                if (begin.HasParameters) //Parameter order must be the same
                //                {
                //                    for (var i = 0; i < begin.Parameters.Count; i++)
                //                    {
                //                        var prm = method.CreateParameterInstruction(i);
                //                        il.InsertBefore(xstFirst, prm);
                //                    }
                //                }
                //                il.InsertBefore(xstFirst, il.Create(OpCodes.Call, impBegin));
                //
                //
                //
                //                Instruction insertion = il.Create(OpCodes.Ret);
                //                Instruction endpoint = null;
                //                il.InsertAfter(lastInstruction, insertion);
                //
                //
                //
                //                var nop = il.Create(OpCodes.Nop);
                //                if (null == endpoint) endpoint = nop;
                //                il.InsertBefore(insertion, nop);
                //
                //                if (end.HasParameters) //Parameter order must be the same
                //                {
                //                    for (var i = 0; i < end.Parameters.Count; i++)
                //                    {
                //                        var prm = method.CreateParameterInstruction(i);
                //                        il.InsertBefore(insertion, prm);
                ////                        il.InsertBefore(lastInstruction, prm);
                //                        if (null == endpoint) endpoint = prm;
                //                    }
                //                }
                //
                //                var injected = new List<Instruction>();
                //                var total = 0;
                //                while (total++ < 6)
                //                {
                //                    var ins = method.Body.Instructions.Except(injected).First(xx => xx.OpCode == OpCodes.Ret);
                //
                //                    if (end.HasParameters) //Parameter order must be the same
                //                    {
                //                        for (var i = 0; i < end.Parameters.Count; i++)
                //                        {
                //                            var prm = method.CreateParameterInstruction(i);
                //                            il.InsertBefore(ins, prm);
                //                        }
                //                    }
                //                    il.InsertBefore(ins, il.Create(OpCodes.Call, impEnd));
                //                    il.InsertBefore(ins, il.Create(OpCodes.Pop));
                //
                //                    injected.Add(ins);
                //                }
                //
                //                //Since we are instructed to expect a cancellation, a hardcoded false is expected. If false then return the default.
                //                if (beginIsCancellable)
                //                {
                //
                //                }

                return wrapped;
            }
            else throw new NotSupportedException("Non Void methods not yet supported");
        }