-
Notifications
You must be signed in to change notification settings - Fork 44
Mod API Reference
Making new mods for PrimeHack is relatively painless. We have our own mod system to make this process easy.
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 {}
};
}
This function is executed every frame, and is used to update behaviour and values in PrimeHack and the game.
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.
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.
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.
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.
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.
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);
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.
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.
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.
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
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.