-
Notifications
You must be signed in to change notification settings - Fork 13
Guide: Hooking game functions
Note
This guide is written with YYToolkit Next in mind. While it is possible to adapt these methods to YYToolkit Legacy, doing so would require using undocumented functions such as MmGetScriptData
.
Also, note that the code snippets below omit any error handling. If your module relies on these hooks, ensuring their integrity is critical!
Hooking refers to a process of intercepting engine functions for the purpose of altering or monitoring arguments and return values. While hooking is a generally supported process, issues may sometimes arise, and it is always better to use the CreateCallback function if applicable.
void Hook(
[out] RValue* Result,
[in, optional] CInstance* Self,
[in, optional] CInstance* Other,
[in] int ArgumentCount,
[in, optional] RValue* Arguments
);
A reference (or pointer) to a buffer into which the result of the function is written. If the function returns no value, this parameter is left untouched. This parameter is never nullptr
, and can safely be turned into RValue&
.
An optional pointer to the instance referred to via the self
keyword. This value may be nullptr
if the function is being called by another YYToolkit module. You may use this pointer with API functions such as GetInstanceMember to manipulate variables of the instance.
An optional pointer to the instance referred to via the other
keyword. This value may be nullptr
if the function is being called by another YYToolkit module. If the currently executing snippet of code has no other instance, the Self
and Other
arguments point to the same instance. You may use this pointer with API functions such as GetInstanceMember to manipulate variables of the instance.
The number of arguments passed to the function.
A pointer to the first element in an array of RValue
, with the valid indices ranging from 0
to ArgumentCount - 1
.
You may create a hook for a built-in function by using GetNamedRoutinePointer to get the a pointer to the engine function. This pointer will be supplied to the MmCreateHook Aurie API function. For an example, refer to the code snippet below.
TRoutine game_function = nullptr;
TRoutine original_function = nullptr;
// Get a pointer to the target function using YYToolkit's interface
g_YYTKInterface->GetNamedRoutinePointer(
"Target Function",
reinterpret_cast<PVOID*>(&game_function)
);
// Create the hook
MmCreateHook(
g_ArSelfModule,
"My Hook",
game_function,
Hook,
reinterpret_cast<PVOID*>(&original_function)
);
RValue& Hook(
[in, optional] CInstance* Self,
[in, optional] CInstance* Other,
[out] RValue& ReturnValue,
[in] int ArgumentCount
[in, optional] RValue** Arguments
);
An optional pointer to the instance referred to via the self
keyword. This value may be nullptr
if the function is being called by another YYToolkit module. You may use this pointer with API functions such as GetInstanceMember to manipulate variables of the instance.
An optional pointer to the instance referred to via the other
keyword. This value may be nullptr
if the function is being called by another YYToolkit module. If the currently executing snippet of code has no other instance, the Self
and Other
arguments point to the same instance. You may use this pointer with API functions such as GetInstanceMember to manipulate variables of the instance.
A reference (or pointer) to an engine-allocated buffer, into which the return value of the script is written. This reference is always valid, even for scripts that return no value.
The number of arguments passed to the function.
An optional pointer to the first element in an array of RValue*
, which are interpreted as arguments to the script. If the ArgumentCount
parameter is set to 0, this pointer may be nullptr
.
The function always returns the reference pased into the ReturnValue
parameter.
Caution
Returning anything other than ReturnValue
from a hooked script function is undefined behavior.
The engine may become "confused" on what the return value is, which could have an impact on game stability.
You may create a hook on a script function by using GetNamedRoutinePointer, and extracting the function pointer from the CScript
object.
CScript* script_data = nullptr;
int script_index = 0;
// Get the script data
g_YYTKInterface->GetNamedRoutinePointer(
"Target script name",
reinterpret_cast<PVOID*>(&script_data)
);
// Create the hook
MmCreateHook(
g_ArSelfModule,
"My Hook",
script_data->m_Functions->m_ScriptFunction,
Hook,
reinterpret_cast<PVOID*>(&original_function)
);