#!/usr/bin/env python3 """Build RT-4D app image with narrow single-byte CP1251 Cyrillic support. Mechanism (all in the recoverable MCU app region, no SPI writes): 1. dispatch patch @0x08008A6A: cmp #0x80 -> #0xFF (bytes 0x80..0xFE take the narrow 7px single-byte path instead of the 14px CJK double-byte path) 2. blitter redirect @0x08007FDA: bl SPI_read -> bl CAVE 3. CAVE routine (appended): for glyph src addr >= 0x0019C540 (font index >=96, i.e. byte>=0x80) memcpy the 14-byte glyph from our in-flash Cyrillic table; otherwise tail-call the original SPI_read (ASCII unchanged). 4. Cyrillic glyph table (appended): 128 cells (byte 0x80..0xFF) x 14 bytes, column-major 7x14 (2 bytes/col LE, bit0=top) — same format as the ASCII font. This test only fills the few glyphs needed to render one CP1251 word. """ import sys, struct from keystone import Ks, KS_ARCH_ARM, KS_MODE_THUMB BASE = 0x08002800 CAVE_ADDR = 0x08028860 # file 0x26060 CYR_BASE = 0x08028900 # file 0x26100 (glyphs for byte 0x80..0xFF) SPI_READ = 0x08021828 # original glyph-load routine (thumb entry+1) IMG_END = 0x08029000 # extend image to cover the font table # ---- glyph editor: 14 rows x 7 cols, '#'=on ; encode column-major 2B/col LE -- # Glyphs are drawn <=6 columns wide (col 6 stays blank) so the fixed 7px advance # always leaves >=1px between letters. def enc(rows): assert len(rows) == 14 out = bytearray() for c in range(7): v = 0 for r in range(14): if c < len(rows[r]) and rows[r][c] == '#': v |= 1 << r out += struct.pack(' 6px-wide 14-tall bitmap (col 6 blank => 1px gap) 0xC7: G(".####.","#....#",".....#","..###.",".....#","#....#",".####."), # З 0xEE: G(".####.","#....#","#....#","#....#",".####.", top=5), # о 0xED: G("#....#","#....#","######","#....#","#....#", top=5), # н 0xFB: G("#....#","#....#","##...#","#.#..#","##...#", top=5), # ы } def main(): inp = sys.argv[1] if len(sys.argv) > 1 else "rt4d_stock_v3.25_abs_0x08002800.bin" outp = sys.argv[2] if len(sys.argv) > 2 else "rt4d_ru_font_test.bin" stock = open(inp, "rb").read() img = bytearray(b"\xff" * (IMG_END - BASE)) img[:len(stock)] = stock def w(va, data): img[va-BASE:va-BASE+len(data)] = data ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB) # --- CAVE --- cave_asm = f""" movw r3, #0xC540 movt r3, #0x0019 cmp r1, r3 blo do_spi subs r1, r1, r3 movw r3, #{CYR_BASE & 0xFFFF} movt r3, #{CYR_BASE >> 16} adds r1, r1, r3 copy: ldrb r3, [r1] strb r3, [r0] adds r1, r1, #1 adds r0, r0, #1 subs r2, r2, #1 bne copy bx lr do_spi: movw r3, #{SPI_READ & 0xFFFF} movt r3, #{SPI_READ >> 16} orr r3, r3, #1 bx r3 """ cave, _ = ks.asm(cave_asm, CAVE_ADDR) cave = bytes(cave) assert len(cave) <= (CYR_BASE - CAVE_ADDR), "cave too big" w(CAVE_ADDR, cave) # --- blitter redirect: bl CAVE at 0x08007FDA --- bl, _ = ks.asm(f"bl #{CAVE_ADDR}", 0x08007FDA) assert len(bl) == 4 orig = bytes(img[0x08007FDA-BASE:0x08007FDA-BASE+4]) w(0x08007FDA, bytes(bl)) # --- dispatch patch: cmp r0,#0x80 -> #0xFF at 0x08008A6A --- dpo = 0x08008A6A - BASE assert img[dpo] == 0x80 and img[dpo+1] == 0x28, f"unexpected {img[dpo]:02x} {img[dpo+1]:02x}" img[dpo] = 0xFF # --- Cyrillic glyph table --- for code, g in GLYPHS.items(): w(CYR_BASE + (code-0x80)*14, g) # --- menu string: "Zone Set 06" -> CP1251 "Зоны"+pad+"06" --- rec = 0x0802543D old = b"Zone Set 06" assert bytes(img[rec-BASE:rec-BASE+16]) == old new = "Зоны".encode("cp1251") + b" "*10 + b"06" assert len(new) == 16, len(new) w(rec, new) # sanity: vectors intact sp, rst = struct.unpack_from("{bytes(bl).hex()}") print(f'"Зоны" cp1251 = {new[:8].hex(" ")}') print(f"image {len(img)} bytes -> {outp}") if __name__ == "__main__": main()