The Plugin Macro

Note: In this section, we'll be making the following assumptions:

Plugin name: "Tutorial Plugin"

Plugin ID: "com.tutorial.plugin"

DLL Name: "tutorial-plugin.dll"

In the previous step, you set up a basic plugin, but now we want to actually get our plugin to do something! In this section, we'll be talking about the plugin macro, and what you can do with it.

The plugin macro

You may have seen this snippet in your code:

#[plugin(id = "com.tutorial.plugin")]
mod plugin {}

The #[plugin(id = "")] macro does a lot of work in the background to make your life much easier. It provides helper functions for reading settings, and also makes patching memory and hooking functions much easier.

To start with, let's make a basic function hook.

Making a function hook

For our first function hook, we're going to make a very flawed "God Mode" plugin, which you, and every other NPC, invincible. Super useful! (lol).

use std::ffi::{c_char, c_void}; // Add this line to the top of your file, where the other imports are.

#[plugin(id = "com.tutorial.plugin")]
mod plugin {
	#[hook_signature]
	extern "C" fn proc_dmg_and_stamina(motile_ptr: *mut c_void, _: f32) -> c_char {
		register!("53 56 48 8D 64 24 D8 48 89 CB 40 30 F6 8B 05 ?? ?? ?? ?? 89");

		info!("Stopping damage for motile pointer: {:p}", motile_ptr);

		proc_dmg_and_stamina(motile_ptr, 0.0)
	}
}

The code above does the following:

The bytes shown here are a signature used to find the start of a function, which is used to calculate damage done to an entity in-game.

  1. Scans for the bytes 53 56 48 8D 64 24 D8 48 89 CB 40 30 F6 8B 05 ?? ?? ?? ?? 89 in memory.
  2. Gets the pointer to the first byte in that sequence.
  3. Patches the game code to run our proc_dmg_and_stamina function, instead of the original game function.
  4. Creates a reference to the original game function inside proc_dmg_and_stamina with the same name. This can be used to call the original function.
  5. Calls the original function with the same motile pointer, but replacing the 2nd argument with 0.0, causing no damage to be done to the entity.

Let's go through the code, line-by-line.

The Hook macro and function declaration

#[hook_signature]
extern "C" fn proc_dmg_and_stamina(motile_ptr: *mut c_void, _: f32) -> c_char {

The first line, #[hook_signature], tells the plugin that you're trying to hook a function by using a byte signature. EMF will scan through the game's memory to find a specific sequence of bytes (?? can be used where you don't know what the bytes will be). When it finds the pointer, it will use that to replace the original function with the function on the line below.

The second line is where we define our function. extern "C" is used to tell Rust to use the C ABI when compiling the function.

ABI stands for "Application Binary Interface". By telling the Rust compiler to use the C ABI for our function, we're telling it that the function should be callable in the same way it would be in C.

This is necessary for compatibility, as Exanima uses the C Calling Convention, so any functions we expose to Exanima should use the same convention. If it were using a different calling convention, there could be hard-to-notice bugs occuring that might bite us later.

Our function, proc_dmg_and_stamina takes in two arguments:

  • A mutable pointer (*mut c_void) to a "motile" (an entity)
  • A 32-bit floating point number, which we call _ as we don't use it. This argument represents some kind of damage multiplier.

The function then returns a c_char, which essentially means any 8-bit/1-byte value.

The register macro

The first line in our function is this strange macro, what does it do?

register!("53 56 48 8D 64 24 D8 48 89 CB 40 30 F6 8B 05 ?? ?? ?? ?? 89");

The code above is a special macro, who's behaviour depends on the hook macro provided previously. Since we provided #[hook_signature], it is expecting us to return a string containing a signature.

The code in this macro will run this code when the plugin is first initialised. It will find the pointer from the signature, and use that to replace the original function with our own one.

To help you visualise what it's doing, we can look at what it'd look like if we were to use #[hook_pointer] instead of #[hook_signature].

register!({
	let signature = "53 56 48 8D 64 24 D8 48 89 CB 40 30 F6 8B 05 ?? ?? ?? ?? 89";
	let pointer = emf_rs::Memory::sig_scan(sig);
	return pointer;
});

You can run any code you want in the register!() macro, but there can only be one per hook or patch, and it must return either a pointer or a signature.

Calling the original function

In the last line, we call the original function. It might look like we're just recursively calling our own function, but there's some helpful code created by our hook macro that lets us call the original function instead.

extern "C" fn proc_dmg_and_stamina(motile_ptr: *mut c_void, _: f32) -> c_char {
	...
	proc_dmg_and_stamina(motile_ptr, 0.0)
}

If for any reason, you wanted to recursively call the hook function, you can use hook_name::func. For example:

extern "C" fn proc_dmg_and_stamina(motile_ptr: *mut c_void, _: f32) -> c_char {
	// This will cause an infinite loop as we recursively call our function.
	proc_dmg_and_stamina::func(motile_ptr, 0.0);
}

Now that we've gone through all the code, head over to the next page to see how we get your hook working.