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>
47 lines
1.5 KiB
Python
47 lines
1.5 KiB
Python
import struct, re
|
|
EXES = ['/Users/jing/Documents/yutong/yutongapp/TP5(En).exe',
|
|
'/Users/jing/Documents/yutong/yutongapp/TP5(Cn).exe']
|
|
|
|
def crc_table16(poly, reflect):
|
|
t=[]
|
|
for i in range(256):
|
|
if reflect:
|
|
c=i
|
|
for _ in range(8):
|
|
c = (c>>1) ^ (poly if c&1 else 0)
|
|
else:
|
|
c=i<<8
|
|
for _ in range(8):
|
|
c = ((c<<1)^poly)&0xffff if c&0x8000 else (c<<1)&0xffff
|
|
t.append(c&0xffff)
|
|
return t
|
|
|
|
def crc_table32(poly, reflect):
|
|
t=[]
|
|
for i in range(256):
|
|
if reflect:
|
|
c=i
|
|
for _ in range(8):
|
|
c = (c>>1) ^ (poly if c&1 else 0)
|
|
else:
|
|
c=i<<24
|
|
for _ in range(8):
|
|
c = ((c<<1)^poly)&0xffffffff if c&0x80000000 else (c<<1)&0xffffffff
|
|
t.append(c&0xffffffff)
|
|
return t
|
|
|
|
polys16 = [0x1021,0x8408,0x8005,0xa001,0x3d65,0xa6bc,0xc867,0x0589,0x8bb7,0x8d95,0x1DCF,0x755B]
|
|
for path in EXES:
|
|
d=open(path,'rb').read()
|
|
print('===', path, len(d))
|
|
for p in polys16:
|
|
for refl in (0,1):
|
|
t = crc_table16(p, refl)
|
|
blob = b''.join(struct.pack('<H',x) for x in t[:16])
|
|
i = d.find(blob)
|
|
print(' tbl16 poly=%04x refl=%d -> %s' % (p,refl, hex(i) if i>=0 else '-'))
|
|
# generic immediate search for 16-bit polys used in bitwise loop:
|
|
# look for "xor ax, imm16" = 66 35 xx xx or "xor eax, imm32" = 35 xx xx xx xx
|
|
for m in re.finditer(rb'\x66\x35(..)', d, re.S):
|
|
pass
|