Skip to content

Commit bfbfe89

Browse files
committed
build(release): bundle picotool.exe for lab-mode fallback flash
build.ps1 fetches the Raspberry Pi pre-built picotool 2.2.0-3 from raspberrypi/pico-sdk-tools the first time and caches it under tools/picotool/, then stages it next to couchlink.exe so the release zip ships with it. The cache is gitignored; the .gitignore file is the only tracked artifact under tools/picotool/. Lab-mode's force_bootsel handler resolves picotool.exe via the bridge exe directory first, so it works out-of-the-box when running from an extracted release zip. If the firmware-side CDC + UDP reboot paths both fail (because the firmware is wedged), the bridge falls back to `picotool reboot -u -f` and the operator gets a clean BOOTSEL drive without anyone physically holding the button. README.txt in the zip now also calls out `couchlink save-wifi` (DPAPI vault for the lab-mode `wifi_apply_saved` path) and lab-mode itself. Verified locally: build.ps1 -SkipPico -SkipBridge downloads + stages picotool.exe (6.1 MB) and the resulting binary runs cleanly (`picotool help` works).
1 parent e88b13d commit bfbfe89

3 files changed

Lines changed: 53 additions & 1 deletion

File tree

bridge/src/wifi_vault.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,11 @@ mod tests {
258258
fn dpapi_round_trip_in_memory() {
259259
let payload = b"\x01\x05home2\x08hunter12";
260260
let cipher = dpapi_protect(payload).expect("protect");
261-
assert_ne!(cipher.as_slice(), payload.as_slice(), "ciphertext == plaintext");
261+
assert_ne!(
262+
cipher.as_slice(),
263+
payload.as_slice(),
264+
"ciphertext == plaintext"
265+
);
262266
let back = dpapi_unprotect(&cipher).expect("unprotect");
263267
assert_eq!(&back, payload, "round-trip changed bytes");
264268
}

build.ps1

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,43 @@ if (-not (Test-Path -LiteralPath $FirmwarePicow)) {
8484
Copy-Item -LiteralPath $FirmwarePico2w -Destination (Join-Path $StageDir "couchlink-pico2w.uf2") -Force
8585
Copy-Item -LiteralPath $FirmwarePicow -Destination (Join-Path $StageDir "couchlink-picow.uf2") -Force
8686

87+
# picotool ships alongside the bridge so lab-mode's `force_bootsel`
88+
# fallback can drop a wedged Pico into BOOTSEL drive mode without a
89+
# physical button press. We vendor the official pre-built binary from
90+
# raspberrypi/pico-sdk-tools and cache it under tools/picotool/ so
91+
# subsequent builds don't re-download.
92+
$PicotoolVersion = "2.2.0-3"
93+
$PicotoolAssetName = "picotool-2.2.0-a4-x64-win.zip"
94+
$PicotoolUrl = "https://github.com/raspberrypi/pico-sdk-tools/releases/download/v$PicotoolVersion/$PicotoolAssetName"
95+
$PicotoolCacheDir = Join-Path $RepoRoot "tools\picotool"
96+
$PicotoolExe = Join-Path $PicotoolCacheDir "picotool.exe"
97+
if (-not (Test-Path -LiteralPath $PicotoolExe)) {
98+
Write-Host ""
99+
Write-Host "Fetching picotool $PicotoolVersion from raspberrypi/pico-sdk-tools..." -ForegroundColor Cyan
100+
New-Item -ItemType Directory -Force -Path $PicotoolCacheDir | Out-Null
101+
$PicotoolZip = Join-Path $PicotoolCacheDir $PicotoolAssetName
102+
try {
103+
# TLS 1.2 + UseBasicParsing for Windows PowerShell 5.1.
104+
[Net.ServicePointManager]::SecurityProtocol = `
105+
[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
106+
Invoke-WebRequest -Uri $PicotoolUrl -OutFile $PicotoolZip -UseBasicParsing
107+
} catch {
108+
throw "Failed to download picotool from $PicotoolUrl : $_"
109+
}
110+
Expand-Archive -LiteralPath $PicotoolZip -DestinationPath $PicotoolCacheDir -Force
111+
Remove-Item -LiteralPath $PicotoolZip -Force
112+
if (-not (Test-Path -LiteralPath $PicotoolExe)) {
113+
# Some sdk-tools releases nest picotool.exe inside a versioned
114+
# subfolder; find it and lift it up.
115+
$found = Get-ChildItem -LiteralPath $PicotoolCacheDir -Recurse -File -Filter picotool.exe | Select-Object -First 1
116+
if ($null -eq $found) {
117+
throw "picotool.exe not found in extracted archive at $PicotoolCacheDir"
118+
}
119+
Copy-Item -LiteralPath $found.FullName -Destination $PicotoolExe -Force
120+
}
121+
}
122+
Copy-Item -LiteralPath $PicotoolExe -Destination (Join-Path $StageDir "picotool.exe") -Force
123+
87124
$ScriptFiles = @(
88125
"setup.ps1",
89126
"doctor.ps1",
@@ -127,6 +164,12 @@ Each subcommand also has a one-shot wrapper script. Right-click and
127164
flash.ps1 re-flash without re-running setup
128165
configure-wifi.ps1 re-send Wi-Fi credentials (Pico must be in setup mode)
129166
167+
Lab-mode also ships in this zip. The host runs `couchlink lab-mode` to
168+
open a remote-test session; `couchlink save-wifi` saves Wi-Fi creds to
169+
a DPAPI-encrypted local vault (only this Windows login can decrypt).
170+
picotool.exe is bundled so the lab-mode `force_bootsel` fallback can
171+
drop the Pico into BOOTSEL drive mode even if the firmware is wedged.
172+
130173
The wrappers record a transcript under
131174
%LOCALAPPDATA%\ParsecCouchLink\data\logs
132175
alongside the bridge's own logs, so one folder has everything a

tools/picotool/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# The pre-built picotool.exe is fetched from raspberrypi/pico-sdk-tools
2+
# at build time, not vendored into git. build.ps1 caches the zip and
3+
# the extracted exe here so subsequent builds skip the download.
4+
*
5+
!.gitignore

0 commit comments

Comments
 (0)