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
+51
View File
@@ -0,0 +1,51 @@
# Bootloader
How the device boots, from power-on to app. Static analysis of the IC300 SPI
dump (kept local, never shipped) + TI C6748 public boot docs.
## Chain
1. **SoC ROM (ARM9)** reads boot-mode pins → SPI flash master mode.
2. ROM parses the **AIS image** at flash `0x0` (magic `0x41504954`, `TIPA`).
3. AIS prelude runs **PLL/DDR/EMIF init** (opcodes `0x5853590D` ×2 at file
`0x8`/`0x1C`), so DDR is usable before any load.
4. ROM processes **10× Section Load** (`0x58535901`), copying code/data to
`0xC7074630–0xC70B0598` (see table below), then **JumpClose** (`0x58535906`,
file `0x38660`) to entry **`C70A28A0`**.
5. Entry is DSP code (C674x reset prelude `ZERO b0; mvc b0,ier; mvc b0,csr`,
stack align) — from here the DSP owns the system; ARM ROM's job is done.
No AIS CRC opcodes are present.
## AIS section table (file off → DDR, size)
| File | DDR | Size | Role |
|---|---|---|---|
| `0x000050` | `C7074630` | `0x1D0` | header/code |
| `0x00022C` | `C7074800` | `0x33B00` | main code (updater lives here) |
| `0x033D38` | `C70A8300` | `0x28CC` | rodata (strings, vtables) |
| … | `C70AABD0`/`C70AABE0`/`C70AABF0` | `C`/`C`/`D44` | small records |
| … | `C70AF000`/`C70AF4E8`/`C70AF5EC`/`C70AF660` | `200`/`104`/`70`/`F38` | tables/tail |
Re-split any dump with `tools/ais_unpack.py`.
## What the bootloader contains
- Full C++ application core (libc++, TI CGT): `Foundation::SystemUpdater`,
`SystemVerify`, `BootLoader(Delegate)`, HAL drivers (`N3HAL` SPI/I2C/UART/SD),
OLED/display stack, crash dumper (`Legacy NMI Exception`, register dump).
- Updater methods per flash section: `updateBootSection`, `updateMainSection`
(the SD `.bin` = SYSTEM), `updatePresetSection`, `updateMCUSection`,
`updateMCUBootSection`, all taking `CatMetaData::ROMSection`.
- Checksum engine: bitwise CRC-32/IEEE at **`C70A0A60`**
(`NOT` init/final in/around the routine ⇒ zlib chaining `F(buf,len,init)`),
4 KiB-chunk driver at **`C708E4E4`**, file check at **`C709E7C0`**
(16-byte header CRC seed 0 → chained `0x40000` chunks → `CMPEQ` vs stored),
orchestrated from **`C708678C`**. Ghidra decompilations that proved this are
described in `RE-PROCESS.md` §3–4 (project kept local).
## Rest of IC300 flash
Past the AIS image: `FF` to `~0x100000`, data to `~0x1EFFFF`, `FF` gap
`0x1F–0x4Fxxxx`, data `0x500000–0xFC7C17` (app/data sections loaded by the
updater; exact section table not yet mapped — open question in `FINDINGS.md`).
IC301 is separate factory content (`AILS`, pattern names, 24 WAVs).
+53
View File
@@ -0,0 +1,53 @@
# Firmware update (SD path)
How a stock or modded `.bin` gets onto the device and exactly what is verified.
No hardware access needed — SD card + OLED only.
## Trigger
File named `Lofi-12 XT.bin` at SD root (from the official update ZIP:
https://sonicware.jp/pages/downloads), then hold **PAD while powering on**.
Updater (`SYSTEM UPDATE`) lives in SPI AIS flash, **not** in the `.bin`, so a
bad SYSTEM image can always be reverted with a stock SD.
## Stages (OLED text)
1. `Checking... 0%` — parse + validate the file (checks below). Failures:
`The firmware file not exist.`, `This card is invalid format.`,
`ERROR : This file is invalid.` / `Check the firmware file.`
2. `Are you sure?` — `[CLR] : No / [OK] : Yes`.
3. Per section (`>> BOOT / SYSTEM / PRESET / MCU / MCU Boot`, prompts `BOOT:`…):
`Erasing...`, `Writing...`, `Verifying...` with `%` progress.
4. `100%, done.` / `Update completed.` (`100%, NG.` / `Update failed.` on error)
→ `Please restart.`
## Checks performed on the `.bin`
1. Magic: `cmtd` @`0x00`, `mmtd` @`0x30`, `sect` chain from `0x54`.
2. `cmtd+0x04` filesize equals actual file size.
3. `sect` chain tiles EOF exactly (`next = off + 12 + len`); load addresses must
land in DDR (`0xC2xxxxxx`); entry point must sit inside the code sect.
4. **Checksum** `cmtd+0x08` — recomputed and compared (`CMPEQ`, reject =
`ERROR : This file is invalid.`). Rule (proven 3/3, see below):
CRC32-IEEE, init 0, over the whole file with bytes `[8:12]` replaced by
`0xC27C6282`. `mmtd+0x08` flags are covered as-is (no special handling).
## Forging a valid image
```bash
python3 tools/lofi_patch_string.py stock.bin mod.bin OldString NewStringX
python3 tools/prove_checksum.py mod.bin # expect MATCH
```
`lofi_pack.py`/`lofi_patch_string.py` stamp the checksum automatically
(`lofi_image.compute_checksum`); `--checksum keep` preserves the old value.
`analysis/solve_ckseed.py` re-derives the seed constant from any stock image
(GF(2) solve, expect `C27C6282`).
## Notes
- Public ZIPs ship only the SYSTEM section; BOOT/PRESET/MCU use separate files.
- Only same-length string/asset/constant edits keep every address stable
(Level-0). Longer content needs slack-space pointer patching (`lofi_xref.py`).
- The AT32 USB MCU updates through `updateMCUSection` — protocol unreversed;
leave MCU sections alone.
+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`.
+78
View File
@@ -0,0 +1,78 @@
# SPI flash dumping (CH341a)
Required once: backs up the bootloader/app (`IC300`) and factory data (`IC301`)
before any modding, and produced the dumps every finding here rests on. Dumps
stay local — never commit or publish them (vendor IP; `.gitignore` blocks
`*.bin`). Involved enough to deserve its own page; checklist version in
`RE-PROCESS.md` §1.
## 0. What you need
- Black CH341a Mini Programmer (v1612-class flow below; others similar),
SOIC-8 clip, multimeter, a Linux host with `flashrom`.
- Target: core module, `IC300` (boot+app) + `IC301` (data), both `MX25L12833F`.
## 1. Identify the chips
Factory stickers cover the markings — peel carefully, photograph first.
Confirm SOIC-8 pin 1 (dot/dimple, lower-left on both packages below) and probe
`VCC`/`GND` powered on (`VCC` = 3.3 V on this board). The exact part name
matters for `flashrom -c`.
![IC300 + IC301, stickers removed — both MX25L12833F](photos/ic300-ic301-closeup.jpg)
## 2. Fix the programmer voltage (do not skip)
The black board's 3.3/5 V jumper only switches ZIF `VCC`. The CH341A chip itself
stays on 5 V USB, so `CS/MOSI/CLK` idle at ~5 V even in the 3.3 V position —
out of spec for 2.7–3.6 V flash (degrades/kills). Verify: plug in empty, meter
`GND → CS/MOSI/CLK`. If ~5 V, either do the 3.3 V mod (feed CH341 `VCC` pin 28
from the `AMS1117` 3.3 V output) or use a level-shifter adapter board. Re-meter:
all signals ~3.3 V before touching the device.
## 3. Select SPI mode
Two personalities: `1a86:5523` + `ttyUSB0` = UART mode (useless here),
`1a86:5512` = SPI mode (`flashrom` needs this). Move the P/SPI jumper, replug,
confirm with `dmesg` (`New USB device found, idVendor=1a86, idProduct=5512`).
## 4. Permissions and containers
- `Couldn't open device … errno=13` = permissions. Test with `sudo`; make it
permanent with a udev rule for `1a86:5512` (`MODE="0666"`) +
`udevadm control --reload-rules && udevadm trigger`.
- `sudo` inside distrobox/toolbox is **not** host root for USB. Run on the host:
`distrobox-host-exec sudo flashrom …` (or `flatpak-spawn --host …`); podman /
docker need `--privileged -v /dev/bus/usb:/dev/bus/usb`.
## 5. Dump (read-only)
1. Device fully unpowered. Hold the SoC in reset if clipping in-circuit (avoids
bus contention with the C6748 driving SPI); `WP`/`HOLD` pulled high.
2. Seat the SOIC-8 clip on pin 1 → pin 1. A slipped clip shorts rails — reseat,
don't force.
3. Per chip, read **twice** to scratch files, then keep the verified copy as
the canonical dump:
`flashrom -p ch341a_spi -c <exact-chip-name> -r ic300_a.bin` (then `_b`).
4. `sha256sum ic300_*.bin; cmp ic300_a.bin ic300_b.bin` — must be identical;
expect exactly 16,777,216 B per MX25L12833F. Third read on any mismatch.
Then save one verified copy as `ic300.bin` (`ic301.bin` for the other chip).
Canonical names everywhere: `ic300.bin` / `ic301.bin`.
5. Never `-w`/`-E` (write/erase) during backup. Writing is only for recovery
with a verified dump in hand.
## 6. Sanity-check the dumps
- IC300 starts `54 49 50 41` (`TIPA` = AIS `0x41504954`), entropy ~6.9.
- IC301 starts `AILS`, pattern names in clear, `RIFF/WAVE`s from `0x8007E0`.
- `onlySD == 0` string-set test vs the running firmware version
(see `RE-PROCESS.md` §2) confirms which release is flashed.
## Troubleshooting
| Symptom | Cause → fix |
|---|---|
| `1a86:5523`, `ttyUSB0` | UART mode → move mode jumper, replug |
| `Couldn't open … errno=13` (even with sudo in container) | USB not passed through → run on host (§4) |
| `FF…`/`00…` reads, JEDEC ID unknown | Clip seating / wrong `-c` name / chip still powered by board → reseat, hold reset, re-probe `VCC` |
| Signals meter ~5 V | Unmodded black CH341a → §2 before retrying |
+52
View File
@@ -0,0 +1,52 @@
# Hardware
Static facts about the Lofi-12 XT hardware (own teardown + photos in
`photos/`). Behavioral/firmware side: `../FINDINGS.md`, `bootloader.md`.
## Boards
| PCB | Role |
|---|---|
| `405-VTOR-3852` | I/O board (jacks, MIDI, relays `HFD4/5`, USB-C area) |
| `405-VTOR-3849B` | Main board (`28-AUG-2023` rev on unit): SD slot, MCU, power, FFC to UI |
| `405-VTOR-3853` | Jack sub-board (`2/JUN/2022`) |
| core module (unmarked) | Compute: SoC + DDR + 2× SPI flash, board-to-board `CN200A/CN201A/CN203A/CN203B` |
## Chips (all confirmed visually)
- **TI TMS320C6748** (`TMS320 C6748EZW T / 13CP99W`) — main SoC, ARM9 + C674x DSP.
- **EtronTech EM68C16CWQG-25H** (`B07DY15MSYA0051`) — 1 Gbit DDR2.
- **2× Macronix MX25L12833F** (`MXIC MX 25L12833F M2I-10G`), 16 MiB SPI NOR each,
silkscreen `IC300` (boot+app) / `IC301` (data). Factory stickers cover the
marking (`C047`/`C949`-style labels) — peel + photograph before any clip work.
- **ARTERY AT32F421K8T** — USB/aux MCU, separate firmware (strings reference
`updateMCUSection`/`updateMCUBootSection`; protocol not reversed).
## Memory map (DDR2, base `0xC0000000`, 128 MB)
| Region | Use |
|---|---|
| `0xC2xxxxxx` | SD `.bin` application image (v1.5: code `C2B80C00`, strings `C2D84DE0`) |
| `0xC7074630–C70B0598` | SPI AIS bootloader image, entry `C70A28A0` |
| `0xC706F280` | Updater constant (file/scratch buffer area) |
| `0xC27C6282` | Updater DDR scratch; value reused as checksum seed |
## Rails / probing
- Flash `VCC` measures 3.3 V in-circuit — never apply 5 V (see `RE-PROCESS.md` §1).
- Jack board silkscreen: `A+7.5V / AGND / A-7.5V` analog rails, `DSU`/`AGNC`.
- Main-board silkscreen tables name `UART1_RX1/TX1`, `SPI1_SCK/MOSI`, `SD_*`,
`USB_DP/DN`, key matrix (`KS1–KS6`, `EN_1A–5B`), `TP1–TP5` — candidates for
UART/JTAG mapping (continuity not yet traced; see `uart-zoom.jpg`).
## Photos
![Core module: TMS320C6748 + DDR2 + IC300/IC301](photos/core-module-top.jpg)
*Core module — SoC, DDR2 and both SPI flashes (bottom edge).*
![IC300 (left) + IC301 (right), stickers removed](photos/ic300-ic301-closeup.jpg)
*Both are Macronix MX25L12833F, 16 MiB. Pin-1 dots face down-left; note the
`IC300`/`IC301` silkscreen between the packages.*
![UART area](photos/uart-zoom.jpg)
*Close-up of the UART test-point area (unannotated).*
Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB