Example #1
0
        /// <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: ...");
        }
Example #2
0
        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);
            }
        }