From 7a29a11a2c78a7ec44ab89940d0fdaeee40ac753 Mon Sep 17 00:00:00 2001 From: Vince Speelman Date: Thu, 5 Nov 2026 21:27:32 -0500 Subject: [PATCH 1/4] feat(tools): add a Mac-side UBOOT rescue tool for a crash-looping BlackBox tools/uboot_rescue.py waits for the pedal in its JieLi mask-ROM USB boot state (4C4A:8057), loads the pinned wl82loader.bin into RAM, backs up the whole flash, checks the head against the official package and, only with --write, rewrites the differing 4 KiB sectors in [0x4000, end of image) with read-back. It is read-only by default, whitelists the loader commands (no chip erase, no key burn), refuses a wrong chip key or flash size, and resumes on rerun. libusb is called through ctypes; no pip packages. Tested against a simulated UBOOT pedal (make rescue-check, part of make test); the protocol has not yet been run on the real pedal beyond finding it and reading its descriptor. --- Makefile | 5 +- README.md | 2 +- docs/BUILDING.md | 1 + docs/RESCUE.md | 83 +++++ tests/run_tests.sh | 3 + tests/test_op.mjs | 4 + tests/test_uboot_rescue.py | 255 ++++++++++++++++ tests/uboot_sim.py | 116 +++++++ tools/op.mjs | 7 + tools/pocketx_fw.py | 20 +- tools/uboot_rescue.py | 611 +++++++++++++++++++++++++++++++++++++ 11 files changed, 1099 insertions(+), 8 deletions(-) create mode 100644 docs/RESCUE.md create mode 100644 tests/test_uboot_rescue.py create mode 100644 tests/uboot_sim.py create mode 100644 tools/uboot_rescue.py diff --git a/Makefile b/Makefile index 591ee81..65eec47 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,10 @@ # 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-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-check +doctor build test package clean shell image serve rescue-check: @node tools/op.mjs $@ render: @node tools/op.mjs render "$(IN)" "$(OUT)" "$(PRESET)" diff --git a/README.md b/README.md index 5e58bde..6e23bb5 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ 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, +used by Felucca, CLIPPER and SLOOP. A pedal that loops can also be restored from a Mac with no extra hardware (`docs/RESCUE.md`, `tools/uboot_rescue.py`). Keep the SD-card recovery at hand (`flash/`: fm1buddy on an ESP32-S3, `makeimage.bat`). **Without internet**: double-click `INSTALL-OFFLINE.bat` (Windows; nothing to install: a small PowerShell server, diff --git a/docs/BUILDING.md b/docs/BUILDING.md index de42e5f..c033441 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -35,6 +35,7 @@ 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-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..bb24baf --- /dev/null +++ b/docs/RESCUE.md @@ -0,0 +1,83 @@ +# 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 two commands + +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. + +``` +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-