- 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
Each title cost about nine console calls (two listings per drive plus an
unfiltered appinfo query). The drives' user/patch and user/addcont folders are now
listed once per read and shared; a title's DLC folder is listed only when it has
one; appinfo asks for APP_VER and CONTENT_VERSION only. A listing or version query
that fails (not one that is absent) marks the detail unread, and the snapshot keeps
what the last read found instead of recording 'no update, no DLC'.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
A killed download left a short file under the real name, so every retry got 409
'Move or delete it first', and two downloads of one link wrote one file. Bytes now
land in <name>.partial and are renamed when complete; a retry resumes from the
partial after re-checking its tail against the origin (a mismatch starts over); a
file already complete is reported done; a second live download of the same file is
refused with the first one's id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
A panic in an install task left its console 'busy' until the engine restarted, and
a poisoned lock broke installs on every console. The task now holds a guard that
fails the job and frees the slot; every JobStore lock recovers from poisoning;
finish forgets the stop request and keeps the newest 200 finished statuses. The
per-console key uses the shared host normalisation, so IPv6 addresses no longer
collapse to a prefix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Each job's blocking work now holds the drop guard transfers use (a panic marks the
job failed) plus guards that stop its ticker and free the build's partial file or
the unpack's folder and active-set entry. The tickers skip building events nobody
listens to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
The 500 ms poll, the 5 s job list and every 200 ms progress event carried the
whole planned file list (megabytes on a big folder), cloned under the jobs lock
each time. The snapshot now has files_count; the client reads the list once per
job. Events are not built when nobody subscribes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
clear-slot, users/list, remoteplay/devices, tmdb/fetch, bps inspect/apply, pkg install cancel,
appinfo/set, time/state get/set, pkg/parse and the fan-curve routes lost their last caller with
today's client clean-up; their handlers, types and the core code only they used go too, as does
the search-index module only tests used.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
- DLC content class: a DLC's param.json names neither applicationCategoryType
nor applicationDrmType (all seven DLC packages read, four publishers), and
its container is 0x21 / 0x0a020000. content_class gave it the game pair.
- The flat layout refuses a u2c delta past a byte instead of clamping it at
255, which sent the console's read to the wrong record (the block layout
already refused). Two scale tests built such trees with the flat writer;
they now use larger files, or the block layout.
- A build's .partial is removed on every way out but success: an I/O error in
the in-memory writer, a failed rename, a panic. A separate Kraken spool is
removed the same way, and its name carries the process and a count, so two
builds of one content id into different folders no longer share (and
truncate) one spool.
- The in-memory writer has the flat layout only; a block-layout request (the
default) goes to the streaming writer instead of being written flat.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Packs are read only by ampr_emu 0.4+ built with pack support; an older one
would find every packed file missing. The game's fakelib/libSceAmpr.sprx is
recognised by its own strings (the AMPRPAK4 magic, ampr_assets.index and its
'(c) Drakmor' version), and a title that does not import libSceAmpr, ships no
runtime, or ships one without pack support is refused before a job starts
and again in the build. No runtime binary is shipped or swapped in.
The inspection reports it as ampr_packs {runtime_version, refusal} for
libSceAmpr titles, which the Convert screen uses to offer the option.
Checked by hand against real modules: Lazy_AMPR's bundled 0.4.2.1 is
recognised as pack-capable; a dump's older ampr_emu (AMPRIDX3 only) is not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
POST /api/exfat/build takes ampr_lz4 {level, block_kib, profile_toml}. The
game's files are packed first (ampr_pack::image::prepare) into a spool beside
the output, and the unchanged image writer is handed an overlay of the folder:
the loose files, a regenerated ampr_emu.index listing every original file,
the manifest with its CRC and runtime sidecars, and the volumes, without the
packed originals. Folders packing empties stay as empty folders. The image is
read back and hash-checked as before; the spool is removed afterwards, and a
stale one is swept like a stale .partial.
exFAT only and not with .ffpfsc: the packs are already compressed. A test
writes such an image, reads it back through the exFAT reader and decodes every
packed file through the pack reader to its source bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Fixtures written by drakmor's ampr_pack.py and build_ampr_index.py for a small
tree (regenerated in the test from the same formulas), with the two profiles
they were given. The tests check that our ampr_emu.index is byte-identical,
that our reader decodes both of their pack sets through their CRC sidecars,
that an all-store profile gives their exact bytes, and that a compress profile
plans the same records, codecs and volumes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Engine: drop the unused /api/fakelibs/manifest, /api/collection/summary and
/api/ps5/content-db/backup routes, the payload FTP client (core ftp.rs, FTP_START/STATUS)
and NOTIF_SEND, and pub items only their own tests called. The DPI reason strings now come
from the payload_lifecycle constants everywhere.
Shims: the legacy helper replace flow (engine legacy_helper/legacy_guard, the helper/state
and helper/replace routes, the status-error fold), the client's helper_old banner/state and
the payload's legacy takeover and retired-folder removal are gone. An old helper that does
not speak AVA1 now reads as no helper, and the normal send-the-helper flow replaces it. The
6.0 console folder cleanup now starts from a successful status probe.
Payload: remove the FTP server and its self-tests, notif.send, ftp.start/ftp.status
(ids 119, 120, 123 retired in the schema), the unused ShellUI sensor/install RPCs,
runtime_reconcile_mounts, register_services_init, hw_temps_get_text and
mgmt_call_fs_mkdir. MGMT_METHODS.md describes the current method table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Deletes tests/install-fallback-hw.mjs, the mobile audit probe and its allowlist,
scripts/requirements.txt, tests/lab/verify-pending.sh, bench/baselines/ and client/icons/
(nothing references them; the bundle icons live in client/src-tauri/icons). audit-scripts
lists fpkg-diff.py as a manual research tool and no longer counts a script naming itself as
a reference. SPEC.md no longer says zip/7z/RAR sources fall back to FTX2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
A port of drakmor/ampr_emu's packer (tools/ampr_pack.py and
ampr_pack_format.py, GPL-3): the AMPRPAK4 manifest, AMPRDAT3 volumes,
AMPRCRC1 chunk-CRC sidecar and AMPRCFG1 runtime settings, with a reader that
validates what ampr_emu validates and decodes files chunk by chunk.
The planner follows the original: drakmor's TOML profile with the same keys,
defaults and checks, fnmatch-style rules where the last match wins, auto-loose
sampling of large incompressible files, balanced/hash/round-robin lanes,
page-safe placement (small chunks share an I/O page but never cross one),
raw fallback, per-lane block dedup, volume rollover, and the same build id, so
an all-store profile gives byte-identical output to drakmor's tool.
LZ4 is raw blocks: lz4_flex for fast mode, and a hash-chain encoder written
here for the hc levels, since lz4_flex has none. A built-in profile packs
everything inside a folder and keeps executables, system and backport folders,
AMPR's own files, text configuration and already-compressed media loose; the
exclusion list was informed by Nazky's Lazy_AMPR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
A dump's own ampr_emu.index described the folder as dumped, so a package
carried it stale after the param.json rewrite, executable repairs, left-out
files and the icons the container takes. ampr_emu trusts its sizes. The
builder now drops the dump's copy and generates one from the final file list
(keystone included), and two paths differing only in case stop the build with
both named instead of a progress line.
Readiness parses the folder's index and reports a stale or unreadable one
instead of passing it unchecked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
The engine already registered a cancel flag for downloads and the AVA1
download honours it, but the Stop button only stopped polling. Stop now
calls /api/jobs/{id}/cancel; the button and tooltip say Cancel. The
download job's outcome handling is split into run_download_job so a test
can drive a real AVA1 download against a folder host and check that a
cancelled job ends failed with transfer_cancelled. Also: the strings this
and the Upload commit changed in en.ts, and the install hint now points at
Games instead of Installed Apps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
lane_task paced each lane to the cap on its own, so a job on N lanes (up
to 8) sent N times the cap. One Pacer per job now hands out the byte
budget to every lane, and an idle stretch no longer banks a burst.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
The sending console now dials the receiving one and pushes the job itself.
The receiving console hands out a one-job ticket (c2c.allow) for the sender's
key, and the sender shows it in its handshake (c2c.send). The receiver takes it
as an ordinary upload into the ticket's one root. Each side holds the other to
that one job: no calls, no other job, no other root.
The engine tries the direct route first and falls back to its relay when the
consoles can't connect, a helper is older, or the direct send stops. The job on
the receiving console is the engine's own under the same id, so either route
resumes what the other left. The queue row shows the route ("Straight between
the consoles", Beta) or "Through this computer" and why.
Verified on two PS5s (FW 13.60): 842 MB / 401 files direct in about 11 s, both
directions; a cancel then resume; checksums identical. Spec: SPEC.md §18.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Image builds and compressions wrote <name>.partial into the output folder
and nothing tracked them: one interrupted (app closed, engine restarted)
stayed for good, and on Windows the single delete after a failure often
lost to an antivirus or the indexer holding the just-written file, so the
whole image stayed on the system drive. Every build now marks its partial
as being written; each conversion sweeps the output folder of build
partials and unpack folders no running job owns, before it writes and
after it fails, retrying a delete for a few seconds. Only the app's own
.pkg/.ffpfsc/.ffpfs/.exfat/.ffpkg partials are touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
A resume skips the entries already uploaded. Skipping a file that
continues in the next volume left its continuation to be read as one
more entry, so the walk fell out of step with the listing and the upload
failed with "the archive lists its entries in a different order than it
extracts them". A continuation header is now skipped with the file it
belongs to. Reproduced with a two-volume RAR5 set in the tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
The engine's cross-site guard let the app's image requests through only
for the console icon routes, so every Collection cover
(/api/collection/games/<id>/cover) was refused: a game with no online
cover showed its initials and the game page a broken image. That
read-only route is allowed now, and the game page's cover falls back to
the console's icon if it still fails.
A title with no cheats is an empty list, not a red error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
- An install after a re-read queues what the console still lacks, or
says it has it already (it queued the stale choice).
- The installed list counts as complete only when the internal app
folder listing answered in full; a failed listing no longer marks
app.db-only titles not installed.
- Both engine reads count a title's app folder on any drive, so a game on
the M.2 drive no longer flips between installed and not.
- The summary shows only consoles the app has; the engine is told on
every game page open, and when the last console is removed.
- A finished send or install re-reads the console it ran on.
- Snapshot writes in refresh and keep run off the async workers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
console_snapshot keeps, per console IP and title, what the last read found
(installed, version, update, DLC) and when, in state/console-snapshots.json.
The Collection's console check and the installed-apps list fill it; a
complete installed list marks titles that left as not installed.
GET /api/games/{id} joins it with the Collection (copies, and per console
what the drives could bring it); POST /api/games/{id}/refresh reads one
title now; POST /api/console-snapshots/keep forgets removed consoles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
Every test's temp dir is a TempDir that is removed on drop, whether the
test passed, failed or panicked, so a run leaves nothing in $TMPDIR.
AVA1_KEEP_TMP=1 keeps them for a post-mortem. Rigs hold the dir after
the server so the server stops first. 284 tests pass on macOS and Linux.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
- Every desktop report failed with "Duplicate filename: README.txt": the
bundle skips a name it already wrote, and screenshots are numbered.
- The engine refused a stream install of a package another console was
still pulling; the rival check is now per console.
- The Convert queue refused the same game for a second console, and an
image built for one console showed as busy on the others.
- Bug report: a console switch no longer writes one console's draft over
another's, a created report's draft stays cleared, and a failed
Download zip says so.
- Engine: a browser upload that goes away mid-way no longer leaves the
partial package for a week; a failed link download no longer blocks its
retry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
- Bug report: one page, everything in the chosen time frame, captures to
pick from, always redacted; GitHub / Discord / zip.
- Engine and app keep a timeline; helper log mirrored from each PS5.
- Console switches no longer land results on the wrong console.
- Buy me a coffee on Home.
- Dependency updates (#420-#425), chacha20poly1305 0.11 port.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
- Bug report wizard (4 steps, time range, GitHub/Discord/zip), redacted on both sides.
- Engine event journal + app journal merged into one timeline; helper stderr mirrored from each PS5.
- Results that finish after a console switch land on the console they started on.
- Faster local gate and CI (nextest, parallel lanes, sharded C interop/ASan, cache restore/save).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
- Power telemetry reads the four ICC getters under the hw fault guard with
oversized zeroed buffers; a crash now _exit()s so the AVA1 port is freed
instead of the process wedging in the core dump.
- Files: New folder on a row's menu and a folder menu on the list's empty
space (New folder, Paste here, Refresh).
- Payloads: "Send file" tab is "Send payload".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9