static void InjectSaveHook() { var saveWorld = typeDef_WorldFile.GetMethod("saveWorld", MethodFlags.Public | MethodFlags.Static, typeSys.Boolean.ToTypeDefOrRef(), typeSys.Boolean.ToTypeDefOrRef()); MethodDef invokeSaveWorld; var saveWorldDel = context.CreateDelegate("Terraria.PrismInjections", "WorldFile_OnSaveWorldDel", typeSys.Void, out invokeSaveWorld, typeSys.Boolean); var onSaveWorld = new FieldDefUser("P_OnSaveWorld", new FieldSig(saveWorldDel.ToTypeSig()), FieldAttributes.Public | FieldAttributes.Static); typeDef_WorldFile.Fields.Add(onSaveWorld); OpCode[] toFind = { OpCodes.Ldloc_S, OpCodes.Call, OpCodes.Leave_S }; Instruction[] toInject = { Instruction.Create(OpCodes.Ldsfld, onSaveWorld), Instruction.Create(OpCodes.Ldarg_0), Instruction.Create(OpCodes.Callvirt, invokeSaveWorld) }; var swb = saveWorld.Body; using (var swbproc = swb.GetILProcessor()) { var instr = swb.FindInstrSeqStart(toFind).Next(swbproc).Next(swbproc); for (int i = 0; i < toInject.Length; i++) { swbproc.InsertBefore(instr, toInject[i]); } } }
/// <summary> /// Wraps a method using a fancy delegate. Replaces all references of the method with the wrapped one and creates an "On[MethodName]" hook which passes the method's parent type followed by the type parameters of the original method. /// </summary> /// <param name="context">The current Cecil context.</param> /// <param name="delegateNS">The namespace of the delegate type to create.</param> /// <param name="delegateTypeName">The name of the delegate type to create.</param> /// <param name="origMethod">The method to wrap.</param> public static void Wrap(this MethodDef origMethod, DNContext context, string delegateNS, string delegateTypeName, string fieldName) { MethodDef invokeDelegate; //SingletonTRArr[0] = origMethod.DeclaringType.ToTypeSig(); ////If anyone knows a better way to insert one element at the beginning of an array and scoot ////all the other elements down one index then go ahead and do it lol. I dunno how2array. var delegateArgs = origMethod.Parameters.Select(p => p.Type).ToArray(); var newDelegateType = context.CreateDelegate(delegateNS, delegateTypeName, origMethod.ReturnType, out invokeDelegate, delegateArgs); var newMethod = origMethod.ReplaceAndHook(invokeDelegate, origMethod, fieldName); // you're not special anymore! if ((origMethod.Attributes & MethodAttributes.SpecialName) != 0) origMethod.Attributes ^= MethodAttributes.SpecialName; if ((origMethod.Attributes & MethodAttributes.RTSpecialName) != 0) origMethod.Attributes ^= MethodAttributes.RTSpecialName; origMethod.ReplaceAllMethodRefs(newMethod, context); }
static void AddPlaceThingHook() { /* * * IL_2D37: ldsfld int32 Terraria.Player::tileTargetX * IL_2D3C: ldsfld int32 Terraria.Player::tileTargetY * IL_2D41: ldarg.0 * IL_2D42: ldfld class Terraria.Item[] Terraria.Player::inventory * IL_2D47: ldarg.0 * IL_2D48: ldfld int32 Terraria.Player::selectedItem * IL_2D4D: ldelem.ref * IL_2D4E: ldfld int32 Terraria.Item::createTile * IL_2D53: ldc.i4.0 * IL_2D54: ldloc.s 78 * IL_2D56: ldarg.0 * IL_2D57: ldfld int32 Terraria.Entity::whoAmI * IL_2D5C: ldloc.s 72 * IL_2D5E: call bool Terraria.WorldGen::PlaceTile(int32, int32, int32, bool, bool, int32, int32) * IL_2D63: stloc.s 79 */ OpCode[] toFind = { OpCodes.Ldsfld, OpCodes.Ldsfld, OpCodes.Ldarg_0, OpCodes.Ldfld, OpCodes.Ldarg_0, OpCodes.Ldfld, OpCodes.Ldelem_Ref, OpCodes.Ldfld, OpCodes.Ldc_I4_0, OpCodes.Ldloc_S, OpCodes.Ldarg_0, OpCodes.Ldfld, OpCodes.Ldloc_S, OpCodes.Call, OpCodes.Stloc_S }; var placeThing = typeDef_Player.GetMethod("PlaceThing"); MethodDef invokePlaceThing; var onPlaceThingDel = context.CreateDelegate("Terraria.PrismInjections", "Player_OnPlaceThingDel", typeSys.Void, out invokePlaceThing, typeSys.Boolean); var onPlaceThing = new FieldDefUser("P_OnPlaceThing", new FieldSig(onPlaceThingDel.ToTypeSig()), FieldAttributes.Public | FieldAttributes.Static); typeDef_Player.Fields.Add(onPlaceThing); var ptb = placeThing.Body; using (var ptbproc = ptb.GetILProcessor()) { Instruction[] toInj = { Instruction.Create(OpCodes.Ldsfld, onPlaceThing), Instruction.Create(OpCodes.Ldloc, ptb.Variables[79]), Instruction.Create(OpCodes.Callvirt, invokePlaceThing) }; var first = ptb.FindInstrSeqStart(toFind); for (int i = 0; i < toFind.Length - 1; i++) { first = first.Next(ptbproc); } foreach (var i in toInj.Reverse()) { ptbproc.InsertAfter(first, i); } } }
static void AddPreDrawHook() { OpCode[] toFind = { /* * spriteBatch.Draw(...); * * IL_29AF: ldsfld class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.SpriteBatch Terraria.Main::spriteBatch * IL_29B4: ldc.i4.0 * IL_29B5: ldsfld class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.BlendState [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.BlendState::AlphaBlend * IL_29BA: ldsfld class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.SamplerState [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.SamplerState::LinearClamp * IL_29BF: ldsfld class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.DepthStencilState [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.DepthStencilState::None * IL_29C4: ldarg.0 * IL_29C5: ldfld class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.RasterizerState Terraria.Main::Rasterizer * IL_29CA: ldnull * IL_29CB: ldarg.0 * IL_29CC: ldfld valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Matrix Terraria.Main::Transform * IL_29D1: callvirt instance void [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.SpriteBatch::Begin(valuetype [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.SpriteSortMode, class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.BlendState, class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.SamplerState, class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.DepthStencilState, class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.RasterizerState, class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.Effect, valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Matrix) */ OpCodes.Ldsfld, OpCodes.Ldc_I4_0, OpCodes.Ldsfld, OpCodes.Ldsfld, OpCodes.Ldsfld, OpCodes.Ldarg_0, OpCodes.Ldfld, OpCodes.Ldnull, OpCodes.Ldarg_0, OpCodes.Ldfld, OpCodes.Callvirt }; var draw = typeDef_Main.GetMethod("Draw"); var spriteBatch = typeDef_Main.GetField("spriteBatch"); MethodDef invokePreDraw; var onPreDrawDel = context.CreateDelegate("Terraria.PrismInjections", "Main_OnPreDrawDel", typeSys.Void, out invokePreDraw, spriteBatch.FieldType); var onPreDraw = new FieldDefUser("P_OnPreDraw", new FieldSig(onPreDrawDel.ToTypeSig()), FieldAttributes.Public | FieldAttributes.Static); typeDef_Main.Fields.Add(onPreDraw); var drb = draw.Body; using (var drbproc = drb.GetILProcessor()) { Instruction[] toInj = { Instruction.Create(OpCodes.Ldsfld, onPreDraw), Instruction.Create(OpCodes.Ldsfld, spriteBatch), Instruction.Create(OpCodes.Callvirt, invokePreDraw) }; var first = drb.FindInstrSeqStart(toFind); for (int i = 0; i < toFind.Length; i++) { first = first.Next(drbproc); } foreach (var i in toInj.Reverse()) { drbproc.InsertAfter(first, i); } // rewire the if before it to end at the injected instructions instead of the code we looked for //foreach (var i in drb.Instructions) // if (i.Operand == first) // i.Operand = toInj[0]; // not rewiring the if will lead to invalid IL, because the target instruction won't exist (because we're removing it here) } }
static void ReplaceSoundHitCalls() { #region ReflectProjectile { // introduce a new scope level here so variables can be reused in the StrikeNPC part var reflectProjectile = typeDef_NPC.GetMethod("ReflectProjectile"); // first statement in the method is "Main.PlaySound(...);" // instruction after the "call Main.PlaySound" is a ldc.i4.0 (first one in the method) MethodDef invokeSoundHit; var onReflProjSoundHit = context.CreateDelegate("Terraria.PrismInjections", "NPC_ReflectProjectile_PlaySoundHitDel", typeSys.Void, out invokeSoundHit, typeDef_NPC.ToTypeSig(), typeSys.Int32); var reflectProjectile_PlaySoundHit = new FieldDefUser("P_ReflectProjectile_PlaySoundHit", new FieldSig(onReflProjSoundHit.ToTypeSig()), FieldAttributes.Public | FieldAttributes.Static); typeDef_NPC.Fields.Add(reflectProjectile_PlaySoundHit); using (var rpproc = reflectProjectile.Body.GetILProcessor()) { rpproc.RemoveInstructions(reflectProjectile.Body.Instructions.TakeWhile(i => i.OpCode.Code != Code.Ldc_I4_0).ToArray() /* must enumerate this already, invalidoperation will be thrown otherwise */); var first = reflectProjectile.Body.Instructions[0]; rpproc.InsertBefore(first, Instruction.Create(OpCodes.Ldsfld, reflectProjectile_PlaySoundHit)); rpproc.EmitWrapperCall(invokeSoundHit, first); } } #endregion #region StrikeNPC { var strikeNpc = typeDef_NPC.GetMethod("StrikeNPC"); MethodDef invokeSoundHit; var onStrikeNpcSoundHit = context.CreateDelegate("Terraria.PrismInjections", "NPC_StrikeNPC_PlaySoundHitDel", typeSys.Void, out invokeSoundHit, new[] { typeDef_NPC.ToTypeSig(), typeSys.Int32, typeSys.Single, typeSys.Int32, typeSys.Boolean, typeSys.Boolean, typeSys.Boolean }); var strikeNpc_PlaySoundHit = new FieldDefUser("P_StrikeNPC_PlaySoundHit", new FieldSig(onStrikeNpcSoundHit.ToTypeSig()), FieldAttributes.Public | FieldAttributes.Static); typeDef_NPC.Fields.Add(strikeNpc_PlaySoundHit); var snb = strikeNpc.Body; using (var snproc = snb.GetILProcessor()) { OpCode[] toRem = { OpCodes.Ldarg_0, OpCodes.Ldfld, // NPC.soundHit OpCodes.Ldc_I4_0, OpCodes.Ble_S, // <after the call> OpCodes.Ldc_I4_3, // soundHit ID OpCodes.Ldarg_0, OpCodes.Ldflda, // Entity.position OpCodes.Ldfld, // Vector2.X OpCodes.Conv_I4, OpCodes.Ldarg_0, OpCodes.Ldflda, // Entity.position OpCodes.Ldfld, // Vector2.X OpCodes.Conv_I4, OpCodes.Ldarg_0, OpCodes.Ldfld, // NPC.soundHit OpCodes.Call // Main.PlaySound(int, int, int, int) }; var instrs = snb.FindInstrSeqStart(toRem); var instrs_ = snproc.RemoveInstructions(instrs, toRem.Length); Instruction newF; snproc.InsertBefore(instrs_, newF = Instruction.Create(OpCodes.Ldsfld, strikeNpc_PlaySoundHit)); snproc.EmitWrapperCall(invokeSoundHit, instrs_); for (int i = 0; i < snb.Instructions.Count; i++) { if (snb.Instructions[i].Operand == instrs) { snb.Instructions[i].Operand = newF; } } } } #endregion }