Commit Graph
32 Commits
Author SHA1 Message Date
phantomptr 5a28819d25 Release v5.39.0 2026-09-28 21:45:11 -07:00
phantomptr 05fa3178db Release v5.38.0 2026-09-28 18:13:57 -07:00
phantomptr 3190458798 Release v5.37.2 2026-09-28 16:47:58 -07:00
phantomptr d8e62af42d Release v5.37.1 2026-09-28 14:55:47 -07:00
PhantomPtrandClaude Opus 5.5 6f5abad2b5 elfldr that recovers by itself; 75-minute publish builds; 5.37.1 notes (#338)
* feat(elfldr): vendor elfldr with a read deadline, embedded in the engine

elfldr serves one connection at a time and its reads had no deadline,
so a client that went silent without closing (a dropped link, a machine
or console asleep mid-send) left it running but deaf on :9021 — users
had to load elfldr again. Measured on the Phat (FW 5.10): the stock
build answered nothing while a silent connection stayed open; with a
15 s SO_RCVTIMEO it dropped that connection and answered the next
request at once, and a payload sent with 10 s pauses still loaded.

third_party/elfldr is upstream 02cfe91 plus ps5upload.patch (GPLv3,
credited), with the tested ELF checked in so every engine build embeds
it; `make elfldr` rebuilds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(elfldr): the engine swaps a console's stock elfldr for the patched one

GET /api/ps5/elfldr/health tells healthy / stuck / absent from a request
elfldr answers without running anything. POST /api/ps5/elfldr/ensure
replaces the standard elfldr.elf with the embedded patched build when it
answers and isn't the one this engine installed (a reboot or autoloader
brings the stock one back), leaves any other loader on :9021 alone, and
returns once the new one answers. On the Phat: upgraded (pid 685,
answering at once), then 'current'; the helper was untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(publish): give each client build 75 minutes

The Intel macOS build ran past the 45-minute limit for v5.37.0 (about
33 minutes for v5.35.0 and v5.36.0), which cancelled it and skipped
create-release, so v5.37.0 was never published.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(engine): history tests get their own temp dirs

TmpDir named directories by pid and nanoseconds, but macOS time ticks in
microseconds: two tests starting together shared a directory and one
deleted it under the other (a_legacy_host_port_file_is_still_read read
nothing). A counter makes each name unique. The elfldr embed test reads
the embedded bytes directly instead of clearing an env var mid-run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(elfldr): the app keeps the patched elfldr in place and never sends into a stuck one

Before sending the helper, the app checks the console's loader: a stuck
one (took the connection, never answered) is waited out for 20 s — the
patched build clears in 15 — and one that stays stuck is reported
plainly ('load elfldr again') instead of a socket error. Once the helper
is up, the engine swaps the stock elfldr for the patched build: at once
after a fresh helper send (a reboot or wake brings the stock one back),
else at most every 10 minutes per console. Desktop, browser and Android
share the engine routes; strings in all 20 languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): 5.37.1 — elfldr recovers by itself; 5.37.0 folded in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(elfldr): swap it in when the helper is already up at launch

Only ensurePayloadCurrent asked for the patched elfldr, and a helper
that is already up when the app opens never goes through it — on the
Phat the stock elfldr stayed. The status poll's 'arrives at up' edge
(first sighting included) now asks too, after the same settle as the
log capture and never during a transfer.

Hardware (Phat FW 5.10, web build and Android app): stock elfldr
replaced ~10 s after opening; a silent :9021 connection held while the
helper was shut down — the helper came back through the patched
elfldr; with the stock elfldr wedged, the Android app sent nothing for
150 s (health: stuck), then redeployed and swapped elfldr once released.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(convert): the compress stage pays for reading and writing on one thread

kraken_image::compress runs the encoder on a pool, but one thread reads
the source and writes each compressed block, so the stage takes the
longer of encoding and read+write — not the slower of reading and
encoding, as the estimate assumed. With the Windows Battlefield 6 run's
speeds the model now gives 75 min for a 71-min build.

The estimate also reports the speeds it measured (rates), because the
small read/write samples can be fooled by caches: on a source read an
hour earlier the read sample hit the file cache at 8.6 GB/s and the
estimate came out at 25 min for a 54-min build. Calibrating from past
builds is the follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): build-time estimate, stated as measured

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(docker): 25 minutes for the PR image checks

Both PR gates build the whole image (engine, and the client for the web
UI one) in about 7-10 minutes; the engine gate took 9m55s and the web UI
gate was cancelled at its 10-minute limit on PR #338.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:55:05 -07:00
PhantomPtrandClaude Opus 5.5 832e2afcd2 fix(docker): the web UI image bundles the FAQ and Changelog (#337)
* fix(docker): the web UI image bundles the FAQ and Changelog

Since the FAQ and Changelog screens bundle ../FAQ.md and ../CHANGELOG.md,
the web UI image's frontend stage failed: it built the client at /src
and .dockerignore excluded every *.md. The stage now keeps the repo
layout (/repo/client) and copies the two files in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(scripts): the audit counts a script's node:test file as used

scripts:check runs every *.test.mjs by pattern, so the test beside
stale-client-lib.mjs is named nowhere and the strict audit called it an
orphan (repo scripts failed on all three OSes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:04:23 -07:00
phantomptr ce76e9dec5 Release v5.37.0 2026-09-28 12:16:16 -07:00
phantomptrandClaude Opus 5.5 c155c08c88 fix(convert): the time estimate covers the whole build, at the speed the drives allow
A 234 GiB game estimated at 17 min took 71: the estimate timed only
compression, of blocks already in memory, one thread multiplied by the
thread count. It now times the source's sequential reads and the
output drive's writes, runs the sample on every thread at once, and
adds the package's write and verify to compression bounded by the
slower of reading and encoding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:41:06 -07:00
phantomptrandClaude Opus 5.5 071487f955 fix(engine): listing a folder that isn't there is a 404, not a 502
Install Package lists every drive's package folder; a drive without one
logged a gateway error. The body is unchanged, which is what the app
reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:31:50 -07:00
phantomptrandClaude Opus 5.5 31dc0d9182 fix(engine): a recovery line counts every failure of the outage
It reported only the unlogged repeats, so one logged 502 followed by a
200 read "recovered after 0 failure(s)".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:14:18 -07:00
phantomptrandClaude Opus 5.5 cf7ddbccab fix(engine): exit when the desktop shell dies, even with nowhere to print
The parent-watch thread's first eprintln! panicked on the closed stderr
pipe a dead shell leaves behind, so the engine never shut down and held
its port (reproduced with the v5.36.0 app: killed shell, engine alive
25 s later). Shutdown is now triggered before any message, messages use
a write that cannot panic, and the notify keeps a permit so a parent
that dies during start-up isn't missed (it fell to the 10 s hard exit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:12:53 -07:00
phantomptrandClaude Opus 5.5 d0bbf5da74 fix(remote): a RAR volume already in the folder is never overwritten
The copy itself got a 409 when its name was taken, but a set's other
volumes were written over, and cleanup would then delete them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:06:24 -07:00
phantomptrandClaude Opus 5.5 93e02d8969 feat(convert): Game language — declare one PlayGo language in the package
Battlefield 6's eboot imports scePlayGoGetLanguageMask: it asks the
install which language it has, and every converted package answered
"all 31, en-US first". Convert's Options card now takes one language;
the package's playgo-scenario.json then lists it as the default and
only language (the shape Sony's own scenarios take). Carried through a
single run and the Convert queue; reset per game.

Checked on the Phat (FW 5.10): a fr-FR Minecraft build installs and
plays like the all-language control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:49:40 -07:00
phantomptrandClaude Opus 5.5 9b21f5a2e8 feat(remote): a multi-part .rar on a saved server is copied with every volume
UnRAR finds the rest of a set beside its first volume, so copying only
game.part1.rar (or game.rar of an .r00/.r01 set) could never extract. The
server copy now brings the set's other volumes into the same folder and
cleanup removes them too - for Upload, a batch item's turn, and Convert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:01:54 -07:00
phantomptrandClaude Opus 5.5 c1b03a9910 test(engine): hand-run throughput test for the console reader Convert uses
Measured 2026-09-28 on the Phat over this Mac's Wi-Fi: 7.8 MB/s for 2 GiB,
the same as curl on the same file (7.9 MB/s) — the reader adds no overhead;
the link is the limit (117 MB/s wired in the spike).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:56:39 -07:00
phantomptrandClaude Opus 5.5 7872b0fc4f feat(convert): a queue - several games converted one after another, installs handed to the console queue
Add this game / Add games from a folder… queue builds on this computer, one at
a time, each shown by the Convert screen's own stage view. After converting:
keep the package, or Stream & install / Upload & install - the package is
handed to the console queue and the next build starts while it installs
(a console dump still swaps inside its build). Optional delete after a
verified install. Reorder, remove, stop after the current build; a build
interrupted by a restart is built again, and the engine now clears an
interrupted build's stale .pkg.partial instead of refusing the rebuild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 07:28:22 -07:00
phantomptrandClaude Opus 5.5 d0970f0de3 fix(pkg): name the container's own entries (digests, entry_keys, image_key, metas…)
The viewer's Files tab listed them as bare ids (#0001, #0010, …).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 06:57:43 -07:00
phantomptrandClaude Opus 5.5 104b08f9ac test(installer-client): the fake daemons read the whole request line
The client writes a request and its newline separately; a fake that answered
after the first segment closed over unread bytes, and the reset beat the
reply to the client (hello_over_socket flaked under load).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 06:54:37 -07:00
phantomptrandClaude Opus 5.5 21aee22efa feat(viewer): Files, Images and Related tabs; packages and games on a server or the PS5
The viewer reads remote:// and ps5:// sources through the engine's opener, and
.pkg parsing now works over any reader, so a staged package opens straight
from the console: every Install Package row gets View details, reading the
local original when there is one. New GET /api/game/inspect/files lists a
package's entries or a tree's files (a windowed list for 200k-file dumps);
Images shows and saves every artwork; Related groups the title's other
library and queued packages base → update → DLC, each viewable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 06:45:36 -07:00
phantomptrandClaude Opus 5.5 3c558ba0ed feat(convert): read a game in place on a saved server or the PS5
A server folder or image is built straight off the server through the
engine's server file system (8 MiB read-ahead, retries) - no copy stage;
only a server archive is still copied, then unpacked. 'On the PS5' browses
the console and reads a dump through ftpsrv (:2121) as ps5://host/path;
installed games under /user/app are refused as encrypted. source::open
resolves remote:// and ps5:// through an opener the engine registers, so
build, estimate, inspect and the viewer all read them. Compression still
copies a server image first (it reads local disk).

Verified on the Phat (5.10): inspect + build of an image on the console
over ps5://, same package size as the local build; /user/app refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 01:08:22 -07:00
phantomptrandClaude Opus 5.5 ebcc541173 feat(convert): build from a .zip, .7z or .rar archive
An archive is unpacked into .ps5upload-extract-<job> in the output folder as
its own Convert stage (progress, Cancel), the game found inside - a folder
however wrapped, or a single .exfat/.ffpkg/.ffpfsc image - is built, and the
unpack is removed when the run ends. Space for the files plus the package is
checked first; unpack folders left by an earlier run are swept. RAR takes a
password (never stored); a wrong one on a content-encrypted RAR now says so
instead of 'File CRC error', for uploads too. zip/7z are built without their
encryption features, so a protected one is refused with that reason. A server
archive is copied, then unpacked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:53:45 -07:00
phantomptrandClaude Opus 5.5 da44b7193f feat(fpkg): convert and inspect a .ffpfsc through the image inside it
PfscReader is Read + Seek over a .ffpfsc's inner file, decoding 64 KiB zlib
blocks on demand with a small cache; the single-file layout parser is shared
with verify and bounds-checked for images users bring. exFAT and UFS2 sources
now open from any reader. A package built from a .ffpfsc is byte-identical
to the one built from its image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:38:32 -07:00
phantomptrandClaude Opus 5.5 808780f2e3 fix(viewer): cache sees folder PARAM and split-part edits; safer reads and labels
- Cache key covers a folder's PARAM files and a package's split parts, and a
  hit keeps its token alive.
- Content-id slicing can't panic; text read from an image or folder is capped;
  a failing OS random source is an error, never a guessable token.
- SFO counts and levels show in decimal (flags and versions stay hex).
- "Entries" for a package, "Parental level" for PS4; Copy uses the shared
  clipboard helper; a reply for an earlier path or console is ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:29:19 -07:00
phantomptrandClaude Opus 5.5 ead8079a6e test(installer-client): fix the fake daemon's macOS read race
An accepted socket inherits the listener's O_NONBLOCK on BSD/macOS, so a read
before the client wrote returned WouldBlock and the request went unrecorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:29:19 -07:00
phantomptrandClaude Opus 5.5 c0ada35d3e fix(inspect): PS5 sources are games by applicationCategoryType, with their pubtools build date
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 23:33:49 -07:00
phantomptrandClaude Opus 5.5 2ed82f8554 feat(inspect): /api/game/inspect with a token cache, its image route, Tauri commands and browser mapping
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 23:19:56 -07:00
phantomptrandClaude Opus 5.5 4af627a092 feat(engine): GameInspection — one description of any local package, image or game folder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 23:15:44 -07:00
phantomptrandClaude Opus 5.5 9a3d19d34e fix(pkg): clippy — read the entry table with as_chunks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 23:11:59 -07:00
phantomptrandClaude Opus 5.5 51adbe60cd feat(pkg): typed PARAM.SFO values and the package entry table
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 23:11:45 -07:00
PhantomPtrandClaude Opus 5.5 76016015c7 PS5Upload v5.36.0 (#336)
* feat(pkg): choose which drive install packages are kept on

Uploaded packages always went to internal storage
(/user/data/ps5upload/pkg_library), which fills the console's own drive.
The Volumes screen now lets you mark a drive "Use for packages"; new
packages are staged under <drive>/ps5upload/pkg_library on it.

- Per console, remembered on this device. Internal stays the default.
- Falls back to internal storage, with a notice, when the chosen drive
  isn't connected.
- The package library lists packages on every drive; rows show the drive.
- The folder chain on the drive is created one level at a time.
- The External Packages scan skips a drive's ps5upload/ folder, so staged
  packages aren't listed twice.

Hardware (Phat, FW 5.10): Star Wars Racer Revenge base+patch staged on
/mnt/ext0 installed, patched 01.00->01.02 and launched in 2 of 3 trials
(80 MB/s); the first stalled after the header read and is covered by the
daemon-recycle fix that follows. Internal control: 1/1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(install): recycle the installer daemon after a stalled delivery

A console-path install that stalls leaves the daemon's loopback job
"serving" until Sony reads every byte, which never happens, so every later
install was refused as "an install is already in progress" until the
daemon restarted, although the failure told the user to retry.
Measured on the Phat (FW 5.10). A stalled daemon-served install now stops
the daemon the same way a wedged stream refusal already did, and the next
install's ensure brings up a fresh one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* PS5Upload v5.36.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:18:45 -07:00
PhantomPtrandClaude Opus 5.5 c7bb83d04d chore(scripts): rewrite release posts, drop outdated scripts; README highlights (#334)
release-posts.js
- Reads whole changelog groups (bold heading + wrapped bullets), not just
  the first line of each bullet.
- X: link counted as 23 chars, replies packed to fit 280, markdown stripped.
- Discord: full bullets when they fit 2000 chars, short ones otherwise;
  Reddit: real markdown sections instead of a code block.
- Says "pre-release" when GitHub does (gh), --prerelease/--stable to
  override; --only and --out to pick a platform and write files.
- make release-post calls it directly; the .sh wrapper is gone.

Removed (outdated)
- ps5-install-trace.py, ps5-install-watch.py, fpkg-mount-test.py: drove
  /api/pkg/install/start and the dpi-* routes, which no longer exist.
- gather-fakelibs.py: superseded by the app's Backport scan.
- pull_ps5_debug.py: pulled a debug dir the helper no longer writes to.

hw-test-loop.sh ran cargo from the repo root (no workspace there); it now
points at engine/Cargo.toml.

README: a Highlights section up top (unified install, Convert to FPKG,
sleep and wake), Android/web in the platforms badge, tested firmware 5.10 &
9.60.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 06:10:10 -07:00
phantomptrandClaude Opus 5.5 c14782944d PS5Upload v5.35.0
Upload, install, convert and manage PS5 game packages over the network.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 04:31:13 -07:00