TP5 is a 2012 Windows application supplied with the Guangzhou-Tongda LED
destination signs fitted to Yutong buses. It has no preview, so building a
destination list is guess-and-check, and it only runs on Windows.
The bus never talks to TP5 — the sign controller reads a .td5 file off an SD
card, and that is the whole interface. So this replaces the software without
touching any hardware or protocol: it just has to write byte-correct .td5.
Formats reverse-engineered from the sample files and TP5(En).exe, then verified
byte-for-byte:
.td5 the file the bus reads. Fixed-layout binary; each destination block
carries a CRC-16/ARC over block[3..len] and a rand() block id, which
together looked like one 4-byte field because RAND_MAX is 0x7fff.
.tp5 the editable project. Line-based text, UTF-16BE hex strings.
.font the sign's own bitmap fonts, each glyph row XORed with its char code.
The app is one self-contained HTML file: live LED preview at the real sign size
with real scrolling, spreadsheet/CSV import, multi-page destinations, undoable
delete, and export to both .td5 and .tp5.
Verified:
- rebuilds a real 46,080-byte TP5 export byte-for-byte with a recomputed CRC
- all 36 stored CRCs verify against the implementation
- driven through its own UI, re-exporting the real file differs in 7 bytes,
all of them the export timestamp
- running on a real bus: signs and driver's controller both correct
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
39 lines
1.1 KiB
Python
39 lines
1.1 KiB
Python
import struct
|
|
from parse import records
|
|
|
|
rs = records()
|
|
oc = [r for r in rs if r['name'].startswith('OC')]
|
|
assert len(oc)==8
|
|
L = set(len(r['blk']) for r in oc)
|
|
print('lengths', L)
|
|
|
|
b = [r['blk'] for r in oc]
|
|
f = [r['field'] for r in oc]
|
|
|
|
# show differing byte positions
|
|
n = len(b[0])
|
|
diff = [i for i in range(n) if len(set(x[i] for x in b))>1]
|
|
print('differing byte offsets:', [hex(i) for i in diff])
|
|
for i in diff:
|
|
print(hex(i), [hex(x[i]) for x in b])
|
|
|
|
# GF(2) affine test: for equal-length messages, if h is affine over GF(2),
|
|
# then h(A)^h(B)^h(C)^h(D) == 0 whenever A^B^C^D == 0.
|
|
# Find quadruples among the 8 whose message-XOR is 0.
|
|
import itertools
|
|
def bx(x,y):
|
|
return bytes(p^q for p,q in zip(x,y))
|
|
found=0
|
|
for c in itertools.combinations(range(8),4):
|
|
m = bytes(n)
|
|
for i in c: m = bx(m,b[i])
|
|
if m == bytes(n):
|
|
v = 0
|
|
for i in c: v ^= f[i]
|
|
print('quad', c, 'msgxor=0 fieldxor=%08x' % v)
|
|
found+=1
|
|
print('quads with zero msg xor:', found)
|
|
|
|
# Alternative affine test using the base-block trick:
|
|
# Build a matrix over the differing bytes only.
|