This commit is contained in:
Dejvino
2026-09-30 22:48:06 +02:00
commit 8913cb4314
37 changed files with 3190 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
> 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 (v1.1.156)
- File: `Lofi-12 XT.bin` (firmware v1.1.156)
- Size: 1,595,605 bytes (`0x1858D5`)
- SHA256: `617c3efea92399b0e3ed40890dd67a75013b0132059fa713076cae68a1908ac5`
- Folder also contains `.DS_Store`; no sample folders (unlike v1.5.205)
- Same SD-root `SYSTEM UPDATE` flow as v1.5.205
## Hardware context
Same platform as v1.5.205 (see `../Lofi-12XT_v1.5.205/Lofi-12XT_v1.5.205/reversed.md`):
TI TMS320C6748 (ARM9 + C674x DSP), DDR2 base `0xC0000000`. All `sect`
load addresses are `0xC2xxxxxx` (DDR2 snapshot loaded by the SPI-flash bootloader).
## Container format (same Sonicware custom format as v1.5.205)
```
[cmtd 48B][mmtd 36B][sect x7 -> EOF, exact fit]
```
### cmtd (0x00–0x2F)
| Off | Value | Meaning |
|-----|-------|---------|
| 0x00 | `cmtd` | magic |
| 0x04 | 1595605 | u32 LE filesize (matches) |
| 0x08 | `0xF58AF539` | u32 checksum (?) — differs per version, algorithm TBD |
| 0x10–0x2F | `(12, 1, 3, 1, 1, 156, 48, 1595557)` | container (?, 1, 3), fw (1, 1, 156), hdr len 48, remaining |
### mmtd (0x30–0x53)
| Off | Value | Meaning |
|-----|-------|---------|
| 0x30 | `mmtd` | magic |
| 0x34 | 1595557 | remaining size |
| 0x38 | `6e 25 13 20 ff ff ff ff` | timestamp/flags? (build-specific) |
| 0x40 | `(1, 1, 156)` | fw version |
| 0x4C | `0xC26C4820` | **entry point** (inside big code sect, verified below) |
| 0x50 | 7 | sect count |
### sects
| # | File off | Load addr | Len | End addr | Role |
|---|----------|-----------|-----|----------|------|
| 0 | 0x000054 | C27753B8 | 85912 (`0x14F98`) | C278A350 | asset/blob |
| 1 | 0x014FF8 | C2774C6C | 656 (`0x290`) | C2774EFC | float table |
| 2 | 0x015294 | C2775000 | 512 (`0x200`) | C2775200 | record table |
| 3 | 0x0154A0 | C2589800 | 1389120 (`0x153240`) | C26DCA40 | **main code (.text), C6000 DSP** |
| 4 | 0x1686EC | C2589508 | 8 | C2589510 | zeros (gap/patch slot, same as v1.5.205) |
| 5 | 0x168700 | C2755488 | 115561 (`0x1C369`) | C27718F1 | **.rodata/.data** (all readable strings) |
| 6 | 0x184A75 | C27737F8 | 3668 (`0xE54`) | C277464C | float ramp tail |
Chain verified: `next_off = off + 12 + len`, ends exactly at EOF (`0x1858D5`).
Memory tiling: code `C25895xx–C26DCA40`, data `C2755488–C277464C` (same
code→data→assets split as v1.5.205, just at lower DDR addresses).
## Code identification
- Entry `0xC26C4820` → file off `0x1504CC`. C64x-LE disassembly:
`ZERO b0; mvc b0,ier; mvc b0,csr; mvk 0x37f4,b15; mvklh -0x3d89,b15;
and -8,…` — identical reset prelude shape as v1.2.179/v1.5.205, only the
stack immediates differ. Confirms C6000 DSP code.
- Big sect entropy 6.890, zeros 175,494 (12.6%) — same code+data mix as v1.5.205.
- String sect cross-refs: 5,854 words into code range + 2,932 self-pointers
(vs 6,362 + 3,141 in v1.5.205 — table growth with features).
- Total `strings>=4`: 10,902 (vs 11,110 in v1.2.179; v1.5.205 higher).
## Data sect contents (file 0x168700–0x184A75)
- Same C674x crash dumper (`Legacy NMI Exception`, `IERR=`, `A0=…A31=`, `B0=…B31=`,
`NTSR/ITSR/IRP/SSR/AMR`, `14AppSongBrowser`, `N3HAL…Driver`, libc++ `NSt3__`).
- Older `FileUtil` API shape: signatures use `(…S1_S1_PA256_c…Fv…)` /
`(…S1_jjPjS2_…)`; **no `removeResourceFork`** symbols at all (present in v1.5.205),
and no `Rb` (bool) params — file-DB / resource-fork handling postdates this build.
- v1.5-only feature strings absent: `Snip Loop` 0 hits, `PATTERN MIXDOWN` 0,
`AUDIO EXPORT` 0. (`Isolator` 1 hit, `Reverse` 1, `Compressor` 2 — isolated
pre-existing occurrences, not the v1.5 master-FX set.)
## Differences vs newer builds
| | v1.1.156 | v1.2.179 | v1.5.205 |
|---|---|---|---|
| size | 1,595,605 | 1,606,197 | 1,707,049 |
| nsect | 7 (with 8 B patch slot) | 6 (slot absent) | 7 (slot back) |
| code len | 1,389,120 | 1,399,200 | 1,490,112 |
| strings len | 115,561 | 117,825 | 125,081 |
| code base | C2589800 | C258D800 | C2B80C00 |
| entry | C26C4820 | C26CA4C0 | C2CD1AA0 |
v1.1→v1.2 is a small delta (+10 kB code); v1.2→v1.5 is the big jump (+91 kB code,
+7 kB strings: audio export, 4 new master FX, 16 slices, MIDI Note Map, …).
+96
View File
@@ -0,0 +1,96 @@
> 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 (v1.2.179)
- File: `Lofi-12 XT.bin` (firmware v1.2.179)
- Size: 1,606,197 bytes (`0x188235`)
- SHA256: `30ea9eeb58cfeb2cddc68f1b15f65f2586d6956df27009eb9045ca78b616a212`
- Same SD-root `SYSTEM UPDATE` flow as the other builds
## Hardware context
Same platform as v1.5.205 (see `../Lofi-12XT_v1.5.205/Lofi-12XT_v1.5.205/reversed.md`):
TI TMS320C6748 (ARM9 + C674x DSP), DDR2 base `0xC0000000`. All `sect`
load addresses are `0xC2xxxxxx` (DDR2 snapshot loaded by the SPI-flash bootloader).
## Container format (same Sonicware custom format)
```
[cmtd 48B][mmtd 36B][sect x6 -> EOF, exact fit]
```
Note: **6 sects** — the 8-byte zero patch slot (`C2589508` in v1.1.156,
`C2B809E8` in v1.5.205) is absent here.
### cmtd (0x00–0x2F)
| Off | Value | Meaning |
|-----|-------|---------|
| 0x00 | `cmtd` | magic |
| 0x04 | 1606197 | u32 LE filesize (matches) |
| 0x08 | `0xDFF0D8C2` | u32 checksum (?) — differs per version, algorithm TBD |
| 0x10–0x2F | `(12, 1, 3, 1, 2, 179, 48, 1606149)` | container (?, 1, 3), fw (1, 2, 179), hdr len 48, remaining |
### mmtd (0x30–0x53)
| Off | Value | Meaning |
|-----|-------|---------|
| 0x30 | `mmtd` | magic |
| 0x34 | 1606149 | remaining size |
| 0x38 | `4e b6 e4 04 ff ff ff ff` | timestamp/flags? (build-specific) |
| 0x40 | `(1, 2, 179)` | fw version |
| 0x4C | `0xC26CA4C0` | **entry point** (inside big code sect, verified below) |
| 0x50 | 6 | sect count |
### sects
| # | File off | Load addr | Len | End addr | Role |
|---|----------|-----------|-----|----------|------|
| 0 | 0x000054 | C277B320 | 83496 (`0x14628`) | C278F948 | asset/blob |
| 1 | 0x014688 | C277AB18 | 660 (`0x294`) | C277ADAC | float table |
| 2 | 0x014928 | C277B000 | 512 (`0x200`) | C277B200 | record table |
| 3 | 0x014B34 | C258D800 | 1399200 (`0x1559A0`) | C26E31A0 | **main code (.text), C6000 DSP** |
| 4 | 0x16A4E0 | C275A7A0 | 117825 (`0x1CC41`) | C27773E1 | **.rodata/.data** (all readable strings) |
| 5 | 0x18712D | C27793E8 | 4348 (`0x10FC`) | C277A4E4 | float ramp tail |
Chain verified: `next_off = off + 12 + len`, ends exactly at EOF (`0x188235`).
## Code identification
- Entry `0xC26CA4C0` → file off `0x151800`. C64x-LE disassembly:
`ZERO b0; mvc b0,ier; mvc b0,csr; mvk -0x6c1c,b15; mvklh -0x3d89,b15;
and -8,…` — same reset prelude as v1.1.156/v1.5.205, only stack immediates
differ. Confirms C6000 DSP code.
- Big sect entropy 6.887, zeros 177,496 (12.7%) — same code+data mix.
- String sect cross-refs: 5,909 words into code range + 2,967 self-pointers
(vs 5,854 + 2,932 in v1.1.156; 6,362 + 3,141 in v1.5.205).
- Total `strings>=4`: 11,110; meaningful (≥10 chars) in data sect: 1,053.
## Data sect contents (file 0x16A4E0–0x18712D)
- Same C674x crash dumper, `14AppSongBrowser`, `N3HAL…Driver`, libc++ `NSt3__`
as v1.1.156.
- Same older `FileUtil` API shape as v1.1.156 (`(…S1_S1_PA256_c…Fv…)`,
`(…S1_jjPjS2_…)`); **no `removeResourceFork`** — that API (plus `Rb` params
and `Fvi` callbacks) appears only in v1.5.205.
- v1.5-only feature strings absent: `Snip Loop` 0, `PATTERN MIXDOWN` 0,
`AUDIO EXPORT` 0 (same isolated `Isolator`/`Reverse`/`Compressor` hits as v1.1.156).
## Differences vs the other builds
| | v1.1.156 | v1.2.179 | v1.5.205 |
|---|---|---|---|
| size | 1,595,605 | 1,606,197 | 1,707,049 |
| nsect | 7 (with 8 B patch slot) | **6 (slot absent)** | 7 (slot back) |
| code len | 1,389,120 | 1,399,200 | 1,490,112 |
| strings len | 115,561 | 117,825 | 125,081 |
| code base | C2589800 | C258D800 | C2B80C00 |
| entry | C26C4820 | C26CA4C0 | C2CD1AA0 |
v1.1→v1.2 is a small delta (+10 kB code, +2 kB strings — matches the v1.2
changelog: per-track swing, new filters LSF/HSF, EVEN slice mode, PAD hold/ROLL,
PTN MUTE, …). v1.2→v1.5 is the big jump (+91 kB code: audio export, 4 new master
FX, 16 slices, MIDI Note Map, …). The coming/going 8 B zero sect looks like
alignment/patch-slot churn in their image generator rather than a real payload.
+140
View File
@@ -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).
+51
View File
@@ -0,0 +1,51 @@
# Firmware v1.5.205 — analyzed image
Deep dive on the newest analyzed release (the version running on the dumped
unit: every `strings≥6` in this image also occurs in IC300). Older releases and
deltas: `rev-diff.md`. Container/code basics: `FINDINGS.md` §4.
## Vitals
- File `Lofi-12 XT.bin`: **1,707,049 B**, sha256
`a8bffa65919c0a2d4ff2ce77d16301551af4d2964497b8804deed49ce4198026`,
checksum `B17C0857`, triple (1, 5, 205), container (1, 3).
- Entry **`C2CD1AA0`** (DSP reset prelude), **7 sects**, chain tiles EOF exactly.
## Composition
| # | Load addr | Len | Role |
|---|---|---|---|
| 0 | `C2DA7600` | 86,168 | asset/blob (`7e xx` bitmap/font/waveform pattern) |
| 1 | `C2DA6DD4` | 688 | float table (filter/window coeffs) |
| 2 | `C2DA7400` | 512 | fixed-record table (presets/patterns) |
| 3 | `C2B80C00` | 1,490,112 | **code** (C674x LE, entropy ~6.89, ~12.6% zeros) |
| 4 | `C2B809E8` | 8 | zero slot (alignment churn, not usable space) |
| 5 | `C2D84DE0` | 125,081 | **rodata** (all strings, vtables; 6,362 code-ptrs, 3,141 self-ptrs) |
| 6 | `C2DA5680` | 4,312 | float ramp tail |
## What it does (from strings/symbols)
- Groovebox app: patterns/songs/tracks/scenes, 16-slice engine, pad handling
(`AppPad`), step sequencer + transport with precount, per-track swing/mute.
- **Audio export** (`AppAudioExport*`, `DialogAudioExporting`, `MixDown~`):
pattern/song mixdown + individual tracks, `MIXDOWN FILE NAME:`.
- **MIDI Note Map** (`AppMIDINoteMap`), MIDI I/O + CC step-lock handling.
- Master FX: `MIsolator`, `MRackComp`, `SlipRoll`, `HoldDelay`; sample `REVERSE`;
tag search, file normalize/convert, `FILE DATABASE` (1,024 files/folder).
- Storage: SD via `N3HAL` drivers; project chunk tags
(`PJST/SMPS/PTD2/PTDT/PJCF/SONG/TRAU/…`); audio `RIFF/WAVE/smpl/cue`,
`MThd/MTrk`; `FileUtil` recursive copy/search + `removeResourceFork`.
- UI: OLED via custom `RenderBuffer` pipeline + `StepEditPageController`
(`updateAppLED`); `SYSTEM INFO` (VERSION/BOOT/SYSTEM/MCU); `SYSTEM UPDATE`;
full C674x crash dumper (`Legacy NMI Exception`, `A0–A31/B0–B31`).
- Toolchain traces: libc++ RTTI (`NSt3__`…) ⇒ TI clang-based CGT.
## Learned / verified
- Unpacked + repacked with `tools/` → byte-identical (`cmp` clean).
- `Threshold→ThresholX` Level-0 mod forges to self-consistent `462F735B`.
- Top string-sect zero gaps (388/256/223/≈196 B) bound same-build string growth
without pointer surgery.
- v1.2→v1.5 added ~91 KiB code (export + note-map + 4 FX + transport rework);
callback ABI migrated `Fv→Fvi/Fvii/Fviii`; display symbols turned over
(`RenderBufferIhLi128ELi128ELi1327E` gone) — see `rev-diff.md`.