/// <summary> /// Change the following code sequence in Wiring.HitWireSingle /// num145 = Utils.SelectRandom(Main.rand, new short[5] /// { /// 359, /// 359, /// 359, /// 359, /// 360, /// }); /// /// to /// /// var arr = new short[5] /// { /// 359, /// 359, /// 359, /// 359, /// 360, /// } /// arr = arr.ToList().Add(id).ToArray(); /// num145 = Utils.SelectRandom(Main.rand, arr); /// /// </summary> /// <param name="il"></param> private void HookStatue(HookIL il) { // obtain a cursor positioned before the first instruction of the method // the cursor is used for navigating and modifying the il var c = il.At(0); // the exact location for this hook is very complex to search for due to the hook instructions not being unique, and buried deep in control flow // switch statements are sometimes compiled to if-else chains, and debug builds litter the code with no-ops and redundant locals // in general you want to search using structure and function rather than numerical constants which may change across different versions or compile settings // using local variable indices is almost always a bad idea // we can search for // switch (*) // case 56: // Utils.SelectRandom * // in general you'd want to look for a specific switch variable, or perhaps the containing switch (type) { case 105: // but the generated IL is really variable and hard to match in this case // we'll just use the fact that there are no other switch statements with case 56, followed by a SelectRandom HookILLabel[] targets = null; while (c.TryGotoNext(i => i.MatchSwitch(out targets))) { // some optimising compilers generate a sub so that all the switch cases start at 0 // ldc.i4.s 51 // sub // switch int offset = 0; if (c.Prev.MatchSub() && c.Prev.Previous.MatchLdcI4(out offset)) { ; } // not enough switch instructions if (targets.Length < 56 - offset) { continue; } var target = targets[56 - offset]; if (target == null) { continue; } // move the cursor to case 56: c.GotoLabel(target); // there's lots of extra checks we could add here to make sure we're at the right spot, such as not encountering any branching instructions c.GotoNext(i => i.MatchCall(typeof(Utils), nameof(Utils.SelectRandom))); // goto next positions us before the instruction we searched for, so we can insert our array modifying code right here c.EmitDelegate <Func <short[], short[]> >(arr => { // convert the array to a list, add our custom snail, and back to an array var list = arr.ToList(); list.Add((short)npc.type); return(list.ToArray()); }); // hook applied successfully return; } // couldn't find the right place to insert throw new Exception("Hook location not found, switch(*) { case 56: ..."); }
private void HookBeeType(HookIL il) { // Start the Cursor at the start var c = il.At(0); // Try to find where 566 is placed onto the stack if (!c.TryGotoNext(i => i.MatchLdcI4(566))) { return; // Patch unable to be applied } // Choose a random implementation of the patch. This is just to show different patch approaches. var random = new Terraria.Utilities.UnifiedRandom(); int choice = random.Next(3); // Main.rand is null during mod loading. if (choice == 0) { // Move the cursor after 566 and onto the ret op. c.Index++; // Push the Player instance onto the stack c.Emit(Ldarg_0); // Call a delegate using the int and Player from the stack. c.EmitDelegate <Func <int, Player, int> >((returnValue, player) => { // Regular c# code if (player.GetModPlayer <ExamplePlayer>().strongBeesUpgrade&& Main.rand.NextBool(10) && Main.ProjectileUpdateLoopIndex == -1) { return(ProjectileID.Beenade); } return(returnValue); }); } else if (choice == 1) { // Make a label to use later var label = il.DefineLabel(); // Push the Player instance onto the stack c.Emit(Ldarg_0); // Call a delegate popping the Player from the stack and pushing a bool c.EmitDelegate <Func <Player, bool> >(player => player.GetModPlayer <ExamplePlayer>().strongBeesUpgrade&& Main.rand.NextBool(10) && Main.ProjectileUpdateLoopIndex == -1); // if the bool on the stack is false, jump to label c.Emit(Brfalse, label); // Otherwise, push ProjectileID.Beenade and return c.Emit(Ldc_I4, ProjectileID.Beenade); c.Emit(Ret); // Set the label to the current cursor, which is still the instruction pushing 566 (which is followed by Ret) c.MarkLabel(label); } else { var label = il.DefineLabel(); // Here we simply adapt the dnSpy output. This approach is tedious but easier if you don't understand IL instructions. c.Emit(Ldarg_0); // We need to make sure to pass in FieldInfo or MethodInfo into Call instructions. // Here we show how to retrieve a generic version of a MethodInfo c.Emit(Call, typeof(Player).GetMethod("GetModPlayer", new Type[] { }).MakeGenericMethod(typeof(ExamplePlayer))); // nameof helps avoid spelling mistakes c.Emit(Ldfld, typeof(ExamplePlayer).GetField(nameof(ExamplePlayer.strongBeesUpgrade))); c.Emit(Brfalse_S, label); c.Emit(Ldsfld, typeof(Main).GetField(nameof(Main.rand))); // Ldc_I4_S expects an int8, aka an sbyte. Failure to cast correctly will crash the game c.Emit(Ldc_I4_S, (sbyte)10); c.Emit(Call, typeof(Utils).GetMethod("NextBool", new Type[] { typeof(Terraria.Utilities.UnifiedRandom), typeof(int) })); c.Emit(Brfalse_S, label); // You may be tempted to write c.Emit(Ldsfld, Main.ProjectileUpdateLoopIndex);, this won't work and will simply use the value of the field at patch time. That will crash. c.Emit(Ldsfld, typeof(Main).GetField(nameof(Main.ProjectileUpdateLoopIndex))); c.Emit(Ldc_I4_M1); c.Emit(Bne_Un_S, label); c.Emit(Ldc_I4, ProjectileID.Beenade); c.Emit(Ret); // As Emit has been inserting and advancing the cursor index, we are still pointing at the 566 instruction. // All the branches in the dnspy output jumped to this instruction, so we set the label to this instruction. c.MarkLabel(label); } }