> 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('