Files
lofi-12xt-firmware/docs/firmware/notes-v1.5.205.md
T
2026-09-30 22:48:06 +02:00

6.4 KiB
Raw Blame History

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:

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).