Locate Keihin tables + render real fuel maps in the viewer
Reversed the inner map format from l.java (Nc/Lb) and validated it against the stock reference maps: - reconstruct_rom.py: decoded map -> unpack directory -> 384KB flat ROM. In the flat ROM, production (20187) vs aftermarket (20188) differ only 1.4% (vs 96% packed), i.e. real fuel enrichment. - table_map.py + TABLES.md: fe = c.a[Qd*48]; base = (fe[0]&0x2F0)<<12 = 0x50000; table = base + fe[k]. Located 9 tables (main/low-throttle fuel per cylinder, base/idle fuel, ignition by gear x4), 32 RPM rows x 20 throttle cols, with real axes (RPM fe[8], throttle fe[27]) and the AFR curve (fe[2], 128=lambda 1.00). Validated: main-fuel delta is uniformly richer in the aftermarket map. Viewer now has a "Triumph tables" tab: drop a real .hex (or pick two) and it decodes, unpacks, and renders the fuel/ignition tables as heatmaps with real axes, plus an A->B difference view. build_viewer.py embeds the c.a/s.a directory so any map resolves in-browser. Catalogue dropdown: download_maps.py fetches map .hex files into maps_cache/; serve.py serves the viewer over http so the dropdown can fetch them (drag-and-drop still works on file://). Proprietary map binaries (*.hex, *.dec.bin, maps_cache/) are gitignored — code and docs only.
This commit is contained in:
@@ -13,11 +13,16 @@ TunerPro XDF encodes, but pulled straight out of the app.
|
||||
|
||||
`zc(byte[])` validates and indexes a loaded map:
|
||||
|
||||
- **Magic:** the first 4 bytes, masked `& 0xFF00FF60`, must equal `0x18008060`.
|
||||
- **Family byte:** `bArr[0]` selects the table directory —
|
||||
`(bArr[0] & 0x7B) == 0x69` → `r.a`; `bArr[0] == 0x68` → `t.a`; else `s.a`.
|
||||
(`0x67`/`0x68`/`0x69` are the map "generation" markers.)
|
||||
- **Calibration signature:** 4 bytes at **offset 20** (big-endian). `sc()`
|
||||
- **Magic:** the first 4 bytes read as a **little-endian** u32, masked
|
||||
`& 0xFF00FFE0`, must equal `0x18001360`. Equivalently, by byte:
|
||||
`byte0 & 0xE0 == 0x60`, `byte1 == 0x13`, `byte3 == 0x18`. (`j5()` is
|
||||
little-endian; big-endian does not satisfy the family bytes, so this is
|
||||
settled.)
|
||||
- **Family byte:** `byte0` selects the table directory —
|
||||
`(byte0 & 0x7B) == 0x69` → `r.a` (0x69); `byte0 == 0x68` → `t.a`; else `s.a`
|
||||
(0x67). These are the map "generation" markers, all consistent with the magic.
|
||||
- **Calibration signature:** 4 bytes at **offset 20**, read **big-endian** (note:
|
||||
different endianness than the magic — this is how `sc()` reads it). `sc()`
|
||||
searches the directory for the record whose `field[0]` equals this value.
|
||||
|
||||
## Directory → metadata → geometry
|
||||
@@ -46,6 +51,21 @@ Table **dimensions and axes** come from the runtime (`MainActivity.T8`/`U8` for
|
||||
rows/cols) and the `title_axis` labels (Throttle %, MAP hPa, RPM, Load %, Temp,
|
||||
Gear). Cells are **16-bit big-endian** with per-table scaling.
|
||||
|
||||
## The body is encrypted (verified against real maps)
|
||||
|
||||
Four real stock maps pulled from TuneECU's server
|
||||
(`research/reference-maps/`) confirmed the **header** decode exactly — but their
|
||||
bodies are **encrypted**: uniform ~7.91 bit/byte entropy, and two maps that
|
||||
should differ only in fueling (20187 vs 20188) share just 0.1% of bytes. A
|
||||
low-entropy footer (last ~5 KB) holds the key/signature material.
|
||||
|
||||
The loader reflects this: `zc()` copies 16 bytes at offset 8 into `Kd` (key/IV)
|
||||
for `byte0=0x67` maps; `p8()` reads key bytes from the file **tail** and derives
|
||||
a key via `m.Sb()`, then `m.uc()` unpacks using the `c.b` geometry. Likely
|
||||
AES-128 (same machinery as the ECU seed/key). **Until this is reversed, the table
|
||||
offsets below describe the *decrypted* map and can't be validated against a real
|
||||
file.** The four reference maps are the ciphertext test vectors for that work.
|
||||
|
||||
## Editing / write-back (this is the whole trick)
|
||||
|
||||
TuneECU keeps a **running 16-bit checksum** at a calibration-specific offset and
|
||||
|
||||
Reference in New Issue
Block a user