Skip to main content

Overview

The HotWheels SDK provides two primary hooking mechanisms for intercepting game functions:

VMT Hooking

Virtual Method Table hooking for class methods

Detour Hooking

MinHook-based detours for any function

Virtual Function Hooking

What is VMT Hooking?

VMT (Virtual Method Table) hooking exploits C++ polymorphism by intercepting virtual function calls. Every class with virtual methods has a vtable containing function pointers.

Getting Virtual Functions

The SDK provides utilities to retrieve virtual function pointers:
namespace virtual_func
{
    template<typename T, typename... VA>
    constexpr T call(void* thisptr, std::size_t index, VA... args)
    {
        using fn = T(__thiscall*)(void*, decltype(args)...);
        return (*static_cast<fn**>(thisptr))[index](thisptr, args...);
    }
    
    template<typename T = void*>
    constexpr T get(void* thisptr, std::size_t index)
    {
        return (*static_cast<T**>(thisptr))[index];
    }
}
Source: utils/vfunc/vfunc.h:4-18

Usage Example

// Call a virtual function directly
auto result = virtual_func::call<bool>(instance, 24, param1, param2);

// Get a virtual function pointer
auto fn_ptr = virtual_func::get(g_interfaces.client, 24);
Virtual function indices are specific to each game version and may change with updates. Always verify indices when updating.

Detour Hooking with MinHook

Detour Hook Class

The SDK wraps MinHook functionality in a convenient class:
namespace detour
{
    class hook
    {
    public:
        hook() = default;
        explicit hook(void* func, void* detour) 
            : m_base_fn(func), m_replace_fn(detour) {};
        
        bool create(void* func, void* detour);
        bool replace();
        bool remove();
        bool restore();
        
        inline bool is_hooked() const;
        
        template<typename T>
        T get_original()
        {
            return static_cast<T>(m_original_fn);
        }
        
    private:
        bool m_hooked = false;
        void* m_base_fn = nullptr;
        void* m_replace_fn = nullptr;
        void* m_original_fn = nullptr;
    };
}
Source: utils/detour/detour.h:5-36

Creating Detours

The detour system follows this workflow:
1

Initialize MinHook

Call MH_Initialize() once at startup:
if (const auto status = MH_Initialize(); status != MH_OK) {
    console::print<console::log_level::FATAL>(
        _("Failed to initialize MinHook [ status: {} ]"),
        MH_StatusToString(status)
    );
    return false;
}
2

Create Hook

Use the create() method to set up the hook:
bool detour::hook::create(void* func, void* detour)
{
    m_base_fn = func;
    if (!m_base_fn)
        return false;
        
    m_replace_fn = detour;
    if (!m_replace_fn)
        return false;
        
    if (const auto status = MH_CreateHook(m_base_fn, m_replace_fn, &m_original_fn); 
        status != MH_OK) {
        console::print<console::log_level::FATAL>(
            _("detour::hook::create() failed [ status: {} | base: {:#08X} ]"),
            MH_StatusToString(status),
            reinterpret_cast<std::uintptr_t>(m_base_fn)
        );
        return false;
    }
    
    return replace();
}
Source: utils/detour/detour.cpp:5-41
3

Enable Hook

The replace() method activates the hook:
bool detour::hook::replace()
{
    if (!m_base_fn || m_hooked)
        return false;
        
    if (const auto status = MH_EnableHook(m_base_fn); status != MH_OK) {
        console::print<console::log_level::FATAL>(
            _("detour::hook::replace() failed [ status: {} ]"),
            MH_StatusToString(status)
        );
        return false;
    }
    
    m_hooked = true;
    return true;
}
Source: utils/detour/detour.cpp:43-61

Calling Original Functions

Always call the original function from your detour:
bool __fastcall hooks::detours::create_move(
    sdk::c_cs_player* ecx, 
    void*, 
    float input_sample_time, 
    sdk::c_user_cmd* cmd
)
{
    // Get original function
    static auto og = hooks::create_move.get_original<hooks::create_move_fn>();
    
    // Validate parameters
    if (!cmd || !cmd->command_number)
        return og(ecx, input_sample_time, cmd);
    
    // Store in context
    g_ctx.cmd = cmd;
    
    // Call original
    if (og(ecx, input_sample_time, cmd))
        ecx->set_local_view_angles(cmd->view_angles);
    
    // Custom logic
    g_aimbot.run();
    g_movement.movement_fix(cmd, g_prediction.backup_vars.view_angles);
    
    return false;
}
Source: hooks/hooks.cpp:46-82
Always store the original function in a static variable to avoid repeatedly calling get_original(), which can impact performance.

Hook Helper Template

For convenience, the SDK includes a template-based hook helper:
template<class T>
class hook_helper
{
private:
    void* source;
    void* original;
    
public:
    template<typename S, typename D>
    void create(S source, D destination, const char* name = _("undefined"))
    {
        if (MH_CreateHook(
            reinterpret_cast<void*>(source),
            reinterpret_cast<void*>(destination),
            &original
        ) != MH_OK)
            console::print<console::log_level::FATAL>(
                _("Failed to create hook {} - [ {} -> {} ]"),
                name,
                reinterpret_cast<void*>(source),
                reinterpret_cast<void*>(destination)
            );
            
        if (MH_EnableHook(reinterpret_cast<void*>(source)) != MH_OK)
            console::print<console::log_level::FATAL>(
                _("Failed to enable hook {}"),
                name
            );
            
        this->source = source;
        
        console::print<console::log_level::NORMAL>(
            _("Hooked {} - [ {} -> {} ]"),
            name,
            reinterpret_cast<void*>(source),
            reinterpret_cast<void*>(destination)
        );
    }
    
    T* get_original()
    {
        return reinterpret_cast<T*>(original);
    }
};
Source: globals/hooks/hooking.h:7-50

Common Hook Examples

The most important hook for input manipulation:
namespace hooks
{
    inline detour::hook create_move;
    using create_move_fn = bool(__thiscall*)(
        sdk::c_cs_player*, 
        float, 
        sdk::c_user_cmd*
    );
}

// Installation
HOOK(hooks::create_move.create(
    virtual_func::get(g_interfaces.client, 24),
    hooks::detours::create_move
));
Source: hooks/hooks.inl:7-11 and hooks/hooks.cpp:22

Hook Management

Global Hook Declarations

Declare hooks in the hooks namespace:
namespace hooks
{
    inline WNDPROC wnd_proc;
    
    // Detour hooks
    inline detour::hook create_move;
    inline detour::hook send_net_msg;
    
    // Function signatures
    using create_move_fn = bool(__thiscall*)(
        sdk::c_cs_player*,
        float,
        sdk::c_user_cmd*
    );
}
Source: hooks/hooks.inl:2-12

Cleanup and Unloading

Properly cleanup hooks on unload:
void hooks::impl::unload()
{
    MOCKING_TRY;
    
    // Disable and remove all hooks
    MH_DisableHook(MH_ALL_HOOKS);
    MH_RemoveHook(MH_ALL_HOOKS);
    
    // Uninitialize MinHook
    MH_Uninitialize();
    
    // Restore WndProc
    LI_FN(SetWindowLongA)(
        hotwheels::window,
        GWL_WNDPROC,
        reinterpret_cast<LONG_PTR>(hooks::wnd_proc)
    );
    
    MOCKING_CATCH();
}
Source: hooks/hooks.cpp:30-44
Always restore hooks and cleanup MinHook before unloading the DLL to prevent crashes.

Best Practices

Always define type-safe function signatures:
// Good - type safe
using create_move_fn = bool(__thiscall*)(
    sdk::c_cs_player*,
    float,
    sdk::c_user_cmd*
);
auto og = hook.get_original<create_move_fn>();

// Bad - error prone
auto og = (void*)hook.get_original();
Cache original function pointers in static variables:
bool __fastcall hooked_function()
{
    // Good - called once
    static auto og = hook.get_original<fn_type>();
    
    // Bad - called every time
    auto og = hook.get_original<fn_type>();
}
Always check hook creation status:
if (!hooks::create_move.create(target, detour)) {
    console::print<console::log_level::FATAL>(
        "Failed to create hook"
    );
    return false;
}
Match calling conventions exactly:
  • __thiscall for member functions
  • __stdcall for WinAPI functions
  • __fastcall for optimized functions
  • __cdecl for C-style functions

Troubleshooting

Symptoms: Immediate crash when hook is calledSolutions:
  • Verify function signature matches exactly
  • Check calling convention (__thiscall, __stdcall, etc.)
  • Ensure original function is being called
  • Validate all parameters before use

Next Steps

Memory

Learn about pattern scanning and netvars

Architecture

Understand the overall SDK structure

Build docs developers (and LLMs) love