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>
34 lines
1021 B
Python
34 lines
1021 B
Python
import struct, sys
|
|
|
|
PATH = "/Users/jing/Documents/yutong/test files/EXPRESS 1.td5"
|
|
|
|
def load():
|
|
return open(PATH, 'rb').read()
|
|
|
|
def records(d=None):
|
|
if d is None: d = load()
|
|
out = []
|
|
off = 0x200
|
|
ptrs = []
|
|
while True:
|
|
p = struct.unpack_from('<I', d, off)[0]
|
|
off += 4
|
|
if p == 0xffffffff: break
|
|
ptrs.append(p)
|
|
for p in ptrs:
|
|
name = d[p:p+0x10].decode('latin1').rstrip()
|
|
name2 = d[p+0x10:p+0x20].decode('latin1').rstrip()
|
|
blkoff, blklen = struct.unpack_from('<II', d, p+0x30)
|
|
blk = d[blkoff:blkoff+blklen]
|
|
field = struct.unpack_from('<I', blk, 1)[0]
|
|
out.append(dict(ptr=p, name=name, name2=name2, blkoff=blkoff, blklen=blklen, blk=blk, field=field))
|
|
return out
|
|
|
|
if __name__ == '__main__':
|
|
d = load()
|
|
rs = records(d)
|
|
print(len(rs), 'records')
|
|
for r in rs:
|
|
print("%-18s ptr=%06x blkoff=%06x blklen=%04x field=%08x" % (
|
|
r['name'], r['ptr'], r['blkoff'], r['blklen'], r['field']))
|