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>
Этот коммит содержится в:
@@ -0,0 +1,79 @@
|
||||
import struct, collections
|
||||
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)
|
||||
|
||||
print("### 0x010000 vs channels 0x004000 (compare first 0x1000) ###")
|
||||
same=sum(1 for i in range(0x1000) if data[0x010000+i]==data[0x004000+i])
|
||||
print(f"match to channels@0x4000: {same}/{0x1000} = {100*same/0x1000:.1f}%")
|
||||
# channel region 0x004000 first records
|
||||
print("channels@0x004000 head:")
|
||||
print(hd(0x004000,96))
|
||||
print("region@0x010000 head:")
|
||||
print(hd(0x010000,96))
|
||||
|
||||
print("\n### 0x03E000 DMRhub context: this looks like a contact/radioid record ###")
|
||||
print(hd(0x03e000,32))
|
||||
# Is contacts region at 0x05c000? compare structure - here name at offset 4
|
||||
print("contacts region @0x05e000 (occupied):")
|
||||
print(hd(0x05e000,128))
|
||||
|
||||
print("\n### 0x0D0000 Key records - 48 byte stride. Compare vs dmr_keys 0x082000 ###")
|
||||
print("dmr_keys @0x082000:")
|
||||
print(hd(0x082000,160))
|
||||
print("encrypt-in-region @0x0D0000 rec0/rec1 detail:")
|
||||
print(hd(0x0d0000,48))
|
||||
# check stride: 'Key N' every 0x30
|
||||
for i in range(5):
|
||||
o=0x0d0000+i*0x30
|
||||
print(f"rec{i} @{o:08x}: b0={data[o]:02x} b1={data[o+1]:02x} name={data[o+2:o+16]!r} tail={data[o+16:o+24].hex()}")
|
||||
|
||||
print("\n### how many Key records populated in 0x0D0000 (+24KB) ###")
|
||||
cnt=0; last=None
|
||||
for i in range(24576//0x30):
|
||||
o=0x0d0000+i*0x30
|
||||
if data[o]!=0xff and data[o]!=0x00:
|
||||
cnt+=1; last=i
|
||||
print(f"populated key records: {cnt}, last idx {last}")
|
||||
|
||||
print("\n### 0x126000 record analysis (32-byte stride) ###")
|
||||
for i in range(8):
|
||||
o=0x126000+i*32
|
||||
r=data[o:o+32]
|
||||
print(f"rec{i} @{o:08x}: {r.hex()}")
|
||||
# decode fields: bytes 14,15,16 = 19 0a 0e -> 0x19=25,0x0a=10,0x0e=14 => date 2025-10-14
|
||||
print("byte14-19 as date candidates (YY MM DD HH MM SS):")
|
||||
for i in range(8):
|
||||
o=0x126000+i*32
|
||||
r=data[o:o+32]
|
||||
print(f" rec{i}: {r[14]:02d}-{r[15]:02d}-{r[16]:02d} {r[17]:02d}:{r[18]:02d}:{r[19]:02d} b1(type)={r[1]} dmr_le={struct.unpack('<I',r[6:10])[0]} field10={struct.unpack('<I',r[10:14])[0]}")
|
||||
# count records
|
||||
cnt=sum(1 for i in range(20480//32) if data[0x126000+i*32]!=0x00 or any(b!=0 for b in data[0x126000+i*32:0x126000+i*32+16]))
|
||||
print(f"nonzero-ish records among {20480//32}")
|
||||
# call_log region is 0x088000; occupied run was 0x092000. compare
|
||||
print("\ncall_log region occupied @0x092000:")
|
||||
print(hd(0x092000,128))
|
||||
|
||||
print("\n### 0x14C000 GBK decode sample ###")
|
||||
seg=data[0x14c000:0x14c000+256]
|
||||
try:
|
||||
print(seg.decode('gbk',errors='replace')[:120])
|
||||
except Exception as e:
|
||||
print("err",e)
|
||||
|
||||
print("\n### 0x164000 pinyin table structure ###")
|
||||
# each entry seems 6 bytes ascii pinyin. period ratio peaked at 6
|
||||
print(hd(0x164000,96))
|
||||
# Check: is this indexed by GBK char? Look further in
|
||||
print(hd(0x170000,96))
|
||||
print("region 0x164000 size 200KB -> 200KB/6 ~ 34000 entries")
|
||||
Ссылка в новой задаче
Block a user