How YimMenuV2's NativeHooks Intercept and Modify GTA V Native Function Calls
YimMenuV2 intercepts GTA V native function calls by hooking the script program's virtual method table (VMT) and swapping entries in the native handler lookup table, allowing custom handlers to execute before or instead of the original game code.
YimMenuV2 is an open-source mod menu for GTA V that leverages low-level hooking techniques to manipulate game behavior at runtime. The NativeHooks system specifically targets the script execution layer by modifying how the game resolves and invokes native functions—the bridge between GTA V's scripting engine and its internal engine functions.
Script Program Registration and VMT Hooking
When GTA V loads a new script program, YimMenuV2 intercepts the initialization process through NativeHooks::RunScriptImpl. This function scans the global Pointers.ScriptPrograms array and invokes RegisterProgram for each active script.
The registration process constructs a NativeHooks::Program object that immediately creates a VMTHook on the program's virtual method table (VMT) at slot 9. Specifically, it replaces the destructor at index 6 with a custom handler called ScrProgram_Dtor. According to the implementation in src/core/hooking/VMTHook.cpp, this ensures that YimMenuV2 can execute cleanup code whenever the game unloads a script.
Saving Original Native Handlers
Before applying any modifications, the constructor preserves the program's original state by copying the native handler table defined in src/types/script/scrProgram.hpp. It takes a snapshot of program->m_NativeEntrypoints into m_OrigHandlers, a member variable that stores the original rage::scrNativeHandler pointers. This preservation is critical because the Program::Cleanup method later uses this backup to restore the original table using memcpy when the script terminates.
Applying Custom Hooks via Handler Table Swapping
The actual interception mechanism occurs in NativeHooks::AddHookImpl. When the menu registers a new hook, it stores a Hook structure containing a NativeIndex and replacement rage::scrNativeHandler in m_RegisteredHooks.
Immediately upon registration, the system walks all registered programs and invokes Program::Apply, which performs the pointer swap as implemented in src/game/backend/NativeHooks.cpp:
auto old_native = NativeInvoker::GetNativeHandler(hook.m_Index);
auto old_native_addr = m_Program->GetAddressOfNativeEntrypoint(old_native);
if (old_native_addr)
*old_native_addr = hook.m_Replacement;
The NativeInvoker::GetNativeHandler helper, defined in src/game/gta/invoker/Invoker.hpp, resolves the absolute address of the original native from its hash index. Then, GetAddressOfNativeEntrypoint retrieves the specific entry point pointer within the program's handler table, which is then overwritten with the custom replacement function.
To hook a native function such as GET_ENTITY_COORDS, developers use the following pattern:
// Register a hook for the native GET_ENTITY_COORDS (index 0x2C)
NativeHooks::AddHook(ALL_SCRIPTS, 0x2C, [](rage::scrNativeCallContext* ctx) -> void {
// Call the original native first
auto* orig = NativeInvoker::GetNativeHandler(0x2C);
orig(ctx);
// Now add our custom behaviour
Vector3* coords = reinterpret_cast<Vector3*>(ctx->GetResult());
LOG(INFO, "Entity {} was queried at ({}, {}, {})", ctx->GetArgument<int>(0), coords->x, coords->y, coords->z);
});
Native Invocation Flow and Execution
When GTA V's script VM subsequently calls a hooked native, the execution flow redirects to the menu's custom handler. The script VM looks up the entry point in m_Program->m_NativeEntrypoints, which now points to the replacement function rather than the original game code.
The replacement handler receives a rage::scrNativeCallContext pointer containing all arguments and return value space. It can choose to forward the call to the original handler (retrieved from m_OrigHandlers) or implement entirely new behavior. This allows YimMenuV2 to monitor, modify, or block native function calls in real-time.
Cleanup and Program Restoration
When a script terminates, the hooked destructor ScrProgram_Dtor executes before the original program destructor. This handler invokes NativeHooks::UnregisterProgram, which triggers Program::Cleanup to restore the original state. The cleanup function copies the saved m_OrigHandlers back into the program's native entry point table using memcpy, effectively removing all traces of the hooks before the VMT hook is disabled.
To unregister all hooks when the menu unloads:
// Unregister all hooks when the menu unloads
NativeHooks::Destroy();
Summary
- VMT Hooking: YimMenuV2 hooks the script program's destructor at VMT index 6 to manage lifecycle events and ensure cleanup runs before script destruction.
- Handler Preservation: Original native function pointers are backed up to
m_OrigHandlersbefore any modifications occur, preventing permanent corruption of game state. - Table Swapping: Custom handlers replace original entries in
m_NativeEntrypointsthroughProgram::Apply, redirecting script VM calls to user-defined functions. - Safe Restoration: Upon script termination,
memcpyrestores the original handler table fromm_OrigHandlers, ensuring clean detachment without leaving dangling pointers.
Frequently Asked Questions
How does YimMenuV2 locate the specific native functions to hook?
YimMenuV2 uses NativeInvoker::GetNativeHandler to resolve the absolute address of a native function from its hash index. This helper traverses the game's native registration tables to find the original rage::scrNativeHandler pointer, which is then used to locate the entry point slot within the specific script program's handler table.
Can multiple hooks be applied to the same native function across different scripts?
Yes. The m_RegisteredHooks vector stores hook definitions globally, and Program::Apply iterates through all registered programs when a new hook is added. Each script program maintains its own copy of the native handler table in m_NativeEntrypoints, allowing the same native index to be hooked independently across multiple simultaneous scripts.
What prevents the game from crashing when the menu unloads?
The ScrProgram_Dtor hook ensures that UnregisterProgram runs before the original program destructor. This triggers Program::Cleanup, which uses memcpy to restore the original m_NativeEntrypoints from m_OrigHandlers. By restoring the original function pointers and disabling the VMT hook before the script memory is released, YimMenuV2 prevents dangling pointer references that could cause crashes.
Does the hook system support calling original natives from within replacement handlers?
Yes. Replacement handlers can access the original native function through the m_OrigHandlers backup table or by calling NativeInvoker::GetNativeHandler directly. The replacement function receives the same rage::scrNativeCallContext structure, allowing it to modify arguments, call the original implementation, and then modify return values before returning control to the script VM.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →