Files
lofi-12xt-firmware/tools/lofi_checksum.py
T
2026-09-30 22:48:06 +02:00

113 lines
3.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Brute-force checksum hypotheses for cmtd+0x08 against all known images.
Tests CRC32/zlib/adler/fletcher/sum/xor variants over multiple spans.
A hypothesis must match ALL builds to be reported as candidate.
Stdlib only.
"""
import binascii, os, struct, sys, zlib
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IMAGES = [
'Lofi-12XT_v1.1.156/Lofi-12XT_v1.1.156/Lofi-12 XT.bin',
'Lofi-12XT_v1.2.179/Lofi-12XT_v1.2.179/Lofi-12 XT.bin',
'Lofi-12XT_v1.5.205/Lofi-12XT_v1.5.205/Lofi-12 XT.bin',
]
def fletcher16(data):
s1 = s2 = 0
for b in data:
s1 = (s1 + b) % 255
s2 = (s2 + s1) % 255
return (s2 << 8) | s1
def fletcher32(data):
s1 = s2 = 0
for i in range(0, len(data), 2):
w = data[i] | (data[i+1] << 8 if i+1 < len(data) else 0)
s1 = (s1 + w) % 0xFFFF
s2 = (s2 + s1) % 0xFFFF
return (s2 << 16) | s1
def sum32(data):
return sum(data) & 0xFFFFFFFF
def xor32(data):
r = 0
for i in range(0, len(data) - 3, 4):
(w,) = struct.unpack('<I', data[i:i+4])
r ^= w
return r & 0xFFFFFFFF
def load():
out = []
for rel in IMAGES:
p = os.path.join(BASE, rel)
d = open(p, 'rb').read()
ck = struct.unpack('<I', d[8:12])[0]
out.append((rel.split('/')[0], d, ck))
return out
def spans(d):
nsect = struct.unpack('<I', d[0x50:0x54])[0]
off = 0x54
payloads = b''
for _ in range(nsect):
ln = struct.unpack('<I', d[off+8:off+12])[0]
payloads += d[off+12:off+12+ln]
off += 12 + ln
z8 = bytearray(d); z8[8:12] = b'\0\0\0\0'
return {
'full': d,
'from_0x04': d[0x04:],
'from_0x0C': d[0x0C:],
'from_0x30': d[0x30:],
'from_0x54': d[0x54:],
'payload_concat': payloads,
'full_ck_zeroed': bytes(z8),
'from0x0C_ck_zeroed': bytes(z8)[0x0C:],
}
def main():
blobs = load()
for name, d, ck in blobs:
print(f"{name}: size={len(d)} stored_ck={ck:08X}")
algs = {
'crc32_le': lambda b: binascii.crc32(b) & 0xFFFFFFFF,
'crc32_be_byteswap': lambda b: struct.unpack('>I', struct.pack('<I', binascii.crc32(b) & 0xFFFFFFFF))[0],
'crc32_complement': lambda b: (~binascii.crc32(b)) & 0xFFFFFFFF,
'adler32': lambda b: zlib.adler32(b) & 0xFFFFFFFF,
'fletcher16': fletcher16,
'fletcher32': fletcher32,
'sum32': sum32,
'xor32': xor32,
}
cands = []
total = 0
for aname, fn in algs.items():
for sname in spans(blobs[0][1]):
total += 1
ok = True
for _, d, ck in blobs:
try:
v = fn(spans(d)[sname]) & 0xFFFFFFFF
except Exception:
ok = False
break
if v != ck:
ok = False
break
status = 'MATCH-ALL' if ok else 'no'
if ok:
cands.append((aname, sname))
# show near-misses? only print matches to stay concise
if ok:
print(f" {aname} x {sname}: {status}")
print(f"tested {total} hypotheses, {len(cands)} full-match candidates")
if not cands:
print("No match: checksum is not plain CRC32/adler/fletcher/sum/xor over obvious spans.")
print("Next: try seeded CRC, TI-AIS style, per-sect accumulate, or mmtd.flags correlation.")
if __name__ == '__main__':
main()