PROPFIND listings, ranged GET reads, Basic auth, optional self-signed certificate per
connection. Same read-only RemoteFs as SMB/FTP/SFTP, so uploads and installs stream from
the server. Loopback-server test covers sign-in, listing, walk and ranged reads.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
A plain unmount first; FW 13.60's shell keeps /mnt/usbN open, so the app offers
"Eject anyway", which syncs and force-unmounts. Verified on the Phat: /mnt/usb0
gone and stayed gone, helper unaffected.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
Engine routes /api/ps5/pldmgr{,/launch,/delete,/autoload,/store} over Payload Manager's
own HTTP API (:8084). Storing into an existing folder is confirmed first, since Payload
Manager's import removes that folder's other files. Scene-tool pills with a web page
(Payload Manager :8084, LZ4 Compression :5920) open it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
cea5c5c2 made a credit wait with an idle console report BN_NETWORK, and
the governor grows lanes (and caps the chunk) on BN_NETWORK, so it began
trying an extra lane where 7.0.6 held. Unintended: more lanes cannot
fetch credit the console has not granted. Control is back to 7.0.6's
rule; only the reported label says "network". A test pins it: a credit
wait with an idle console steers lanes and chunks tick for tick like a
credit wait with no word from the console. The throughput gates measure
the same with the old and new governor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
For shortcuts and loaders that, once set up, only clutter the home
screen. ⋯ → "Hide from home screen" on a Games card; a "Hidden" badge
marks it, "N hidden from the home screen" at the top filters to them,
and ⋯ → "Show on home screen" puts it back.
How: every user's icon table in app.db (tbl_iconinfo_<user>) has a
`visible` flag; the system hides some of its own apps with it. The
helper (app.icon_set, app.hidden_list) flips only that flag: nothing is
deleted, the title stays installed and playable. Guards: title ids only,
system apps (NPXS) refused, tables found by shape (titleId + visible),
app.db backed up once to /data/ps5upload/app.db.before-hide before the
first change, BEGIN IMMEDIATE (fails before any change if the system
holds the lock), and rolled back unless at least one and at most one
row per table changed. The hidden list is read live from app.db, so it
cannot drift from the console, and leaves out the system's own hidden
apps. The home screen redraws when an app is next opened and closed;
the console shows a notification saying so.
Verified on the Phat (FW 13.60): WebKit Autoloader hidden, listed, shown
again; a system app and an unknown id refused; the backup written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
wait_for_ports_waits_for_listeners_that_appear_late picked ports by
binding and dropping them, so on CI (1,500 tests in parallel) another
test could take one and answer on it before the test listened: CI saw
'ready' after 452 ms when the second listener only opened at 800 ms.
The ports are now bound and held without listening (socket2, already in
the lockfile) and start listening on cue. 12 parallel copies pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
The sender called any wait for the console's credit "console memory"
unless the console reported its disk or workers as the limit. When the
console reports its queue ran dry, it is idle, waiting for data: the
credit is slow in transit (here a Windows Internet Connection Sharing
bridge on a CPU busy converting), so the cause is the network.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
When the router hands the PS5 a new address, the saved one stops
answering and the app said the helper was not running. Now, while the
active console is down, the desktop and Android apps scan the network
(at most every five minutes) and ask the engine about each PS5 running
our helper: POST /api/ava1/same-console {old, new} connects to the new
address expecting the key pinned for the old one and answers same,
different or unknown. Only "same" shows a banner: "Your PS5 now answers
at X", with Switch (re-points the console, keeping its name and history)
and Dismiss. Another PS5 is never offered; the check pins nothing.
Tested against the C server (same at a new address, another console, no
pin, nothing answering) and against the real consoles: the Pro's key
pinned under a made-up old address matches the Pro and not the Phat.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
sevenz-rust2 turns 7z sizes (u64 in the format) into usize before using
them as read limits: a block's packed size caps the decoder's input, and
block and file unpacked sizes cap the output and the CRC check. On the
32-bit Android build (armv7, in the APK next to arm64) usize is 32 bits,
so a limit past 4 GiB wraps: a 4.7 GB block was cut to 405 MB of input,
the LZMA decoder ran out inside a chunk and said "dist overflow", and any
file over 4 GiB was cut short. Game backups are tens of GB in one block.
sevenz-rust2 0.23.0 is vendored in third_party/ (as unrar is) with those
sizes kept u64 end to end; nothing changes on 64-bit. 0.24.0 still has
the casts. Verified on linux/arm/v7: a 4.7 GB single-block archive failed
after 405 MB before and decodes in full with its CRC after, and 6 GiB of
LZMA-coded output decodes correctly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
The engine reports what a scan is doing (finding, reading, naming, saving),
which folder and item, and how many games are named; the screen shows it as a
progress card with the item being read and the time so far, and a last-scanned
line afterwards. A second scan request joins the running one: the Collection is
one per computer, and pressing Scan during the automatic refresh used to show
'The collection could not be read: a scan is already running'.
Also removes Import from PS Game Library (the engine route, its code and the
menu items): that tool was never public.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
job.cancel stopped a job on the console without telling the session it
was attached to: the session stayed up, so the sender waited for a
JobDone that never came. The console now tells that session first
(JobDone with ERR_CANCELLED for a job it receives, JobCancel for one it
sends).
The engine's sender also bounds the wait: a receiver that says nothing
about the job for 10 minutes on a live session (no Status, no other
frame) gets JobCancel{ERR_STALLED} and the job fails. The wait for the
map is bounded the same way and honours the cancel flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A JobCancel reaches a job only when it is parked or attached to the
session the cancel came on, so a late cancel from an old direct send
no longer stops the upload the relay re-attached. The sending
console's watcher only hangs up when its job was replaced.
- c2c.send replaces only an earlier send the same device started;
any other job under that id is left alone (ERR_BUSY, before dialling).
- A live ticket decides even for a paired key: the session is the
ticket's restricted one, owned by the ticket's owner.
- A session's terms (restricted/outbound/r_job/r_owner/r_root) are
written in the locked section that publishes it.
- FileRetry is de-duplicated per file and capped at the manifest size.
- c2c.send checks the read policy before stat; a dial that succeeds
with some lanes failing no longer leaves a stale reason; a stale
comment and an unused include are gone.
SPEC.md §11 and §18 say the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/api/jobs/{id}/files clones an Arc under the jobs lock and serializes
after releasing it. The install history write, the source-copy removal and
the organizer's folder stats run off the async workers. The icon cache
recounts its size every 256 writes so the running total cannot drift.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DLC rule (no applicationCategoryType and no applicationDrmType) also
matched a homebrew's minimal param.json. A DLC carries no eboot.bin, so
the rule now requires that too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The progress ticker built its snapshot under the lock but sent it after;
a job finishing in between let a stale 'running' reach clients after
'done'. Both the ticker and set_job now send while holding the lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
One console is one install slot whatever the case of its host name. An
install that ended without reaching Done or Failed is now pruned like a
finished one instead of being kept until restart. iec_bytes no longer
prints "1024.0 KiB": a value that rounds to 1024 of a unit moves up one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
An app folder name of nine bytes with a multi-byte character was sliced
mid-character; title ids are now checked by bytes. A pack_pattern field
wider than 64 ("{id:065536}") is a profile error (400) instead of a
format! panic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
A console-to-console relay now honours the outbound cap (the request's
bandwidth_cap_mbps, else the environment's) like an upload; the direct
route is not paced since its bytes never cross this computer. A tiny cap
no longer rounds to 0 (= no cap), and a lane cancelled while waiting on
the pacer gives its reserved budget back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
An unreadable job status skipped both the give-up and the stall checks, so
a direct send could poll forever. After falling back to the relay, the
engine now also cancels the receiving console's half of the direct job (it
owns it; the journal stays for the resume), and a relay still refused as
busy says why the direct route was given up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
A deep rescan can read a new cover out of a game whose files did not
change, and the cover's URL was versioned only by those files, so the old
image stayed (and the data-URL cache, keyed by game only, answered with the
old bytes too). The engine now records the saved cover's size and modified
time (cover_stamp, an optional index field) and the game page's cover URL
carries it; the client folds it into the cover version and keys the
data-URL cache by version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Builds claim their .partial (and LZ4 spool) before the job exists; a second
build of the same output gets 409 instead of deleting and overwriting the
first one's file. The image writer no longer removes a .partial blindly,
never removes one it could not create, and a cancel that lands after the
image was finished no longer reports it cancelled. The handlers' file
checks run off the async workers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Cancel no longer deletes the .partial under a running worker or a live
retry: the worker removes its own partial on exit, and a cancelled entry
stays live until then. A partial is resumed only when its sidecar
(<name>.partial.json: host, path, size, ETag/Last-Modified) names the same
link and both its head and tail bytes match. Stopped downloads are pruned
from the registry after 30 minutes or beyond 64, and the start route's
file work runs off the async workers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Twelve routes (write-bytes, app lifecycle/register, sdk patch/restore,
unmount, chmod, copy, cleanup, health fix, SMP image-rw, profile activate)
and four pairing routes still fell back to the default console when addr
was missing. They now answer 400. An addr with an empty host (":1234")
also counts as missing. Every client caller already sends addr.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
A package's titleId and an imported library index both became file paths
unchecked. Ids must now be a bare Game ID, an import drops games with a bad
id and re-derives covers, a cover is served only by a bare file name, trash
takes only paths strictly inside a current local root (and de-duplicates),
and organizer moves refuse any path that leaves their folder. The undo log
is rewritten as a run goes and undo replays moves a crash left unrecorded;
same-named folders get their own undo logs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
- Engine: /api/pkg/remote/download/start takes `existing: resume|replace` for a
different file already under the download's name (resume moves it to the
.partial, whose tail is checked against the link; replace deletes it), and
its 409 carries the file's path and sizes. A live download of the same name
is answered before anything is taken over.
- Client starts link downloads over the engine API (lib/linkDownload) so the
409s keep their fields: a duplicate attaches to the running download, and
Install Package asks Resume (when the file is shorter than the link) or
Replace instead of the old 'Move or delete it first'.
- Convert & install shows 'Will install on {console}' while it runs; 'Show in
folder' is hidden where no file manager can open it and says so when
opening fails.
- Install Package says a refresh is held for the running install.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
The link-copy space check said 'GB' for 10^9 bytes while the collection export and
the health checks said 'GB' for 2^30. ps5upload_core::units::iec_bytes is now the
one formatter (KiB, MiB, GiB), used by all three.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
- Artwork cache reads/writes run on the blocking pool; the cache size is tracked
instead of walking the folder on every write; concurrent writes of one entry use
unique temp names.
- Server-folder copies write on the blocking pool and fail on a failed sync.
- Debug/trace lines keep their own 500-line ring, so per-request and per-range
lines no longer push warnings out of the log tail; the pkg-host per-range line is
debug.
- Repeated-failure collapsing is keyed by route and console.
- Delete, move, unregister, process kill, power, user delete, backup restore/delete
and cheat delete answer 400 without an addr instead of acting on the default
console.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9