Last active
June 19, 2021 23:11
-
-
Save 0x0ade/1d1013d6ae1ff450aa76f252b0f3b62c to your computer and use it in GitHub Desktop.
TerrariaHooks / HookGen example using On. += and IL. +=
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Xna.Framework; | |
using Microsoft.Xna.Framework.Graphics; | |
using Mono.Cecil; | |
using Mono.Cecil.Cil; | |
using MonoMod.RuntimeDetour.HookGen; | |
using System; | |
using System.IO; | |
using System.Reflection; | |
using Terraria; | |
using Terraria.ModLoader; | |
namespace HookGenTest { | |
class HookGenTest : Mod { | |
// Used to rotate the hearts. | |
float heartRotation = 0; | |
public override void Load() { | |
// A simple hook. It can also act as a method replacement. | |
On.Terraria.Player.CheckMana += OnCheckMana; | |
// Runtime IL manipulation. | |
IL.Terraria.Main.DrawInterface_Resources_Life += ModifyDrawLife; | |
} | |
public override void Unload() { | |
// We can undo our changes without reloading the game! | |
// On.Terraria.Player.CheckMana -= OnCheckMana; | |
// TerrariaHooks unloads all HookGen hooks (On. += and IL. +=) automatically. | |
} | |
public override void UpdateUI(GameTime gameTime) { | |
base.UpdateUI(gameTime); | |
heartRotation += (float) gameTime.ElapsedGameTime.TotalSeconds; | |
} | |
private bool OnCheckMana(On.Terraria.Player.orig_CheckMana orig, | |
Player player, int amount, bool pay, bool blockQuickMana) { | |
// We can either make this act as a method replacement | |
// or just call our code around the original code. | |
// Let's double the mana cost of everything. | |
// We can pass on custom values. | |
bool spendable = orig(player, amount * 2, pay, blockQuickMana); | |
// ... but give back half of it if it was spent. | |
if (spendable && pay) { | |
player.statMana += amount / 2; | |
} | |
// We can return custom values. | |
return spendable; | |
} | |
private void ModifyDrawLife(HookIL il) { | |
HookILCursor c = il.At(0); | |
// Find all string.Concat calls, intercept each result, modify it. | |
// Go to every Concat call. | |
while (c.TryGotoNext( | |
i => i.MatchCall<string>("Concat") | |
)) { | |
// We want to insert our code after the Concat call. | |
c.Index++; | |
// Insert our modifying delegate. | |
int id = c.EmitDelegate<Func<string, string>>((str) => { | |
return str += ", Hi Placeholder™!"; | |
}); | |
// The delegate returns a modified string that gets used by the rest of the code. | |
} | |
// We also want to rotate the hearts. | |
// Finding the right instruction to do that is a little more complicated... | |
// ... but we know that the following instructions are involved in that order, with a few others in between. | |
while (c.TryFindNext(out HookILCursor[] cursors, | |
// The instruction we want to replace must be somewhen after those few... | |
i => i.MatchLdsfld<Main>("spriteBatch"), | |
i => i.MatchLdsfld<Main>("heartTexture") || i.MatchLdsfld<Main>("heart2Texture"), | |
i => i.MatchNewobj<Color>(), | |
// The ldc.r4 0f for the rotation that we want to change. | |
i => i.MatchLdcR4(0f), | |
// There's another 0f for the layer depth. | |
i => i.MatchLdcR4(0f), | |
// Everything is before a Draw call. | |
i => i.MatchCallvirt<SpriteBatch>("Draw") | |
)) { | |
// Still there? Great! Let's replace the ldc.r4 0f with our own | |
HookILCursor cRot = cursors[3]; | |
cRot.Remove(); | |
cRot.EmitDelegate<Func<float>>(() => { | |
return heartRotation; | |
}); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment