Updates (self-hosted, signed)
Workstream H: waved updates itself from a server its owner runs. Nothing talks to
Insta360 (../TODO.md, “Not planned”). Code: crates/waved-update; screen:
crates/waved-ui/src/screens_v2/ota.rs; fall-back: deploy/S60waved.
Status: host-tested only. No part of this has run on a Wave yet. The checks a device pass must make are under Device checks.
Paths are relative to waved/; ../ is the repository root.
Owner’s quick start
Section titled “Owner’s quick start”# 1. Once: a key pair. The private key stays outside the repo (waved-pack refuses a# path inside a git work tree) with mode 0600.cargo run -p waved-update --bin waved-pack -- keygen ~/.config/waved/update.key# -> prints the public key; keep it, e.g. in your shell profile:export WAVED_UPDATE_PUBKEY=<64 hex characters>
# 2. Every release: bump `version` in waved/Cargo.toml (equal is "up to date"),# build with the key compiled in, strip.WAVED_BUILD_ID=$(git rev-parse --short HEAD) cross build --release \ --target aarch64-unknown-linux-gnu --features "ui,capture-alsa" -p waved$(rustc --print sysroot)/lib/rustlib/aarch64-apple-darwin/bin/llvm-objcopy --strip-all \ target/aarch64-unknown-linux-gnu/release/waved /tmp/waved
# 3. Pack and sign. --include adds deploy files; keep deploy/S60waved (see below).cargo run -p waved-update --bin waved-pack -- pack --key ~/.config/waved/update.key \ --waved /tmp/waved --include deploy/S60waved --out /srv/wave-updates# -> waved-<version>.wup and latest.json
# 4. Serve the directory over HTTP(S) and point the Wave at it (setup page, Advanced):# security.ota_server = "http://updates.lan/wave"waved-pack inspect <file.wup> --pubkey <hex> shows a package’s manifest and checks
its signature. The first build that carries the key has to reach the Wave the old way:
deploy/install.sh over adb (docs/DISPLAY_HARDWARE.md#deploy), which also installs the
current S60waved and keeps the Wave’s config.toml. From then on updates go over the air.
Wave installs a package only if the S60waved that will run at the new version’s first
boots has the current fall-back (update_guard, below):
the package’s own when it carries one, otherwise the one on the Wave. A Wave whose
/etc/init.d/S60waved came from main, or that got only the binary over adb, has none and
refuses with “Wave can’t go back if this update fails”. So does one with an older guard
from this branch (see step 1 of the install). Pack with --include deploy/S60waved (or
install it with deploy/install.sh) and that Wave updates.
Cross.toml passes WAVED_UPDATE_PUBKEY into the build container. A build without it
trusts no key: its System row reads “Off” and it can only be replaced over adb.
waved-pack pack refuses a binary that does not embed the signing key’s public half, so a
release can never lock its owner out.
Package (.wup)
Section titled “Package (.wup)”All integers little-endian.
| Offset | Size | Content |
|---|---|---|
| 0 | 8 | magic WAVEDUP1 |
| 8 | 4 | header length H, 1..=64 KiB |
| 12 | H | header: the manifest, UTF-8 JSON |
| 12+H | 64 | ed25519 signature over bytes 0..12+H |
| 76+H | … | each file’s gzip bytes, in manifest order, back to back, nothing after |
{"format": 1, "version": "0.10.0", "build": "374015f", "created_unix_s": 1791815358, "files": [{"target": "waved", "size": 63000000, "sha256": "…", "stored_size": 22000000, "stored_sha256": "…"}]}- The signature covers the header; the header holds every file’s stored and unpacked SHA-256 and size, so one signature binds every byte.
- Verification is two passes (
format::verify_and_stage): first every stored byte is hashed against the signed header (and nothing may follow), then the trusted bytes are unpacked into the staging directory with size and content checked. Nothing untrusted reaches the decompressor. wavedis required. The other targets are a closed list with fixed device paths; a package cannot name a path:
| Target | Device path | Mode |
|---|---|---|
waved |
/data/waved/waved |
755 |
S60waved |
/etc/init.d/S60waved |
755 |
S03splash |
/etc/init.d/S03splash |
755 |
splash.fb |
/data/waved/splash.fb |
644 |
iptables.rules |
/data/waved/iptables.rules |
644 |
rsyslog.conf |
/etc/rsyslog.conf |
644 |
/etc is an overlay whose upper dir is /data/overlay/upper/etc, so writes there persist
(../docs/GUIDE_DEVICE_ACCESS.md, “Overlay layout”). No rootfs, kernel, partition or
bootloader changes: the root is a read-only SquashFS slot, and writing slots is what
the stock updater does (see Going back to stock).
Server index (latest.json)
Section titled “Server index (latest.json)”Wave fetches <security.ota_server>/latest.json (at most 16 KiB):
{"format": 1, "version": "0.10.0", "build": "374015f", "package": "waved-0.10.0.wup", "size": 23456789, "sha256": "<hex of the whole .wup>", "installed_size": 63000000}package is relative to the index or an absolute http(s) URL. The index is not
signed: it only decides whether to download. The package’s signature decides whether
to install, and its signed version and build must equal the index’s. A hostile server can
make Wave download junk that it then rejects, which a hostile network can do anyway.
Versions
Section titled “Versions”MAJOR.MINOR.PATCH, compared as numbers. The running version is the workspace version;
waved-pack reads the package version out of the binary (waved-version: marker), so the
two cannot disagree.
| Offered vs running | Result |
|---|---|
| newer | installs |
| equal | up to date (with security.ota_allow_downgrade = true and two different known build ids: reinstalls) |
| older | refused, unless security.ota_allow_downgrade = true in config.toml |
The downgrade switch lives on the device, not on the server or in the package, so an attacker who controls the server cannot replay an old signed package. It is not on the setup page.
What Wave does
Section titled “What Wave does”UpdateService (crates/waved-update/src/service.rs), started by main.rs through
device::spawn_device:
- At start: deletes what an earlier update left behind (the staging directory, which
a restart while Waiting leaves full, and half-written
.<name>.newcopies), reads the trial bookkeeping (below) and reports a rollback or an interrupted install as a failure. This runs even with updates off. - Checks 2 min after start, then every 6 h; System → Firmware update checks at once.
An automatic check that cannot reach the server stays quiet; a tapped one fails
visibly. No check or install, tapped or automatic, runs while
update/trialexists: while this version is on trial, or after an install whose restart failed (step 6). Installing on top of an armed trial would make.waved.prevthe new binary, and undoing it as an “interrupted install” would leave the new binary with no trial and no fall-back. An automatic check that finds a skipped version while a failure about that same version is on show leaves the failure up instead of showing Available. - Downloads to
/tmp/waved-update/(tmpfs, RAM;/datais small). An interrupted download resumes with an HTTPRangerequest;package.sha256next to it keeps a part of one package from being continued with another. A server that ignores ranges starts the file over. - Verifies: whole-file SHA-256 against the index, then the signature, then the signed
version against the index and the version rule, then (with any old staging deleted
first) room on
/data, then unpacks to/data/waved/update/staged/(same filesystem as the binary). - Waits until Wave is free (
gate.rs): no recording or pause, no “Send this recording?”, USB storage off (UsbStorageState::phaseisOff: the restart would pull the recordings drive from under the computer, andStarting/Stoppingare mid-handover), no call or ringing on the computer, no phone call through Bluetooth (BluetoothState::callringing or up, orcall_audiostill running), no playback, no Wi-Fi setup, battery ≥ 30 % or USB connected. All of these hold a tapped install too. Only one does not: an automatic install also waits while the computer has the microphone open; a tapped one does not (some apps keep it open all day). Music from a phone (phone_audiowithoutcall_audio) does not hold an install. - Installs (
install.rs, below), shows “Restarting” for a second, then runs/sbin/reboot. If the reboot fails the update is installed and its trial armed, so the phase becomesRestartNeeded(“Restart Wave”), not a failure with Retry: the new version starts when the user turns Wave off and on, or when waved next exits (the watchdog runs the guard, then the new binary). If the installer itself finds a trial pending (TrialPending), it changes nothing and the service shows the same state; it never undoes that trial.
OtaState.progress_pct: download 0–90, verify and unpack 90–98, waiting 98, installing 99,
restarting 100.
/data is 512 MiB with ~440 MB usable and nearly full; the stripped binary is ~63 MB
(docs/DISPLAY_HARDWARE.md, “Deploy”). The download (gzip, smaller than the binary) goes
to RAM. Staging needs the unpacked size plus 16 MiB on /data, measured after any old
staging is deleted; if that is short the update fails with “Not enough space on Wave”
(space.rs). Nothing is deleted to make room: the .prev copies are the last known good
version, and the next install replaces them anyway (step 2 below). After an install /data
holds the new binary and .waved.prev, which is the old binary’s own inode (a hard link,
no copy), so the peak is three binaries: the live one, .waved.prev and the staged one.
Install and fall-back (“trial boots”)
Section titled “Install and fall-back (“trial boots”)”FsInstaller (install.rs), in this order so a power cut at any point leaves a Wave that
starts:
sh -non every staged init script; a syntax error installs nothing. Then the fall-back check: the package’sS60wavedif it carries one, else/etc/init.d/S60waved, must contain the current guard’s lines (install.rs,GUARD_MARKERS):update_guard(),MAX_TRIAL_BOOTS=and the dot-name ruleprev="${path%/*}/.${path##*/}.prev". Otherwise nothing is installed (NoFallBack; automatic checks then pass over that version, a tapped Retry tries again).update_guardalone is not enough: the first guard (f6c0eff) put back$path.prev, which this installer no longer writes, and the second (69a673d) counted starts and went back after three, which one good boot can exceed (both kept incrates/waved-update/testdata/). Change the markers with the script whenever the trial protocol changes.- Hard-link every file about to be replaced to
.<name>.prevbeside it (copy if linking fails), and copy/data/waved/config.tomlto.config.toml.prev(mode 600; a copy, so a save during the trial cannot reach it). - Write
/data/waved/update/trial(tmp file, fsync, rename) and clear the counts. - Replace the deploy files: copy to
.<name>.newin the same directory, fsync, rename. - Rename the staged binary over
/data/waved/waved. sync. The service then reboots.
Every copy beside a file has a dot name (/etc/init.d/.S60waved.prev,
/etc/init.d/.S60waved.new, /data/waved/.waved.prev; Paths::prev, Paths::new_copy).
Stock /etc/init.d/rcS runs every /etc/init.d/S??* with start at boot and rcK runs
them with stop: an S60waved.prev would start a second watchdog loop (two waveds
fighting over the devices, each start counted twice), and a half-written S60waved.new
left by a power cut would be run. When the service starts it deletes any .<name>.new a
cut install left.
update/trial is plain lines so busybox sh can read it:
version 0.10.0build 374015fprevious 0.9.3restore /etc/init.d/S60wavedrestore /data/waved/wavedrestore /data/waved/config.tomlremove /data/waved/splash.fbconfig.toml is listed like the files the update replaces (remove when there was none),
because every config section denies unknown fields: an older waved rejects a file a newer
one saved with a key it does not know, and its .bak too, and runs on defaults (first-run
screen, PIN off, API token and uploads gone). New keys are saved only when they differ
from their default (Going back over adb), which narrows this but
does not remove it. Settings changed during a trial are lost on a rollback: the old
waved gets the settings it had when the update was installed.
While it exists, the watchdog loop in deploy/S60waved (update_guard) counts boots:
the first start of a boot writes /tmp/waved-trial-boot and appends a line to
update/boots; every start appends to /tmp/waved-trial-starts (/tmp is a tmpfs, empty
after each boot). At the start of the third boot (MAX_TRIAL_BOOTS = 2), or after more
than 10 starts in one boot (MAX_TRIAL_STARTS_PER_BOOT, a crash loop), it moves each
restore file back from its .<name>.prev (${path%/*}/.${path##*/}.prev), deletes each
remove file, moves trial to update/rolled-back and starts the old waved. Boots, not
starts, because one boot can take several starts: S60waved SIGTERMs the first waved after
every boot at 5 s (display workaround), and on 2026-10-13 the speaker (hw:0,1) was busy for
the first ~10 s of a boot, so waved exited and restarted three more times (5 starts in one
boot; a start count of 3 would have rolled back a good update on its first boot).
The new waved commits (deletes trial, the counts and .config.toml.prev) once it has
run 60 s with AudioState::capture_healthy true; until then it starts no other update.
The old waved finds rolled-back at its start, reports “Version 0.10.0 didn’t start, so
Wave went back” and adds the version to update/skip, which automatic checks pass over; a
tapped Retry or Install clears it. If waved finds a trial that is not its own version,
the install was cut between steps 3 and 5: it puts the deploy files and the settings back
and reports “The update stopped before it finished”.
.prev files stay after a commit as the last known good version, until the next install
replaces them with the version it replaces.
What the user sees
Section titled “What the user sees”System → Firmware update (system.png, centred when System opens) shows Off, Check now, Checking, Up to date, Available, 42 %, Ready, Installing, Restart or Failed. A tap checks when there is nothing to install, installs and opens the update screen when a version is Available, and otherwise only opens the update screen. After a failure that screen says why before anything installs again: a row tap must not quietly reinstall a version that rolled back.
The update screen (ota.png) is the dial as a progress ring with “Installing update”, the
percentage, “waved 0.10.0”, “Keep Wave plugged in” and “It restarts by itself when done”, and
nothing to tap. Installing and Restarting take over the screen from anywhere
(kit/router.rs); only power off overrides it. A failure stays on screen as “Update
failed”, “Wave is still on 0.9.3”, the reason, Retry and Back. A failed reboot shows
“Restart Wave”, “waved 0.10.0 is installed”, “Turn Wave off and on to finish” and only
Back (update_restart_needed golden).
| Failure | Shown reason |
|---|---|
| server unreachable (tapped check) | Can’t reach the update server |
| index malformed | The update server’s list is broken |
| download cut | The download stopped |
| hash mismatch, larger than announced | The download was damaged |
| signature fails | The update isn’t signed for this Wave |
| malformed package | The update package is broken |
| signed version or build differs from the index | The update doesn’t match the server’s list |
| signed version not allowed | The update is older than this version |
| no room | Not enough space on Wave |
no current update_guard in the S60waved the trial would boot with |
Wave can’t go back if this update fails |
| installer error | The update could not be installed |
| cut between trial and swap | The update stopped before it finished |
| new version never committed | Version X didn’t start, so Wave went back |
Decisions
Section titled “Decisions”| # | Decision | Cost if wrong |
|---|---|---|
| U1 | Own .wup format (signed JSON header, gzip files) instead of tar or swupdate’s CPIO |
Needs waved-pack inspect to look inside. |
| U2 | Closed target list: waved and the deploy/ files; no rootfs, kernel or partitions |
Anything else still needs adb. A bad S60waved breaks the fall-back itself; sh -n catches syntax only, and the guard check only that the current guard’s lines are there. |
| U3 | Unsigned index, signed package | A hostile server can waste a download. |
| U4 | Version from the binary; downgrades only by the device’s own switch | Every release must bump the workspace version. |
| U5 | Download to /tmp (RAM), stage on /data |
A reboot mid-download starts it over. |
| U6 | File-level fall-back in S60waved, 2 boots (or 10 starts in one boot), commit after 60 s with capture healthy |
A binary that hangs without exiting is rolled back only after two power cycles. A Wave whose microphones fail for unrelated reasons never commits and rolls back on the third boot. |
| U7 | Wait until free; manual installs ignore “computer listening” | An app that holds the microphone open blocks automatic installs. |
| U8 | /sbin/reboot after install |
Unverified; a failure shows “Restart Wave” with nothing to retry (the trial is already armed) and blocks further installs until Wave restarts. |
| U9 | Public key from WAVED_UPDATE_PUBKEY at build time; waved-pack refuses binaries without it |
A build made without the key can be replaced only over adb. |
| U10 | USB storage and Bluetooth phone calls (ringing, up, or call audio) hold every install, tapped ones too; phone music does not | A computer that keeps the drive, or a module state that never clears call_audio, holds installs until it does (the update waits; nothing fails). Phone music is cut by the restart. |
| U11 | The installer accepts only an S60waved with the current guard’s lines (GUARD_MARKERS), not any update_guard |
A Wave with an older guard refuses every package that does not carry S60waved until deploy/install.sh (or a package with it) installs the current one. A future guard that changes the protocol must change the markers too, or old Waves accept a package whose guard would not undo it. |
| U12 | Config keys added since the pre-M3 waved are saved only when not the default; config/default.toml has them as comments |
A non-default value (table in Going back over adb) still makes an older waved run on defaults. A key added later without the same treatment breaks downgrades again (PRE_M3_KEYS test in waved-config catches it for defaults). |
| U13 | deploy/install.sh never replaces the Wave’s config.toml and copies it to config.toml.before-<time> first; it refuses while an OTA trial is pending; a first install ends with a reboot, an update restarts the watchdog |
The copies pile up on /data (a few KB each); delete old ones by hand. A trial that never commits blocks adb installs until it rolls back or update/trial is removed by hand. |
Device checks
Section titled “Device checks”Not verified on a Wave. Before trusting this, a device pass should check:
update_guardunder busyboxsh(read -r,$((…)),mv -f,${path%/*},${path##*/}): runWAVED_DIR=/tmp/g sh /etc/init.d/S60waved guardagainst a fake trial in/tmp/g. Done on the host with thebusybox:1.36image (four passes: dot-named copies ofS60waved,S03splash,wavedandconfig.tomlrestored, aremovefile deleted,S??*lists only the scripts); the Wave’s own busybox build is still to check.- Hard links and renames through the
/etcoverlay:ln /etc/init.d/S60waved /etc/init.d/.S60waved.prev, then a.S60waved.new+ rename, then a reboot to see both persist in/data/overlay/upper/etc/init.dand that rcS starts one waved, not two (ps | grep -c '[w]aved$'). /sbin/rebootfrom waved runs the rcK stop scripts and the unit comes back.- Free space on
/dataand/tmp(df -h) against a real package;statvfsfigures. - The whole cycle against a LAN server (
python3 -m http.server), including Range resume (kill Wi-Fi mid-download) and a forced rollback (a package whose binary exits at once). - The 60 s commit:
update/trialdisappears about a minute after the second start. - rustls against the owner’s HTTPS server (certificate roots are compiled in by
reqwest’s
rustls-tls; a self-signed server needs plain HTTP on the LAN, which the signature makes safe). deploy/install.shas an update, the first time (host-tested against a fakeadbonly): before it, notemd5sum /data/waved/config.tomlanddf -h /data /tmp. Check that the Wave’sadb shelloutput is read right (theecho readyprobe,\rstripped), that busyboxpsshows the watchdog as/bin/sh /etc/init.d/S60waved startboth after a boot (rcS) and after astart-stop-daemonstart, thatconfig.tomlhas the same md5 after andconfig.toml.before-<time>is there with mode 600, that/tmp/waved.oldholds the old binary, and that exactly one waved runs afterwards (ps | grep -c '[w]aved$'; one watchdog:ps | grep -c '[S]60waved start').- The guard check against the Wave’s own
/etc/init.d/S60waved:grep -c 'MAX_TRIAL_BOOTS=' /etc/init.d/S60waved. A Wave whose script predates boot counting refuses a package withoutS60waved(“Wave can’t go back if this update fails”) untilinstall.shor a package carryingdeploy/S60wavedreplaces it. - The gate on the Wave: with an update Ready, a phone call through Bluetooth (ringing, answered, hung up) keeps it Waiting until the call and its audio end; USB storage on holds a tapped Install until the drive is ejected and Wave has the volume back.
- Going back: with Mic height Medium, Bluetooth on and lockdown off, install the pre-M3
build (
main, 3cc3707) withWAVED_BINARY=... deploy/install.sh; it must start without the first-run screen and with the API endpoint kept. Then install this build again.
Device facts used here
Section titled “Device facts used here”| Fact | Evidence |
|---|---|
eMMC mmcblk0; system_a/system_b 512 MiB SquashFS, mounted read-only as / |
../docs/GUIDE_DEVICE_ACCESS.md:457-480; ../backups/state/device_state.txt:29-48 (main checkout only, gitignored) |
/data = mmcblk0p13, ext4, 512 MiB; overlay upper dirs for /etc, /opt, /usr |
same; ../firmwares/fw_v1.2.2.6/extracted/rootfs/etc/init.d/S02overlayfs (main checkout only) |
A/B: boot_a/b, system_a/b; slot state in misc (AvbABData “AB0”: priority, tries, successful_boot); U-Boot bcb_cmd=…get_valid_slot |
../backups/partitions/p07_misc.img, p04_env.img (main checkout only) |
/tmp tmpfs ~1.9 GiB; RAM 3.8 GB |
../docs/REPORT_DEVICE_DUMP.md:26-27; ../docs/npu/lab-notebook/2026-07-28-poc3-runtime-driver-trace.md:191 |
waved lives in /data/waved; S60waved in the /etc overlay; the SquashFS is never modified |
deploy/install.sh |
Going back over adb
Section titled “Going back over adb”An older waved goes back on with deploy/install.sh (WAVED_BINARY=<that build>; its
S60waved is the checkout’s). The script keeps the Wave’s config.toml and copies it to
/data/waved/config.toml.before-<UTC time> before every install, so the settings an older
waved could read are still there.
Every config section denies unknown fields, so an older waved rejects a config.toml with
a key it does not know (and its .bak, which the newer waved may have written too) and
runs on defaults: first-run screen, PIN off, API token and uploads gone. Its settings
service does not write over that file; a save on the setup page moves it aside to
config.toml.unloadable. The keys added in milestone 3 (waved-config, PRE_M3_KEYS test) are saved only
when they differ from their default, so a Wave that never changed them goes back to the
pre-M3 waved (main at 3cc3707) with its settings. These still break that downgrade:
| Key | Written when |
|---|---|
device.mic_height |
Mic height set to Low or High (Medium is the default) |
bluetooth.enabled |
Bluetooth switched off |
security.release_lockdown |
release lockdown on (setup page) |
security.ota_allow_downgrade |
set to true by hand |
security.device_password changed format in milestone 3 (a pin1$… record instead of the
PIN). The pre-M3 waved loads it (any string up to 256 characters) and has no PIN screen,
so nothing is locked; the newer waved reads the record again when it comes back.
To go back with settings that break it: before installing the older build, remove those
lines from /data/waved/config.toml (watchdog stopped, docs/DISPLAY_HARDWARE.md#deploy),
or afterwards copy a config.toml.before-<time> from before the newer install over
config.toml (mode 600) and restart the watchdog.
Going back to stock (H4)
Section titled “Going back to stock (H4)”Research only: nothing here has been tested, and the slot-writing paths flash
partitions, which waved’s own updater never does. Firmware files are gitignored and live in
the main checkout’s firmwares/ (../firmwares/README.md).
How stock installs a .swu
Section titled “How stock installs a .swu”- The vendor container is a 4-byte magic (
!C#\x01consumer,1BOTbusiness), a 1 MiB JSON header, then a swupdate CPIO at0x100004(../firmwares/README.md, “Container format”). pspapp(libins_entry.so, decompiled in../firmwares/fw_v1.2.2.6/decompiled/):extract_and_install_packagecopies the CPIO to/tmp/.software.swu(~line 147325) and runsswupdate -i /tmp/.software.swu -k /etc/swupdate-public.pem(~147620);SW_Slot_Switch()runsswupdate -s(~147992); then it wipes/data:cd /data && ls /data | grep -v overlay | grep -v common_settings | xargs rm -rfandcd /data/overlay/upper && ls /data/overlay/upper | grep -v opt | xargs rm -rf(~45729), and has the MCU reboot with a slot change (~45712).- Sources it installs from: its online download (Insta360 cloud, out of scope),
/mnt/udisk/psp.swudropped over USB storage (~168789), and the PC client. /usr/bin/swupdateis Amlogic’s build: it finds the active slot from/proc/mounts, writesrootfs.squashfsto the inactive/dev/system_*,boot.imgto the inactive boot slot,dtb.img,udisk_s.exfatto/dev/psp_udisk_s, andMCU.bin,BT.bin,DONGLE.binto/tmp/insta360/(sw-descriptionin eachextracted/directory). Packages are signed (sw-description.sig, 512-bit RSA,/etc/swupdate-public.pem). swupdate refuses older versions only with-N, which stock does not pass, and everysw-descriptionsaysversion = "1.0.1".
Paths back, least invasive first
Section titled “Paths back, least invasive first”- Soft revert (normal path). waved never touches the SquashFS slots, so the unit still
runs the stock rootfs it had.
deploy/revert.shremoves everythingdeploy/install.shand updates put in the overlay (/data/overlay/upper):S60waved, theS60pspapp_daemonstub,S03splash, the patchedS89usbgadget,/etc/rsyslog.conf, thelibapplogrs.sostub, and the.prev/.newcopies updates leave besideS60waved,S03splashandrsyslog.conf. Then it removes/data/waved. After a rebootpspappruns again and ADB is off, as on stock. It leaves the recordings in/mnt/udisk/recordingsand the saved Wi-Fi networks (/etc/wpa_supplicant.conf). - Stock updater, after a soft revert. With
pspappback, copy an original vendor package (container included, e.g.software_model_1_1_6_6.swu) to the recordings volume aspsp.swu(/mnt/udisk/psp.swu) and letpspappinstall it. It writes the inactive slot, switches, and wipes/dataas above. Untested: whether the USB drop needs a UI confirmation, and whetherMCU.bin/BT.binfrom an older package downgrade the MCU or Bluetooth firmware. - Manual swupdate over adb. Run
revert.shfirst (a manual install does not wipe/data, so waved’s overlay would keep replacing stock on the new slot). Cut the CPIO from0x100004into/tmp(for 1.2.2.6, concatenateInsta360WaveFW_model_swu_only.binandtail_extra.binfirst: the CPIO runs pastbin_size,../firmwares/fw_v1.2.2.6/CHANGELOG.md), thenswupdate -i /tmp/x.swu -k /etc/swupdate-public.pem && swupdate -s && reboot. Checkcat /etc/hwrevisionfirst:sw-descriptionrequires hardware1.0, andS99_PSPLauncheroverwrites/etc/hwrevisionin the overlay on every boot withNONEor an ADC index, which may fail swupdate’s compatibility check. - Raw restore.
../backups/README.mdrestores captured partitions withdd;misc(p07_misc.img) puts the slot choice back. Last resort: U-Boot’supdate(usb_burning,recovery_from_udisk), Amlogic MaskROM (pyamlboot) or UART.
Going back to waved afterwards: deploy/install.sh again (after the stock updater’s wipe,
/data/waved is gone).