Init
This commit is contained in:
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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, …).
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
|
||||

|
||||
|
||||
## 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 |
|
||||
@@ -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 — SoC, DDR2 and both SPI flashes (bottom edge).*
|
||||
|
||||

|
||||
*Both are Macronix MX25L12833F, 16 MiB. Pin-1 dots face down-left; note the
|
||||
`IC300`/`IC301` silkscreen between the packages.*
|
||||
|
||||

|
||||
*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 |
Reference in New Issue
Block a user