Removes the *.hex/maps_cache gitignore rule (explicit user call, reversing the earlier no-redistribution stance) so the official TuneECU catalogue maps, derived SAI/O2-delete composites, and the checksum/composition tooling are actually available to pull up on a phone browser when using the real TuneECU app. Also folds in tonight's KWP2000 fixes (TesterPresent keep-alive, connect-failure cleanup, slow-init StartCommunication fix) and the accumulated research docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FP2GaxS9HkUdL5sLBnjKje
66 lines
2.6 KiB
Python
66 lines
2.6 KiB
Python
"""Flat-ROM checksum for the Triumph Keihin twin family -- reverse-engineered
|
|
and validated Aug 2026 (see ../WRITE_PATH.md for the full trace).
|
|
|
|
Source: `com.tuneecu.l.Ac(int, boolean)` in the decompiled TuneECU app,
|
|
called from MainActivity's map-info display ("Checksum : %04x", flagged
|
|
"*No-OEM"/"Error" on mismatch). This is a 16-bit sum-of-words checksum over
|
|
the calibration region of the flat ROM (0x50000-0x60000 for this ECU
|
|
family -- the exact same base/end this project's own `table_map.py` already
|
|
uses), stored in the last 2 bytes of that region.
|
|
|
|
Validated against 6 real, unmodified, official TuneECU downloads:
|
|
20187/20188/20191/20192/20262/20313 -- all computed == stored, exact.
|
|
|
|
One deliberate non-match, informative rather than a bug: the community
|
|
SAI/O2-delete file (20188Map2009AIRBOXBONNY.hex) has the exact same stored
|
|
checksum as its unmodified base (20188Map.hex) -- whoever built it edited
|
|
a few bytes by hand and never recomputed the checksum. TuneECU's own code
|
|
path for this looks like a *display/validity* check (flags "*No-OEM" /
|
|
"Error" in the UI) rather than a proven hard ECU-side write gate -- that
|
|
community file apparently worked for people despite the stale checksum,
|
|
which is consistent with this being an app-side sanity indicator rather
|
|
than (or in addition to) something the ECU's own bootloader independently
|
|
verifies during the real flash transfer. That ECU-side question is NOT
|
|
resolved by this -- see caveats below.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
CAL_BASE = 0x50000
|
|
CAL_END = 0x60000
|
|
|
|
|
|
def compute(rom: bytes, base: int = CAL_BASE, end: int = CAL_END) -> int:
|
|
"""16-bit sum-of-words checksum over rom[base:end-2], byte-swapped read
|
|
(matches `l.Ac()`'s `bArr[pos ^ 1] | (bArr[pos] << 8)` exactly)."""
|
|
length = (end - base) - 2
|
|
total = 0
|
|
i = 0
|
|
while i < length:
|
|
pos = base + i
|
|
total += rom[pos ^ 1] | (rom[pos] << 8)
|
|
i += 2
|
|
return total & 0xFFFF
|
|
|
|
|
|
def stored(rom: bytes, end: int = CAL_END) -> int:
|
|
return (rom[end - 2] << 8) | rom[end - 1]
|
|
|
|
|
|
def patch(rom: bytearray, base: int = CAL_BASE, end: int = CAL_END) -> None:
|
|
"""Recompute and write the checksum into rom[end-2:end] in place."""
|
|
value = compute(bytes(rom), base, end)
|
|
rom[end - 2] = (value >> 8) & 0xFF
|
|
rom[end - 1] = value & 0xFF
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
from reconstruct_rom import flat_rom
|
|
|
|
for path in sys.argv[1:] or ["20187Map.hex"]:
|
|
rom = flat_rom(open(path, "rb").read())
|
|
c, s = compute(rom), stored(rom)
|
|
print(f"{path:40s} computed={c:04x} stored={s:04x} {'MATCH' if c == s else 'MISMATCH'}")
|