Files
viktorиClaude Opus 4.8 ae36c3b729 RT-4D: реверс прошивки, русификация, кастомный UI, флешеры
- Полный RE стока V3.25 (Cortex-M4F) + FM100B: карта памяти, протокол, codeplug, UI-архитектура
- Русификация: свой CP1251-шрифт + патч рендера, перевод меню и надписей, ребренд Ru-4D V3.25
- Блюпринт переделки UI + C-тулчейн (clang thumbv7em), доказан инъекцией
- Готовые флешеры: WebSerial .html и Windows .exe со вшитой прошивкой
- Дамп SPI рации, стоковая прошивка, инструменты сборки

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:47:22 +09:00

119 строки
4.3 KiB
Python

#!/usr/bin/env python3
"""First-look recon of the RT-4D 4MB SPI dump."""
import sys, string
DUMP = r"C:/Users/vikto/Documents/Claude/rt-4d/radio-spi-dump.bin"
data = open(DUMP, "rb").read()
N = len(data)
print(f"== FILE == {N} bytes (0x{N:X}) = {N/1024/1024:.2f} MB\n")
# Known map (from jcalado constants.py)
SPI_REGIONS = {
"calibration": (0x000000, 0x001000, 0x40),
"main_settings": (0x002000, 0x001000, 0x90),
"channels": (0x004000, 0x00C000, 0x91),
"zones": (0x01C000, 0x020000, 0x92),
"contacts": (0x05C000, 0x010000, 0x93),
"groups": (0x07C000, 0x003000, 0x94),
"dmr_keys": (0x082000, 0x003000, 0x95),
"call_log": (0x088000, 0x00C000, 0x96),
"default_sms": (0x094000, 0x001000, 0x97),
"msg_drafts": (0x095000, 0x010000, None),
"msg_inbox": (0x0A5000, 0x010000, None),
"msg_outbox": (0x0B5000, 0x010000, None),
"schedules": (0x0C6000, 0x008000, 0x98),
"dtmf_names": (0x0C7000, 0x000100, 0x80),
"fm_settings": (0x0D6000, 0x001000, 0x99),
}
def classify(b):
if all(x == 0xFF for x in b): return "empty(FF)"
if all(x == 0x00 for x in b): return "zero(00)"
return "DATA"
# ---- occupancy map at 4KB granularity ----
PAGE = 0x1000
pages = N // PAGE
kinds = [classify(data[i*PAGE:(i+1)*PAGE]) for i in range(pages)]
# collapse to runs
print("== OCCUPANCY (4KB pages) ==")
runs = []
s = 0
for i in range(1, pages+1):
if i == pages or kinds[i] != kinds[s]:
runs.append((s*PAGE, i*PAGE, kinds[s]))
s = i
data_bytes = sum(1 for k in kinds if k == "DATA")*PAGE
for a, b, k in runs:
tag = ""
if k == "DATA":
# which known regions overlap
names = [n for n,(ra,rs,_) in SPI_REGIONS.items() if ra < b and ra+rs > a]
tag = " <- " + (",".join(names) if names else "??? UNKNOWN")
print(f" 0x{a:06X}-0x{b:06X} {(b-a)//1024:>5}KB {k}{tag}")
print(f"\n DATA pages total: {data_bytes/1024:.0f}KB of {N/1024:.0f}KB")
# ---- known region occupancy check ----
print("\n== KNOWN REGIONS (populated?) ==")
for name,(addr,size,rid) in SPI_REGIONS.items():
if addr+size > N:
print(f" {name:14} 0x{addr:06X} OUT OF RANGE"); continue
reg = data[addr:addr+size]
nonff = sum(1 for x in reg if x != 0xFF)
first = reg[:12].hex(' ')
rids = "None" if rid is None else hex(rid)
print(f" {name:14} 0x{addr:06X}+0x{size:05X} rid={rids:>4} nonFF={nonff:>6}/{size:<6} first={first}")
# ---- DTCN magic (settings bank detection) ----
print("\n== DTCN magic (beta bank markers) ==")
for off in (0x002FFC, 0x003FFC):
print(f" @0x{off:06X}: {data[off:off+4]!r}")
mcount = data.count(b'DTCN')
print(f" total 'DTCN' occurrences in dump: {mcount}")
# ---- calibration hexdump ----
print("\n== CALIBRATION region 0x000000..0x000100 ==")
for r in range(0, 0x100, 16):
chunk = data[r:r+16]
hexs = ' '.join(f'{x:02X}' for x in chunk)
asc = ''.join(chr(x) if 32<=x<127 else '.' for x in chunk)
print(f" {r:04X} {hexs} {asc}")
# ---- address book / high region probe ----
print("\n== HIGH REGION (0x0E0000+) probe ==")
for addr in (0x0D7000, 0x0E0000, 0x100000, 0x200000, 0x300000, 0x3F0000):
reg = data[addr:addr+64]
nonff = sum(1 for x in reg if x != 0xFF)
asc = ''.join(chr(x) if 32<=x<127 else '.' for x in reg[:32])
print(f" 0x{addr:06X} nonFF={nonff}/64 ascii={asc!r}")
# ---- strings ----
print("\n== STRINGS (printable runs >=4, first 60) ==")
printable = set(bytes(string.printable[:-6], 'ascii'))
runs_s = []
cur = bytearray(); start = 0
for i,x in enumerate(data):
if 32 <= x < 127:
if not cur: start = i
cur.append(x)
else:
if len(cur) >= 4: runs_s.append((start, bytes(cur)))
cur = bytearray()
if len(cur) >= 4: runs_s.append((start, bytes(cur)))
print(f" total string runs: {len(runs_s)}")
for off, s in runs_s[:60]:
try: txt = s.decode('ascii')
except: txt = repr(s)
print(f" 0x{off:06X} {txt}")
# ---- first channel records ----
print("\n== CHANNEL records @0x004000 (48 bytes each) ==")
CH = 0x004000; SZ = 48
for i in range(6):
rec = data[CH+i*SZ:CH+i*SZ+SZ]
if all(x==0xFF for x in rec):
print(f" ch{i}: <empty>"); continue
print(f" ch{i}: {rec.hex(' ')}")
asc = ''.join(chr(x) if 32<=x<127 else '.' for x in rec)
print(f" ascii: {asc}")