diff --git a/.claude/skills/openpocket-rescue/SKILL.md b/.claude/skills/openpocket-rescue/SKILL.md new file mode 100644 index 0000000..b86d027 --- /dev/null +++ b/.claude/skills/openpocket-rescue/SKILL.md @@ -0,0 +1,68 @@ +--- +name: openpocket-rescue +description: Rescue a Donner Pocket X / M-VAVE BlackBox that restarts in a loop or no longer boots, from a Mac with no extra hardware (make rescue, tools/uboot_rescue.py, over the chip's USB UBOOT boot state). Use when the installer's rescue mode cannot finish, the pedal loops after a bad image, or a flash left the app area broken. Covers when to use it, the safety rules, who runs the sudo write, and how to read the result. +--- + +# Rescue a looping or bricked pedal + +The pedal's mask ROM always enters the USB boot state ("UBOOT": USB `4C4A:8057`, shown as `WL80UBOOT1.00`) when the app is bad, +and it cannot be damaged. `tools/uboot_rescue.py` waits for that state, loads JieLi's RAM loader and rewrites the app area from +the official package. Full walkthrough and the record of the first real run: `docs/RESCUE.md`. + +## When to use it + +- The pedal restarts in a loop and the installer page's rescue mode (or `node tools/op.mjs flash`) cannot finish. Try those first: + they need no root. +- The pedal stays in the boot state (no loop, no sound) after an interrupted flash: the rescue finishes the job. +- Not for Bluetooth or firmware behaviour questions, and not on a pedal that works. + +## Rules (read first) + +1. **Read-only first.** `make rescue` writes nothing to the flash (the loader goes to RAM only) and saves a backup of the whole flash + to `~/OpenPocket-rescue`. Read its output before anything else. +2. **The captain runs the write.** `make rescue-write` and every `sudo` command need the captain's password and instruction. An + agent runs neither: prepare the exact command and give it with the `!` prefix. +3. The write range is only whole 4 KiB sectors in `[0x4000, end of the package)`, `[0x4000, 0xA3000)` for BlackBox. Never write + the head `[0, 0x4000)` or anything above the package. The tool refuses; do not work around it. +4. Never chip erase (`FB02`) and never burn a key (`FC12`). The tool cannot send them; do not add them. +5. Stop on any `REFUSED` (wrong device, chip key not `0x980F`, flash not 1 MiB, head differs from the package). Nothing was written; + report the message. +6. Keep the first backup. Never commit or publish a backup or a package: they hold the official firmware. +7. After a restore, do not reinstall the Bluetooth test image until the cause of the loop is known. + +## How + +``` +brew install libusb # once +make doctor # rescue lines: libusb, official package, loader, root +make rescue # read-only: INQUIRY, loader to RAM, key, flash id, backup, head check, sectors that differ +make rescue-write # only on the captain's instruction, after the read-only run passed +``` + +Options go after `ARGS=` (`make rescue ARGS="--package FILE.fwsc"`; `--image flash.bin`, `--loader FILE`, `--backup-dir DIR`, +`--attempts N`). Plug the pedal straight into the Mac (no hub). Start the command first; it waits for the pedal and starts by +itself the moment it appears (the pedal is in UBOOT about 44 s of every 70 s while it loops). Needs the official BlackBox package +(default `~/Documents/BlackBox (2).fwsc`) and Python 3. + +## Reading the result + +| Output | Meaning | +| --- | --- | +| `INQUIRY: WL82 UBOOT1.00 1.00` | the right device | +| `loader running: chip key 0x980f, flash id ... (1024 KiB)` | the checks passed; record the flash id | +| `backup saved: ...dump.bin` | the rollback copy is complete on disk | +| `head [0, 0x4000) matches the image` | the package fits this pedal | +| `N of 160 sectors ... differ` | what a write would do; a rerun after a partial write shows fewer | +| `Read-only run finished, nothing was written` | exit 0, the read-only run is done | +| `Done: N sectors written and verified` | exit 0 after `--write`; power-cycle the pedal | +| `the pedal went away ... waiting for it again` | the window closed; it resumes by itself | +| exit 1 `ERROR` | setup: libusb, `sudo`, missing package; the message names the fix | +| exit 2 `REFUSED` | a safety check stopped it, nothing written | +| `LIBUSB_ERROR_ACCESS` | run with `sudo` (`make rescue` does); if it persists as root, report it | + +Save the output of a real run (`make rescue 2>&1 | tee rescue.log`) and add its numbers to `docs/RESCUE.md`. + +## Tests + +`make rescue-check` runs the tool against a simulated UBOOT pedal (protocol, guards, resume, no hardware); it is part of +`make test`. Hardware behaviour is not covered by any test. diff --git a/Makefile b/Makefile index 591ee81..c5146a2 100644 --- a/Makefile +++ b/Makefile @@ -10,11 +10,18 @@ # make serve the installer and editor on http://localhost:8765/ (offline install) # make render IN=guitar.wav [OUT=out.wav] [PRESET=1] a WAV through the engine # make appimage [DUMP=fm1-1.bin] fm1-300.bin for the SD-card recovery +# make rescue [ARGS=..] restore a looping BlackBox from its UBOOT state, read-only (needs sudo and libusb: docs/RESCUE.md) +# make rescue-write [ARGS=..] the same, writing the official firmware back (run `make rescue` first) +# make rescue-check the UBOOT rescue tool (tools/uboot_rescue.py) against a simulated pedal; needs only Python 3 # `make NATIVE=1 build` skips Docker (toolchain installed with firmware/setup.sh). -.PHONY: doctor build test package clean shell image serve render appimage -doctor build test package clean shell image serve: +.PHONY: doctor build test package clean shell image serve render appimage rescue rescue-write rescue-check +doctor build test package clean shell image serve rescue-check: @node tools/op.mjs $@ render: @node tools/op.mjs render "$(IN)" "$(OUT)" "$(PRESET)" appimage: @node tools/op.mjs appimage "$(DUMP)" +rescue: + @node tools/op.mjs rescue $(ARGS) +rescue-write: + @node tools/op.mjs rescue --write $(ARGS) diff --git a/README.md b/README.md index 5e58bde..4025bdf 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,9 @@ setting was still the official firmware's (it survives a warm reset), so the ped the DSP slower and crackle, so v33 leaves them as the boot loader does again.) Being tested on the BlackBox. For a pedal that restarts in a loop, the page has a **rescue mode** (step 2): tick it, press Connect, plug the pedal in; the install starts the moment the pedal answers and halts its DSP at once. A BlackBox can always go back to its official firmware from the page (step 3). It is the same JieLi update system as the M-VAVE FM-1, where this method is -used by Felucca, CLIPPER and SLOOP. Keep the SD-card recovery at hand (`flash/`: fm1buddy on an ESP32-S3, -`makeimage.bat`). +used by Felucca, CLIPPER and SLOOP. If the pedal still loops, restore it from a Mac with no extra hardware: `make rescue` (read-only check and backup), then +`make rescue-write` (`docs/RESCUE.md`; it needs `brew install libusb`, sudo and the official BlackBox file). The second route is the +SD-card recovery (`flash/`: fm1buddy on an ESP32-S3, `makeimage.bat`). **Without internet**: double-click `INSTALL-OFFLINE.bat` (Windows; nothing to install: a small PowerShell server, `tools/serve_offline.ps1`) or run `make serve` (macOS, Linux; `tools/serve.py`, needs Python 3). It serves the installer diff --git a/docs/BUILDING.md b/docs/BUILDING.md index de42e5f..14600c4 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -35,6 +35,8 @@ Apple silicon and other ARM machines Docker emulates it (Rosetta or QEMU); you c | `make serve` | the installer and editor on `http://localhost:8765/` (offline install; needs Python 3 on the host) | | `make render IN=guitar.wav [OUT=..] [PRESET=1]` | a DI recording through the engine | | `make appimage DUMP=fm1-1.bin` | `fm1-300.bin` for the SD-card recovery | +| `make rescue` / `make rescue-write` | restore a looping BlackBox from its UBOOT state from a Mac (read-only first; asks for sudo; `ARGS="--package FILE"` for options): `docs/RESCUE.md` | +| `make rescue-check` | the UBOOT rescue tool (`tools/uboot_rescue.py`, see `docs/RESCUE.md`) against a simulated pedal; needs only Python 3 | `make NATIVE=1 build` skips Docker and uses a toolchain installed on the machine (`sh firmware/setup.sh` installs it in `~/.jieli`; Linux x86-64 only). `tests/run_tests.sh` also runs directly on macOS and Linux with only a C compiler, Node and diff --git a/docs/RESCUE.md b/docs/RESCUE.md new file mode 100644 index 0000000..971829e --- /dev/null +++ b/docs/RESCUE.md @@ -0,0 +1,104 @@ +# Rescuing a crash-looping BlackBox from a Mac (no extra hardware) + +Use this when the pedal restarts in a loop and the installer's rescue mode cannot finish. Between crashes the pedal sits +about 44 seconds in the JieLi mask-ROM USB boot state ("UBOOT": USB 4C4A:8057, shown by macOS as `WL80UBOOT1.00`). The +mask ROM is chip ROM, so it cannot be damaged. `tools/uboot_rescue.py` waits for that state, loads JieLi's RAM loader and +rewrites the app area of the flash from the official package, then restarts the pedal. + +## One-time setup (macOS) + +``` +brew install libusb +``` + +That is all. The tool is one Python 3 file that calls libusb through `ctypes` (no `pip install`). It needs two files: + +- The official package, `~/Documents/BlackBox (2).fwsc` (BlackBox_016) by default, or `--package FILE.fwsc`, or a raw flash + image with `--image flash.bin`. Every CRC of a package is checked before it is used. +- JieLi's `wl82loader.bin`, downloaded once into the git-ignored `build/cache/` from kagaimiq/jl-uboot-tool (commit + `c32b28e`, the same pin as `flash/installer-fm1buddy-pocketx.bat`) and checked against SHA-256 + `d41da6126760c9d66660bcc0cac8d27d221806c5e369a8036921efe68dca5376` on every run. It is not in this repository. `make clean` + removes the cache; `--loader FILE` uses a copy you downloaded yourself. + +## The commands + +`make rescue` and `make rescue-write` (or `node tools/op.mjs rescue [--write]`, the entry point next to `flash`) run the tool with +`sudo` for you and pass options on (`make rescue ARGS="--package FILE.fwsc"`). `make doctor` shows whether libusb, the package and +the loader are in place. The `sudo python3 tools/uboot_rescue.py` lines below are the same thing written out. + +macOS keeps its own mass-storage driver on the pedal in this state, and libusb needs root to take the interface from it. +Plug the pedal straight into the Mac (no hub), start the command, and it waits for the pedal and starts by itself the moment +it appears. + +1. **Read-only first.** Nothing is written to the pedal's flash; the loader goes to RAM only. + +``` +make rescue # = sudo python3 tools/uboot_rescue.py +``` + +It checks that the pedal answers INQUIRY as `WL82 UBOOT1.00`, loads the loader, reads the chip key (must be `0x980F`) and +the flash id (must be a 1 MiB flash), saves a backup of the whole flash to `~/OpenPocket-rescue/blackbox-