APP_DB_QUERY answered at most 256 rows, and fewer when its 64 KiB reply
filled, with nothing saying the list was cut. A title installed by a
homebrew installer (no /user/appmeta folder) that sorted past that point
disappeared from the installed list.
Payload: app.db.query takes an optional {"offset","limit"} window and
answers offset/next/more/truncated; a full buffer now ends the page with
more=true instead of closing the list. An empty request is page one, so
older engines keep working.
Engine: appdb_query follows the pages (at most 32) and marks the list
truncated when it could not finish (or an older helper returned a full
256). The installed list reads app.db once per request, adds registered
titles whose app folder exists, reports incomplete, and a truncated list
never proves a title absent for install verification.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
Per the 2026-10-16 rest-mode detection spec (client side). The engine's
status verdict (power: awake/rest/unreachable, reason console_rest_mode)
is read defensively, so this builds alone and lights up once the engine
side lands.
- HostRuntime.power, set by the status poller; rest flips the console down
at once (no miss debounce) and is logged on the edge.
- Status bar, console tabs, Home badge/metric, game page console rows:
"In rest mode" in the calm accent, "Not answering" for unreachable.
- ConnectionGate: rest-mode notice with Wake / Wake & sign in from that
console's own profile (shared useConsoleWake), and a not-answering notice
naming Settings > System > Power Saving > Features Available in Rest Mode.
- Connection Check: a closed :9021 on a console DDP says is asleep reads
"Your PS5 is in rest mode. Wake it, then check again", no VPN hints.
- PowerControl reads the shown console's credentials and seeds its power
state from the poller.
- Auto-redeploy holds while the console rests (no TCP probes); keep-awake
skips resting consoles.
- Upload queue parks an upload that meets rest mode as "Waiting for the
PS5 to wake" (pending, no spent recovery attempt) and resumes it on the
helper-up edge, desktop and browser; Files uploads stop instead of
retrying into a sleeping console and resume the same way.
- 16 new strings (joberr.console_rest_mode among them) in all 21 locales.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
When the helper's port and the loader both refuse, or the session connect
gets no TCP answer, require_in asks DDP (2 SRCH within ~800 ms). A 620
reply is the new token console_rest_mode ("The PS5 is in rest mode. Wake
it, and the helper can be sent again."). No reply after a timeout stays
ava1_unreachable with a plainer message. DDP 200 or silence after a
refusal keeps helper_not_ava1 as before. A verdict DDP explained is
cached with the pool refusal for 12 s (PROBED_REFUSAL_TTL, past the
client's 10 s poll); other refusals keep their 5 s / 30 s TTLs. The probe
is injectable (Pool::with_power_probe) and skips loopback hosts.
GET /api/ps5/status failures answer {error, reason, power}. Upload,
download and relay stalls on a console DDP calls asleep fail with
console_rest_mode instead of the bare "no durable progress".
payload_check (Tauri and the browser twin) passes reason and power
through; payloadCheck returns them as optional fields.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
Engine: POST /api/ps5/helper/stop { addr } sends node.shutdown over the
paired AVA1 session (payload_lifecycle::shutdown_running_payload) and waits
for the helper's port to go quiet before answering {state: "stopped"};
{state: "not_running"} when nothing answered. Refused with 409
{reason: "busy"} while this engine runs a transfer, download, relay,
console file operation or install against that console (a per-console busy
count held by each job, plus the install slot); 502 {reason: "refused" |
"still_running"} otherwise.
Client: "Stop helper" beside Send/Resend on the Connection screen, behind a
confirm. A stop marks the console "stopped by you" (persisted in
localStorage, so desktop and browser builds keep it across restarts). The
reconnect loop, a queue's mid-run recovery and unattended queue runs (wake
resume, re-attach after restart) leave a marked console alone. Send helper,
Check, the setup wizard, Quick bring-up and starting an upload clear the
mark, as does a helper seen answering a minute after the stop. Connection
and the screen gate show "Stopped" rather than a missing helper. Strings in
all 21 locales.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
Below the sm breakpoint a Files row is checkbox, icon, name (ellipsized,
full name in title), size and one ⋮ that opens the row's actions in the
desktop order with the same labels and icons, Delete last in red. The
desktop buttons and the menu come from one list (buildRowActions), so
both run the same handlers. The menu is the row's existing right-click
menu: portaled, keyboard-navigable, closed by outside tap and Escape,
placed against the ⋮ and flipped above it near the bottom of the screen.
The selection bar keeps "N selected" on one line and scrolls its actions
sideways on a phone; the Recent list sits on the opaque floating-menu
surface above the rows at every width (it was the 4% page tint, so the
rows read through it). Toolbar, drive chips, path bar, sort chips and
rows are denser at phone width; Add folder / New folder / Refresh show
their icon alone there. The desktop layout is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
Engine: POST /api/folder/build unpacks a game image (or, once the pkg
reader lands, a debug-key .pkg) into <out>/<name>. It runs as a job with
Write and Verify stages; every path is checked before anything is written
(.., absolute, empty components, names this OS cannot hold, file/folder
clashes, case clashes on a case-insensitive drive); data streams in 8 MiB
chunks with cancel per chunk; the folder is written as <name>.partial/
(marked so the output-folder sweep can tell it from a user's own) and
renamed only after the read-back: every size and empty folder, and the
SHA-256 of a seeded sample (or every file) re-read from a freshly opened
source. An existing target, a plain folder source and a shortfall of
free space (files + 64 MiB) are refused before the job starts.
Client: a third "What to make" tile, A game folder. A folder source
disables Make with "This is already a folder"; .pkg is accepted as a
source; then is keep only; the finished card shows the folder and Show in
folder. The queue, prefs, pipeline and stage list know the folder kind.
Strings in all 21 locales.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
Pausing a routed send now ends both consoles' halves, so its journal is free
to resume from any device with any helper. The driving engine keeps a shared
record of each send on the receiving console (/data/ps5upload/c2c/<tx>.json)
with a 45 s lease refreshed every 15 s; another device lists those sends
(GET /api/transfer/ps5-to-ps5/remote), takes one over or watches a direct one
(POST /api/transfer/ps5-to-ps5 with adopt: take | watch, same tx_id), and the
engine that had it stops driving without ending anything ("taken over by").
Helper: a new caps bit, CAP_SHARED_JOBS (4). job.status / job.cancel answer
any paired device for uploads and downloads (operations and local copies stay
owner-only), and a JobOpen moves a parked upload of another device to the
opener; an attached one is still refused. SPEC §18.5-18.6.
Test connection in Send to another console runs the console's net.reach probe
from the sending console to the dial target (plus the relay legs, and an
optional real 4 KiB direct send). Tasks shows "Sends from other devices" with
Watch / Take over / Resume here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3cPwhp3UrzayBfyy9PDcN
native_job_notify turns true only after a successful /api/activity poll, so a
remote engine on an older version leaves job notifications with the page. The
BgStrings parity test reads the Rust file through Vite (no node types in the
client tsconfig); a clippy fix in the tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3cPwhp3UrzayBfyy9PDcN
Files gets "Decompress to .exfat…" on a .ffpfsc again. The first build (pulled
before 7.0.23) inflated the blocks in the helper with vendored zlib, and the helper
died with SIGBUS within minutes of loading it; once the console froze. This one has
no decoder: the helper mounts the container read-only the way ShadowMount+ mounts a
.ffpfsc (LVD image type 9, read-only single-save flags, 4096-byte sectors, pfs with
mkeymode=AC), so the kernel decodes it, and copies the file inside to <name>.part,
then renames it in the same folder. The kernel lists that file rounded up to whole
64 KiB blocks; only the inode's real length is copied.
The checks from the first build stay: the container's block table is read before the
kernel sees it, a destination that exists and a drive without room for the image plus
1 GiB are refused, the size and exFAT boot sector are checked before the rename, and
Stop removes the .part and the mount. One unpack runs at a time. The mount is not in
the mount table the helper sees, so it is recorded on the /mnt tmpfs (owner pid, LVD
unit, .part) and a helper that starts after one died mid-copy releases it; a record of
a live instance is left alone.
Measured on the FW 13.60 Phat: a 315 MB image (127 MB container) in 3.5 s and a 4.5 GB
one (3.0 GB container) in 65 s, both byte-identical to the source; Stop, a takeover
mid-copy and a stale record all left no mount, LVD unit or .part behind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3cPwhp3UrzayBfyy9PDcN
- lib/bgWork.ts names the engine jobs the page starts (queue and Upload screen
uploads, installs, File System downloads), tells the host whether the page is
showing, the notification setting and the notification texts in the person's
language, and passes the host's bg-tick / bg-job-finished to the queue.
- lib/wakeableSleep.ts: the upload queue's and install polling's sleeps end on
the host's clock when the hidden window's timers are throttled; a finished job
ends the status-poll sleeps at once. Settle delays still wait their full time.
- Job-outcome notifications are tagged jobOutcome; the OS mirror skips them
when the host reports native_job_notify (false in the browser build: the old
behaviour).
- keepAwakeHold.ts holds the host's background-work hold, refreshed every
minute, while a transfer runs or the upload queue is draining.
- 17 bg_* strings in all 21 languages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3cPwhp3UrzayBfyy9PDcN
bg_activity.rs polls the engine's /api/activity every second from Rust, so a
hidden or frozen WebView no longer decides whether work is running or has
ended. A pure step() turns each snapshot into start/update/finished/stop
actions: the first poll only records seq, an engine restart resets it, a
renderer hold refreshed within 5 minutes counts as work, and nothing active for
30 s stops. Finished jobs are emitted to the page (bg-job-finished, plus a
bg-tick heartbeat while work runs) and announced through the OS when the app is
in the background, in the language the page sends.
Android: a connectedDevice foreground service (dataSync as fallback) with a
partial wake lock and a Wi-Fi lock keeps the process and its in-process engine
alive, with an ongoing progress notification and a separate result channel. The
Kotlin class, manifest entries and R8 keep rules are written by
android-postinit-patch.sh. JNI_OnLoad and the context helpers move to
android_jni.rs so both callers share them; app classes load through the
context's class loader. The mobile engine start is now once per process, as
Activity recreation can run setup again.
Desktop: notifications through the notification plugin's Rust API when the
main window is hidden, minimised or unfocused; backgroundThrottling disabled on
the window (macOS 14+).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K3cPwhp3UrzayBfyy9PDcN
- Receiver writes sce_sys/param.json and param.sfo as .ava-hold and puts them in
place when the job finishes: ShadowMount+ registered a folder whose eboot.bin
was still arriving, failed twice and skipped the title for good (repro on
13.60).
- AVA1 SkipProgress (0x30): a sender reading past data the receiver holds (a
resumed solid .rar/.7z) keeps the job alive; the console ended it as stalled
after 900 s on every retry.
- RAR5 unknown-size entries no longer wrap the unpacked total.
- Upload queue: stalled rows offer Restart transfer, stopped/interrupted rows
offer Resume, "Retry failed uploads" actually runs them and is translated as
an action.
- Payload Manager tab: Update for payloads stored from the Catalog, keeping the
boot-list place; repository installs are left to Payload Manager.
- Files: drag folders (and, without drag-out, files) onto a folder or the
breadcrumb to move them.
- README links the compose file from the feature list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
Saving settings re-checked every root still existed, so once one was
gone nothing could be saved, removing that root included. Only roots
being added are checked now. A scan skips a root it can't open instead
of failing, keeps its caches, and lists it in missing_roots; Collection
names it and Folders marks it "Not found, skipped".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
13.60's libSceUserService exports no create call, and the old path
(GetInitialUser + SetUserName) only renamed the initial user; in a
payload GetInitialUser fails with 0x80940004. user.create now answers
with "add the user on the PS5" and touches nothing; Profile says so
instead of showing the button.
Also fix the worker-commit crash tests' race: the sender now waits for
credit and stops once the job stopped or finished on its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
New helper method net.ports (147, frame 265/266): binds each TCP and UDP
port and closes it at once (never connects, so no loader sees a silent
client), then binds 127.0.0.1 and the LAN address to tell network,
console-only and outgoing sockets apart. The socket sysctls
(net.inet.*.pcblist, kern.file) answer ENOENT on FW 13.60. A full pass
takes ~890 ms on the Phat. The engine names well-known ports and the
Payloads screen lists them, with Open only for web UIs and unknown ports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
This reverts commit 1f27c1a560.
Every helper build that linked the vendored zlib died within minutes on a
FW 13.60 Phat with SIGBUS in a system library touching helper code at
zlib's inflateEnd (logged by the new fault detail: addr=0x200161159,
rip=0x88007fe60), most often right after an app kill. One such build was
running when the console went down during a game launch and did not come
back. The same helper without zlib ran through repeated launches and kills.
The decompress op goes back out until it can run without linking zlib into
the helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
The Files queue row showed speed and time left, but the task the same
transfer drives in the Tasks view had only its bar: the bridge passed bytes
and never the rate the queue and the single-file transfer already compute.
Both now pass the rate and, when the total is known, the time left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
The updater downloads the new package and opens its folder. On Bazzite and
other rpm-ostree systems that left people to work out `rpm-ostree uninstall`
plus `rpm-ostree install` by hand. A packaged Linux copy now gets the one
command to run, with a Copy button: `rpm-ostree uninstall <pkg> --install
<rpm> && systemctl reboot` on rpm-ostree (the package name read from the rpm
database), `sudo dnf install` for rpm, `sudo apt install` for deb. The README's
Bazzite row says the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
Asked on Discord: share a working autoload setup with friends. The Playlists
panel gets Export… and Import…: one JSON file with every playlist, the
auto-loader's choice and the custom repos the catalogue knows. Import adds the
repos first (their ids come from host/owner/name, so repo steps resolve with
no remapping), then the playlists beside the ones already here with new ids
and names kept apart; the auto-loader choice is taken only when none is set.
Strings in all 21 locales.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
Files gets "Decompress to .exfat…" on a .ffpfsc. The helper writes the image
the container holds beside it, under the image's own name, and keeps the
source; nothing crosses the network, where the round trip through a computer
was 191 GB of Wi-Fi for a 56 GB container in the report.
The helper (new job op FFPFSC_UNPACK, id 10 in the AVA1 schema) reads the
single-file PFS the way the engine's reader does, inflates each 64 KiB block
with vendored zlib 1.3.1 (inflate side only, third_party/zlib), and writes
<name>.part. It refuses a drive without room for the image plus 1 GiB and a
destination that exists, checks the size and, for an .exfat, the boot
sector, then renames within the folder. Progress and Stop go through the
op-status/op-cancel routes; the engine route is POST /api/ps5/fs/unpack. The
Files screen shows a progress line with Stop and a task. Strings in all 21
locales.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
"Runs on FW x and later (backported)" showed whenever every module's SDK was
below the firmware param.json declares, which an ordinary dump built with an
older SDK also is (asked on Discord). Backports stamp the FW 4.00 pair; other
cases now read "its modules allow it; it declares y".
When the game's ampr_emu cannot serve packs and putting 0.4.2.1 into the image
would fix that, the LZ4 line said "Not available for this game", which read as
a dead end (#449). It now says to tick the checkbox above it. New strings in
all 21 locales.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
Android's save dialog returns a content:// file the zip writer cannot open, so
the backup was redirected to Downloads while the file the person picked stayed
at 0 bytes, and the success message named that empty file. On Android the
backup now skips the dialog, goes to Downloads as <title>-<date-time>.zip (a
second backup no longer replaces the first), and the message names the real
file. The zip command returns the path it wrote on every platform.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik