Hooking - blu-dev/smashline GitHub Wiki
THE FOLLOWING IS STILL EXPERIMENTAL AND IS NOT GUARANTEED TO WORK PROPERLY
Difference from Skyline
In a massive oversimplification, skyline-style hooks work by replacing the executable instructions at the beginning of a function to redirect it to our own function. This has the positive effect of working 100% of the time when that function is called, but it also means that multi-hooks become a lot more difficult to implement.
Smashline-style symbol hooks function by replacing the address of the symbol of a function and pointing it back to our own code. This makes multi-hooks a lot more feasible, but also has the downside of being unreliable when hooking "static" modules (meaning parts of the game code that don't get loaded/unloaded during program execution).
Why should I use these
Symbol hooks are disposable where skyline-style hooks are not. Skyline-style hooks require a jump table which gets filled in during program execution, meaning that there is at least some limit to how many hooks you can do at runtime, even if they go unused after a module is unloaded. Symbol hooks don't have this jump table, meaning that when the module goes out of memory the only thing left dangling is the pointer to the original function (which Smashline also takes care of on module unload).
Why you shouldn't use these
While they sound really useful (multi-hooking hype!!!), they aren't fully functional yet. They are only reliable for lazy hooking NRO modules, which is still useful but not quite as useful as they could be.
Now that I know I probably shouldn't use these, how do I use them
The syntax for these is actually pretty simple. The macro consists of two arguments: module
and symbol
.
module
can be either a string literal as the name of the module (such as"common"
) or thestatic
keyword to denote that it is a symbol hook on a static modulesymbol
is a string literal that is the symbol you are hooking (such as"_Unwind_GetIP"
)
#[allow(non_snake_case)]
#[hook(module = "common", symbol = "_ZN7lua2cpp12L2CAgentBaseD1Ev")]
fn L2CAgentBase_dtor(agent_base: &mut L2CAgentBase) {
println!("destroying L2CAgentBase");
original!()(agent_base);
}
// probably won't work because it's a static hook
#[hook(module = static, symbol = "_ZN3app8lua_bind24WorkModule__is_flag_implEPNS_26BattleObjectModuleAccessorEi)]
fn is_flag_hook(boma: *mut BattleObjectModuleAccessor, what: i32) -> bool {
let value = call_original!(boma, what);
println!("Getting flag {:#x}: {}", what, value);
value
}
#[skyline::main(name = "smashline-example")]
pub fn main() {
smashline::install_hooks!(
L2CAgentBase_dtor,
is_flag_hook
);
}
Hooks provide both the original!
and call_original!
macros, which can be used to call the original function in the same style as skyline hooks. If the replacement function is called and the original function is null
, the plugin will panic. When a module is unloaded, all of the original!
macros that reference it will be set to null
to try and prevent this behavior.