Files
viktor 5f4d207aa5 Мост к DMR-модулю, разбор спектра REFV DualTachyon, карта запчастей прошивки
Баузбенд FM100B:
- найден прозрачный мост ПК<->модуль (включение с зажатой МЕНЮ), проверен живьём
- расшифрована контрольная сумма кадров (one's complement, BE), сверена с ответом рации
- инструмент tools/fm100b.py (ping/send/raw/scan)
- cmd 0x25 = запрос версии, модуль отвечает V1.2.0.32 (совпало с офиц. образом)
- разобран диспетчер входящих кадров: 193 записи, реальный код у 10 команд
- cmd 0x59 = индикация приёма, она же управляет гейтом звука PA14
- исправлено: boot-handshake это cmd 0x84, а не 0x64 (research/re/dmr.md)

Спектроанализатор REFV DualTachyon (docs/refw-spectrum.md):
- вызывается как функция горячей клавиши №22 Analog Spectrum
- вход 0x08009CAA -> обычный 0x080139E4 / по зоне 0x08015318
- спектр это экран №11; тик 0x08013BA4 (автомат на 4 состояния), клавиши 0x08013BFC

Декомпозиция (docs/firmware-parts.md):
- два процессора + внешний SPI = три канала внедрения
- карта ресурсов SPI: шрифты, пиньинь, голос, таблица Unicode
- найден штатный загрузчик ресурсов FontVoicePicture (шрифты/голос/картинки)
- дерево меню целиком: MIC/SPK Gain, RX/TX Limit, SMS Format уже в стоке
- аудио двухступенчатое: PA2 питание УНЧ, PA14 гейт от DMR-модуля

Прочее:
- везде исправлен режим прошивки: тангента PTT вместо клавиши "*"
- устаревший Ru-4D_Flasher.exe удалён из репозитория
- добавлены инструменты реверса: xref, refs, gpiomap, gpioscan, schem
2026-07-22 21:17:20 +09:00

77 строки
2.9 KiB
Python

#!/usr/bin/env python3
"""Map every GPIO write in the RT-4D app: find calls to the low-level GPIO
helpers and back-resolve (port, mask, state) from the preceding instructions."""
import struct, re, capstone, os
BASE = 0x08002800
P = "/home/viktor/claude/rt-4d/stock-fw/rt4d_stock_v3.25_abs_0x08002800.bin"
IMG = open(P, "rb").read()
md = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
PORTS = {0x40020000: "GPIOA", 0x40020400: "GPIOB", 0x40020800: "GPIOC",
0x40020C00: "GPIOD", 0x40021000: "GPIOE", 0x40021400: "GPIOF",
0x40021800: "GPIOG"}
# low-level writers discovered by disassembly
HELPERS = {0x08021C6E: "gpio_set_state(port,mask,state)",
0x08021C56: "gpio_bsrr_write(port,mask)",
0x08021C4C: "set_bit0(reg,val)"}
def word(va):
o = va - BASE
return struct.unpack_from("<I", IMG, o)[0] if 0 <= o <= len(IMG) - 4 else None
def lit_of(ins):
m = re.search(r"\[pc, #(?:0x)?([0-9a-fA-F]+)\]", ins.op_str)
if m and ins.mnemonic.startswith("ldr"):
return word(((ins.address + 4) & ~3) + int(m.group(1), 16))
return None
# linear sweep, keep a sliding window to resolve register values
def scan():
hits = []
win = []
for ins in md.disasm(IMG, BASE):
win.append(ins)
if len(win) > 14:
win.pop(0)
if ins.mnemonic == "bl" and ins.op_str.startswith("#"):
tgt = int(ins.op_str[1:], 16)
if tgt in HELPERS:
regs = {}
for p in win[:-1]:
mm = re.match(r"(r\d+)", p.op_str)
dst = mm.group(1) if mm else None
if not dst:
continue
v = lit_of(p)
if v is not None:
regs[dst] = v
elif p.mnemonic in ("movs", "mov.w", "mov") and "#" in p.op_str:
try:
regs[dst] = int(p.op_str.split("#")[1], 0)
except Exception:
pass
elif p.mnemonic == "movw" and "#" in p.op_str:
try:
regs[dst] = int(p.op_str.split("#")[1], 0)
except Exception:
pass
hits.append((ins.address, HELPERS[tgt], regs.get("r0"), regs.get("r1"), regs.get("r2")))
return hits
if __name__ == "__main__":
rows = scan()
print(f"{len(rows)} GPIO-helper call sites\n")
print(f"{'site':<12}{'helper':<32}{'r0(port/reg)':<22}{'r1(mask/val)':<14}{'r2'}")
for site, h, r0, r1, r2 in rows:
pn = PORTS.get(r0, f"0x{r0:08X}" if r0 is not None else "?")
mask = f"0x{r1:X}" if r1 is not None else "?"
bit = ""
if r1 and r1 and (r1 & (r1 - 1)) == 0:
bit = f" (bit{r1.bit_length()-1})"
print(f"0x{site:08X} {h:<32}{pn:<22}{mask+bit:<14}{r2 if r2 is not None else ''}")