"""Reconstruct the flat ECU ROM image from a decoded TuneECU map, and locate calibration tables. Chain (all reversed from the decompile, verified against real stock maps): encrypted .hex --dc()--> decoded map --unpack directory--> flat 0x60000 ROM The decoded map carries its own unpack directory (from MainActivity.p8): desc_len = le16(dec[28]); base i21 = desc_len marker le16(dec[i21+31]) == 0x6F66 count dec[i21+33] entries count * (le32 dest_offset, le32 length) at i21+34 data packed chunks follow, copied verbatim to flat_rom[dest:dest+len] Reconstructing into the flat ROM is what makes sibling maps comparable: 20187 (production) vs 20188 (aftermarket) diff drops from 96% (packed, misaligned) to 1.4% (flat ROM) — the difference is real fuel enrichment, not noise. Table pointers live in the calibration-metadata record fe = c.a[Qd*48], where Qd comes from the directory lookup (s.a record for the map's signature). Table address = fe[39] (base, 0x50000) + fe[k]. VERIFIED: fe[11]=0x6990 -> 0x56990 is a main fuel table; 20188-minus-20187 there is uniformly positive (richer), exactly as an aftermarket-exhaust tune should be. """ from __future__ import annotations import struct from pathlib import Path from decode_map import decode FLAT_MARKER = 0x6F66 def flat_rom(encrypted_or_decoded: bytes) -> bytes: """Return the reconstructed flat ROM. Accepts an encrypted .hex or a decoded map.""" d = encrypted_or_decoded # Bytes 0..3 are plaintext in BOTH forms, so detect via the description block # at offset 30 (readable ASCII once decoded). if not all(c in (10, 13) or 32 <= c < 127 for c in d[30:45]): d = decode(d) le = lambda o, n: int.from_bytes(d[o:o + n], "little") i21 = le(28, 2) if le(i21 + 31, 2) != FLAT_MARKER: raise ValueError(f"bad unpack marker 0x{le(i21+31,2):04X} (expected 0x6F66)") count = d[i21 + 33] entries = [(le(i21 + 34 + k * 8, 4), le(i21 + 38 + k * 8, 4)) for k in range(count)] p = i21 + 34 + count * 8 rom = bytearray(b"\xff" * max(o + l for o, l in entries)) for off, ln in entries: rom[off:off + ln] = d[p:p + ln] p += ln return bytes(rom) def read_table_u16(rom: bytes, offset: int, cols: int, rows: int, be: bool = True) -> list[list[int]]: fmt = ">H" if be else " 0) print(f"\naftermarket-minus-production: {pos}/{len(deltas)} cells richer " f"(mean {sum(deltas)/len(deltas):+.0f}) -> enrichment, as expected")