FM100B = Sicomm SCT3258TD (документирован); Talker Alias, агент, SPI-инструменты, восстановление TX
Главное: - Опознан DMR-чип: FM100B = Sicomm SCT3258TD. Датащит Packet Interface (126 стр) в docs/. Открывает калибровку/AMBE/вокодер по документации. Родня: UV-4R, китайские клоны. Реализовано: - Приём Talker Alias (cmd 0x60) — позывной крупно на экране (build_ta.py) - Интерактивный агент — управление FM100B по COM без перешивки (build_agent.py + agent.py) - Полная карта команд FM100B с живой рации (fm100b-commands.md) - Работа с SPI: чтение/запись/восстановление codeplug и калибровки (spi_read/spi_wr/spi_restore) - Конструктор дисплея + редактор прошивки (tools/display-editor) Документация: - docs/SCT3258_packet_interface.txt/.pdf — спека DMR-чипа - docs/fm100b-commands.md — карта команд - docs/dmr-tx-recovery.md — диагноз/восстановление DMR-передачи Инструменты: flash_ps.py (прошивка через WSL/powershell), sniff.py (снифер кадров), hotspot_rxoffset_sweep.py, apply_patch.py, build_* сборщики. Важно: DMR TX-калибровка сбита в экспериментах со SET-командами модулю; восстановление через offset-компенсацию / .updat Radtel / калибровку по прибору (docs/dmr-tx-recovery.md).
Этот коммит содержится в:
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Диагностическая сборка: зеркалит все кадры от DMR-модуля в USB-порт.
|
||||
|
||||
Перехватывается единственный вызов `fm100b_on_frame` (0x08006348) из разборщика
|
||||
кадров. Наша функция выдаёт в компьютерный порт маркер F1 F2, длину и сам кадр,
|
||||
после чего передаёт управление штатному обработчику — логика радио не меняется.
|
||||
|
||||
python3 build_sniffer.py # -> stock-fw/rt4d_sniffer.bin
|
||||
python3 tools/flash_ps.py stock-fw/rt4d_sniffer.bin
|
||||
python3 tools/sniff.py 60 # слушать 60 секунд
|
||||
|
||||
Сборка временная: для обычной работы вернуть stock-fw/rt4d_ru_batt.bin.
|
||||
"""
|
||||
import struct
|
||||
from keystone import Ks, KS_ARCH_ARM, KS_MODE_THUMB
|
||||
|
||||
BASE = 0x08002800
|
||||
SRC = "stock-fw/rt4d_ru_batt.bin"
|
||||
OUT = "stock-fw/rt4d_sniffer.bin"
|
||||
|
||||
ON_FRAME = 0x08006348 # void fm100b_on_frame(u8 *frame)
|
||||
CALL_SITE = 0x08018DD8 # единственный bl на него
|
||||
TX_BYTE = 0x08018BFC # void usart6_tx_byte(u8) — порт к компьютеру
|
||||
|
||||
|
||||
def thumb_bl(src, dst):
|
||||
off = (dst - (src + 4)) >> 1
|
||||
s = (off >> 23) & 1; i1 = (off >> 22) & 1; i2 = (off >> 21) & 1
|
||||
imm10 = (off >> 11) & 0x3FF; imm11 = off & 0x7FF
|
||||
j1 = (~i1 ^ s) & 1; j2 = (~i2 ^ s) & 1
|
||||
return struct.pack("<HH", 0xF000 | (s << 10) | imm10,
|
||||
0xD000 | (j1 << 13) | (j2 << 11) | imm11)
|
||||
|
||||
|
||||
def main():
|
||||
img = bytearray(open(SRC, "rb").read())
|
||||
end = BASE + len(img)
|
||||
code_at = (end + 3) & ~3
|
||||
img += b"\xff" * (code_at - end)
|
||||
print(f"основа: {SRC}, {len(img)} Б; свой код с 0x{code_at:08X}")
|
||||
|
||||
o = CALL_SITE - BASE
|
||||
assert bytes(img[o:o + 4]) == thumb_bl(CALL_SITE, ON_FRAME), \
|
||||
f"по 0x{CALL_SITE:08X} не вызов обработчика кадров"
|
||||
print("точка перехвата подтверждена")
|
||||
|
||||
ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB)
|
||||
|
||||
# --- пролог: длина кадра = 9 + BE16(len), маркер F1 F2 + длина ---
|
||||
partA = bytes(ks.asm(f"""
|
||||
push {{r4, r5, r6, lr}}
|
||||
mov r4, r0
|
||||
ldrb r5, [r4, #6]
|
||||
ldrb r6, [r4, #7]
|
||||
orr.w r5, r6, r5, lsl #8
|
||||
adds r5, #9
|
||||
uxtb r5, r5
|
||||
movs r0, #0xF1
|
||||
bl #{TX_BYTE}
|
||||
movs r0, #0xF2
|
||||
bl #{TX_BYTE}
|
||||
mov r0, r5
|
||||
bl #{TX_BYTE}
|
||||
movs r6, #0
|
||||
""", code_at)[0])
|
||||
|
||||
# --- тело цикла (без обратного перехода) ---
|
||||
loop_at = code_at + len(partA)
|
||||
body = bytes(ks.asm(f"""
|
||||
ldrb r0, [r4, r6]
|
||||
bl #{TX_BYTE}
|
||||
adds r6, #1
|
||||
cmp r6, r5
|
||||
""", loop_at)[0])
|
||||
|
||||
# --- blt назад на начало цикла (keystone не умеет метки) ---
|
||||
br_at = loop_at + len(body)
|
||||
off = (loop_at - (br_at + 4)) >> 1
|
||||
blt = struct.pack("<H", 0xDB00 | (off & 0xFF))
|
||||
|
||||
# --- эпилог: передать кадр штатному обработчику ---
|
||||
tail_at = br_at + 2
|
||||
tail = bytes(ks.asm(f"""
|
||||
mov r0, r4
|
||||
bl #{ON_FRAME}
|
||||
pop {{r4, r5, r6, pc}}
|
||||
""", tail_at)[0])
|
||||
|
||||
code = partA + body + blt + tail
|
||||
img += code
|
||||
img[o:o + 4] = thumb_bl(CALL_SITE, code_at)
|
||||
print(f"снифер: {len(code)} Б, цикл на 0x{loop_at:08X}, blt смещение {off}")
|
||||
|
||||
open(OUT, "wb").write(bytes(img))
|
||||
print(f"готово: {OUT}, {len(img)} Б")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Ссылка в новой задаче
Block a user