Главное: - Опознан 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).
162 строки
6.8 KiB
Python
162 строки
6.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Напряжение батареи вместо штатной иконки + уборка полосы у всплывающего окна.
|
|
|
|
Патчи:
|
|
1. Свой код в свободном месте после конца образа (0x08029000):
|
|
stub — пустышка `bx lr`
|
|
batt_draw — рисует "8.4V" мелким шрифтом 5x8 на странице 0
|
|
tick_router — вызывает оригинал, затем batt_draw (только на главном экране)
|
|
2. `bl 0x08008300` @0x080148E2 — отрисовка штатной шкалы -> stub (иконки больше нет).
|
|
3. `bl 0x08010A00` @0x0801E2A4 — «напряжение -> индекс 0..3» убрано (2 x nop),
|
|
чтобы флаг перерисовки взводился на каждые 0.1 В, а не только на порогах.
|
|
4. `bl 0x0800842C` @0x080085DE — верхняя полоса рамки всплывающего окна убрана.
|
|
5. `bl 0x0801DFD0` @0x08020806 — последний вызов цепочки отрисовки в ui_tick_normal
|
|
перенаправлен на tick_router.
|
|
|
|
Почему через конец цикла: раньше мы рисовали из середины кадра (из цепочки обновления
|
|
батареи), и любой более поздний вывод затирал цифры — при входящем вызове «7» теряла
|
|
перекладину и читалась как «1». Теперь наш текст ложится последним и перекрывает всё.
|
|
"""
|
|
import struct
|
|
from keystone import Ks, KS_ARCH_ARM, KS_MODE_THUMB
|
|
|
|
BASE = 0x08002800
|
|
SRC = "stock-fw/rt4d_ru_full.bin"
|
|
OUT = "stock-fw/rt4d_ru_batt.bin"
|
|
|
|
SMALL_CHAR = 0x080089AC # void small_char(u8 page, u8 x, u8 ch, u8 dim)
|
|
BATT_VOLT = 0x200008B0 # u16, напряжение * 10
|
|
G_SCREEN = 0x200008B3 # u8, текущий экран (0 = главный)
|
|
|
|
GAUGE_CALL = 0x080148E2 # bl 0x08008300 — штатная шкала батареи
|
|
GAUGE_FUNC = 0x08008300
|
|
LEVEL_CALL = 0x0801E2A4 # bl 0x08010A00 — напряжение -> индекс
|
|
LEVEL_FUNC = 0x08010A00
|
|
TOPBAR_CALL = 0x080085DE # bl fill_col_run(page=1) — верхняя полоса рамки
|
|
FILL_RUN = 0x0800842C
|
|
TICK_CALL = 0x08020806 # bl 0x0801DFD0 — последний вызов отрисовки за кадр
|
|
TICK_FUNC = 0x0801DFD0
|
|
|
|
MYCODE = 0x08029000
|
|
X0, X1, X2, X3 = 108, 113, 118, 123 # позиции четырёх символов
|
|
|
|
|
|
def thumb_bl(src: int, dst: int) -> bytes:
|
|
off = dst - (src + 4)
|
|
assert -(1 << 24) <= off < (1 << 24), "цель BL слишком далеко"
|
|
off >>= 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())
|
|
print(f"исходный образ: {len(img)} Б, конец на 0x{BASE + len(img) - 1:08X}")
|
|
assert BASE + len(img) == MYCODE, "образ не заканчивается там, где ждём код"
|
|
ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB)
|
|
|
|
def check(addr, target, what):
|
|
o = addr - BASE
|
|
cur = bytes(img[o:o + 4])
|
|
assert cur == thumb_bl(addr, target), \
|
|
f"по 0x{addr:08X} ({what}) не тот вызов: {cur.hex(' ')}"
|
|
return o
|
|
|
|
o_gauge = check(GAUGE_CALL, GAUGE_FUNC, "шкала батареи")
|
|
o_level = check(LEVEL_CALL, LEVEL_FUNC, "напряжение->индекс")
|
|
o_bar = check(TOPBAR_CALL, FILL_RUN, "верхняя полоса рамки")
|
|
o_tick = check(TICK_CALL, TICK_FUNC, "хвост отрисовки")
|
|
print("все четыре точки патча подтверждены")
|
|
|
|
# ---- раскладка своего кода ----
|
|
stub_addr = MYCODE
|
|
stub = bytes(ks.asm("bx lr", stub_addr)[0])
|
|
|
|
batt_addr = stub_addr + len(stub)
|
|
batt_asm = f"""
|
|
push {{r4, r5, r6, lr}}
|
|
movw r4, #{BATT_VOLT & 0xFFFF}
|
|
movt r4, #{(BATT_VOLT >> 16) & 0xFFFF}
|
|
ldrh r4, [r4]
|
|
uxtb r4, r4
|
|
movs r1, #205
|
|
muls r1, r4, r1
|
|
lsrs r5, r1, #11
|
|
movs r1, #10
|
|
muls r1, r5, r1
|
|
subs r4, r4, r1
|
|
movs r0, #0
|
|
movs r1, #{X0}
|
|
mov r2, r5
|
|
add r2, #0x30
|
|
movs r3, #0
|
|
bl #{SMALL_CHAR}
|
|
movs r0, #0
|
|
movs r1, #{X1}
|
|
movs r2, #0x2E
|
|
movs r3, #0
|
|
bl #{SMALL_CHAR}
|
|
movs r0, #0
|
|
movs r1, #{X2}
|
|
mov r2, r4
|
|
add r2, #0x30
|
|
movs r3, #0
|
|
bl #{SMALL_CHAR}
|
|
movs r0, #0
|
|
movs r1, #{X3}
|
|
movs r2, #0x56
|
|
movs r3, #0
|
|
bl #{SMALL_CHAR}
|
|
pop {{r4, r5, r6, pc}}
|
|
"""
|
|
batt = bytes(ks.asm(batt_asm, batt_addr)[0])
|
|
|
|
# tick_router: оригинал -> проверка экрана -> batt_draw.
|
|
# keystone не умеет метки, поэтому условный переход кодируем вручную.
|
|
tick_addr = batt_addr + len(batt)
|
|
partA = bytes(ks.asm(f"""
|
|
push {{r4, lr}}
|
|
bl #{TICK_FUNC}
|
|
movw r4, #{G_SCREEN & 0xFFFF}
|
|
movt r4, #{(G_SCREEN >> 16) & 0xFFFF}
|
|
ldrb r4, [r4]
|
|
cmp r4, #0
|
|
""", tick_addr)[0])
|
|
bne_addr = tick_addr + len(partA)
|
|
partB = bytes(ks.asm(f"bl #{batt_addr}", bne_addr + 2)[0])
|
|
bne = struct.pack("<H", 0xD100 | (((len(partB) - 2) // 2) & 0xFF)) # bne -> за partB
|
|
partC = bytes(ks.asm("pop {r4, pc}", bne_addr + 2 + len(partB))[0])
|
|
tick = partA + bne + partB + partC
|
|
|
|
code = stub + batt + tick
|
|
print(f"свой код: {len(code)} Б (stub 0x{stub_addr:08X}, "
|
|
f"batt_draw 0x{batt_addr:08X}, tick_router 0x{tick_addr:08X})")
|
|
|
|
img += code
|
|
while len(img) % 4:
|
|
img.append(0xFF)
|
|
|
|
# ---- патчи ----
|
|
img[o_gauge:o_gauge + 4] = thumb_bl(GAUGE_CALL, stub_addr)
|
|
print("штатная шкала батареи отключена")
|
|
img[o_level:o_level + 4] = b"\x00\xbf\x00\xbf"
|
|
print("преобразование в индекс убрано — перерисовка на каждые 0.1 В")
|
|
img[o_bar:o_bar + 4] = b"\x00\xbf\x00\xbf"
|
|
print("верхняя полоса рамки убрана")
|
|
img[o_tick:o_tick + 4] = thumb_bl(TICK_CALL, tick_addr)
|
|
print("вывод напряжения перевешен на конец кадра")
|
|
|
|
open(OUT, "wb").write(bytes(img))
|
|
print(f"готово: {OUT}, {len(img)} Б")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|