Init
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
> Historical snapshot (2026-09-26 working notes). Two claims are superseded:
|
||||
> update entry is hold-PAD-while-powering-on (see ../firmware-update.md),
|
||||
> and the cmtd checksum is solved (see ../../tools/README.md).
|
||||
|
||||
# Lofi-12 XT.bin — Reverse Engineering Notes
|
||||
|
||||
- File: `Lofi-12 XT.bin` (firmware v1.5.205)
|
||||
- Size: 1,707,049 bytes (`0x1A0C29`)
|
||||
- SHA256: `a8bffa65919c0a2d4ff2ce77d16301551af4d2964497b8804deed49ce4198026`
|
||||
- Source: `Lofi-12XT_v1.5.205` update ZIP (also contains `Dibiase/`, `Ski Beatz/` sample folders)
|
||||
- Update method: copy `.bin` to SD root, boot with button held → `SYSTEM UPDATE`
|
||||
|
||||
## Hardware context
|
||||
|
||||
Based on community teardown (SmplTrek / Lofi-12 XT / Cydrums share platform):
|
||||
|
||||
- TI TMS320C6748 (ARM9 + C674x floating-point DSP) — main SoC
|
||||
- ARTERY AT32F421K8T (ARM Cortex-M, USB/aux)
|
||||
- Etron EM68C16CWQG 1 Gbit DDR2, base `0xC0000000`
|
||||
- Macronix MX25L12833F SPI flash (bootloader lives here, parses SD `.bin`)
|
||||
|
||||
All `sect` load addresses are `0xC2xxxxxx`, i.e. DDR2. The `.bin` is a DDR snapshot
|
||||
loaded by the SPI-flash bootloader, not a flash image itself.
|
||||
|
||||
## Container format (Sonicware custom, not AIS/ELF)
|
||||
|
||||
```
|
||||
[cmtd 48B][mmtd 36B][sect x7 -> EOF, exact fit]
|
||||
```
|
||||
|
||||
### cmtd (file header, 0x00–0x2F, 48 bytes)
|
||||
|
||||
| Off | Bytes | Meaning |
|
||||
|-----|-------|---------|
|
||||
| 0x00 | `63 6d 74 64` (`cmtd`) | magic |
|
||||
| 0x04 | `29 0c 1a 00` | u32 LE filesize = 1707049 (matches) |
|
||||
| 0x08 | `57 08 7c b1` | u32 checksum (?) `0xB17C0857`, algorithm TBD (not plain CRC32) |
|
||||
| 0x0C | `00 00 00 00` | reserved |
|
||||
| 0x10 | `0c 00 00 00` | 12 (?) |
|
||||
| 0x14 | `01 00 00 00` | container ver major? (1) |
|
||||
| 0x18 | `03 00 00 00` | container ver minor? (3) |
|
||||
| 0x1C | `01 00 00 00` | firmware major (1) |
|
||||
| 0x20 | `05 00 00 00` | firmware minor (5) |
|
||||
| 0x24 | `cd 00 00 00` | firmware patch (205) → **v1.5.205** |
|
||||
| 0x28 | `30 00 00 00` | 48 = cmtd header len |
|
||||
| 0x2C | `f9 0b 1a 00` | remaining size = filesize − 48 = 1707001 |
|
||||
|
||||
### mmtd (image header, 0x30–0x53, 36 bytes)
|
||||
|
||||
| Off | Bytes | Meaning |
|
||||
|-----|-------|---------|
|
||||
| 0x30 | `6d 6d 74 64` (`mmtd`) | magic |
|
||||
| 0x34 | `f9 0b 1a 00` | remaining size |
|
||||
| 0x38 | `e8 bc f8 4f` | timestamp/flags? (`0x4FF8BCE8`) |
|
||||
| 0x3C | `ff ff ff ff` | −1 |
|
||||
| 0x40 | `01 00 00 00` | fw major (1) |
|
||||
| 0x44 | `05 00 00 00` | fw minor (5) |
|
||||
| 0x48 | `cd 00 00 00` | fw patch (205) |
|
||||
| 0x4C | `a0 1a cd c2` | **entry point `0xC2CD1AA0`** (inside big code sect) |
|
||||
| 0x50 | `07 00 00 00` | sect count = 7 |
|
||||
|
||||
### sect (0x54 → EOF)
|
||||
|
||||
Each: `73 65 63 74` (`sect`) | u32 LE load_addr | u32 LE len | payload.
|
||||
Chain verified: `next_off = off + 12 + len`, ends exactly at EOF (`0x1A0C29`).
|
||||
|
||||
| # | File off | Load addr | Len | End addr | Role |
|
||||
|---|----------|-----------|-----|----------|------|
|
||||
| 0 | 0x000054 | C2DA7600 | 86168 (`0x15098`) | C2DBC698 | asset/blob (`7e xx` patterned) |
|
||||
| 1 | 0x0150F8 | C2DA6DD4 | 688 (`0x2B0`) | C2DA7084 | float table (~−97…−102) |
|
||||
| 2 | 0x0153B4 | C2DA7400 | 512 (`0x200`) | C2DA7600 | record table, links #1→#0 |
|
||||
| 3 | 0x0155C0 | C2B80C00 | 1490112 (`0x16BCC0`) | C2CEC8C0 | **main code (.text), C6000 DSP** |
|
||||
| 4 | 0x18128C | C2B809E8 | 8 | C2B809F0 | zeros (gap/patch slot) |
|
||||
| 5 | 0x1812A0 | C2D84DE0 | 125081 (`0x1E899`) | C2DA32D9 | **.rodata/.data** (all readable strings) |
|
||||
| 6 | 0x019FB45 | C2DA5680 | 4312 (`0x10D8`) | C2DA6758 | float ramp (~−99.34 step) |
|
||||
|
||||
Sorted by address they tile `C2B809E8…C2DBC698` with small gaps (descriptors/BSS).
|
||||
|
||||
Unpacked with:
|
||||
|
||||
```python
|
||||
import struct
|
||||
off = 0x54
|
||||
while d[off:off+4] == b'sect':
|
||||
a, b = struct.unpack('<II', d[off+4:off+12])
|
||||
open(f'sect_addr{a:08X}_off{off:06X}.bin','wb').write(d[off+12:off+12+b])
|
||||
off += 12 + b
|
||||
```
|
||||
|
||||
Split files in `/tmp/opencode/lofi/sect*_addr*.bin` (7 files).
|
||||
|
||||
## Code identification
|
||||
|
||||
- Entry `0xC2CD1AA0` → file off `0x16646C`. Disassembled as **TMS320C64x LE**
|
||||
(capstone `CS_ARCH_TMS320C64X`) gives a textbook C6000 reset prelude:
|
||||
`ZERO b0; mvc b0,ier; mvc b0,csr; mvk/mvklh b15/b14; and -8,…`
|
||||
(disable interrupts, align stack). ARM/Thumb disassembly of the big sect
|
||||
yields ~nothing (2× `E92D` push in 1.5 MB; `bx lr` hits are coincidental
|
||||
Thumb-dense false positives).
|
||||
- Data sect has 6,362 words pointing into the big code range (`C2BDxxxx`,
|
||||
e.g. C++ vtables) + 3,051 self-pointers. Big sect itself has ~0 pointers
|
||||
into data (PC-relative `addkpc` style) + 102 self-words — consistent with
|
||||
C6000 `.text` vs `.rodata` split.
|
||||
- Big sect entropy ~6.9–7.0 with ~12% zeros (code+data mix, not packed/encrypted);
|
||||
tail (`+0x140000…`) drops to ~3.0 with repeating 64 B zero-padded structs
|
||||
(data/BSS area).
|
||||
|
||||
## Data sect contents (file 0x1812A0–0x19FB45)
|
||||
|
||||
- C++ RTTI/mangled names (libc++, `NSt3__`, so TI clang-based CGT):
|
||||
`FileUtil::removeResourceFork/copyFolderRecursive/convFsRecursive/…`,
|
||||
`14AppSongBrowser/TestControl/TestEncoder`, `13AppFileRename/FileSelect/
|
||||
SongRename/SystemInfo/SystemMenu`, `12AppSceneMenu/TempoMenu/TrackMenu`,
|
||||
`11AppSDReader/SongEdit`, `10AppTapeRec/TestADC/TestLED`, `N3HAL9I2CDriverE/
|
||||
SPIDriver/IODriver/SDDriver`, `N8SoundOSC4StepE`, `N5Asset5PLockE`, …
|
||||
- C674x crash dumper: `Legacy NMI Exception`, `IERR=`, `Fetch packet`,
|
||||
`Execute packet`, `Opcode/Privilege/Loop buffer`, `A0=…A31=`, `B0=…B31=`,
|
||||
`NTSR/ITSR/IRP/SSR/AMR/RILC/ILC, Exception at, EFR/NRP`, cache/security faults.
|
||||
- UI strings: `Lofi-12XT`, `PATTERN MIXDOWN/MENU`, `PROJECT SAVE AS/SELECT`,
|
||||
`SONG MIXDOWN`, `SCENE/TRACK MANAGER`, `CARD/MIDI SETTING`, `ARE YOU SURE?`,
|
||||
`PROCESSING/COPYING…`, `Threshold`, `Tempo: 120.0`, `*.wav`, …
|
||||
- Container/chunk tags: `RIFF/WAVE/fmt /smpl/labl/adtl/data/cue /MThd/MTrk`,
|
||||
project tags `PJST/SMPS/PTD2/PTDT/PJCF/SONG/TRAU/TMAM/…`, `TRACK0/TRK0`.
|
||||
|
||||
## Small sects
|
||||
|
||||
- #0 (86 kB): byte pattern `00 7e 7e 00 … 7e xx 00 0f` — bitmap/font/waveform asset.
|
||||
- #1/#6: LE float32 tables (filter/window coeffs?).
|
||||
- #2 (512 B): fixed records, e.g. `f6 54 3c 00 5a a3 xx 00 … 6a 61 00`
|
||||
(`ja\0`/`jma\0` fragments) — preset/pattern table.
|
||||
|
||||
## Open questions / next steps
|
||||
|
||||
1. Checksum at `cmtd+0x08` (`0xB17C0857`) — not CRC32 of obvious spans; brute-force
|
||||
variants (BE, seeded, Fletcher, TI AIS style) before attempting repack.
|
||||
2. Full C64x disassembly of sect #3 (TI CGT / Ghidra C6000, or capstone script);
|
||||
recover vtables using pointers in sect #5.
|
||||
3. Diff vs older `.bin` (e.g. v1.2.x) to isolate v1.5 feature code.
|
||||
4. Confirm ARM9 vs DSP split (ARM9 may be bootloader-only in SPI flash;
|
||||
everything in `.bin` looks like DSP).
|
||||
Reference in New Issue
Block a user