Hooks
Introduction
In ExLaunch, there are three types of hooks that are available to us. Each of them is useful for different reasons.
Generally, hook definitions will look similar to this:
HOOK_DEFINE_<TYPE>(<Name>) {
static <ReturnType> Callback(<arguments>) {
// Code
}
};
Replace Hook
This is the most common type of hook. It replaces the code of a function with the code defined in the hook.
Here is an example replace hook for the Dpr.Battle.Logic.MainModule$$GetMaxFollowPokeLevel
function:
HOOK_DEFINE_REPLACE(Dpr_Battle_Logic_MainModule_GetMaxFollowPokeLevel) {
static uint8_t Callback(Dpr::Battle::Logic::MainModule::Object* __this) {
// Setting the obedience threshold to always be Lv. 100.
return 100;
}
};
The offset a replace hook is installed at is the offset of the replaced function.
Trampoline Hook
This hook acts similarly to a replace hook, but allows the original function to be called from within it, therefore keeping it "intact" in a way.
Here is an example trampoline hook for the FieldManager$$CheckAvailableFieldItem
function:
HOOK_DEFINE_TRAMPOLINE(ShortcutCheckAvailability) {
static bool Callback(FieldManager::Object* __this, uint16_t itemno) {
if (CanUseRegisteredCustomItem(itemno)) {
return true;
}
return Orig(__this, itemno);
}
};
As shown above, to call the original function, you simply call Orig()
with the arguments defined for Callback()
.
The offset a trampoline hook is installed at is the offset of the replaced function.
Inline Hook
This is the most complicated type of hook to deal with, but it can be very powerful when used to its full potential.
Instead of installing it at the start of a function, this hook is installed at any offset and replaces one instruction. Because of this, we have direct access to registers instead of a function's arguments.
Here is an example inline hook at offset 01daca9c
, for the FieldPlayerEntity$$CheckSwim
function:
HOOK_DEFINE_INLINE(CheckSurfFlags) {
static void Callback(exl::hook::nx64::InlineCtx* ctx) {
bool result = true;
result &= PlayerWork::GetSystemFlag((int32_t)FlagWork_SysFlag::BADGE_ID_C06);
result &= PlayerWork::GetBool((int32_t)FlagWork_Flag::FE_HIDEN_03_GET);
ctx->X[0] = result ? 1 : 0;
}
};
The replaced instruction above is a simple call to FlagWork$$GetSysFlag
. We replace it with the original check, and also a second check to a work value.
To access registers, we use the ctx
argument. It has a uint64_t
array called X
to access the 64-bit registers, and a uint32_t
array called W
for the 32-bit registers. These can be read and written to directly.
The W
registers represent the lower 32 bits of the corresponding X
registers. They are technically not a separate set of registers.
A function's arguments are typically stored in registers X0
to X7
at the start of it. When returning a value, a function writes that value to register X0
.