cheatscan is a small Rust library for memory scanning.
It is designed around the classic cheat-search workflow:
- start from an initial RAM block
- scan for an exact value, or compare against the previous value
- keep narrowing the candidate addresses over multiple scans
use cheatscan::{ComparisonType, Configuration, ScanValue, Scanner};
let mut scanner = Scanner::new_from_unknown(config, &initial_block)?;
scanner.scan(&next_block, ComparisonType::Gt, ScanValue::PreviousValue)?;
println!("{} candidates", scanner.count());The crate currently exposes:
- a Rust API
- a C-compatible FFI with generated headers in
include/
- Supports multiple types:
u8,u16,u32,i8,i16,i32,f32 - Little-endian and big-endian reads
- Aligned and unaligned scans
- Exact-value scans
- Previous-value scans
- Incremental narrowing of candidate addresses
- Allocations are limited to initialization and result storage
- Works in native (Rust/C) and WASM environments
Core public types are exported from src/lib.rs:
ScannerScanValueValueTypeComparisonTypeScanErrorConfigurationEndiannessAlignment
use cheatscan::{Alignment, Configuration, Endianness, ValueType};
let config = Configuration {
value_type: ValueType::U16,
endianness: Endianness::Little,
alignment: Alignment::Aligned,
base_address: 0x8000,
};Use Scanner::new_from_unknown(...) when the first snapshot is only used as the baseline.
use cheatscan::{ComparisonType, Configuration, ScanValue, Scanner};
let initial_block = [10_u8, 20, 30, 40];
let next_block = [10_u8, 19, 31, 40];
let mut scanner = Scanner::new_from_unknown(config, &initial_block)?;
scanner.scan(&next_block, ComparisonType::Gt, ScanValue::PreviousValue)?;
let results: Vec<u32> = scanner.results().collect();Use Scanner::new_from_known(...) when the first snapshot should already be filtered against a known value.
use cheatscan::{ComparisonType, Configuration, ScanValue, Scanner};
let initial_block = [1_u8, 0, 2, 0, 1, 0];
let scanner = Scanner::new_from_known(
config,
&initial_block,
ComparisonType::Eq,
ScanValue::U16(1),
)?;
let results: Vec<u32> = scanner.results().collect();After initialization, the API distinguishes between:
scan(...): consumes a new RAM blockscan_again(...): refines results on the current RAM block
Use scan(...) when you have a new snapshot of memory:
- supports exact value comparisons
- supports
PreviousValuecomparisons (compares against the previous block)
scanner.scan(&next_block, ComparisonType::Gt, ScanValue::PreviousValue)?;Use scan_again(...) to apply additional filters without providing a new RAM block:
- reuses the last scanned block
- supports only exact value comparisons
ScanValue::PreviousValueis not allowed
scanner.scan(&next_block, ComparisonType::Gt, ScanValue::PreviousValue)?;
scanner.scan_again(ComparisonType::Gt, ScanValue::U8(4))?;This allows composing multiple conditions on the same snapshot:
- first filter: compare against previous value
- subsequent filters: refine using exact values
scanner.scan(&next_block, ComparisonType::Gt, ScanValue::PreviousValue)?;
scanner.scan_again(ComparisonType::Gt, ScanValue::U8(4))?;
scanner.scan_again(ComparisonType::Lt, ScanValue::U8(100))?;This is equivalent to:
- value increased since last snapshot
- AND value > 4
- AND value < 100
count()returns the current number of candidatesresults()returns materialized result addresses, already offset bybase_address
Before the first filtering pass:
count()reports the implicit candidate countresults()is empty because no concrete result set has been materialized yet
Relevant scanner errors include:
TypeMismatch: scan value does not matchvalue_typeInvalidRamBlockLength: next RAM block length differs from the initial oneInitialScanValueRequired:new_from_known(...)was called withScanValue::PreviousValueRamBlockTooSmall: the RAM block cannot contain even one value of the configured type
See src/scanner/scan_error.rs for details.
The FFI layer is designed to be:
- ABI-stable
- WASM-friendly
- free of implicit struct layouts or hidden allocations
All functions use explicit scalar arguments and pointer/length pairs.
Unknown initial value:
Scanner *cheatscan_new_from_unknown(
uint8_t value_type,
uint8_t endianness,
uint8_t alignment,
uint32_t base_address,
const uint8_t *initial_block_ptr,
size_t initial_block_len,
uint8_t *out_error);Known initial value is exposed as typed constructors:
cheatscan_new_from_known_u8cheatscan_new_from_known_u16cheatscan_new_from_known_u32cheatscan_new_from_known_i8cheatscan_new_from_known_i16cheatscan_new_from_known_i32cheatscan_new_from_known_f32
Previous-value scan:
uint8_t cheatscan_scan_previous(
Scanner *scanner,
const uint8_t *next_block_ptr,
size_t next_block_len,
uint8_t cmp);Exact-value scans are typed:
cheatscan_scan_u8cheatscan_scan_u16cheatscan_scan_u32cheatscan_scan_i8cheatscan_scan_i16cheatscan_scan_i32cheatscan_scan_f32
Each scan function returns 0 on success, or a ScanError code on failure.
Candidate count:
uint32_t cheatscan_count(Scanner *scanner);Copy result addresses into a caller-provided buffer:
size_t cheatscan_write_results(
Scanner *scanner,
uint32_t *out_results_ptr,
size_t out_results_len,
size_t offset);This lets you page through results without exposing raw internal pointers.
Destroy a scanner with:
void cheatscan_free(Scanner *scanner);Build the Rust crate:
cargo buildRun tests:
cargo testBuild release artifacts:
cargo build --releaseThe crate is currently configured to produce Rust library artifacts, and the repository also contains generated C headers in include/.
The scanner domain lives under src/scanner/:
mod.rs: public facadescanner.rs:Scannercomparison.rs: comparison operatorsscan_error.rs: scanner errorsvalue_reader.rs: typed byte readersvalue_type.rs: supported primitive types
The FFI layer lives in src/ffi.rs.
cheatscan is currently focused on stabilizing the core scanning API.
The Rust API and the C-facing header are the primary surfaces for now. Higher-level bindings can be added later once the core API is considered stable.
This project is licensed under the MIT License.
See the LICENSE file for details.