OpenRemap Docs
← Overview

openremap scan-maps

Structural calibration map scanner. Finds monotonically-increasing 16-bit sequences (RPM/load breakpoints) and the 2D data tables that follow them without requiring manufacturer identification. Works on any binary regardless of ECU family.

Use this to:

  • Health-check a binary — genuine ECU files have hundreds or thousands of axes; encrypted or corrupt files have almost none.
  • Discover maps in unsupported ECUs — feed the offsets into WinOLS or ECM Titanium as starting points.
  • Explore a binary's structure before committing to extractor development.

Run openremap identify <file> first to see manufacturer and SW info; run this after to see the map structure.


Usage

openremap scan-maps <FILE> [OPTIONS]

Options

Option Default Description
--top, -n 20 Number of top-scoring tables to show.
--min-score, -s 0.85 Minimum table score in [0, 1]. Higher = fewer false positives.
--region, -r (calibration region) Restrict scanning to a byte range: 0xSTART-0xEND or START-END. Overrides the calibration-region default.
--whole-file off Scan the whole file instead of only the detected calibration region — shows tables outside it.
--json off Output as JSON instead of human-readable text.
--max-series-tables 16 Max consecutive shared-axis tables to probe after each anchor. Set to 1 to disable.
--show-series off Group tables sharing identical X/Y axes with indented └─ continuation rows.
--xrefs off Code-reference signal — disassemble the code regions (capstone) and give tables whose data is statically referenced by code a small score bonus plus a ⟶code marker / JSON xref evidence. Known families decode directly; unknown families fall through to the CPU-detection cascade (trial-decode, fork-isolated). Adds ~4 s per file on a 4 MB EDC17.

Calibration-region default

By default the scan is limited to the calibration region — the flash area the layout segmenter labels as calibration (sectors containing high-score tables). Junk tables that the scanner finds in code / erased / mixed sectors are hidden and counted (in JSON as tables_hidden); the human output notes them:

  304 table(s) outside the calibration region hidden — use --whole-file to scan the whole file.
  • --whole-file scans everything (code-sector junk included).
  • --region overrides the default entirely — an explicit range wins.
  • No calibration signal (small / synthetic / unfamiliar binaries) falls back to whole-file behaviour automatically — the default never hides tables where it cannot find a calibration region.

The layout estimate is structural inference (no manufacturer database), so it can be wrong on unusual binaries — the fallback and --whole-file / --region overrides keep that harmless: a wrong estimate only changes what the report shows, never recipe or patch output.

Shared-axis detection

Real ECUs often place multiple calibration tables (fuel, timing, boost, EGR) consecutively after a single pair of RPM×Load breakpoint axes:

[RPM axis][Load axis][Fuel table][Timing table][Boost table][EGR table]

The scanner detects the first table normally, then probes forward for additional blocks with identical dimensions (same cols, rows, cell width) sharing the same axes. Each block must pass the same scoring pipeline as the anchor — garbage, code, or mismatched-geometry data stops the series.

Use --max-series-tables 1 to disable this and report only the primary table per axis pair.


Code references (--xrefs)

The code-reference (xref) signal is a positive counterpart to the heuristic scoring: instead of only penalising suspicious content (e.g. address-looking axes), it rewards confirmed references.

With --xrefs, the code regions of the binary are disassembled with capstone and every byte offset that a real instruction statically references is collected. A table whose data block is referenced by code is almost certainly a genuine calibration map:

  • its score gets a small bonus (+0.06, capped at 1.0), so it ranks above unreferenced lookalikes;
  • it is marked ⟶code in the human listing;
  • JSON gains a top-level xrefs summary (status / arch / base_address / reference_count / …) and an xref evidence block on each table (referenced_by_code, data_refs, axis_refs, insns).

Safety contract:

  • Presence-only — a table is never demoted for lacking a reference (most ECU code reaches maps through base-register addressing that is not statically resolvable). Absence proves nothing.
  • Arch-gated — supported families only: EDC17 (TriCore movh.a+lea address materialisation) and Denso/Hitachi SuperH (absolute mov.l/mov.w). Everything else skips silently.
  • Load base auto-detected — ECU code references physical addresses (EDC17 calibrations live at 0x80000000+) while the file is a linear image; the base is inferred per file from table-data hits, no hardcoded manufacturer knowledge.

Cost: ~4 s per file on a 4 MB EDC17 (capstone decode of ~2.6 MB of code) — opt-in for batch scans.


Scoring

Each 2D table is scored on a multi-dimension heuristic:

  • Axis quality — how smoothly monotonic each axis is (no reversals, no gaps, reasonable step sizes for calibration breakpoints).
  • Table smoothness — row-to-row and column-to-column gradient continuity. Real calibration data is smooth; code and random data are not.
  • Dimensionality — plausible row×column dimensions for ECU maps (e.g. 16×16 is common; 2×400 is not).
  • Stripe penalty — repeated identical rows/columns (common in padding and lookup tables, absent in real maps).
Score Meaning
≥ 0.90 High confidence — smooth calibration surface, genuine axis values.
0.85–0.90 Plausible — likely a real calibration map; may have sharp transitions or clamp regions.
0.75–0.85 Mixed — some real maps, some encoded data. Use --show-series to inspect context.
< 0.75 Low confidence — mostly coincidental structures. Lower --min-score if exploring unsupported ECUs.

The default --min-score of 0.85 returns ~70% fewer tables than the old default of 0.75 while keeping >90% of genuine calibration maps. Lower it to 0.55 for exhaustive scanning; raise to 0.90 for the cleanest signal.


Example output

  original.bin

  ✓  Genuine calibration binary
  15,967 axes  •  1,985 tables  •  4,194,304 bytes

      Offset       Dim   Cells    Score      X Axis      Y Axis
  ──────────────────────────────────────────────────────────────
  0x000376F2     32×16  u16 LIT  0.977  0x00037692  0x376D2
  0x002214D4      32×6  u16 LIT  0.965  0x00221488  0x2214C8
  0x0002CBC8      16×8  u8 LIT  0.969  0x0002CB98  0x2CBB8
  0x00037BE2      32×8  u16 LIT  0.964  0x00037B92  0x37BD2
  0x00288B80     16×16  u16 LIT  0.960  0x00288B3E  0x288B5E

  … and 1980 more.  Use --top 1985 to see all, or --min-score 0.8 to filter.

Health signal

The first line after the filename is a quick health assessment:

Axes found Signal Meaning
≥ 1,000 ✓ Genuine calibration binary Expected for a real ECU.
100–999 ⚠ Few axes Possibly corrupted, trimmed, or a partial dump.
< 100 ✗ Very few axes Likely encrypted, non-ECU, or empty.

JSON output

openremap scan-maps ecu.bin --json --top 5
{
  "file": "ecu.bin",
  "file_size": 4194304,
  "axes_count": 15967,
  "tables_count": 1985,
  "layout_filtered": true,
  "tables_hidden": 304,
  "tables": [
    {
      "offset": 227058,
      "cols": 32,
      "rows": 16,
      "cell_width": 2,
      "byte_order": "little",
      "x_axis_offset": 226962,
      "y_axis_offset": 227026,
      "score": 0.977
    }
  ]
}

layout_filtered is true when the calibration-region default applied; tables_hidden counts tables outside it. With --whole-file both are false / 0. Axes are capped at 200 in JSON output to keep the payload reasonable.


Notes

  • The scanner is structural, not semantic — it looks for byte patterns that look like calibration axes and tables. It does not know whether a found table is fuel, timing, boost, or EGR. You interpret what you find.
  • The scanner only detects consecutive [X axis][Y axis][data] layouts. Some ECUs share one axis across multiple data blocks; these shared-axis layouts are not currently detected.
  • Runtime is ~0.5–3 seconds for a typical 1–4 MB binary.
  • Offsets in JSON output are always absolute file offsets, regardless of --region.

Command Reference
openremap identify → identify.md — manufacturer and SW identification
openremap cook → cook.md — diff and build a tuning recipe