-
Notifications
You must be signed in to change notification settings - Fork 8
4. Function Hooking
The most common kind of mod is function hooking. Function hooking, in the context of Skyline, is "replacing" a function with your own. This page will walk you through making a simple mod for a hypothetical game.
Attributes, in Rust, are additional data associated with a given function. The format of an attribute is:
#[attribute]
fn my_function() {
}
They go inside of #[]
s and act as "modifiers" to a given function.
The hook attribute is one we use to mark a function as a hook. It currently supports two modes: offset
and replace
:
-
offset
- replace a function which begins at a given offset from the start of the.text
section. -
replace
- replace a function given a function pointer
Example of offset
:
use skyline::hook;
#[hook(offset = 0x31bf2e0)]
fn my_function_replacement() {
println!("The function ran!");
}
This will result in every time the function at offset 0x31bf2e0
is called, it instead prints "The function ran!" to the logger.
Example of replace
:
use skyline::hook;
use nnsdk::fs::CreateDirectory;
#[hook(replace = CreateDirectory)]
fn create_directory_hook(directory_path: *const c_char) {
println!("A directory was just created");
call_original!(directory_path)
}
This replaces nn::fs::CreateDirectory
(the nnsdk function for making a folder) with a hook that creates logs that a directory was created, then calls the original function. call_original!()
is a macro only present in hooks that allows you to call the non-replaced version of the function being hooked. This is often used for functions that want to add additional functionality before/after a function without actually replacing the function itself.
If, in the above example, call_original!() wasn't used, the directory would not get created as any time the CreateDirectory function is called, it would only call the hook.
Marking a function as a hook isn't enough for you changes to take effect. To actually replace the function and enable the hook, you must use install_hook!
(or, if you're doing multiple at once, install_hooks!
)
#[skyline::main(name = "create_dir_example")]
fn main() {
install_hook!(create_directory_hook);
}
You might also have noticed the attribute skyline::main
. This is an attribute used to mark the main
function (the function that runs when the plugin is first loaded when the game boots). In it you specify the internal name of the plugin, this is useful as it will be displayed in crash logs to show if it was your code that crashed.
Let's say you want to mod your game of choice to take the randomness out of critical hits. You've already located the function where it calculates if the you get a critical hit or not, and you're lucky enough that the game exports this as a dynamic symbol. First up you need to import your function:
extern "C" {
fn check_is_critical_hit() -> bool;
}
This import is done via a standard extern block (Note: ignore the link attribute on this page, it is unnecessary on the switch).
Next up we need to create a hook for this function that always returns true (so we always get a critical hit)
#[hook(replace = check_is_critical_hit)]
fn critical_hit_hook() -> bool {
true
}
And lastly we need to install our hook:
#[skyline::main(name = "always_crit")]
fn main() {
install_hook!(critical_hit_hook);
}
And if we put that all together we get the following:
use skyline::{hook, install_hook};
extern "C" {
fn check_is_critical_hit() -> bool;
}
#[hook(replace = check_is_critical_hit)]
fn critical_hit_hook() -> bool {
true
}
#[skyline::main(name = "always_crit")]
fn main() {
install_hook!(critical_hit_hook);
}
And that's all it takes to make a simple mod that replaces a function with another that always returns true!