Skip to content

Mod API Reference

Sir Mangler edited this page Oct 25, 2021 · 1 revision

Making new mods for PrimeHack is relatively painless. We have our own mod system to make this process easy.

PrimeMod Class

Each mod implements the PrimeMod class, implementing two primary functions and a third optional one.

namespace prime {

class MyMod : public PrimeMod {
public:
  void run_mod(Game game, Region region) override;
  bool init_mod(Game game, Region region) override;
  void on_state_change(ModState old_state) override {}
};

}

run_mod

This function is executed every frame, and is used to update behaviour and values in PrimeHack and the game.

init_mod

This function is executed when a mod is enabled, typically when the game starts. This function is generally used to apply code changes required for the mod, as well as to setup values that may be specific to that version or region. Do note that addresses and offsets should be defined in the address database for clean and easy access. This function must return true to indicate it has successfully initiated.

on_state_change

This function is used when a mod needs to alter it's behaviour to accommodate a new state. The states include:

  • ENABLED - Typical running state, with code changes applied.
  • DISABLED - Mod is disabled, no code changes are applied.
  • CODE_DISABLED - Mod is executing, but the code changes are not applied or have been reverted.

HackConfig

The HackConfig class is used to initiate and enable mods, and to act as a layer between PrimeHack and Dolphin for settings and binds.

Mods must be added to the HackConfig with the hack_mgr.add_mod function, and enabled with hack_mgr.enable_mod. Practical examples of the HackManager can be seen in the HackConfig class and the HackManager class itself.

Address Database

PrimeHack mods are designed to run the same mod code for each game and version that is supported. As most addresses are not consistent between versions, they must be individually found and defined for your mod. The address database makes this clean and easy to track. Many of the addresses you may want to use are already included in this file.

To access an address from the database, you can simply run LOOKUP(my_address); for static addresses/values and LOOKUP_DYN(my_address); for dynamic addresses needing deferencing. The variable will be automatically initialized and assigned for your use.

Practical example:

  LOOKUP_DYN(ball_state);
  if (read32(ball_state) == 0) {
    writef32(calculate_yaw_vel(), angular_momentum);
  }

If you need to add a new address, you can add a new line in the correct section of the AddressDBInit file,

addr_db.register_address(Game game, std::string_view name, u32 addr_ntsc_u = 0, u32 addr_pal = 0, u32 addr_ntsc_j = 0);

You may also wish to add a dynamic address, utilizing pointers. This can be done like so:

addr_db.register_dynamic_address(Game::PRIME_1, "player", "state_manager", {mrt1(0x84c), rt0});

This registers the address player, which is based off the state_manager address (defined earlier). It is an offset of 0x84c, which is then deferenced with rt0.

The cheat engine equivalent syntax would be [state_manager + 0x84c]. For more examples of this function, please reference the file.

Code Changes (Assembly Patching)

Many of our mods make use of code changes to achieve their functionality. A simple example of this is our SpringBallButton mod. The most basic use of add_code_change is to change an instruction, and to guarantee it is safely reverted when necessary.

void SpringballButton::springball_code(u32 start_point) {
  u32 lis, ori;
  std::tie<u32, u32>(lis, ori) = prime::GetVariableManager()->make_lis_ori(4, "springball_trigger");

  add_code_change(start_point + 0x00, lis);  // lis r4, springball_trigger_upper
  add_code_change(start_point + 0x04, ori);  // ori r4, r4, springball_trigger_lower
  add_code_change(start_point + 0x08, 0x88640000);  // lbz r3, 0(r4)
  add_code_change(start_point + 0x0c, 0x38A00000);  // li r5, 0
  add_code_change(start_point + 0x10, 0x98A40000);  // stb r5, 0(r4)
  add_code_change(start_point + 0x14, 0x2C030000);  // cmpwi r3, 0
}

This function utilises a start_point address, which is used in the init_mod function to specify the location of the SpringBall function in each game and region. This is very strongly advised for simplicity, for ease of adding new version support and to produce a very clean mod file.

Code Change Grouping

You can specify a group with your code change: add_code_change(u32 addr, u32 code, std::string_view group = "");

This group is used to disable individual blocks of code changes, so that your code changes may be more variable to accommodate additional settings or more complex behaviours.

You can modify the state of the individual group like this example: set_code_group_state("beam_change", ModState::DISABLED);

EmuVariableManager

The variable manager is used to store variables inside the emulated memory, for the game to access itself. This can be used alongside the make_lis_ori function to easily produce asm instructions for loading this value's address into a specific register. The springball example above makes use of this manager.

You must initialize the variable ininit_mod with the register_variable function. You can then update the value with set_variable in your run_mod function.

This manager is especially useful for updating values in specific functions, without needing to change that value everywhere else, for example global values. It is also a quick way to load custom data, such as a flag to trigger a control.

More Information / Help

Ultimately the best way to learn would be to look at the existing mods for guidance. If you would like advice or have any specific questions, feel free to join our discord and ask in our #developer-chat channel.

FAQ

How do I add UI?

This depends on what you are doing and where you are adding it. This recent pull request shows the exact changes needed to add a Graphical Option: https://github.com/shiiion/dolphin/pull/73/files

Controls are defined in the WiimoteEmu.cpp file. You can find it here.

How do I find addresses? / How do I reverse engineer the game

The best starting place is usually the dolphin debugger and it's cheat search. You can enable the debugger in Config > Interface. Static analysis with tools such as Ghidra are also recommended.

You may wish to reference this tutorial video if you are a beginner: https://www.youtube.com/watch?v=IOyQhK2OCs0&list=PL6GfYYW69Pa2L8ZuT5lGrJoC8wOWvbIQv

Will you accept my mod?

We're very open to contributions! You can push your changes to your own fork and create a pull request with us so we can review and potentially merge your changes.