Skip to content

joshleaves/cheatscan

Repository files navigation

cheatscan

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/

Features

  • 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

Rust API

Core public types are exported from src/lib.rs:

  • Scanner
  • ScanValue
  • ValueType
  • ComparisonType
  • ScanError
  • Configuration
  • Endianness
  • Alignment

Configuration

use cheatscan::{Alignment, Configuration, Endianness, ValueType};

let config = Configuration {
  value_type: ValueType::U16,
  endianness: Endianness::Little,
  alignment: Alignment::Aligned,
  base_address: 0x8000,
};

Unknown Initial Value

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

Known Initial Value

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

Follow-up Scans

After initialization, the API distinguishes between:

  • scan(...): consumes a new RAM block
  • scan_again(...): refines results on the current RAM block

scan(...)

Use scan(...) when you have a new snapshot of memory:

  • supports exact value comparisons
  • supports PreviousValue comparisons (compares against the previous block)
scanner.scan(&next_block, ComparisonType::Gt, ScanValue::PreviousValue)?;

scan_again(...)

Use scan_again(...) to apply additional filters without providing a new RAM block:

  • reuses the last scanned block
  • supports only exact value comparisons
  • ScanValue::PreviousValue is 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

Example

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

Results

  • count() returns the current number of candidates
  • results() returns materialized result addresses, already offset by base_address

Before the first filtering pass:

  • count() reports the implicit candidate count
  • results() is empty because no concrete result set has been materialized yet

Errors

Relevant scanner errors include:

  • TypeMismatch: scan value does not match value_type
  • InvalidRamBlockLength: next RAM block length differs from the initial one
  • InitialScanValueRequired: new_from_known(...) was called with ScanValue::PreviousValue
  • RamBlockTooSmall: the RAM block cannot contain even one value of the configured type

See src/scanner/scan_error.rs for details.

C / FFI API

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.

Construction

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_u8
  • cheatscan_new_from_known_u16
  • cheatscan_new_from_known_u32
  • cheatscan_new_from_known_i8
  • cheatscan_new_from_known_i16
  • cheatscan_new_from_known_i32
  • cheatscan_new_from_known_f32

Scanning

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_u8
  • cheatscan_scan_u16
  • cheatscan_scan_u32
  • cheatscan_scan_i8
  • cheatscan_scan_i16
  • cheatscan_scan_i32
  • cheatscan_scan_f32

Each scan function returns 0 on success, or a ScanError code on failure.

Reading Results

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.

Lifetime

Destroy a scanner with:

void cheatscan_free(Scanner *scanner);

Build

Build the Rust crate:

cargo build

Run tests:

cargo test

Build release artifacts:

cargo build --release

The crate is currently configured to produce Rust library artifacts, and the repository also contains generated C headers in include/.

Project Layout

The scanner domain lives under src/scanner/:

The FFI layer lives in src/ffi.rs.

Current Status

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.

License

This project is licensed under the MIT License.

See the LICENSE file for details.

About

A lightweight memory scanning library for Rust, C, and WASM. Implements classic cheat-engine style workflows with a stable, low-level FFI and zero-allocation scan passes.

Topics

Resources

License

Stars

Watchers

Forks

Contributors