# RT-4D: прошивка по запчастям и точки внедрения своего кода Цель документа — разложить устройство на независимые «детали», для каждой указать **где лежит, чем читается, чем пишется и как туда внедрить своё**. Ключевой факт: рация состоит из **двух процессоров** с раздельными прошивками, плюс внешний SPI-флеш с ресурсами. Это три независимых канала внедрения. --- ## 0. Карта на одном экране | # | Деталь | Носитель | Размер | Пишется чем | Статус | |---|---|---|---|---|---| | 1 | Загрузчик MCU | внутр. flash `0x08000000` | 10 КБ | **ничем** (неприкосновенен) | страховка от кирпича | | 2 | Прошивка MCU (UI+логика) | внутр. flash `0x08002800` | ~152 КБ | `flash_rt4d.py` | ✅ наш основной цех | | 3 | Прошивка DMR-модуля FM100B | своя flash в модуле | 1.5 МБ | `DMR Upgrade Tool.exe` / наш мост | ✅ канал открыт | | 4 | Ресурсы: шрифты, пиньинь, юникод | внешний SPI 4 МБ | ~1 МБ | `Unicode Download.exe` | ✅ найден штатный загрузчик | | 5 | Голосовые подсказки | внешний SPI | 569 КБ | тот же загрузчик | найден регион | | 6 | Кодплаг (каналы/контакты/зоны) | внешний SPI | ~100 КБ | штатная CPS | формат менять нельзя | --- ## 1. MCU — ARM Cortex-M4F (клон STM32F407, реально **Artery AT32**) Опознан по раскладке GPIO: `SCR +0x18` (установить), `CLR +0x28` (сбросить), `TOGR +0x2C` — это Artery, а не ST. ``` 0x08000000 ┌────────────────────────┐ │ загрузчик (10 КБ) │ НЕ ТРОГАЕМ — он всегда поднимет рацию 0x08002800 ├────────────────────────┤ │ приложение ~152 КБ │ ← сюда пишем мы │ 0x08008A50 draw_string│ │ 0x080207DC ui_tick │ │ 0x08028860 наш кейв │ │ 0x08029000 наш C-код │ 0x08028000 └────────────────────────┘ SRAM 128 КБ, g_screen @0x200008B3 ``` ### Как попасть в режим прошивки **Выключить → включить, удерживая тангенту PTT.** Протокол загрузчика (реализован в `flash_rt4d.py`): ``` рукопожатие : слать 0xFF, пока не придёт 0xFF стирание : [39 33 05 10]+CK, затем [39 33 05 55]+CK → ждать ACK 0x06 запись : [57 offHi offLo] + 1024 байт + CK → ждать ACK 0x06 CK = (0x48 + сумма байтов) & 0xFF ``` ### Три проверенных приёма внедрения **а) Точечный патч байта** — самый дешёвый. Пример (русификация): в `draw_string` стояло `cmp #0x80` — всё, что ≥0x80, считалось китайским иероглифом шириной 14 px. Меняем на `cmp #0xFF` по адресу `0x08008A6A` — и диапазон 0x80–0xFE уходит в узкую 7-пиксельную ветку. Один байт открыл кириллицу. **б) Кодовый кейв** — свободное место после конца кода, куда кладём свою функцию, и перенаправляем на неё вызов. У нас кейв на `0x08028860`, таблица глиф на `0x08028900`. **в) Перехват вызова (hook)** — подменяем цель инструкции `bl`. Две главные точки в `ui_tick_normal @0x080207DC`: ``` 0x080207E6 bl → отрисовка экрана (0x0801E1BC) 0x08020816 bl → обработка клавиш (0x0801E6EC) ``` Обе диспетчеризуются по `g_screen @0x200008B3` (значения 0–11) через две tbb-таблицы. > **Урок, оплаченный кирпичом:** перехватывать надо **поверх** штатного обработчика, а не > вместо него. Когда мы подменили отрисовку главного экрана целиком, пропала преамбула > `ui_draw_dispatch` — и клавиши умерли, осталась только тангента. Правильно: вызвать > оригинал, потом дорисовать своё. > > И второй урок: `BATT_READ @0x08010960` — **не** безопасный листовой геттер, вызов из > хука вешает рацию намертво (обработчик HardFault — пустой бесконечный цикл). ### Свой C-код в прошивке ```bash clang --target=thumbv7em-none-eabi -mcpu=cortex-m4 -mthumb -Os \ -ffreestanding -fno-builtin -nostdlib -fno-pic -c my.c ld.lld -Ttext=0x08029000 -e my_draw_router my.o -o my.elf llvm-objcopy -O binary --only-section=.text my.elf my.bin ``` --- ## 2. FM100B — DMR-баузбенд, **отдельный процессор** Не Cortex-M. Образ `FM100B_V1.2.0.32_20260130.bin` (1 527 808 Б) начинается классической таблицей векторов ARM32 (`B` + семь `LDR PC,[PC,#0x14]`), обработчики по адресам `0x0301xxxx` с взведённым битом 0 → **ARMv5T (ARM926-класс), код в Thumb**. Внутри крутится RTOS: видны имена устройств `rom / flash0 / flash1 / sd / udisk` и константы MD5. ### Связь MCU ↔ FM100B — USART3 (`0x40004800`) ``` 68 | cmd | b | sub | CKhi CKlo | lenHi lenLo | payload… | 10 ``` Контрольная сумма — «интернетная» (one's complement) сумма 16-битных BE-слов по всему кадру, причём **на время расчёта поле CK = 0xFFFF**, результат кладётся BE в `[4..5]`: ```c u32 s=0; while(len>1){ s += (b[0]<<8)|b[1]; b+=2; len-=2; } if(len) s += b[0]<<8; while(s>>16) s = (s&0xFFFF) + (s>>16); ck = ~s & 0xFFFF; ``` Проверено на живом ответе рации: кадр `68 84 00 00 87 7b 00 00 10` → расчёт даёт `0x877B`. ✅ Функции MCU: `fm100b_send1 @0x0801B044`, `fm100b_send @0x0801B0C4`, приём `fm100b_rx_parse @0x08018CB0`, диспетчер `fm100b_on_frame @0x08006348`, насос `poll_serial @0x08003050` (обязателен в любом цикле ожидания). ### Прозрачный мост ПК ↔ FM100B — **включение с зажатой клавишей МЕНЮ** Найдено в `boot_mode_check @0x08003060`: ```c if (key == 0x0A) { // МЕНЮ GPIOB->CLR = 2; delay(200); GPIOB->SCR = 2; // PB1 = питание модуля, перезапуск for(;;) { bl 0x08019720; // ПК → FM100B bl 0x08019758; } // FM100B → ПК } if (key == 0x0F) baud = 256000; // клавиша '#' if (key == 0x11) bridge_flag = 1; // SIDE1 ``` На экране при этом «Обновление DMR чипа». Клавиша `#` = 256000 подтверждается официальным changelog CPS V2.01 — независимое совпадение. Инструмент: **`tools/fm100b.py`** (`ping` / `send` / `raw` / `scan`). Под WSL порт открывается через `powershell.exe` — WSL2 не пробрасывает COM напрямую. ### Что модуль отвечает живьём | cmd | ответ | смысл | |---|---|---| | `0x25` | `01 02 00 32` | **версия прошивки модуля = V1.2.0.32** | | `0x24` | `01 00 40 01` | идентификатор/возможности | | `0x04` | `03` | параметр | | `0x27`, `0x28` | `00` | статусы | | `0x01`, `0x03`, `0x0C`, `0x1A` | status 0 | приняты | | `0x05` | status `0xFF` | отказ (нужен payload) | | `0x02` | молчит | команды нет | Версия `01 02 00 32` совпала с именем официального файла `FM100B_V1.2.0.32` — значит `0x25` это **подтверждённый запрос версии**. REFV ориентируется на более старую V1.2.0.27. ### Направление «модуль → MCU» Диспетчер `fm100b_on_frame` — таблица 193 смещений с базой `0x0800636C`, handler = база + запись. Обрабатывается 104 кода, но 94 из них — двухбайтовые заглушки. **Реальный код только у десяти:** `05, 06, 07, 09, 0A, 25, 3E, 42, 59, 62`. Разобран `cmd 0x59` — индикация активности приёма, и она же рулит звуком: ```c payload[0]==1 → GPIOA->SCR = 0x4000; // PA14 включить payload[0]==0 → GPIOA->CLR = 0x4000; // PA14 выключить ``` ### Семантика из официального changelog Числа там **шестнадцатеричные** (проверка: `0x33` в таблице отсутствует, `0x51` есть): | cmd | назначение | |---|---| | `0x66` | девиация передачи, широкая/узкая полоса (аналог) | | `0x67` | компандер (только аналоговые каналы) | | `0x51` | вокодер, формат 2 — AGC | | `0x3E` | **обновление прошивки модуля — НЕ СЛАТЬ вслепую** | Полный словарь имён команд (`ATC_*Req/Cnf/Ind`, ~180 штук) лежит строками прямо в образе модуля начиная с `0x0CDAE7`: `ATC_ChFreqSetReq`, `ATC_CallProcessReq`, `ATC_RadioIDSetReq`, `ATC_SMSProcessReq`, `ATC_MICCodecGainSet`, `ATC_SPKCodecGainSet`, `ATC_RssiReadReq`, `ATC_CalledContactINfoQuery`, `ATC_ChSlotSetReq` и т.д. --- ## 3. Аудио — двухступенчатое | Линия | Что делает | Как дёрнуть | |---|---|---| | **PA2** | питание УНЧ **LN4898 (U502)**, цепь `AF MUTE4898` = вывод 12 MCU | вкл `*(u32*)0x40020018 = 4` / выкл `*(u32*)0x40020028 = 4` | | **PA14** | гейт звука от DMR-модуля | вкл `*(u32*)0x40020018 = 0x4000` / выкл `0x40020028` | PA2 подтверждён трижды: принципиальная схема + сток + REFV. Штатный API со счётчиком ссылок: `audio_request @0x0801B574`, `audio_release @0x0801A348`, `audio_off_if_idle @0x0801A824`, флаги `0x20000C6E` (бит15 = активен). **Громкость — аналоговый потенциометр VR500, программно не регулируется.** --- ## 4. Внешний SPI-флеш 4 МБ — ресурсы Карта занятых регионов (по нашему дампу `radio-spi-dump.bin`): | Адрес | Размер | Содержимое | |---|---|---| | `0x000000` | 20 КБ | настройки/кодплаг | | `0x0D0000` | 24 КБ | таблица клавиш (видно `"Key 1"`) | | `0x126000` | 20 КБ | картинки (кандидат на **загрузочный логотип**) | | `0x14C000` | 45 КБ | индекс/сортировка иероглифов GB2312 | | **`0x164000`** | 200 КБ | **таблица пиньиня** (китайский ввод) | | **`0x198000`** | 680 КБ | **шрифты**: ASCII 7×14 @`0x19C000`, CJK 14×14 @`0x19E000` | | **`0x352000`** | 569 КБ | **голосовые подсказки** (8-бит PCM, тишина = `0x80`) | | **`0x3F0000`** | 64 КБ | **таблица Unicode↔GB** (начинается `4E 02 4E 04 4E 05`…) | Формат глифа: **колоночный**, 7 колонок × 14 строк, 2 байта на колонку little-endian, бит 0 = верхний пиксель. CJK — 14 колонок, 28 байт. ### Штатный загрузчик ресурсов — найден `Unicode Download.exe` из пакета RT-4D 20251113 внутри называется **`FontVoicePicture`** — то есть официальный канал заливки **шрифтов, голоса и картинок**. Именно он пишет регионы `0x164000`, `0x198000`, `0x352000`, `0x3F0000`. Из официального readme: *«этот инструмент обязателен после обновления прошивки, иначе текст SMS будет в кракозябрах»* — потому что SMS кодируется Unicode, а рации нужна таблица соответствия из `0x3F0000`. ⇒ Это готовый путь для замены **загрузочного логотипа** и **своих шрифтов** без патча MCU. --- ## 5. CPS-протокол — контракт совместимости (USART6 `0x40011400`) Менять **нельзя**, иначе отвалится штатная CPS: ``` 0x34/0x10 уведомление · 0x52 чтение SPI · 0x40 и 0x90–0xA5 запись регионов 0x34/0xEE закрытие · контрольная сумма = простая сумма, seed 0 ``` Это третий, независимый UART — с кадрами FM100B (`0x68…0x10`) он никак не пересекается. --- ## 6. Каталог прошивок — база для сравнения версий `.../DMRhub-sale/firmware-catalog/fw/radtel/rt-4d/` — 32 файла, от V3.10 (ноя-2024) до фев-2026, все версии CPS, плюс `rt-4d_data_lost_fixing.rar`. Извлечено **7 версий прошивки DMR-модуля**, все ровно по 1 527 808 Б, значит побайтовое сравнение тривиально: ``` FM100B_V1.2.0.13_20250430 .16_20250620 .18_20250707 .21_20250819 .22_20250916 .24_20251010 .32_20260130 ← стоит в рации ``` Полезное из changelog V3.18: *«оптимизирована связь между DMR-баузбендом и MCU, оптимизированы некоторые команды связи»* — то есть протокол между версиями менялся. --- ## 7. Что уже сделано и работает - ✅ Извлечение стока из официального RAR через 4 слоя вложенности (RAR → ZIP → .NET `Ido_Update` → Intel-HEX в куче управляемых строк `#US`) - ✅ Полный цикл патч → сборка → прошивка → восстановление - ✅ Русификация: 483 записи меню + 335 вхождений в rodata + 11 инлайн-строк, 33 глифа - ✅ Живой двусторонний обмен с DMR-модулем с ПК - ✅ Аудио: обе линии найдены и подтверждены ## 9. Где какие функции живут (для правки «фишек») Дерево меню целиком извлечено: **181 пункт + 304 набора значений** (`menu_strings.json`, записи по 20 байт: 16 байт метки + позиция). Структура — 7 подменю: | Подменю | Что внутри (по позициям) | |---|---| | **Basic Set** | 01 Radio Name … 31 LCD Contrast, 32 Freq Input, 33 Reverse CH Dir, 34 Carrier LED, **35 RSSI Refresh**, 36 APO, 39 Initialization, 41 Version | | **Key Define** | 01 Second PTT, 02–05 боковые клавиши, 06–15 «цифра + долгое нажатие» | | **Analog Set** | 01 SQ Level … **06 MIC Gain, 07 SPK Gain, 08 Glitch TH**, 09 Detect Range, 11–19 DTMF, 21–23 VOX | | **Digital Set** | 01 Personal ID … 06 SQ Level, **07 MIC Gain, 08 SPK Gain, 09 TX Denoise, 10 RX Denoise**, 11 Contacts Set, 17 Call Log | | **Channel Set** | 01 DMR Or Analog, **02 RX/TX Limit**, 03 CH Alias, 04 TX Power, 06 TOT, 09–18 аналоговые `(A)`, 19–28 цифровые `(D)` | | **Message** | 01 New SMS, 02 Inbox, 03 Outbox, 04 Drafts, **07 SMS Format, 08 SMS Font**, 09 SMS Prompt | | **Zone Set** | зоны | **Важно:** `MIC Gain` / `SPK Gain` **не спрятаны** — они штатные пункты меню, причём раздельные для аналога и цифры. Открывать ничего не нужно, можно сразу менять диапазоны значений или логику. `SMS Format` в стоке принимает значения **`Hytera / Motorola / Unicode / GBK`** — то есть выбор формата уже реализован. ### Спектроанализатор Пунктом меню **не является** — это функция назначаемой клавиши **`Analog Spectrum`**. Полный список функций клавиш (оттуда же берутся «фишки»): ``` Monitor(Analog) H/L Power Dual Standby TX Priority Scanning Backlight On-off Roger Beep FM Radio Talkaround Alarm Freq Detect CTC/DCS Scan Send Single Tone Status Query Remote Monitor Remote Stun Remote Kill Remote Wake Up Online Check Called Show RX AM/FM Switch Analog Spectrum SQ Freq Step DA Switch NOAA Mode Save CH New SMS Jump To SMS Menu Brightness Analog CH VOX Zone Select Promiscuous Mode Dual Slot On-off Time Slot Switch Color Code SW DMR Encrypt Off Jump To RX List Jump To Contact Jump To DTMF Sel ``` Из changelog V3.18: точки скана подняты с 81 до 105, три индикатора слева сверху вниз — **RSSI, Noise, Glitch**. Значит правка спектра = правка этого экрана в MCU (экран выбирается через `g_screen @0x200008B3`). ### Таблица диапазонов для `RX/TX Limit` ``` 18-64 64-136 136-174 174-240 240-320 320-400 400-480 480-560 560-620 840-920 920-1000 МГц ``` ⚠️ Таблицы на `0x0801F4D0` и `0x08011218` — это сегментация ВЧ-тракта (VCO/ФАПЧ), **их патчить нельзя**, это не проверка лимита. ## 8. Ближайшие возможности 1. **Загрузочный логотип** — регион `0x126000` + штатный `FontVoicePicture`. 2. **Диff версий FM100B** — 7 образов одинакового размера, изменения видны сразу. 3. **Выравнивание словаря команд** — сопоставить ~180 имён `ATC_*` из образа модуля с числовыми кодами (нужна база загрузки образа, указатели смещены). 4. **Своя логика поверх стока** — хук `0x080207E6`, обязательно вызывая оригинал.