- Полный 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>
79 строки
3.6 KiB
Python
79 строки
3.6 KiB
Python
import struct, sys, io
|
|
sys.stdout=io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
DUMP=r"C:/Users/vikto/Documents/Claude/rt-4d/radio-spi-dump.bin"
|
|
data=open(DUMP,'rb').read()
|
|
|
|
# 0x0D0000 key region: list all populated records, their index byte0-1 (LE u16) and names
|
|
print("### 0x0D0000 key records: idx(le16)/name ###")
|
|
recs=[]
|
|
for i in range(24576//0x30):
|
|
o=0x0d0000+i*0x30
|
|
if data[o]==0xff and data[o+1]==0xff: continue
|
|
idx=data[o]|(data[o+1]<<8)
|
|
name=data[o+2:o+16].split(b'\xff')[0].decode('latin1','ignore')
|
|
tail=data[o+16:o+24].hex()
|
|
recs.append((i,idx,name,tail))
|
|
print(f"total populated: {len(recs)}")
|
|
print("first 3:", recs[:3])
|
|
print("records 252-258:", recs[252:258])
|
|
print("last 3:", recs[-3:])
|
|
# check monotonic idx
|
|
idxs=[r[1] for r in recs]
|
|
print("idx min",min(idxs),"max",max(idxs),"monotonic:",idxs==sorted(idxs))
|
|
print("count distinct idx:",len(set(idxs)))
|
|
|
|
# main_settings region: is 0x010000 exactly channels backup? 0x010000 is inside zones region (0x01C000 starts later).
|
|
# Actually 0x010000 is BEFORE channels end? channels=0x004000+0xC000=0x010000. So 0x010000 is the byte right AFTER channels!
|
|
print("\n### boundary check ###")
|
|
print("channels region: 0x004000 .. 0x010000 (end).")
|
|
print("So 0x010000 is a SEPARATE region right after channels, holding a 2nd copy of first 2 channels.")
|
|
# zones region 0x01C000; but DMRhub found at 0x01e004 (inside zones) and 0x03e004.
|
|
# 0x01C000+0x20000=0x03C000 end of zones. 0x03E000 is AFTER zones.
|
|
print("zones region: 0x01C000 .. 0x03C000. DMRhub at 0x01e004 is INSIDE zones region.")
|
|
print("0x03E000 (after zones) also has DMRhub -> a backup bank of the 0x01E000 data.")
|
|
print(" -> 0x01E000 and 0x03E000 are paired A/B banks, 0x20000 apart (ZONE_AB_BANK_OFFSET).")
|
|
print(f" 0x03E000-0x01E000 = 0x{0x03E000-0x01E000:X}")
|
|
|
|
# 0x010000: is there a matching bank 0x20000 later? 0x010000+0x20000=0x030000
|
|
print("\n### does 0x010000 have A/B twin at 0x030000? ###")
|
|
same=sum(1 for i in range(0x1000) if data[0x010000+i]==data[0x030000+i])
|
|
print(f"0x010000 vs 0x030000 match: {same}/{0x1000}")
|
|
# hexdump 0x030000
|
|
def hd(off,n=64):
|
|
out=[]
|
|
for i in range(0,n,16):
|
|
c=data[off+i:off+i+16]
|
|
h=' '.join(f'{b:02x}' for b in c)
|
|
a=''.join(chr(b) if 32<=b<127 else '.' for b in c)
|
|
out.append(f'{off+i:08x} {h:<47} {a}')
|
|
return '\n'.join(out)
|
|
print(hd(0x030000,80))
|
|
|
|
# What region is 0x010000 really? It has channel records. channels region is 1024*48=0xC000 exactly.
|
|
# 0x004000..0x010000 IS the channels region (0xC000). 0x010000 begins the NEXT thing.
|
|
# But 0x010000 duplicates ch0/ch1. This is the beta41 "channels bank B"? No - stock.
|
|
# More likely: 0x010000 = VFO/current-operating channel scratch (VFO A and VFO B saved as channel records)
|
|
print("\n### 0x010000 interpretation: VFO A/B saved channels ###")
|
|
print(hd(0x010000,96))
|
|
|
|
# 0x126000 records: confirm 32-byte call-log entries with timestamps. Count valid.
|
|
print("\n### 0x126000 call-log-style: count records with valid date ###")
|
|
valid=0
|
|
for i in range(20480//32):
|
|
o=0x126000+i*32
|
|
yy=data[o+14]
|
|
if 20<=yy<=40: # plausible year 2020-2040
|
|
valid+=1
|
|
print(f"records with plausible date byte (yy 20-40): {valid}")
|
|
# show last few valid
|
|
for i in range(20480//32):
|
|
o=0x126000+i*32
|
|
if data[o+14]==0 and all(b==0 for b in data[o:o+20]):
|
|
print(f"first all-zero record at idx {i} (@{o:08x})")
|
|
break
|
|
|
|
# 0x100000: bitmap? width guess. Non-zero starts 0x190. Try rendering as 1bpp.
|
|
print("\n### 0x100000 as bitmap: nonzero span ###")
|
|
nz=[i for i in range(0x1000) if data[0x100000+i]!=0]
|
|
print(f"nonzero bytes: {len(nz)}, span 0x{nz[0]:x}..0x{nz[-1]:x}")
|