[Memhack] Scanner - Redacted-Rice/ITB-RedactedRiceExts GitHub Wiki
Scanner
The Scanner is memhack's memory scanning system, similar to memedit's scanner or Cheat Engine. It allows you to search for specific values in the game's memory, filter results, and track down memory addresses.
The scanner was optimized to minimize scan time so it is suitable for use in game as long as it is not called too frequently. Scans tend to take between 100-200 ms on my device.
Important: Direct access via Memhack structs is always preferred over scanning. Scanning is meant to be a debugging and reverse engineering tool or a last resort if you cannot reverse engineer a stable pointer chain.
Overview
The scanner works by:
- First Scan - Searches all readable memory for values matching your criteria
- Rescan - Filters previous results based on new criteria (value changed, increased, etc.)
- Iteration - Repeat rescans until you find the specific address you're looking for
Common use cases:
- Finding memory addresses for unknown game data
- Reverse engineering memory structures
- Discovering field offsets in structs
- Verifying struct layouts
Table of Contents
- Creating Scanners
- Scanning Operations
- Working with Results
- Scanner Management
- Advanced Features
- Examples
Creating Scanners
Constructor
Scannermemhack.dll.scanner.new(dataType, options)
| Argument name | Type | Description |
|---|---|---|
dataType |
string | Type of data to scan for (see Data Types) |
options |
table | Optional configuration table |
Creates a new scanner for the specified data type.
Data Types
| Type | Description | Value Example |
|---|---|---|
"byte" |
8-bit unsigned integer (0-255) | 42 |
"int" |
32-bit signed integer | 1000 |
"float" |
32-bit floating point | 3.14 |
"double" |
64-bit floating point | 3.14159265359 |
"bool" |
Boolean (1 byte) | true or false |
"string" |
Null-terminated string | "Hello" |
"byte_array" |
Array of bytes | "\x01\x02\x03" |
"struct" |
Complex structure (see Struct Scanner) | StructSearch object |
Scan Options
The options table supports:
| Option | Type | Default | Description |
|---|---|---|---|
maxResults |
int | 100000 | Maximum number of results to store |
alignment |
int | 0 | Memory alignment requirement (0 = no requirement, 4 = must be 4-byte aligned) |
checkTiming |
bool | false | If true, logs timing information for scan operations |
Example:
-- Create scanner with default options
local scanner = memhack.dll.scanner.new("int")
-- Create scanner with custom options
local scanner = memhack.dll.scanner.new("float", {
maxResults = 10000,
alignment = 4,
checkTiming = true
})
Scanning Operations
First Scan
tablescanner:firstScan(scanType, value)
| Argument name | Type | Description |
|---|---|---|
scanType |
string | Type of scan (see Scan Types) |
value |
varies | Value to scan for (type must match scanner's dataType) |
Performs the initial scan of all readable memory. Returns a table with:
resultCount- Number of matching addresses foundmaxResultsReached- Boolean indicating if the max result limit was reached
Example:
local scanner = memhack.dll.scanner.new("int")
local result = scanner:firstScan("exact", 100)
LOG("Found " .. result.resultCount .. " matches")
if result.maxResultsReached then
LOG("WARNING: Max results reached, scan may be incomplete")
end
Rescan
tablescanner:rescan(scanType, value)
| Argument name | Type | Description |
|---|---|---|
scanType |
string | Type of scan (see Scan Types) |
value |
varies | Value to compare against (type must match scanner's dataType) |
Filters previous results based on new criteria. Returns a table with:
resultCount- Number of matches remaining after filter
Example:
-- First scan for exact value
scanner:firstScan("exact", 100)
-- Change the value in-game, then rescan
scanner:rescan("increased", 100) -- Find addresses that increased
-- Change again
scanner:rescan("exact", 150) -- Find addresses now exactly 150
Scan Types
| Scan Type | Description | First Scan | Rescan | Requires Value |
|---|---|---|---|---|
"exact" |
Find addresses with exact value | ✓ | ✓ | Yes |
"not" |
Find addresses NOT matching value | ✓ | ✓ | Yes |
"increased" |
Value increased since last scan | ✗ | ✓ | No (value ignored) |
"decreased" |
Value decreased since last scan | ✗ | ✓ | No (value ignored) |
"changed" |
Value changed since last scan | ✗ | ✓ | No (value ignored) |
"unchanged" |
Value unchanged since last scan | ✗ | ✓ | No (value ignored) |
Note: increased, decreased, changed, and unchanged can only be used in rescans (they require a previous scan for comparison).
Note: increased, decreased can only be used for numeric type scans. They are not supported for string, byte_array, or struct.
Example:
-- Find all addresses with value 100
scanner:firstScan("exact", 100)
-- Find addresses that increased (new value doesn't matter for this scan type)
scanner:rescan("increased", 0) -- value parameter ignored but required
-- Find addresses that are now exactly 150
scanner:rescan("exact", 150)
Working with Results
Getting Results
tablescanner:getResults(options)
| Argument name | Type | Description |
|---|---|---|
options |
table | Optional configuration for pagination and value reading |
Options table:
| Option | Type | Default | Description |
|---|---|---|---|
offset |
int | 0 | Starting index for pagination (0-based) |
limit |
int | 1000 | Maximum number of results to return |
readValues |
bool | false | If true, reads current value at each address |
Returns a table with:
results- Array of result objects (1-indexed)totalCount- Total number of results availableoffset- Offset used for this pagelimit- Limit used for this page
Each result object contains:
address- Memory address (int)value- Current value at address (only ifreadValueswas true)
Example:
-- Get first 100 results without reading values
local results = scanner:getResults({ offset = 0, limit = 100 })
LOG("Showing " .. #results.results .. " of " .. results.totalCount .. " results")
for i, result in ipairs(results.results) do
LOG(string.format("Address: 0x%X", result.address))
end
-- Get first 10 results with current values
local results = scanner:getResults({ offset = 0, limit = 10, readValues = true })
for i, result in ipairs(results.results) do
LOG(string.format("Address: 0x%X = %s", result.address, tostring(result.value)))
end
Result Count
intscanner:getResultCount()
Returns the total number of results currently in the scanner.
Example:
local count = scanner:getResultCount()
LOG("Current results: " .. count)
Scanner Management
Reset
voidscanner:reset()
Resets the scanner to its initial state, clearing all results. Call this before starting a new scanning session.
Example:
scanner:reset()
LOG("Scanner reset, ready for new scan")
Advanced Features
Struct Scanner
The struct scanner allows searching for complex memory structures with multiple fields at specific offsets.
The struct scanner keys on a specific byte as an optimization. This does NOT need to be the first byte in the struct. You should choose a value that is most unique. For example, if you are searching for a pilot, key on the first byte of the ID string "Pilot_Hotshot" or the "P" instead of the level "1". This is because "P"'s byte value is 0x70 which is much less common in memory than 0x01 would be.
Important: Choosing a "bad"/common value for the key can really slow down the scan.
-- Create a struct scanner
local scanner = memhack.dll.scanner.new("struct")
-- Define the structure to search for
local search = memhack.dll.scanner.StructSearch.new(0x64, 0x20) -- keyByte ("C" in name), keyOffset
-- Add fields at specific offsets
search:addField(0x04, "int", 7)
search:addField(0x10, "int", 1)
search:addField(0x20, "string", "Camila")
-- Scan for structures matching this pattern
scanner:firstScan("exact", search)
StructSearch
StructSearch is used to define structure patterns for the struct scanner.
StructSearch.new
StructSearchmemhack.dll.scanner.StructSearch.new(keyByte, keyOffset)
| Argument name | Type | Description |
|---|---|---|
keyByte |
int or string | Byte value to identify structs (0-255, single char, or hex string like "0x64") |
keyOffset |
int | Offset of the key byte within the struct (default: 0) |
Creates a new structure search definition.
addField
voidsearch:addField(offset, dataType, value)
| Argument name | Type | Description |
|---|---|---|
offset |
int or string | Byte offset of field (int or hex string like "0x10") |
dataType |
string | Data type (same as scanner data types except "struct") |
value |
varies | Expected value at this offset |
Adds a field constraint to the structure search.
Example:
local search = memhack.dll.scanner.StructSearch.new(0x42, 0)
search:addField(0x04, "int", 1000)
search:addField(0x08, "bool", true)
search:addField("0x10", "string", "Ralph") -- offset as hex string
Examples
Scanning for Strings
-- Scan for pilot name
local scanner = memhack.dll.scanner.new("string")
scanner:firstScan("exact", "Camila")
local results = scanner:getResults({ limit = 5, readValues = true })
LOG("Found " .. results.totalCount .. " addresses with 'Camila'")
for i, result in ipairs(results.results) do
LOG(string.format("Address: 0x%X, Value: %s", result.address, result.value))
end
Tracking Value Changes
local scanner = memhack.dll.scanner.new("int")
-- Find all values that are currently 0
scanner:firstScan("exact", 0)
LOG("Found " .. scanner:getResultCount() .. " addresses with value 0")
-- Wait for something to change in game...
scanner:rescan("changed", 0) -- Value parameter ignored for "changed"
LOG("After change: " .. scanner:getResultCount() .. " addresses changed")
Finding a Struct
-- Define a pilot structure pattern
local search = memhack.dll.scanner.StructSearch.new(0xD4, 0) -- vtable byte at offset 0
-- Add known field values
search:addField(0x30, "int", 50) -- XP = 50
search:addField(0x38, "int", 2) -- Level = 2
search:addField(0x54, "int", 1) -- Previous timelines = 1
-- Scan for matching structures
local scanner = memhack.dll.scanner.new("struct")
scanner:firstScan("exact", search)
LOG("Found " .. scanner:getResultCount() .. " matching structs")
-- Get addresses
local results = scanner:getResults({ limit = 5 })
for i, result in ipairs(results.results) do
LOG(string.format("Struct at: 0x%X", result.address))
-- Wrap as Pilot struct to verify
local pilot = memhack.structs.Pilot.new(result.address, true)
if pilot then
LOG(" Name: " .. pilot:getNameStr())
end
end
Performance Notes
- First scans search all readable memory and can take several seconds
- Use
maxResultsto limit memory usage and improve performance - Use
alignmentfor data types that must be aligned (e.g., 4 for ints on some systems) - Rescans are much faster as they only check previous results
- The scanner uses parallel processing (multi-threaded) automatically
Safety Notes
- Scanning is read-only and safe (won't modify memory)
- Results may point to invalid memory after game state changes
- Always validate addresses before using them with structs
- Scanner allocates memory from a special heap to avoid detecting previous/other scanner results
Constants
Access scan types and data types through constants:
-- Scan types
memhack.dll.scanner.SCAN_TYPE.EXACT -- "exact"
memhack.dll.scanner.SCAN_TYPE.NOT -- "not"
memhack.dll.scanner.SCAN_TYPE.INCREASED -- "increased"
memhack.dll.scanner.SCAN_TYPE.DECREASED -- "decreased"
memhack.dll.scanner.SCAN_TYPE.CHANGED -- "changed"
memhack.dll.scanner.SCAN_TYPE.UNCHANGED -- "unchanged"
-- Data types
memhack.dll.scanner.DATA_TYPE.BYTE -- "byte"
memhack.dll.scanner.DATA_TYPE.INT -- "int"
memhack.dll.scanner.DATA_TYPE.FLOAT -- "float"
memhack.dll.scanner.DATA_TYPE.DOUBLE -- "double"
memhack.dll.scanner.DATA_TYPE.BOOL -- "bool"
memhack.dll.scanner.DATA_TYPE.STRING -- "string"
memhack.dll.scanner.DATA_TYPE.BYTE_ARRAY -- "byte_array"
memhack.dll.scanner.DATA_TYPE.STRUCT -- "struct"
memhack.dll.scanner.DATA_TYPE.ITB_STRING -- "itb_string" (also accepts alias "itbstring")