Overview
The HotWheels SDK provides comprehensive memory manipulation utilities for:
Pattern Scanning Find functions and data in game modules
Netvars Access network variables and offsets
Address Helpers Navigate and manipulate memory addresses
Module System
Module Class
The module system provides utilities for working with game DLLs:
namespace modules
{
class impl
{
public:
impl () = default ;
explicit impl ( const char* mod_name )
{
module_name = mod_name;
module_handle = get_module_handle_safe (mod_name);
};
address pattern_scan ( const char* sig );
template < class T >
T find_interface ( const char* interface_name );
constexpr HMODULE get_module_handle_safe ( const char* name );
HMODULE module_handle{};
private:
const char * module_name{};
std :: uintptr_t find_pattern (
std :: uint8_t* region_start ,
std :: uintptr_t region_size ,
const char* pattern
);
std :: vector < int > pattern_to_bytes ( const char* pattern );
};
}
Source: utils/modules/modules.h:16-43
Global Module Instances
Pre-initialized module instances for common game DLLs:
inline modules ::impl g_client_dll = modules :: impl (CLIENT_DLL);
inline modules ::impl g_engine_dll = modules :: impl (ENGINE_DLL);
inline modules ::impl g_localize_dll = modules :: impl (LOCALIZE_DLL);
inline modules ::impl g_shaderapidx9_dll = modules :: impl (SHADERAPIDX9_DLL);
inline modules ::impl g_vstdlib_dll = modules :: impl (VSTDLIB_DLL);
inline modules ::impl g_server_dll = modules :: impl (SERVER_DLL);
inline modules ::impl g_studio_render_dll = modules :: impl (STUDIORENDER_DLL);
inline modules ::impl g_material_system_dll = modules :: impl (MATERIALSYSTEM_DLL);
inline modules ::impl g_vgui_dll = modules :: impl (VGUI_DLL);
inline modules ::impl g_vgui2_dll = modules :: impl (VGUI2_DLL);
inline modules ::impl g_game_overlay_renderer_dll = modules :: impl (GAMEOVERLAYRENDERER_DLL);
inline modules ::impl g_physics_dll = modules :: impl (PHYSICS_DLL);
inline modules ::impl g_tier0_dll = modules :: impl (TIER0_DLL);
inline modules ::impl g_input_system_dll = modules :: impl (INPUTSYSTEM_DLL);
inline modules ::impl g_data_cache_dll = modules :: impl (DATACACHE_DLL);
inline modules ::impl g_steam_api_dll = modules :: impl (STEAM_API_DLL);
inline modules ::impl g_matchmaking_dll = modules :: impl (MATCHMAKING_DLL);
inline modules ::impl g_file_system_stdio_dll = modules :: impl (FILESYSTEM_DLL);
Source: utils/modules/modules.h:47-64
These global instances are automatically initialized with their respective module handles, making them ready to use immediately.
Pattern Scanning
How Pattern Scanning Works
Pattern scanning searches for byte sequences in memory to locate functions or data:
Pattern Conversion
Convert the pattern string to bytes: std :: vector < int > modules :: impl :: pattern_to_bytes ( const char* pattern )
{
std ::vector < int > vec_bytes = {};
char * start = const_cast < char *> (pattern);
char * end = start + strlen (pattern);
for ( char * current = start; current < end; ++ current) {
if ( * current == '?' ) {
++ current;
if ( * current == '?' )
++ current;
vec_bytes . push_back ( - 1 ); // Wildcard
} else {
// Convert byte to hex
vec_bytes . push_back ( strtoul (current, & current, 16 ));
}
}
return vec_bytes;
}
Source: utils/modules/modules.cpp:35-55
Memory Scanning
Search through the module’s memory: std :: uintptr_t modules :: impl :: find_pattern (
std :: uint8_t* region_start ,
std :: uintptr_t region_size ,
const char* pattern
)
{
std ::vector < int > vec_bytes = pattern_to_bytes (pattern);
for ( unsigned long i = 0 UL ; i < region_size - vec_bytes . size (); ++ i) {
bool byte_found = true ;
for ( unsigned long s = 0 UL ; s < vec_bytes . size (); ++ s) {
if ( region_start [i + s] != vec_bytes [s] && vec_bytes [s] != - 1 ) {
byte_found = false ;
break ;
}
}
if (byte_found)
return reinterpret_cast < std :: uintptr_t > ( & region_start [i]);
}
console :: print < console :: log_level :: FATAL >(
_ ( "Find pattern failed - [ {} ]" ),
pattern
);
return 0 U ;
}
Source: utils/modules/modules.cpp:16-33
Address Retrieval
Return the found address: address modules :: impl :: pattern_scan ( const char* sig )
{
if ( ! module_handle)
console :: print < console :: log_level :: FATAL >(
_ ( "Failed to get handle - [ {} ]" ),
module_name
);
const auto module_address = reinterpret_cast < std :: uint8_t *> (module_handle);
const auto dos_header = reinterpret_cast < PIMAGE_DOS_HEADER > (module_handle);
const auto nt_headers = reinterpret_cast < PIMAGE_NT_HEADERS > (
module_address + dos_header -> e_lfanew
);
const std :: uintptr_t offset = find_pattern (
module_address,
nt_headers -> OptionalHeader . SizeOfImage ,
sig
);
return offset;
}
Source: utils/modules/modules.cpp:3-14
Pattern Syntax
Patterns use hexadecimal bytes with wildcards:
Exact Bytes
With Wildcards
Practical Example
// Find exact sequence
auto addr = g_client_dll . pattern_scan ( "55 8B EC 56 8B F1" );
Source: hooks/hooks.inl:34-35
Pattern scanning can fail if the game is updated. Always verify patterns after game updates and handle failed scans gracefully.
Address Helper Class
Address Wrapper
The address class provides convenient memory navigation:
struct address {
protected:
std :: uintptr_t m_address{};
public:
constexpr address () : m_address { 0 } {};
address ( std :: uintptr_t ad ) : m_address {ad} {};
address ( void* address ) : m_address { reinterpret_cast < std :: uintptr_t > (address)} {};
operator std :: uintptr_t () const { return m_address; }
operator void* () const { return reinterpret_cast < void *> (m_address); }
template < typename T = address>
T to () const { return * (T * )(m_address); }
template < typename T = address>
T as () const { return m_address ? (T)(m_address) : T (); }
template < typename T = address>
T at ( std :: ptrdiff_t offset ) const { return m_address ? * (T * )(m_address + offset) : T (); }
template < typename T = address>
T add ( std :: ptrdiff_t offset ) const { return m_address ? (T)(m_address + offset) : T (); }
template < typename T = address>
T sub ( std :: ptrdiff_t offset ) const { return m_address ? (T)(m_address - offset) : T (); }
template < typename T = address>
T get ( std :: uint8_t dereferences = 1 ) const ;
template < typename T = std :: uintptr_t >
void set ( const T & value );
template < typename T = address>
T relative ( std :: ptrdiff_t offset = 0x 1 ) const ;
};
Source: utils/modules/address/address.h:6-95
Address Methods
Casting
Navigation
Dereferencing
Relative Addresses
Convert addresses to different types: auto addr = g_client_dll . pattern_scan ( "55 8B EC" );
// Cast to pointer
void * ptr = addr . as < void *> ();
// Cast to function pointer
using fn_t = void ( * )();
auto fn = addr . as < fn_t > ();
// Dereference and cast
int value = addr . to < int > ();
Navigate through memory: auto addr = g_client_dll . pattern_scan ( "55 8B EC" );
// Add offset
auto next = addr . add ( 0x 10 );
// Subtract offset
auto prev = addr . sub ( 0x 8 );
// Read at offset
int value = addr . at < int > ( 0x 4 );
Follow pointer chains: auto addr = g_client_dll . pattern_scan ( "A1 ?? ?? ?? ??" );
// Dereference once
auto ptr1 = addr . get < address > ( 1 );
// Dereference multiple times
auto ptr2 = addr . get < address > ( 2 );
// Automatic dereferencing
int value = addr . get < int > ();
Implementation: template < typename T >
T get ( std :: uint8_t dereferences = 1 ) const
{
if ( ! m_address)
return T ();
auto addr = m_address;
while (dereferences -- )
if (addr)
addr = * reinterpret_cast < std :: uintptr_t *> (addr);
return (T)(addr);
}
Source: utils/modules/address/address.h:58-70 Resolve relative jumps and calls: // For instructions like: E8 ?? ?? ?? ?? (call relative)
auto addr = g_client_dll . pattern_scan ( "E8 ?? ?? ?? ??" );
// Resolve relative offset (skip opcode, read offset, add to position)
auto target = addr . relative ( 0x 1 ); // Skip 1 byte (E8 opcode)
Implementation: template < typename T >
T relative ( std :: ptrdiff_t offset = 0x 1 ) const
{
if ( ! m_address)
return T ();
const std :: uintptr_t new_address = m_address + offset;
const auto relative_offset = * reinterpret_cast < std :: int32_t *> (new_address);
if ( ! relative_offset)
return T ();
return (T)(new_address + sizeof ( std :: int32_t ) + relative_offset);
}
Source: utils/modules/address/address.h:82-94
Network Variables (Netvars)
What are Netvars?
Netvars are network-synchronized variables used by Source Engine to replicate entity data between server and client.
Netvar System
namespace netvar
{
std :: uintptr_t get_offset ( sdk :: recv_table * table , const char* var );
std :: uintptr_t get_table ( const char* table , const char* var );
std :: uintptr_t get_table ( sdk :: data_map * table , const char* var );
}
Source: utils/netvar/netvar.h:13-21
Finding Offsets
Recursive Table Search
Search through receive tables recursively: std :: uintptr_t netvar :: get_offset ( sdk :: recv_table * table , const char* var )
{
for ( std :: int32_t i = 0 ; i < table -> props ; i ++ ) {
auto current_prop = table -> props_ptr [i];
// Check if this is the property we want
if ( HASH (var) == HASH ( current_prop . var_name ))
return current_prop . offset ;
// Recursively search sub-tables
if ( auto data_table = current_prop . data_table ) {
std :: uintptr_t offset = get_offset (data_table, var);
if (offset)
return offset + current_prop . offset ;
}
}
return std :: uintptr_t ();
}
Source: utils/netvar/netvar.cpp:13-30
Find Table by Name
Locate the correct receive table: std :: uintptr_t netvar :: get_table ( const char* table , const char* var )
{
auto classes = g_interfaces . client -> get_all_classes ();
for ( auto current_table = classes; current_table; current_table = current_table -> next ) {
if ( HASH (table) == HASH ( current_table -> network_name ))
return get_offset ( current_table -> recv_table , var);
}
return std :: uintptr_t ();
}
Source: utils/netvar/netvar.cpp:32-42
Usage Examples
Basic Netvar
Macro Helper
Nested Properties
// Get offset for player health
auto health_offset = netvar :: get_table ( "DT_BasePlayer" , "m_iHealth" );
// Access health
int health = * reinterpret_cast < int *> (
reinterpret_cast < uintptr_t > (player) + health_offset
);
Netvars are cached on first access. The offset lookup only happens once, then the result is stored in a static variable.
Safe Module Loading
Module Handle Acquisition
The SDK includes a safe module handle getter that waits for the module to load:
constexpr HMODULE modules :: impl :: get_module_handle_safe ( const char* name )
{
HMODULE return_module{};
while ( ! return_module) {
return_module = LI_FN (GetModuleHandleA)(name);
utils :: sleep ( 1 );
}
return return_module;
}
Source: utils/modules/modules.cpp:56-66
This function will block until the module is loaded. Use it during initialization, not in time-sensitive code.
Best Practices
Always verify patterns are unique and won’t match multiple locations: // Good - specific and unique
auto addr = g_client_dll . pattern_scan (
"55 8B EC 83 E4 F8 83 EC 18 56 57 8B F9"
);
// Bad - too short, might match multiple places
auto addr = g_client_dll . pattern_scan ( "55 8B EC" );
Handle failed pattern scans: auto addr = g_client_dll . pattern_scan ( "55 8B EC ..." );
if ( ! addr) {
console :: print < console :: log_level :: FATAL >(
"Failed to find pattern"
);
return false ;
}
Cache netvar offsets in static variables: int& get_health ()
{
static auto offset = netvar :: get_table ( "DT_BasePlayer" , "m_iHealth" );
return * reinterpret_cast < int *> (
reinterpret_cast < uintptr_t > ( this ) + offset
);
}
Always validate addresses before use: auto addr = g_client_dll . pattern_scan ( "..." );
if (addr && addr . as < void *> ()) {
// Safe to use
auto fn = addr . as < function_t > ();
}
Common Patterns
Finding Interfaces
Interfaces can be found using the module’s interface list:
namespace modules
{
struct interface_node {
public:
void* (* get )();
const char * name;
interface_node * next;
};
template < class T >
T find_interface ( const char* interface_name );
}
Source: utils/modules/modules.h:9-29
Memory Reading Pattern
Read Value
Follow Pointer
Resolve Call
// Find and read a value
auto addr = g_client_dll . pattern_scan ( "A1 ?? ?? ?? ??" );
auto value = addr . add ( 1 ). to < int > (); // Skip opcode, read value
Troubleshooting
Pattern Not Found
Wrong Offset
Access Violation
Symptoms: Pattern scan returns null or crashesSolutions:
Verify pattern is correct for your game version
Check if pattern is too short (needs to be unique)
Use a disassembler to find the correct pattern
Add more context bytes to make pattern unique
Symptoms: Netvar returns incorrect dataSolutions:
Verify table name is correct (“DT_CSPlayer”, etc.)
Check variable name matches exactly
Ensure game hasn’t been updated (offsets change)
Use a netvar dumper to verify offsets
Symptoms: Crash when reading/writing memorySolutions:
Validate address is not null
Check if module is loaded
Ensure offset calculation is correct
Verify you’re not accessing freed memory
Next Steps
Hooking Learn how to use found addresses for hooking
Architecture Understand how memory utilities fit in the SDK