"""Named calibration-table map for the Triumph Keihin SH7054 (865 twin, mechanical odo family), derived from Nc()/Lb() in l.java and validated against the stock reference maps. Pipeline (all reversed, see reconstruct_rom.py and decode_map.py): .hex --decode--> packed map --unpack--> flat 0x60000 ROM signature @ decoded[20] --> s.a directory record --> Qd = field[1] fe = c.a[Qd*48] (per-calibration metadata) base = (fe[0] & 0x2F0) << 12 (= 0x50000 here) table offset (flat ROM) = base + fe[] Tables are 32 rows (RPM) x 20 cols (throttle), 16-bit big-endian. Axes: RPM axis = fe[8] (32 breakpoints, e.g. 0..10000) Throttle axis = fe[27] (20 breakpoints, 0..1000 = 0..100.0%) fe-field -> table assignment (validated by production(20187) vs aftermarket(20188) diff and by value range/shape): """ from __future__ import annotations import json import re import struct import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from reconstruct_rom import flat_rom SRC = "/Users/dylan/dojo/tuner/work/jadx_out/sources/com/tuneecu" ROWS, COLS = 32, 20 # RPM x throttle # name, fe field, kind. Confidence: fuel/ignition are strong; base-fuel & AFR noted. TABLE_DEFS = [ ("Main fuel — cylinder 1", 11, "fuel"), ("Main fuel — cylinder 2", 12, "fuel"), ("Low-throttle fuel — cyl 1", 15, "fuel"), ("Low-throttle fuel — cyl 2", 16, "fuel"), ("Fuel — base/idle", 9, "fuel"), ("Ignition advance — gear 1", 19, "ignition"), ("Ignition advance — gears 2–5", 20, "ignition"), ("Ignition advance — gear 6", 21, "ignition"), ("Ignition advance — neutral", 22, "ignition"), ] RPM_AXIS_FE, THR_AXIS_FE = 8, 27 AFR_FE = 2 # interleaved (value,breakpoint); 128 == lambda 1.00 def _load_int_array(java_path: str, field: str) -> list[int]: s = Path(java_path).read_text() m = re.search(rf"\b{field} = \{{(.*?)\}};", s, re.S) return [int(t.strip().rstrip("L")) for t in m.group(1).split(",") if t.strip()] def _u(x: int) -> int: return x & 0xFFFFFFFF def resolve(map_path: str) -> dict: """Reconstruct the flat ROM and resolve every named table's absolute offset.""" ca = _load_int_array(f"{SRC}/c.java", "a") sa = _load_int_array(f"{SRC}/s.java", "a") rom = flat_rom(Path(map_path).read_bytes()) # Resolve Qd by scanning s.a for the record whose field[0] matches the map's # signature (read from the DECODED header); field[1] of that record is Qd. dec_sig = _map_signature(map_path) qd = None for i in range(len(sa) // 8): if _u(sa[i * 8]) == _u(dec_sig): qd = sa[i * 8 + 1] break if qd is None: raise ValueError("signature not found in s.a directory") fe = ca[qd * 48: qd * 48 + 48] base = (fe[0] & 0x2F0) << 12 def table(off): return [ [struct.unpack_from(">H", rom, off + (r * COLS + c) * 2)[0] for c in range(COLS)] for r in range(ROWS) ] out = { "map": Path(map_path).stem, "qd": qd, "base": base, "rpm_axis": _axis(rom, base + fe[RPM_AXIS_FE], ROWS), "throttle_axis": _axis(rom, base + fe[THR_AXIS_FE], COLS), "tables": [], } for name, field, kind in TABLE_DEFS: off = base + (fe[field] & 0x7FFFF) out["tables"].append({ "name": name, "kind": kind, "fe": field, "offset": off, "offset_hex": f"0x{off:X}", "rows": ROWS, "cols": COLS, "data": table(off), }) return out def _axis(rom: bytes, off: int, n: int) -> list[int]: return [struct.unpack_from(">H", rom, off + i * 2)[0] for i in range(n)] def _map_signature(map_path: str) -> int: from decode_map import decode d = decode(Path(map_path).read_bytes()) sig = int.from_bytes(d[20:24], "big") if d[0] == 0x67: sig = (sig & 0xFFFF0000) | (((sig & 0xFFFF) + d[25]) & 0xFFFF) return sig if __name__ == "__main__": r = resolve("20187Map.hex") print(f"map {r['map']} Qd={r['qd']} base=0x{r['base']:X}") print(f"RPM axis: {r['rpm_axis']}") print(f"throttle axis: {r['throttle_axis']}") for t in r["tables"]: flat = [v for row in t["data"] for v in row] print(f" {t['name']:32} {t['offset_hex']:>8} " f"range {min(flat)}..{max(flat)}") Path("table_map.json").write_text(json.dumps( {"defs": [{"name": n, "fe": f, "kind": k} for n, f, k in TABLE_DEFS], "rows": ROWS, "cols": COLS, "axes": {"rpm_fe": RPM_AXIS_FE, "throttle_fe": THR_AXIS_FE}, "reference": r}, separators=(",", ":"))) print("wrote table_map.json")