6.4 KiB
6.4 KiB
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.205update ZIP (also containsDibiase/,Ski Beatz/sample folders) - Update method: copy
.binto 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 off0x16646C. Disassembled as TMS320C64x LE (capstoneCS_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×E92Dpush in 1.5 MB;bx lrhits 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-relativeaddkpcstyle) + 102 self-words — consistent with C6000.textvs.rodatasplit. - 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 tagsPJST/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\0fragments) — preset/pattern table.
Open questions / next steps
- Checksum at
cmtd+0x08(0xB17C0857) — not CRC32 of obvious spans; brute-force variants (BE, seeded, Fletcher, TI AIS style) before attempting repack. - Full C64x disassembly of sect #3 (TI CGT / Ghidra C6000, or capstone script); recover vtables using pointers in sect #5.
- Diff vs older
.bin(e.g. v1.2.x) to isolate v1.5 feature code. - Confirm ARM9 vs DSP split (ARM9 may be bootloader-only in SPI flash;
everything in
.binlooks like DSP).