Skip to content
This repository has been archived by the owner on Jun 8, 2022. It is now read-only.

4. Function Hooking

jam1garner edited this page Jun 11, 2020 · 1 revision

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.

The Hook Attribute

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.

Installing Hooks

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.

Putting it all together

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!