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