static void InjectSaveHook() { var saveWorld = typeDef_WorldFile.GetMethod("saveWorld", MethodFlags.Public | MethodFlags.Static, typeSys.Boolean, typeSys.Boolean); var swb = saveWorld.Body; var swbproc = swb.GetILProcessor(); MethodDefinition invokeSaveWorld; var saveWorldDel = context.CreateDelegate("Terraria.PrismInjections", "WorldFile_OnSaveWorldDel", typeSys.Void, out invokeSaveWorld, typeSys.Boolean); var onSaveWorld = new FieldDefinition("P_OnSaveWorld", FieldAttributes.Public | FieldAttributes.Static, saveWorldDel); 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 instr = swb.FindInstrSeqStart(toFind).Next.Next; for (int i = 0; i < toInject.Length; i++) { swbproc.InsertBefore(instr, toInject[i]); } }
static void InsertSaveLoadHooks() { TypeDefinition typeDef_PlayerFileData = memRes.GetType("Terraria.IO.PlayerFileData"); #region SavePlayer { var savePlayer = typeDef_Player.GetMethod("SavePlayer"); MethodDefinition invokeSavePlayer; var onSavePlayerDel = context.CreateDelegate("Terraria.PrismInjections", "Player_OnSavePlayerDel", typeSys.Void, out invokeSavePlayer, typeDef_PlayerFileData); var onSavePlayer = new FieldDefinition("P_OnSavePlayer", FieldAttributes.Public | FieldAttributes.Static, onSavePlayerDel); typeDef_Player.Fields.Add(onSavePlayer); var spb = savePlayer.Body; var spproc = spb.GetILProcessor(); spproc.Remove(spb.Instructions.Last()); spproc.Emit(OpCodes.Ldsfld, onSavePlayer); spproc.Emit(OpCodes.Ldarg_0); spproc.Emit(OpCodes.Callvirt, invokeSavePlayer); spproc.Emit(OpCodes.Ret); } #endregion #region LoadPlayer { // Insert load hook near end of LoadPlayer var loadPlayer = typeDef_Player.GetMethod("LoadPlayer", MethodFlags.Public | MethodFlags.Static, typeSys.String, typeSys.Boolean); MethodDefinition invokeLoadPlayer; var onLoadPlayerDel = context.CreateDelegate("Terraria.PrismInjections", "Player_OnLoadPlayerDel", typeSys.Void, out invokeLoadPlayer, typeDef_Player, typeSys.String); var onLoadPlayer = new FieldDefinition("P_OnLoadPlayer", FieldAttributes.Public | FieldAttributes.Static, onLoadPlayerDel); typeDef_Player.Fields.Add(onLoadPlayer); var lpb = loadPlayer.Body; var lpproc = lpb.GetILProcessor(); var ldPlayerLoc = TerrariaPatcher.Platform == Platform.Windows ? OpCodes.Ldloc_1 : OpCodes.Ldloc_2; // player.skinVariant = (int)MathHelper.Clamp((float)player.skinVariant, 0f, 7f); OpCode[] toFind = { ldPlayerLoc, // player (for the stfld instruction at the end) ldPlayerLoc, OpCodes.Ldfld, // player.skinVariant OpCodes.Conv_R4, // (float)^ OpCodes.Ldc_R4, OpCodes.Ldc_R4, OpCodes.Call, // MathHelper.Clamp(^, 0f, 7f) OpCodes.Conv_I4, // (int)^ OpCodes.Stfld // ^^.skinVariant = ^ }; Instruction[] toInject = { Instruction.Create(OpCodes.Ldsfld, onLoadPlayer), Instruction.Create(OpCodes.Ldloc_1), Instruction.Create(OpCodes.Ldarg_0), Instruction.Create(OpCodes.Callvirt, invokeLoadPlayer) }; var first = lpb.FindInstrSeqStart(toFind); foreach (var i in toInject) { lpproc.InsertBefore(first, i); } // rewire the if before it to end at the injected instructions instead of the code we looked for foreach (var i in lpb.Instructions) { if (i.Operand == first) { i.Operand = toInject[0]; } } // not rewiring the if will lead to invalid IL, because the target instruction won't exist (because we're removing it here) lpproc.RemoveInstructions(first, toFind.Length); // remove the limitation while we're at it } #endregion }
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) MethodDefinition invokeSoundHit; var onReflProjSoundHit = context.CreateDelegate("Terraria.PrismInjections", "NPC_ReflectProjectile_PlaySoundHitDel", typeSys.Void, out invokeSoundHit, typeDef_NPC, typeSys.Int32); var reflectProjectile_PlaySoundHit = new FieldDefinition("P_ReflectProjectile_PlaySoundHit", FieldAttributes.Public | FieldAttributes.Static, onReflProjSoundHit); typeDef_NPC.Fields.Add(reflectProjectile_PlaySoundHit); 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"); MethodDefinition invokeSoundHit; var onStrikeNpcSoundHit = context.CreateDelegate("Terraria.PrismInjections", "NPC_StrikeNPC_PlaySoundHitDel", typeSys.Void, out invokeSoundHit, typeDef_NPC, typeSys.Int32, typeSys.Single, typeSys.Int32, typeSys.Boolean, typeSys.Boolean, typeSys.Boolean); var strikeNpc_PlaySoundHit = new FieldDefinition("P_StrikeNPC_PlaySoundHit", FieldAttributes.Public | FieldAttributes.Static, onStrikeNpcSoundHit); typeDef_NPC.Fields.Add(strikeNpc_PlaySoundHit); var snb = strikeNpc.Body; 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); instrs = snproc.RemoveInstructions(instrs, toRem.Length); snproc.InsertBefore(instrs, Instruction.Create(OpCodes.Ldsfld, strikeNpc_PlaySoundHit)); snproc.EmitWrapperCall(invokeSoundHit, instrs); } #endregion }
/// <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 MethodDefinition origMethod, CecilContext context, string delegateNS, string delegateTypeName, string fieldName) { MethodDefinition invokeDelegate; SingletonTRArr[0] = origMethod.DeclaringType; //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.IsStatic ? EmptyTRArr : SingletonTRArr).Concat(origMethod.Parameters.Select(p => p.ParameterType)).ToArray(); var newDelegateType = context.CreateDelegate(delegateNS, delegateTypeName, origMethod.ReturnType, out invokeDelegate, delegateArgs); var newMethod = origMethod.ReplaceAndHook(invokeDelegate, origMethod, fieldName); origMethod.ReplaceAllMethodRefs(newMethod, context); }
static void AddOnUpdateKeyboardHook() { OpCode[] search = { OpCodes.Call, //IL_27a6: call valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Input.KeyboardState Microsoft.Xna.Framework.Input.Keyboard::GetState() OpCodes.Stsfld, //IL_27ab: stsfld valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Input.KeyboardState Terraria.Main::keyState OpCodes.Ldsfld, //IL_27b0: ldsfld bool Terraria.Main::editSign OpCodes.Brfalse_S, //IL_27b5: brfalse.s IL_27bd OpCodes.Ldc_I4_0, //IL_27b7: ldc.i4.0 OpCodes.Stsfld, //IL_27b8: stsfld bool Terraria.Main::chatMode OpCodes.Ldsfld, //IL_27bd: ldsfld bool Terraria.Main::chatMode OpCodes.Brtrue_S, //IL_27c2: brtrue.s IL_27cf OpCodes.Ldc_I4_0, //IL_27c4: ldc.i4.0 OpCodes.Stsfld, //IL_27c5: stsfld int32 Terraria.Main::startChatLine OpCodes.Br, //IL_27ca: br IL_2a83 OpCodes.Ldsfld, //IL_27cf: ldsfld int32 Terraria.Main::screenHeight OpCodes.Ldc_I4_3, //IL_27d4: ldc.i4.3 OpCodes.Div, //IL_27d5: div OpCodes.Conv_R4, //IL_27d6: conv.r4 OpCodes.Ldsfld, //IL_27d7: ldsfld class [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.SpriteFont Terraria.Main::fontMouseText OpCodes.Ldstr, //IL_27dc: ldstr "1" OpCodes.Callvirt, //IL_27e1: callvirt instance valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector2 [Microsoft.Xna.Framework.Graphics]Microsoft.Xna.Framework.Graphics.SpriteFont::MeasureString(string) TerrariaPatcher.Platform == Platform.Windows ? OpCodes.Endfinally : OpCodes.Stloc_S, TerrariaPatcher.Platform == Platform.Windows ? OpCodes.Endfinally : OpCodes.Ldloca_S, OpCodes.Ldfld, //IL_27e6: ldfld float32 [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Vector2::Y OpCodes.Div, //IL_27eb: div OpCodes.Conv_I4, //IL_27ec: conv.i4 OpCodes.Ldc_I4_1, //IL_27ed: ldc.i4.1 OpCodes.Sub, //IL_27ee: sub OpCodes.Stsfld, //IL_27ef: stsfld int32 Terraria.Main::showCount OpCodes.Ldsflda, //IL_27f4: ldsflda valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Input.KeyboardState Terraria.Main::keyState OpCodes.Ldc_I4_S, //IL_27f9: ldc.i4.s 38 OpCodes.Call, //IL_27fb: call instance bool [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Input.KeyboardState::IsKeyDown(valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Input.Keys) OpCodes.Brfalse_S, //IL_2800: brfalse.s IL_2864 }; search = search.Where(o => o.Code != Code.Endfinally).ToArray(); var mainUpdate = typeDef_Main.GetMethod("Update"); var mainUpdateBody = mainUpdate.Body; var first = mainUpdateBody.FindInstrSeqStart(search); if (!(first != null && (first = first.Next) != null && (first = first.Next) != null)) { Console.Error.WriteLine("Couldn't find instructions for MainPatcher::AddOnUpdateKeyboardHook()"); } //public virtual void P_OnUpdateInputHook() { } MethodDefinition invokeOnUpdateKeyboardHook; var onUpdateKeyboardDelType = context.CreateDelegate("Terraria.PrismInjections", "Main_Update_OnUpdateKeyboardDel", typeSys.Void, out invokeOnUpdateKeyboardHook, typeDef_Main, mainUpdate.Parameters[0].ParameterType /* HAH I WIN, XNA */); var onUpdateKeyboardDelField = new FieldDefinition("P_Main_Update_OnUpdateKeyboard", FieldAttributes.Public | FieldAttributes.Static, onUpdateKeyboardDelType); typeDef_Main.Fields.Add(onUpdateKeyboardDelField); var mainUpdateProc = mainUpdateBody.GetILProcessor(); mainUpdateProc.InsertBefore(first, Instruction.Create(OpCodes.Ldsfld, onUpdateKeyboardDelField)); mainUpdateProc.EmitWrapperCall(invokeOnUpdateKeyboardHook, first); //mainUpdateProc.InsertBefore(first, Instruction.Create(OpCodes.Call, invokeOnUpdateKeyboardHook)); }