Making Subaru ECUs slightly less mysterious, one ROM at a time.
Cute mascot. Serious hex dumps.
SubuwuTuner is an open-source research and tooling project for Subaru ECUs. The work is split across several adjacent tracks:
- ROM definition development β TOML schema for documenting calibration tables, axes, scaling, units, and policy flags
- ECU communication research β SSM (K-line + CAN) and UDS protocol clients, SecurityAccess seed/key recovery, definition-driven map reads
- CAN protocol analysis β replay, DBC decode, baseline modeling, change detection,
.cdbbundle format - Logging and diagnostics tooling β multi-sink datalogger, live gauge cluster, CSV export
- Flashing infrastructure research β flash orchestrator, manifest + journal, backup store, per-ISA brick-protection model, security-access variants
- Reverse engineering and documentation β bench rig with junkyard ECUs, written handoffs between analyst and implementer sessions, design docs numbered
docs/00throughdocs/44
The project is C++23 throughout, Apache 2.0 licensed, headless-first (every GUI capability is reachable from the subuwutuner-cli), and cross-platform from day one.
A name like SubuwuTuner is a deliberate choice. The codebase is not.
Listed honestly. Working means runs end-to-end today. Experimental means the code path is real but real-world coverage is thin. Research in progress means active investigation. Planned means not started or sketched only.
- ROM viewer + diff β raw binary load, CRC32 / sector hashing, structured per-cell diff, JSON envelope for CI scripting (
rom-info,dump-table,dump-axis,rom-diff,diff) - Definition pack loader β TOML schema with
extendsinheritance, validation, hygiene linting (pack-info,pack-lint) - Definition generation pipeline β
tools/defgen/(Python) lifts factual data from public XML schemas into the TOML pack format .stuneproject model β directory-backed, diffable,working.bin+source.bin+edits.tomlwith audit history (project-new,project-info,project-validate,project-clone)- Table editor with full undo/redo β selection, paste, smooth, interpolate, percent-scale, bulk CSV import/export; every operation goes through
edit::History - Jurisdiction profile system β first-run profile (
motorsport-only,alberta-ca,eu-roadworthy,california-us) drives edit-time warnings on emissions-flagged tables - DTC bitmap toggles β
[[dtc_bitmap]]schema in packs; toggles route through the history layer so they undo cleanly - Auto-tune kernels β MAF scaling kernel and knock-pull ignition kernel, both with engine-safety linting on by default; previewable as
edit::Historyproposals before commit - Custom-features designer β visual node-graph editor (
.stmodfiles), graph linter, IR lowering, SH-2A and RH850 backends at 22-primitive parity - CAN reverse-engineering toolkit β replay, DBC parse/emit/decode,
BaselineModel+ChangeDetector,.cdbbundle (can-replay,can-diff,can-discover,can-decode,can-export-dbc) - Datalogger pipeline β
LiveBufferSPSC ring,LogSessionmulti-sink fan-out,CsvSinkfor downstream tooling - Transport stack β
ITransportabstraction with J2534 v04.04, OBDX Pro VX (DVI codec), native handheld (custom framed codec), andMockTransportfor tests - ECU protocol clients β SSM (K-line + ISO-TP CAN), UDS (DSC, SA, RDBI/WDBI, RMBA/WMBA, RequestDownload, TransferData, RequestTransferExit, SubaruBulkTransfer)
- SecurityAccess recovery β Subaru SSMCAN1 family: factory 16-round Feistel + aftermarket L1 + L3 variants, CLI-selectable via
--sa-variant - Audit log β CRC32-protected append-only log, wired across UDS + Flasher; tamper-evident, NDJSON export
- CLI β 70+ subcommands covering every domain capability; structured emitters offer
--jsonfor scripting - Cross-platform build β Win MSVC, MinGW-w64, Apple Clang (Intel + Apple Silicon), Linux GCC, Linux Clang (ASan + UBSan)
- Live datalogger over OBDX β sustained streaming is wired and bench-tested; the high-rate (50 Hz Γ 20 PIDs) sustained target needs more real-vehicle coverage
- Flash orchestrator β
FlashPlan+ manifest + journal + backup-store +FlashPreflightpolicy gate run end-to-end againstMockTransport; live-ECU validation is the active bench-rig work - Tune library + tuning-knowledge reference β local sortable catalog with delta-detection heuristics; the per-cell delta sign convention has a known issue queued for fix
- Phase D flash-protocol close-out β the
0xB6payload cipher is now byte-confirmed as the same four-round Feistel path on SH-2A and RH850. Safe FCU sequencing, durable array-flash proof, interruption behavior, and restore validation remain open; writes stay gated. - Per-ISA brick-protection HIL coverage β host-side recovery model documented for SH-2A (single-bank serial-boot) and RH850 (dual-bank atomic-swap); bench-rig HIL coverage is the remaining ship gate
- Patch insertion layer (
src/feature_patch/) β finds free RAM, writes hook tables, splices into existing vector tables; needs real ECU vector tables to develop against
- Checksum-repair algorithm implementations (the
IChecksumRepairseam is in place; concretesubaru_std/subaru_alt/subaru_alt2impls wait on byte-validation against known-stock dumps) - Installer + signed manifest auto-update channel (
docs/22-auto-update.md) - Documentation site (MkDocs or similar)
- Closed-loop trim integration for MAF auto-tune
- Boost-trim auto-tune kernel
- STI variants, EJ-era cars, AT variants, BRZ / 86 expansion
- Standalone Doc-18 master plan (PC-tethered handheld) β
docs/18-standalone-master-plan.md
| Family | v1.0 ship target | Bench-rig coverage | Notes |
|---|---|---|---|
| FA20DIT / SH-2A (VA 2015β2021 WRX MT) | yes | LF79002P junkyard ECU on bench | Primary v1.0 platform |
| FA24DIT / RH850 (VB 2022+ WRX MT) | yes | RH850 codegen at SH-2A parity; bench coverage pending hardware | Primary v1.0 platform |
| EJ-era WRX / STI / Forester | v1.x | β | Definition packs and flash routines after v1.0 |
| BRZ / 86 (FA20 / FA24 NA) | v1.x | β | Pack development tracked in docs/04-roadmap.md |
| Capability | Windows | macOS | Linux |
|---|---|---|---|
| GUI, CLI, project work, ROM viewer / editor | β | β | β |
TOML pack loader + defgen pipeline |
β | β | β |
| Auto-tune kernels (MAF + knock-pull) | β | β | β |
| Custom-features designer (SH-2A + RH850 codegen) | β | β | β |
CAN replay / DBC decode / .cdb discovery |
β | β | β |
| Datalogging via OBDX Pro VX (USB-CDC) | β | β | β |
| Flashing via OBDX Pro VX / native handheld | β | β | β |
| Flashing via J2534 adapters | β | Windows-only DLL | Windows-only DLL |
Live CAN bus capture (can-record --live) |
β | β | β |
J2534 v04.04 is a Windows-DLL API; the OBDX / native paths are the cross-platform substitute.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GUI (Dear ImGui) β
β CLI (subuwutuner-cli, 70+) β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββ
β Domain core (C++23, no UI) β
β β
β rom Β· defs Β· edit Β· project Β· profile Β· policy β
β autotune Β· feature Β· feature_codegen Β· diff β
β log Β· audit Β· library Β· config β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββ
β Hardware-touching layers β
β β
β transport ecu (ssm + uds + dit + sa) β
β can / dbc / discover β
β flash (orchestrator + manifest + journal) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
TOML pack format (docs/11-definition-format.md). Each table declares address, dimensions, axis layout, scaling, units, glossary text, and policy flags (safety, emissions). Packs inherit via extends; child packs override one field at a time without forking. pack-lint enforces hygiene in CI. defgen (Python, frozen as a per-platform PyInstaller binary in the release artifacts) lifts factual data from public ECU XML schemas into the TOML format.
st::transport::ITransport is the wire abstraction. Concrete implementations: J2534 (Windows DLL ABI), OBDX Pro VX (DVI codec over USB-CDC), native handheld (custom framed codec), MockTransport (tests). Protocol clients on top: st::ecu::ssm (K-line and CAN framing), st::ecu::uds (standard ISO 14229 services + Subaru bulk-transfer 0xB6), st::ecu::subaru_dit (DIT flash client). SecurityAccess is a SecurityKeyFn strategy: factory Feistel and aftermarket L1 / L3 variants live in src/ecu/src/subaru_security.cpp.
LiveBuffer is an SPSC ring; LogSession fans out to N sinks. CsvSink is the bundled implementation. The GUI's live gauge cluster reads from the same buffer as the file sink, so you can record-while-gauging without extra glue.
st::flash::Flasher::execute(plan, cancel) walks a FlashPlan sector by sector. Pre-write: full-ROM backup via BackupStore (CRC-gated; the orchestrator refuses to run without a verified backup), FlashPreflight policy checks (EcuIdMatch, VinMatch, BatteryVoltageOk, IgnitionState, ChecksumKnown, BackupStorePresent), per-sector erase/write/verify with retry budgets. The manifest + journal pair makes power-loss resume idempotent. Recovery is per-ISA (SH-2A serial-boot path, RH850 atomic-swap path); the model is in docs/31-brick-protection-by-isa.md.
Definition-driven map reads come through the Definition + Rom pairing: cell coordinates resolve via axes, scaling applies on read, units render in the editor. The .stune project format pins both the source ROM and the working ROM; every edit goes through edit::History and serializes to edits.toml (a TOML file you can diff, blame, and code-review).
tools/defgen/ walks a public ECU XML schema, extracts addresses + scalings + axis breakpoints (facts, not prose), and emits TOML packs that map to the SubuwuTuner schema. Per-platform mapping YAMLs handle the schema-evolution rules. The pipeline is fact-only by policy; OEM-authored description prose is not lifted.
code/
βββ src/ Domain core + tooling, organized by module
β βββ core/ Result, Error, units, span types
β βββ rom/ Raw ROM I/O, CRC32, sector hashes
β βββ defs/ TOML pack loader + Definition model
β βββ edit/ Edit ops + History (undo/redo)
β βββ project/ .stune directory model
β βββ policy/ FlashPreflight + jurisdiction gate
β βββ profile/ Jurisdiction profiles
β βββ transport/ ITransport + J2534 / OBDX / native / Mock
β βββ ecu/ SSM, UDS, DIT flash, SecurityAccess
β βββ audit/ Append-only audit log
β βββ log/ Datalogger pipeline + CSV sink
β βββ can/ CAN frame I/O, .asc handling
β βββ dbc/ DBC parser / emitter / decoder
β βββ discover/ CAN signal discovery + .cdb bundle
β βββ flash/ Flasher + FlashPlan + manifest + journal
β βββ autotune/ MAF + knock-pull kernels
β βββ feature/ Custom-features Graph + IR
β βββ feature_codegen/ SH-2A + RH850 backends
β βββ feature_patch/ Patch insertion layer (in progress)
β βββ diff/ Structured ROM diff
β βββ library/ Local tune library
β βββ ai/ Drift classifier + LLM explanations
β βββ ui/ Dear ImGui GUI (subuwutuner-gui)
β βββ cli/ subuwutuner-cli
βββ tools/
β βββ defgen/ Python definition-pack generator
βββ docs/ Design docs (00β44), getting-started, install
βββ tests/unit/ Catch2 tests, by module
βββ fixtures/
βββ demo-pack/ Synthetic always-available example pack
βββ demo.stune/ Demo project (exercisable from a fresh clone)
βββ samples/ Sample `.stmod` graphs and trace files
A more detailed read on any module lives in src/<module>/include/ headers and the corresponding tests/unit/<module>/.
Active work, summer 2026:
- Phase D flash-protocol close-out on the junkyard LF79002P bench rig. Rounds 14β30 of a structured analyst β implementer loop have walked from cold ECU through DSC
0x10 0x02unblock (Phase C), through bulk-transfer writes (Phase D), to the current question of how the state machine transitions to allow0xB7close-out. Handoff archive lives atD:/Subuwu/findings/handoffs/. - Per-ISA brick-protection HIL on the same rig. SH-2A serial-boot recovery is documented in
docs/31; RH850 atomic-swap design has open items the bench will settle. - Patch insertion layer (
src/feature_patch/) β design pinned indocs/30-patch-insertion.md. The custom-features designer already emits valid SH-2A and RH850 patch bytes; the insertion layer is what splices them into real ECU vector tables. - Live datalogger polish β
LiveBufferandLogSessionare wired; the next milestone is sustained-rate validation in a real car.
Realistic. Not a hype list.
- Definition pack coverage β VA + VB WRX MT first (the bench-testable scope); STI, AT variants, EJ-era cars, BRZ / 86 as they clear pack development and flash routine validation
- Definition generation improvements β schema-evolution heuristics, multi-source merge, automated provenance reporting
- Bench testing validation β Tier 4 HIL coverage per
docs/08-testing-strategy.md, deliberate-brick recipes perdocs/31, 100-cycle success target before any car flash - CAN protocol research β fault tolerance under noisy buses, multi-module discovery for engine swaps, automated DBC promotion from
.cdbtraces - Logging enhancements β Aim-style standalone-record mode, multi-sink replay tooling, sustained 100 Hz Γ 20-PID target
- Flashing infrastructure β checksum-repair algorithm landings, delta-flash brick-protection per
docs/40, journal-resume hardening - Custom-features tooling β table-lookup primitives, broader hook coverage, end-to-end flash of community-published feature packs
- Automated tooling β fuzz coverage for the TOML pack loader, mutation tests on
st::flashas a release gate, frozendefgenbinary in CI artifacts - Documentation β MkDocs site, design-doc cross-linking, glossary expansion, contributor onboarding flow
Full per-phase status in docs/04-roadmap.md.
Contributions are very welcome. The project benefits from a lot of different shapes of help:
- Reverse-engineering findings β write up what you learn about a Subaru ECU. The structured analyst β implementer handoff format in
D:/Subuwu/findings/handoffs/is one way; design docs indocs/are another - Definition packs β TOML packs for additional CIDs.
pack-lintwill tell you what's missing - Testing β Catch2 unit tests live next to the code in
tests/unit/<module>/. Bench-rig HIL recipes welcome - Documentation β the design docs are numbered 00β42 with intentional gaps; adding a new doc and cross-linking it is a productive contribution on its own
- Code β C++23,
Result<T>over exceptions in domain code,snake_casefunctions /PascalCasetypes, clang-format LLVM-base / 4 spaces / 100 cols / pointer-binds-right, clang-tidy +-Wall -Wextra -Wpedantic -Werrorclean
See CONTRIBUTING.md for the clean-room methodology, code style enforcement, and PR flow. New contributors should opt into the pre-commit hook:
git config core.hooksPath .githooksThe hook runs clang-format --dry-run --Werror on staged C/C++ files.
cmake --preset linux-gcc # or win-mingw, win-msvc, mac-clang
cmake --build --preset linux-gcc
ctest --preset linux-gcc
./build/linux-gcc/bin/subuwutuner-cli --versionRequirements: C++23 compiler (MSVC 19.40+ / Apple Clang 16+ / GCC 14+ / MinGW-w64 15+), CMake 3.28+, Ninja, Git. No vcpkg, no system packages β every dependency pulls via FetchContent on first configure. First configure takes a couple of minutes; subsequent builds are incremental.
The CI matrix runs ~1700 C++ tests + 36 Python tests across Win MSVC, macOS Apple Clang, Linux GCC, and Linux Clang ASan+UBSan, plus the defgen PyInstaller freeze matrix.
Fifty-seven structured analystβimplementer rounds across a junkyard LF79002P bench rig. The bench died on round 57. Bench rig two will not make the same mistakes β probably it will find new ones. In rough order, what came out of those rounds:
-
Phase C entry β DSC
0x10 0x02unblock. A 5-write Mode0x3Dburst clears the F2/F3/F4 fault cascade ahead of the aggregator tick, then10 02grants50 02. Implemented insubaru-dsc-unblock-sequence --burst-write-extended. Bench-validated repeatably; survived a half-dozen power cycles unchanged. -
Phase D + flash write chain. Wire is
34 04 33 <addr-3> <size-3>β74 20 01 04. SA L1 + Programming session is sufficient; SA L3 is not required for Phase D (round-49 thought otherwise, round-51 refuted it). Phase D accepts negotiations up to at least 2 MB. Erase via31 01 02 01 0F FF FF FFis range-scoped to the negotiated range.0xB6is fixed at exactly 260 wire bytes (SID + 3-addr + 256-data).0x37takes no body. The full chain lives insubaru-flash-write-cycleandsubaru-dsc-unblock-sequence --write-cycle. -
Finalization and sum gate. Stock finalization is SID
0x37; the formerly cited31 01 02 02 01/ ROM0x319Epath is bench-donor or image-specific, not the universal stock commit. The 2 MiB SH-2A runtime invariant is stillu16 BE sum(0x6000..0x200000) == 0x5AA5, including balance word0x1FFFFE. A.3 uses[0x6000,0x280000)and balance0x27FFFE. -
Boot integrity.
FUN_00000C54three-signature gate before the application JMP at0x001F094C:0x5555at0x6000(in FACI-locked EB02),0xAAAAat0x1FFFF2, and*0x6C == *0x6010. Any drift in those bytes and the ECU runs CPU but never powers up its CAN transceiver. -
SecurityAccess. Subaru SSMCAN1 family reverse-engineered clean: factory 16-round Feistel + aftermarket L1 and L3 variants, all CLI-selectable via
--sa-variant. Aftermarket L1 is the bench-default and works against fresh Programming sessions. -
Dispatcher latch. Entering Programming session swaps the SID dispatcher to flash-protocol-only routing. Only
0x34,0xB6,0xB7,0x37are routed afterward; everything else returns NRC0x11. The swap is permanent until power cycle β UDS0x11reset doesn't escape,0x3ETesterPresent doesn't escape, completing the flash chain doesn't escape. Empirically confirmed across many attempts. -
Sector + sum map. Per-sector u16 BE sums for the reference donor are recorded; EB02 / EB03 / EB04A-C / EB05 / EB06β14 boundaries and FACI-lock state are characterized. EB02 is FACI-locked (writes silent at the protocol layer); EB03βEB14 are writable via the chain above.
-
SBOX identity model (round 39).
0xB7readback is a Feistel hash, not raw bytes β except when the input u32's R-half passes the identity-boundary condition (no 5-bit chunk equal to0x1F). Most u32s pass identity, so most readbacks look raw. When R contains a0x1Fchunk, the bytes come back hashed. Different Programming sessions can have different SBOX states; byte-for-byte cross-session comparison of0xB7output is unsafe. Use SSM-A8 RMBA for verification. -
State machine. The COBB-confirmed protocol cycles state
0 β 1on a sector-end-aligned0xB6,1 β 2on0x37, and2 β 0on0xB7at the negotiated start. Commit requires state0plus the sum sentinel. (We later refuted "B7 always transitions state 2β0" β sometimes it stays at 2 β but the rest holds.)
Three layered mistakes, each defensible alone.
The commit gate exists for a reason. Subaru's FA20DIT firmware refuses to commit any flash write unless u16 BE sum(0x6000..0x200000) == 0x5AA5. We learned this AFTER a sequence of cal-research writes had already drifted the sum, then spent a productive afternoon trying to restore it via increasingly-large writes (64 KB β 8 KB β 1.97 MB). The restorations were byte-correct in our staging file. They were not all byte-correct in real flash.
F6 is not a receipt. Every one of 8064 0xB6 blocks during the 2 MB restore returned F6. 0xB7 readback at the negotiated_start matched stock content. The commit still failed the sum check. The next cold boot found enough damage in regions we never verified to halt before CAN init. The FCU's staging buffer is what 0xB7 reads back; real flash is what the sum check (and the boot integrity check) reads. The two can disagree. Ours did.
Verification means crossing a power cycle. The decisive "did this write actually land?" check is an independent flash read in default session after a power cycle. On the adapter path previously called SSM-A8, the adapter translates to SID 0x23 RMBA; 0xB7 staging-buffer readback is not an integrity check.
-
Tune-export pipeline preserves the sum. SubuwuTuner's tune deployment must compute
u16 BE sum(0x6000..0x200000)for the proposed write and, where necessary, include a compensating "balance" cell write so the post-write sum stays at0x5AA5. Locating the COBB-style balance cell is on the round-58 analyst punch list. -
0xB7is for iteration feedback, not verification. Treat it as a debug echo of the FCU staging buffer. Verification = post-power-cycle SSM-A8 RMBA in default session. -
Protocol-level positive β flash-level commit.
F6on a0xB6block,77on a0x37,74on a Phase D β none of those promise the bytes have crossed into real flash. The31 01 02 02 01commit, gated by the sum sentinel, is the only positive that means flash is durable. -
Boot integrity is the line in the sand. Three signatures gate the application JMP. Tune-export pipeline must refuse any write plan that touches those addresses without an explicit override + matching restore plan. (Yes, COBB pre-flight does the same thing.)
-
Bench testing is mandatory and benches are expendable. The original LF79002P bench ECU is currently silent after power-cycle. The former E2-Lite recovery purchase is withdrawn; exact-board probe support is unvalidated. The immediate path is a qualified recovery service or an exact-CID donor, while the DIY SH-2A probe remains a separate research track.
Cost so far: one junkyard ECU and the bench-rig build. Bought: a clean-room model of the Subaru DIT flash protocol, the brick-protection-contract architecture for tune deployment, and the conviction not to trust an FCU staging buffer ever again.
The LF79002P bench ECU is silent after power-cycle. The former E2-Lite recovery purchase is withdrawn; exact-board probe support is unvalidated. The immediate hardware path is a qualified recovery service or an exact-CID replacement donor. Hardware-independent work continues through readiness, checkpoint, preflight, compare, restore, Atlas, reverse-engineering, and Map Explorer tracks. The GUI now binds approved exact-CID calibration regions into offline Flash Review while keeping live observed-CID proof as a hard blocker; the Tables sidebar has category and safety/emissions Map Explorer facets. See docs/47-task-list.md and docs/50-replacement-ecu-intake.md.
SubuwuTuner is experimental software targeting hardware that can cost more than a used car to replace. If you flash an ECU, upset your tuner, light up every warning lamp on the dashboard, or accidentally turn a perfectly good engine controller into a very expensive paperweight, that's on you.
Breaking things responsibly since commit #1.
More formally:
- No warranty. Apache 2.0 β see
LICENSE. Use at your own risk. - Bench testing strongly encouraged. A junkyard ECU on a 12 V PSU is cheap. A new ECU is not.
- Research and educational purposes. Calibration edits, flash routines, and protocol findings are research artifacts. Validating any of them on a vehicle is the user's call and the user's responsibility.
- Engine-safety rules are advisory, not magic. The tool refuses a small set of demonstrably-dangerous edits (delete a knock-sensor threshold table and you get a refusal). Everything else is your decision.
- Jurisdiction policy is yours. The profile system warns where it can; it does not enforce.
docs/06-legal-ethics.mdis the long-form reasoning. - Connecting any of this to a real car β read
DISCLAIMER.mdfirst.
Apache 2.0. Third-party library notices in THIRD_PARTY_NOTICES.md. Clean-room methodology in docs/15-clean-room-engineering.md. Distribution posture for definition packs in docs/17-data-distribution-policy.md.
Bench test first. Full send later.
