- Полный 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>
96 строки
3.5 KiB
Python
96 строки
3.5 KiB
Python
import struct, collections, 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()
|
|
|
|
def hd(off,n=128,base=None):
|
|
base=off if base is None else base
|
|
out=[]
|
|
for i in range(0,n,16):
|
|
c=data[off+i:off+i+16]
|
|
if not c:break
|
|
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'{base+i:08x} {h:<47} {a}')
|
|
return '\n'.join(out)
|
|
|
|
# 0x14C000: GBK char table. Decode as GBK 2-byte
|
|
print("### 0x14C000 GBK char list (first 40 chars) ###")
|
|
seg=data[0x14c000:0x14c000+80]
|
|
chars=[]
|
|
i=0
|
|
while i < len(seg)-1:
|
|
b=seg[i]
|
|
if b>=0x81:
|
|
try:
|
|
ch=bytes([seg[i],seg[i+1]]).decode('gbk')
|
|
chars.append(ch); i+=2; continue
|
|
except: pass
|
|
i+=1
|
|
print(''.join(chars))
|
|
# Size and how it ends
|
|
end=0x14c000+0xB000
|
|
print("tail of 0x14C000 region:")
|
|
print(hd(end-64,64))
|
|
|
|
# Correlate: 0x14C000 is a GBK char table (34KB?) and 0x164000 pinyin (200KB).
|
|
# The pinyin region: are entries fixed 6 bytes each -> index maps to char in char table?
|
|
print("\n### relationship: char count vs pinyin count ###")
|
|
# count 2-byte gbk chars in 0x14C000..0x157000 (44KB used, then zeros)
|
|
charseg=data[0x14c000:0x157000]
|
|
nchars=0
|
|
i=0
|
|
while i<len(charseg)-1:
|
|
if charseg[i]>=0x81 and charseg[i+1]!=0x00:
|
|
nchars+=1; i+=2
|
|
else:
|
|
i+=1
|
|
print(f"approx gbk chars in 0x14C000-0x157000 (44KB): {nchars}")
|
|
# pinyin at 0x164000, 6 bytes each, over 200KB
|
|
print(f"pinyin entries if 6-byte: {0x32000//6}")
|
|
print(f"pinyin entries if 8-byte: {0x32000//8}")
|
|
|
|
# Check pinyin stride precisely: look for the pattern of fixed-width fields
|
|
print("\n### pinyin stride check: line up 'kao','shang','xia' ###")
|
|
# they appeared as 6-char fields: 'kao ','shang ','xia '
|
|
for w in range(4,10):
|
|
# test if every field is exactly w and space padded
|
|
ok=True
|
|
for k in range(20):
|
|
f=data[0x164000+k*w:0x164000+k*w+w]
|
|
# must be ascii letters+spaces
|
|
if not all(32<=b<127 for b in f): ok=False;break
|
|
if ok:
|
|
fields=[data[0x164000+k*w:0x164000+k*w+w] for k in range(12)]
|
|
print(f"w={w}: {[f.decode() for f in fields]}")
|
|
|
|
# 0x100000 region: mostly zeros with some ascii. dump non-zero area
|
|
print("\n### 0x100000 non-zero content ###")
|
|
for i in range(0,0x1000,16):
|
|
c=data[0x100000+i:0x100000+i+16]
|
|
if any(b!=0 for b in c):
|
|
h=' '.join(f'{b:02x}' for b in c)
|
|
a=''.join(chr(b) if 32<=b<127 else '.' for b in c)
|
|
print(f'{0x100000+i:08x} {h:<47} {a}')
|
|
|
|
# 0x03E000 DMRhub: bytes 0-3 = 00 00 00 00 then name. This resembles radio identity/owner?
|
|
# main_settings radio_name at cfg+76. Check cfg region for DMRhub
|
|
print("\n### search whole dump for 'DMRhub' ###")
|
|
o=0
|
|
while True:
|
|
o=data.find(b'DMRhub',o)
|
|
if o<0:break
|
|
print(f" @{o:08x}: {hd(o-8,32,o-8)}")
|
|
o+=1
|
|
|
|
# 0x0D0000 keys: is this the 'groups'/grouplist backup? name 'Key N', b0=index, tail has index at +20
|
|
# Compare with grouplist format (b1==0x01 enabled, name @0x02). Here b0=idx,b1=00,name@2.
|
|
# Actually looks like encryption keys OR group lists. Check groups region 0x07C000
|
|
print("\n### groups region 0x07C000 ###")
|
|
print(hd(0x07c000,96))
|
|
# and encrypt region in .4rdmf is separate. Let's see full first key record 48 bytes across
|
|
print("\n### 0x0D0000 full stride verify: is it 0x30? show rec 100 ###")
|
|
print(hd(0x0d0000+100*0x30,48))
|
|
print("last populated:")
|
|
print(hd(0x0d0000+509*0x30,48))
|