# Reference stock maps (real, from TuneECU's server) Four genuine factory Bonneville calibrations, downloaded from `https://www.tuneecu.fr/Maps/Triumph/Bonneville/Map.hex` (the endpoint the TuneECU app itself uses; found in the decompile at `MainActivity.java:7407`). | File | Map | Fits | |---|---|---| | `20187Map.hex` | 20187 | Bonneville, **production** silencers, mechanical odo | | `20188Map.hex` | 20188 | Bonneville, **aftermarket** silencers, mechanical odo | | `20191Map.hex` | 20191 | production, up to VIN 739050, E25 | | `20192Map.hex` | 20192 | aftermarket, up to VIN 739050, E25 | These are the candidate stock maps for the 2010 T100 (mechanical odometer). Once we dump the bike, its ROM should correspond to one of these. ## What they validated - **Header format is correct.** All four satisfy the reversed magic: little-endian u32 of bytes[0:4] `& 0xFF00FFE0 == 0x18001360`, i.e. `byte0=0x67, byte1=0x13, byte3=0x18`. This confirms the format reversing in `../../viewer/FORMAT.md` and the little-endian correction. - **`c.b` is the table directory.** `m.uc()` reassembles a map by copying tables at the `c.b` (offset,length) pairs — independent confirmation of the geometry. ## What they revealed (the new blocker) **The map body is encrypted.** Evidence: - Uniform entropy ~7.91 bits/byte across the whole body (8.0 = random). - Sibling maps that should differ only in fueling (20187 vs 20188) share just **0.1%** of bytes, with no equal run ≥16 bytes. - A low-entropy footer (last ~5 KB, `H≈3.0`) that holds structured key/signature material — matching `p8()`, which reads its key from the file **tail** (`length-42`, `length-35`, …), derives it via `m.Sb()`, and unpacks via `m.uc()`. So the table offsets in `mapdefs.json` describe the **decrypted** map, and can't be validated against these files until the map decryption is reversed. The signature lookup (`sc()` at header offset 20) does **not** match our clean `s.a` directory for these files — consistent with the real directory key living in the encrypted/footer region, not the header. ## DECRYPTION SOLVED (`decode_map.py`) The distribution format is decrypted by `l.dc()` — a self-synchronising CBC-style XOR stream cipher seeded by the (plaintext) 4-byte header. NOT AES; the earlier `m.uc`/`m.Sb` path is for raw ROM dumps, not these downloads. `p8()` calls `l.dc()` on any map that isn't a raw-ROM size. Reversed, ported, and **verified**: - `decode_map.py` decodes all four maps; encode(decode(x)) == x (round-trip). - Decoded 20187 contains the plaintext strings `Bonneville` / `Production silencers` / `Mechanical odometer` at 0x1E/0x29/0x3E — matching the catalogue. - Entropy drops 7.99 → ~6.1 bit/byte. - **Directory lookup now validates on the decoded map:** signature at offset 20 = `0x0187CA84`, which matches `s.a` record #541 (`field[0]=25676420`). So the whole chain works: decode → signature@20 → `s.a` directory → `c.a`/`c.b`. Decoded files: `*.dec.bin`. ## SOLVED: flat ROM reconstruction + table location (`reconstruct_rom.py`) The decoded map is a **packed** container, which is why a naive byte-diff of two decoded maps showed 96% difference — the packed chunks are misaligned. Each decoded map carries its own **unpack directory** (from `MainActivity.p8`): `base = le16(dec[28])`; a `0x6F66` marker at `base+31`; a count at `base+33`; then `count` (le32 dest_offset, le32 length) entries, followed by the packed data copied verbatim to `flat_rom[dest:dest+len]`. This yields a **384 KB (0x60000) flat ROM** — the real ECU address space. **In the flat ROM, 20187 (production) vs 20188 (aftermarket) differ by only 1.4%** (down from 96%), localised to 26 regions. That is the actual production→ aftermarket calibration change, not noise. Table pointers are in `fe = c.a[Qd*48]` (Qd from the directory lookup). Table address = `fe[39]` (base 0x50000) + `fe[k]`. **VERIFIED:** `fe[11]=0x6990` → `0x56990` is a **main fuel table** — a smooth 20-wide VE surface, and the aftermarket map is richer in 284/320 cells (mean +323), exactly as an aftermarket-exhaust tune should be. The two big high-entropy diff regions (`0x55599`, `0x56990`) are the two fuel tables; the small isolated diffs (e.g. single bytes at `0x50C13`, `0x534AD`, `0x59759`) are prime **SAI / O2 / lambda flag** candidates — now findable because the flat-ROM diff is localised. Still to finish: map the remaining `fe[k]` pointers to named tables (small- throttle fuel, AFR, ignition-by-gear, idle, limiters) and confirm each table's dimensions/axes/scaling from the `fe` metadata (or cross-check with an XDF). The hard part — decrypt, reconstruct, locate — is done and validated. ## Provenance (sha256) ``` cd02cd2a…306c99 20187Map.hex bed451a9…e1ebe4 20188Map.hex 7436b0f2…79ca3a6 20191Map.hex f9b9c60d…ffa10f 20192Map.hex ```