Skip to content
Raytwo edited this page Apr 6, 2022 · 6 revisions

This page will try and regroup some technical terms as well as examples of utilities and techniques that you might not usually need for regular Rust development as a beginner, but that might come in handy in the context of code modding for Nintendo Switch (most examples will be provided for Smash Ultimate).

Useful tips

Calling a function from the game from your code

It might sometimes happen that you come across a function that seems really useful, but it is either very big to rewrite or that are too many unknowns in the way it works.
If you know what arguments (if any) this function takes and can provide them, it is actually possible to call the function directly from your code by doing the following:

// You'd want to not hardcode the offset if you plan to support multiple versions of a game.
#[skyline::from_offset(0x336d890)]
pub fn stop_all_bgm();

fn main() {
    unsafe { stop_all_bgm(); }
}

This macro provided by skyline-rs creates a Rust function behind the scenes that will call the function from the game that exists at the offset you specified (starting from the main region in memory, for more advanced users).
The name is completely arbitrary, but make sure to specify the arguments in a FFI-compatible way if it takes any.

Situations you might encounter

Receiving a null-terminated string

Picture the following: you found a function in the game that receives a string as argument. However, considering the game is written in C/C++, you cannot use the usual Rust types such as String or &str.

This is where from_c_str() from the skyline-rs crate comes in. This is a utility method that tries to reads a C string from a pointer until it encounters a null-terminating character (\0).
It will obviously crash if you provide something else than a string, if the encoding is different than UTF-8 or if there is no null-terminating character, so make sure to double check!

fn change_version_string(c_string: *const c_char) {
    let string_read: String = unsafe { skyline::from_c_str(c_string) };
}

However, a second method called try_from_c_str() is now available and allows you to handle a few cases where it might have failed, which is recommenced over from_c_str().

fn change_version_string(c_string: *const c_char) {
    // Do note that you'd probably use something like a .map() in this situation, and the match statement is used for educational purpose
    let string_read: String = match unsafe { skyline::try_from_c_str(c_string) } {
        Ok(string) => string,
        Err(Utf8Error) => { ... },
    };
}

The reason I introduced you to from_c_str is because older plugins might still make use of it, and you need to be warned of the consequences if it fails (a panic). Be sure to use try_from_c_str from now on, and handle your errors properly!

Important terms

FFI (Foreign Function Interface)

While the name might sound complicated and scary, FFI is actually quite simple, especially if you are already used to languages from the C family.

FFI refers to the intercommunication between different languages (foreign) using a function that respects a specific convention (interface). See? Not so scary anymore! I obviously simplified the explanation quite a bit, but just remember it as a way to make a function visible to other languages and binaries (executables and libraries, such as DLL files) and have them play nice with each others.

In fact, if you have already made a simple code mod plugin, you unknowingly used it already! Most of the standard functions in the language such as opening a file have been reworked to communicate with the game you're modding through FFI.

Situations where you will need it:

  • Exposing a function from your plugin to other plugins, so they can call it. ARCropolis offers this through what is called the API, and the arcropolis_api crate is provided to make it nicer to use.
  • Calling exposed function(s) into your plugin.

Example of exposing a function:

#[no_mangle]
pub extern "C" fn simple_ffi_method(number: usize) {
    println!("Called from another plugin! The number {} was provided.", number);
}
  • Notice the #[no_mangle]? This attribute indicates that the name of the function must not be mangled, which makes it very difficult for other plugins to call it.
  • Observe the extern "C" in the signature of the function. To make things short, this is what makes exposes your function so it can be called by other plugins.
    The "C" part of it indicates that you want to use the C ABI, which I'm not going to explain here. Just know that this is what makes it so everybody is on the same wavelength and can collaborate!

Example of calling an exposed function:

extern "C" {
    fn simple_ffi_method(number: usize);
}

fn main() {
    unsafe {
        simple_ffi_method(69);
    }
}
  • Notice how the simple_ffi_method does not have a body? That is because you're just letting your code know that it exists, somewhere.
  • Pay attention to the unsafe {} scope when calling the function in main. This is necessary because Rust cannot perform its usual safety checks because it cannot see how the imported function works.
    This means that you need to be extra careful when using it to handle whatever could go wrong so your program doesn't crash.
    Good practices would have you make a Rust method that takes Rust arguments, and perform all of the conversions and checks so people can use it safely (which is what arcropolis_api does). This is commonly called a "wrapper".

Not much else to say here, just make sure the function uses the exact same name, and the same arguments in the same order.

Last tidbit about this topic, but due to the fact FFI can be used to communicate between different languages (the C ABI), this also means that you should not provide or expect arguments that are typically from the Rust language such as the String type.

There are sometimes alternatives for this, such as asking for a *const u8 (the equivalent of a *const char in C) and turning it into a Rust type through CString in this case. Make sure to read the official documentation properly, as it is always very thorough in explaining what you should pay attention to!

External resource(s)

  • cheats.rs: Invaluable resource for beginners and veterans alike who need a refresher on the syntax. Consider having it open while following the guides!
Clone this wiki locally