#!/usr/bin/env python3 """Talker Alias — приём: показ позывного вызывающего на экране (шаг 1 из 3). Механика (подтверждена снифером на живом вызове): DMR-модуль присылает позывной кадром cmd 0x60: 68 60 02 00 CK CK 00 LEN 02 01 00.. 10 Сток payload этой команды выбрасывает (обработчик = nop; b default). Наши патчи (поверх rt4d_ru_batt.bin — там уже вольтметр и роутер отрисовки): • ta_hook перехватывает fm100b_on_frame: на 0x60 копирует позывной в буфер RAM и ставит ta_valid; на 0x59 payload=0 (конец приёма) сбрасывает ta_valid; затем всегда вызывает штатный обработчик. • new_tick_router (последним за кадр): самоинициализация настроек по «магии», затем на главном экране рисует вольтметр и — если включено и есть свежий позывной — сам позывной внизу экрана. Буфер RAM 0x2000C7FC..: [magic4][en][valid][len][txt16] (свободный зазор SRAM). Настройка ta_en инициализируется в 1 при каждом включении (постоянное хранение — шаг 2, вместе с меню). Откат — 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_ta.bin" DRAW_STRING = 0x08008A50 # void draw_string(page,x,s,len, mode@[sp]) BIG_CHAR = 0x08008100 # void big_char(page,x,ch,mode) — крупный шрифт 11x24 частоты ON_FRAME = 0x08006348 # void fm100b_on_frame(u8 *frame) FRAME_CALL = 0x08018DD8 # единственный bl on_frame UI_HOOK = 0x08020806 # последний вызов отрисовки в ui_tick_normal TICK_FUNC = 0x0801DFD0 # оригинал этого вызова BATT_DRAW = 0x08029002 # уже в rt4d_ru_batt.bin G_SCREEN = 0x200008B3 # Буфер в окне [0x2000AE48 .. 0x2000B108): выше вершины стека (SP=0x2000AE48, # растёт вниз), ниже первого глобала (0x2000B108). Ничья 192-байтовая зона — # в отличие от прошлого 0x2000C7FC, который оказался внутри чужого буфера. MAGIC_ADDR = 0x2000B000 MAGIC_VAL = 0x52543444 # 'RT4D' TA_EN = 0x2000B004 TA_VAL = 0x2000B005 TA_LEN = 0x2000B006 TA_TXT = 0x2000B007 # 16 байт -> до 0x2000B017 TA_PAGE = 3 # ниже ID, крупный шрифт 24px (страницы 3-5) EQ, NE, HS, LS, GE = 0, 1, 2, 9, 10 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("> 1) & 0xFF if cond is None: out += struct.pack("> 1) & 0x7FF)) else: out += struct.pack("> 16) & 0xFFFF # ---- ta_draw: позывной крупным шрифтом (11x24) по центру экрана ---- # ширина символа 12px, высота 24px (страницы 2-4 = вертикальный центр). # x0 = (128 - len*12)/2 = 64 - len*6. Длину зажимаем к 10 (10*12=120<=128). ta_draw_at = code_at a = Asm(ks, ta_draw_at) a.asm(f""" push {{r4, r5, r6, r7, lr}} movw r4, #{hexlo(TA_LEN)} movt r4, #{hexhi(TA_LEN)} ldrb r4, [r4] cmp r4, #10 """) a.b("len_ok", LS) a.asm("movs r4, #10") a.label("len_ok") a.asm(f""" movs r0, #6 muls r0, r4, r0 movs r5, #64 subs r5, r5, r0 movw r6, #{hexlo(TA_TXT)} movt r6, #{hexhi(TA_TXT)} movs r7, #0 """) a.label("cloop") a.asm("cmp r7, r4") a.b("cdone", HS) a.asm(f""" ldrb r2, [r6, r7] mov r1, r5 movs r0, #{TA_PAGE} movs r3, #0 bl #{BIG_CHAR} adds r5, #12 adds r7, #1 """) a.b("cloop") a.label("cdone") a.asm("pop {r4, r5, r6, r7, pc}") ta_draw = a.build() # ---- new_tick_router ---- tick_at = ta_draw_at + len(ta_draw) r = Asm(ks, tick_at) r.asm(f""" push {{r4, lr}} bl #{TICK_FUNC} movw r0, #{hexlo(MAGIC_ADDR)} movt r0, #{hexhi(MAGIC_ADDR)} ldr r1, [r0] movw r2, #{hexlo(MAGIC_VAL)} movt r2, #{hexhi(MAGIC_VAL)} cmp r1, r2 """) r.b("skipinit", EQ) r.asm(f""" str r2, [r0] movw r0, #{hexlo(TA_EN)} movt r0, #{hexhi(TA_EN)} movs r3, #1 strb r3, [r0] movw r0, #{hexlo(TA_VAL)} movt r0, #{hexhi(TA_VAL)} movs r3, #0 strb r3, [r0] """) r.label("skipinit") # вольтметр — только на главном экране (screen==0) r.asm(f""" movw r4, #{hexlo(G_SCREEN)} movt r4, #{hexhi(G_SCREEN)} ldrb r4, [r4] cmp r4, #0 """) r.b("no_batt", NE) r.asm(f"bl #{BATT_DRAW}") r.label("no_batt") # ДИАГНОСТИКА: позывной рисуем на ЛЮБОМ экране, если есть свежий TA # (условие en временно убрано — сначала убедимся, что приём работает) r.asm(f""" movw r0, #{hexlo(TA_VAL)} movt r0, #{hexhi(TA_VAL)} ldrb r0, [r0] cmp r0, #0 """) r.b("end", EQ) r.asm(f"bl #{ta_draw_at | 1}") r.label("end") r.asm("pop {r4, pc}") tick = r.build() # ---- ta_hook ---- hook_at = tick_at + len(tick) h = Asm(ks, hook_at) h.asm(""" push {r4, r5, r6, lr} mov r4, r0 ldrb r5, [r4, #1] cmp r5, #0x60 """) h.b("check59", NE) # handle 0x60: len = frame[10], clamp 16 h.asm(""" ldrb r5, [r4, #10] cmp r5, #16 """) h.b("len_ok", LS) h.asm("movs r5, #16") h.label("len_ok") h.asm("movs r6, #0") h.label("loop") h.asm(""" cmp r6, r5 """) h.b("afterloop", HS) h.asm(f""" adds r0, r4, #11 ldrb r0, [r0, r6] movw r1, #{hexlo(TA_TXT)} movt r1, #{hexhi(TA_TXT)} strb r0, [r1, r6] adds r6, #1 """) h.b("loop") h.label("afterloop") h.asm(f""" movw r1, #{hexlo(TA_LEN)} movt r1, #{hexhi(TA_LEN)} strb r5, [r1] movw r1, #{hexlo(TA_VAL)} movt r1, #{hexhi(TA_VAL)} movs r0, #1 strb r0, [r1] """) h.b("pass") h.label("check59") # кадр 0x06 = начало (sub 0x60) или конец (sub 0x6f) вызова -> сброс позывного. # На начало сброс срабатывает ДО прихода 0x60 (TA), поэтому не мешает показу. h.asm("cmp r5, #0x06") h.b("pass", NE) h.asm(f""" movw r1, #{hexlo(TA_VAL)} movt r1, #{hexhi(TA_VAL)} movs r0, #0 strb r0, [r1] """) h.label("pass") h.asm(f""" mov r0, r4 bl #{ON_FRAME} pop {{r4, r5, r6, pc}} """) hook = h.build() code = ta_draw + tick + hook img += code while len(img) % 4: img.append(0xFF) print(f"код: ta_draw 0x{ta_draw_at:08X} ({len(ta_draw)}Б), " f"tick 0x{tick_at:08X} ({len(tick)}Б), hook 0x{hook_at:08X} ({len(hook)}Б)") # репоинты img[UI_HOOK - BASE: UI_HOOK - BASE + 4] = thumb_bl(UI_HOOK, tick_at) img[FRAME_CALL - BASE: FRAME_CALL - BASE + 4] = thumb_bl(FRAME_CALL, hook_at) print("UI-хук -> new_tick_router; on_frame -> ta_hook") open(OUT, "wb").write(bytes(img)) print(f"готово: {OUT}, {len(img)} Б") if __name__ == "__main__": main()