Skip to content

Instantly share code, notes, and snippets.

@0x0ade
Last active June 19, 2021 23:11
Show Gist options
  • Save 0x0ade/1d1013d6ae1ff450aa76f252b0f3b62c to your computer and use it in GitHub Desktop.
Save 0x0ade/1d1013d6ae1ff450aa76f252b0f3b62c to your computer and use it in GitHub Desktop.
TerrariaHooks / HookGen example using On. += and IL. +=
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