How the NativeHooks System Invokes GTA Native Functions in YimMenuV2

YimMenuV2 hooks the script-program VMT and overwrites native-handler entry points to intercept and redirect GTA native function calls.

The YimMenuV2 mod menu for GTA 5 intercepts native function calls by manipulating the internal script-program structures used by the RAGE engine. This deep integration allows the NativeHooks system to invoke GTA native functions through custom replacement handlers while preserving the ability to call original implementations. Understanding this mechanism requires examining how the menu patches the virtual-method table (VMT) of script programs and redirects individual native entry points.

Architecture of the NativeHooks System

The NativeHooks module operates by targeting rage::scrProgram instances, the core data structures the game uses to manage script execution. When GTA loads a new script, the system registers the program, creates a VMT hook to monitor its lifecycle, and prepares to swap native function pointers.

Program Registration and VMT Hooking

Registration begins in NativeHooks::RunScriptImpl, which scans the global Pointers.ScriptPrograms array to detect newly loaded scripts. For each program, it calls RegisterProgram to initialize a hook context.

The NativeHooks::Program constructor then creates a VMTHook targeting the program's VMT at slot 9. Specifically, it replaces the destructor at index 6 with ScrProgram_Dtor. This ensures the menu can execute cleanup logic when the script unloads.

Preserving Original Native Handlers

Before applying any modifications, the constructor captures the program's original state. It copies the program->m_NativeEntrypoints array into m_OrigHandlers, creating a snapshot of all native function pointers. This backup is essential for the Program::Cleanup method to restore the original table later.

Installing Custom Replacement Handlers

New hooks are registered via AddHookImpl, which stores a Hook structure containing a NativeIndex and replacement rage::scrNativeHandler. The system immediately iterates through all registered programs and invokes Program::Apply to patch the live entry-point table:

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;

Here, NativeInvoker::GetNativeHandler resolves the absolute address of the original native from its index, then GetAddressOfNativeEntrypoint locates the pointer within the program's native table. The menu overwrites this pointer with the custom handler, redirecting future calls.

Runtime Invocation Flow

When a game script invokes a native like GET_ENTITY_COORDS, the script VM looks up the function address in m_Program->m_NativeEntrypoints. Because the pointer now references the replacement function rather than the original code, control transfers to the menu's implementation first. The replacement handler can forward arguments to the original function (accessed via m_OrigHandlers) or implement entirely new behavior before returning to the script.

Implementation Example

Hooking a specific native requires specifying its hash index and providing a compatible handler function. The following example intercepts GET_ENTITY_COORDS (index 0x2C) to log entity coordinates:

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);
});

The ALL_SCRIPTS scope ensures the hook applies to every script program currently registered with the NativeHooks system.

Cleanup and Restoration

When a script terminates, the hooked destructor ScrProgram_Dtor triggers NativeHooks::UnregisterProgram. The Program::Cleanup method executes two critical operations: it restores the original native handler table using memcpy from m_OrigHandlers, and it disables the VMT hook to prevent dangling pointers. This ensures clean detachment when the menu unloads:

// Unregister all hooks when the menu unloads
NativeHooks::Destroy();

The restoration process guarantees that the game's script engine resumes using the original native implementations, preventing crashes or undefined behavior after the menu exits.

Summary

  • VMT Hooking: The system hooks the script-program destructor (index 6) via slot 9 to monitor script lifecycle and ensure cleanup.
  • Table Patching: AddHookImpl overwrites specific entries in m_NativeEntrypoints after resolving addresses via NativeInvoker::GetNativeHandler.
  • Original Preservation: The constructor snapshots the native table to m_OrigHandlers, enabling restoration during Cleanup.
  • Interception Flow: Script VM calls redirect to custom handlers first, allowing pre/post-processing or complete replacement of native behavior.
  • Safe Teardown: Program::Cleanup restores original handlers and removes VMT hooks using memcpy and the hook destructor.

Frequently Asked Questions

How does YimMenuV2 locate the native function addresses to hook?

According to the source code in src/game/gta/invoker/Invoker.hpp, the NativeInvoker::GetNativeHandler function resolves the absolute memory address of a native function from its hash index. This address is then passed to GetAddressOfNativeEntrypoint within the script program to locate the specific pointer slot that needs replacement.

What prevents the game from crashing when the menu unloads?

The NativeHooks::Program class stores the original native handler table in m_OrigHandlers during construction, as seen in src/game/backend/NativeHooks.cpp. When the script unloads or NativeHooks::Destroy() is called, the Cleanup method uses memcpy to restore the original m_NativeEntrypoints array and disables the VMT hook, returning the program to its unmodified state.

Can hooks target specific scripts rather than all scripts?

Yes. While the example uses ALL_SCRIPTS, the AddHook function accepts a script identifier parameter. The implementation in src/game/backend/NativeHooks.cpp stores hooks in m_RegisteredHooks and applies them only to matching program instances during the registration walk, allowing selective targeting of specific script threads.

Why is the destructor hook (index 6) necessary?

The VMT hook on slot 9 replaces the destructor at index 6 with ScrProgram_Dtor to intercept script termination. This hook ensures NativeHooks::UnregisterProgram executes before the program memory is freed, providing a guaranteed execution path to restore the original native handler table and prevent use-after-free errors.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →