Bundles several feature streams that have been iterating on the working tree: - Developer Tools page (Debug-only via DEVELOPER_TOOLS symbol): hosts the identification card, manual KWP write + transaction log, ROM/EEPROM dump card with progress banner and completion message, persisted custom-commands library, persisted EEPROM passwords library. New service primitives: IKwpService.SendRawCustomAsync / ReadEepromAsync / ReadRomEepromAsync. Persistence mirrors the Clients XML pattern in two new files (custom_commands.xml, eeprom_passwords.xml). - Auto-test orchestrator (IAutoTestOrchestrator + AutoTestState): linear K-Line read -> unlock -> bench-on -> test sequence with snackbar UI and progress dialog VM, gated on dashboard alarms. - BIP-STATUS display: BipDisplayViewModel + BipDisplayView, RAM read at 0x0106 via IKwpService.ReadBipStatusAsync; status definitions in BipStatusDefinition. - Tests page redesign: TestSectionCard + PhaseTileView replacing the old TestPlanView/TestRunningView/TestDoneView/TestPreconditionsView/ TestSectionView controls and their VMs. - Pump command sliders: Fluent thick-track style with overhang thumb, click-anywhere-and-drag, mouse-wheel adjustment. - Window startup: app.manifest declares PerMonitorV2 DPI awareness, MainWindow installs a WM_GETMINMAXINFO hook in OnSourceInitialized and maximizes there (after the hook is in place) so the app fits the work area exactly on any display configuration. - Misc: PercentToPixelsConverter, seed_aliases.py one-shot pump-alias importer, tools/Import-BipStatus.ps1, kline_eeprom_spec.md and dump-functions reference docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.6 KiB
5. EEPROM memory map (8-bit address, 0x00–0xFF)
The on-board serial EEPROM has 256 bytes addressed 0x00–0xFF. The ECU's internal mirror after boot covers a sub-range only (FUN_7568 loads EEPROM 0x40–0x8B into RAM 0x400–0x44B); the upper area is read on demand.
Observed structure (from cross-dump comparison + disassembly):
| EEPROM range | Purpose | Notes |
|---|---|---|
| 0x00–0x3F | Pump cal record A — operating parameters | Read at boot via FUN_4A79 (record-based, 8-byte chunks). Content is largely zero in current dumps. Erasable via cmd 0x05. |
| 0x40–0x8B | Pump cal record B — primary | Block-loaded into RAM 0x400–0x44B at boot by FUN_7568. Contains: temp offset (0x42), dphi seed (0x44) + checksum (0x45), accel-comp seed (0x48), angle-table seed (0x4C), CKP loop seeds (0x50–0x56), redundant copies (0x79–0x7A), serial number ASCII (0x80–0x88) |
| 0x8C–0xBF | Pump cal record C — extension | Not loaded into RAM at boot. Readable only via K-line cmd 0x19 after Zone 3 unlock. Per user observation 2026-05-07: present in physical EEPROM, never consumed by running ECU code. Likely OEM/factory metadata. |
| 0xC0–0xCF | Reserved / unused | Outside any zone bounds in observed configs |
| 0xD0 | Lockout backoff counter | Written by FUN_29D4 failure path, read at boot by FUN_3AD1. Persists failed-auth state across power cycles. |
| 0xD7–0xDF | Zone 0 seed (9 bytes) | Read into RAM 0xB7+ as part of cmd 0x18 success response |
| 0xE1–0xE9 | Zone 1 seed (9 bytes) | Same |
| 0xEA–0xF2 | Zone 2 seed / Zone 8 magic seed (9 bytes) | Same. Shared between Zone 2 and Zone 8. |
| 0xF3–0xFB | Zone 3 seed (9 bytes) | Same |
| 0xFC | Zone 0 use counter | Incremented on each cmd 0x18 + RB0=0x10 success |
| 0xFD | Zone 1 use counter | Same |
| 0xFE | Zone 2 / Zone 8 use counter | Magic-zone access leaves this trace |
| 0xFF | Zone 3 use counter | Same |
The "cal record" boundaries (A/B/C) are inferred from access patterns; the EEPROM does not have explicit headers separating them.
6. Security architecture
6.1 Zone descriptor table
A 4-entry × 12-byte table at ROM 0x5FA0 drives zone authentication. Each entry has:
| Offset | Field | Type | Purpose |
|---|---|---|---|
| +0 | alt_key |
u16-LE | Alternative key (used by cmd 0x18 with RB0 ≠ 0x10) |
| +2 | exp_key |
u16-LE | Primary expected key |
| +4 | zone_start |
u16-LE | First EEPROM address the zone covers |
| +6 | zone_end |
u16-LE | Last EEPROM address (inclusive) |
| +8 | flag_byte |
u8 | RE6/RE7 bit pattern set on success |
| +9 | seed_off |
u8 | EEPROM offset of the 9-byte seed read on success |
| +10 | cnt_off |
u8 | EEPROM offset of the per-zone use counter |
| +11 | reserved | u8 | usually 0xFF (unused) |
A fifth implicit "magic zone 8" is hard-coded in FUN_29D4:
- Key: 0x4453 (ASCII "DS")
- Range: 0x0000–0xFFFE (effectively the whole 64KB MCU address space)
- On success:
RE7 = 0xFF(every gate-bit set, including the cmd 0x1A write gate) - Seed/counter: shares Zone 2's offsets (0xEA / 0xFE)
6.3 Authentication flow (FUN_29D4)
Tester side:
-
Build cmd 0x18 block with:
RB3= 0x03 (request length / sub-count)RB4= zone selector (0, 1, 2, 3, or 8)RB5= key high byteRB6= key low byteRB7= any non-zero byte (acts as continuation flag — without it, the ECU'sRWC4 = 0reset short-circuits the success path)RB0= 0x10 if you also want write access enabled (sets the upper-nibble flag bits AND increments the use counter)- Otherwise
RB0= block length (4 + data bytes)
-
TX block, observe response. ECU returns success block (
RB2 = 0xF0, payload = the 9-byte seed + zone bounds) or error (RB2= 0xE5/error pattern). -
On success, internal flags are set:
- Zone 0 →
RE6 |= 0x01(read) or|= 0x11if RB0=0x10 (read+write) - Zone 1 →
RE6 |= 0x02or|= 0x22 - Zone 2 →
RE6 |= 0x04or|= 0x44 - Zone 3 →
RE7 |= 0x08 - Zone 8 →
RE7 = 0xFF(and cmd 0x1A becomes available)
- Zone 0 →
-
The flags persist for the rest of the K-line session; they reset on session end / power cycle.
ECU-side details an implementer should be aware of:
- The 9-byte seed read at
EEPROM[seed_off..seed_off+8]is echoed back to the tester in the response. It does not gate access — pure transport, presumably so the tool can fingerprint the unit. RE8(lockout state) must be 0 to attempt auth. If non-zero (set by previous failed attempts), the ECU silently fails until the lockout timer (FUN_38BE) decrements RE8 to 0.- Wildcard match: if
exp_key == 0xFFFFin any zone descriptor, ANY tester key is accepted. None of the observed dumps use this, but a future ROM variant might. - Sentinel: if
exp_key == 0x5555, the zone is permanently disabled (cannot be unlocked). None observed yet either.
6.4 Lockout mechanism
Implemented in FUN_29D4 failure path + FUN_38BE timer.
fail #1 -> RE9 = 1, RE8 = 1, stored to EEPROM 0xD0
fail #2 -> RE9 = 2, RE8 = 2
fail #3 -> RE9 = 4
...
fail #8 -> RE9 = 128
fail #9+ -> RE9 = 240 (saturated)
RE8 decrements once per ~30000 ticks (active session) or ~120000 ticks (idle). Each decrement re-writes EEPROM 0xD0 — the lockout therefore persists across power cycles. A wedged unit can require minutes to hours of waiting to retry.
Recommendation for the reader software: read EEPROM 0xD0 before any auth attempt (via cmd 0x19 after Zone 3 unlock — the only safe path that doesn't risk further lockout).
7. Read recipes
7.1 Recipe A — public read of 0x00–0xBF (Zone 3)
This is the safest operation: it requires a static key (0x00FF) that is identical across every variant we have dumps for, has no destructive side-effects, does not increment the magic-zone counter, and covers the full "general data" portion of the EEPROM.
# Step 1 — unlock Zone 3
TX: [ 06 NN 18 03 03 00 FF FF 03 ]
len=06, seq=NN, cmd=0x18, RB3=03, RB4=03 (Zone 3),
RB5=00, RB6=FF (key 0x00FF), RB7=FF (continuation flag),
end=03
ECU response (success): RB2 = 0xF0, plus 9-byte seed @ 0xF3..0xFB.
ECU now has RE7 |= 0x08.
# Step 2 — read EEPROM in 13-byte chunks
for offset in 0x00, 0x0D, 0x1A, 0x27, 0x34, 0x41, 0x4E, 0x5B,
0x68, 0x75, 0x82, 0x8F, 0x9C, 0xA9, 0xB6:
TX: [ 04 NN 19 0D 00 offset 03 ]
cmd=0x19 (read EEPROM), RB3=0x0D (13 bytes), RB5=offset
RX: 13 bytes from EEPROM[offset..offset+12] in the response payload
# 15 chunks * 13 bytes = 195 bytes, covering 0x00..0xC2
# Trim or adjust the last chunk's RB3 to 0x0A so it stops at 0xBF inclusive.
7.2 Recipe B — full read of 0x00–0xFF (Zone 0)
Reads bytes that Zone 3 cannot see (0xC0–0xFF — counters, seeds, lockout). Requires the OEM key 0x00A6.
# Step 1 — unlock Zone 0
TX: [ 06 NN 18 03 00 00 A6 01 03 ]
RB4=00 (Zone 0), RB5=00, RB6=A6, RB7=01
# Step 2 — read EEPROM via cmd 0x03 (per-zone path)
for offset in 0x00, 0x0A, 0x14, ..., 0xF6:
TX: [ 06 NN 03 0A 00 offset 03 ]
cmd=0x03, RB3=0x0A (10 bytes), RB4:RB5 = 0x00:offset (16-bit address)
# 26 chunks * 10 bytes = 260 bytes, slightly over-reads;
# adjust the last chunk's RB3 to stop at 0xFF.
7.3 Recipe C — magic full access (Zone 8) — read AND write
# Step 1 — unlock Zone 8 with the master key
TX: [ 06 NN 18 03 08 44 53 01 03 ]
RB4=08 (magic zone), RB5=44, RB6=53 (key "DS" = 0x4453), RB7=01
# After: RE7 = 0xFF (every gate-bit set).
# All read commands work; cmd 0x1A is also available for writing.
# WARNING: this access path increments EEPROM[0xFE] (the use counter).
# The increment is non-destructive but forensically observable — service
# tools at the OEM can read 0xFE to see how many times Zone 8 was accessed.
7.4 Recipe D — fast-path RAM-mirror dump (no auth)
For EEPROM 0x40–0x8B specifically, you don't need to talk to the EEPROM driver at all — the data is mirrored into RAM 0x400–0x44B at boot and accessible via cmd 0x01 (read RAM, no auth required).
for offset in 0x400, 0x40D, 0x41A, ..., 0x444:
TX: [ 04 NN 01 0D high_addr low_addr 03 ]
cmd=0x01 (read RAM/ROM), RB3=0x0D, RB4=high, RB5=low
# 6 chunks covering 0x400..0x44B = exact mirror of EEPROM 0x40..0x8B.
This recipe is ideal for monitoring tools that want to observe the live EEPROM cache without authentication overhead.
8. Write recipes — DANGER ZONE
Writes require either Zone 0/1/2 unlock with RB0 = 0x10 (cmd 0x0C path) or Zone 8 (cmd 0x1A path).
8.1 Cmd 0x0C — write via OEM zone (with RB0 = 0x10 unlock)
# Step 1 — unlock Zone 0 with write enabled
TX: [ 06 NN 18 03 00 00 A6 01 03 ] # NOTE: RB0 must be 0x10, not 0x06,
# to set the read+write flag pattern
# (RC8 << 4 | RC8) and increment 0xFC.
(Implementer task: verify the RB0=0x10 vs RB0=0x06 distinction by experiment — it's set in FUN_29D4 @ 0x29D4 based on RB0 == 0x10 test, but the call path that actually populates RB0 differs between the dispatcher's path-A and path-B.)
# Step 2 — cmd 0x0C write with verify
TX: [ 0E NN 0C NN 00 offset b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 03 ]
cmd=0x0C, RB3=count<11, RB4:RB5 = address, RB6+ = bytes to write
8.2 Cmd 0x1A — write via Zone 8 (after master unlock)
After Zone 8 is unlocked (RE7 = 0xFF), cmd 0x1A becomes available:
TX: [ ... NN 1A count rb4 offset b1..bN 03 ]
cmd=0x1A, RB3=count<11, RB5=EEPROM offset (8-bit), data bytes from RAM 0xB6+
Each byte is written and immediately read-back-verified internally by FUN_22EA. On verify failure, the response indicates failure and the partial write is left in place.