Files
ps5upload/payload
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
..
2026-09-27 14:18:45 -07:00
2026-09-27 04:31:13 -07:00
2026-09-27 04:31:13 -07:00
2026-09-27 04:31:13 -07:00
2026-09-27 04:31:13 -07:00
2026-09-27 04:31:13 -07:00
2026-09-27 04:31:13 -07:00

Payload

The C payload that runs on the PS5. Built with the PS5 Payload SDK (version pinned in ../scripts/ps5-sdk.env), sent to the console's ELF loader, and left resident until reboot or rest mode.

It listens on two ports:

Port Accepts
9113 Transfer frames only
9114 Everything else — status, filesystem, mount, app, hardware, package, shell

A frame sent to the wrong port is answered with wrong_port rather than half-working.

Layout

src/main.c handles startup: credential elevation, runtime ownership, the management thread, the transfer loop, and cleanup. src/runtime.c is the FTX2 runtime itself — framing, the transaction journal, resume, direct and spooled writes, and most command handlers. src/takeover.c asks an older resident payload to stand down before binding.

The rest of src/ is one module per capability: registration and launch, package install, hardware and sensors, processes, profiles, saves and backup, cheats, FTP, remote play, fan curve, notifications, system registry and time, firmware spoofing, and the SDK version changer.

installer/ builds our own standalone install daemon (ps5upload-installer.elf, TCP :9115) used for package installs on newer firmware. It loads as a companion image on the :9021 loader (never evicting the helper), self-escalates, and answers a JSON-lines protocol (hello/install/job/stop); for a staged file it serves the package to Sony's installer over a 127.0.0.1 HTTP URL. The engine sends it automatically when nothing is listening on :9115.

Prior art

The installer daemon is our own implementation. Its design was informed, as behavioural references only (no code or names are carried into the daemon), by cy33hc's ps5-ezremote-dpi, etaHEN and elf-arsenal's DPI payloads, and itsPLK's on-console PKG Manager (the loopback-serving + cacheability rules).

Build

export PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk
make -C .. payload        # → payload/ps5upload.elf (+ .gz)
make -C .. send-payload PS5_HOST=<ip>

Everything compiles with -Wall -Wextra -Werror.

Testing

Logic that can be separated from the console lives in a header under include/ and gets a host-compiled self-test in tests/, run by make test-payload with the same warning flags. Current cores:

Header Self-test covers
hw_guard.h Recovering from a faulting Sony getter without losing the helper
ptrace_recovery.h Timeout recovery never resuming injected registers
appdb_scan.h Reading app.db — a real SQLite record reader, because column values are stored with no separators between them
ftp_format.h PASV/EPSV/LIST reply shapes, which clients parse strictly
sdk_param.h Rewriting param.json version fields, and reporting when nothing changed
elf_param.h Finding SDK fields via program headers, not by scanning for magic bytes
timed_init.h Bounded one-time init that never starts a second initializer

Prefer adding to this set over testing on hardware: a host self-test runs in milliseconds and can't wedge a console.

Things that will bite you

  • Never rename() across mounts. It panics the kernel. Guard on st_dev.
  • Nothing large on a thread stack. Threads that need room ask for it explicitly (512 KiB–1 MiB); the default is small. A 256 KiB buffer in an FTP handler once overflowed it and wedged consoles hard enough to need a power cycle — bulk buffers are heap-allocated now.
  • Every blocking wait needs a bound. Unbounded ptrace waits froze consoles; Sony IPC init could hang the DPI daemon. Both are now timed, and a timed-out attempt is never restarted concurrently.
  • JSON keys must be snake_case, and any interpolated string must be escaped. The engine parses with serde: a wrong key silently yields a default value, and one bad byte rejects the entire response.
  • Some Sony libraries simply aren't on the console. libSceSqlite.sprx is absent under every lib path, so dlsym(RTLD_DEFAULT, "sqlite3_*") fails on every firmware — not just some. Degrade honestly instead of blaming the firmware.
  • "The platform doesn't ship it" is not "we can't have it." That SQLite conclusion was right about Sony and wrong about us: a static library is just more of our own .text. The payload now links its own SQLite (content_db.c, built from the amalgamation that scripts/install-ps5-sdk.sh fetches). Two full SQL implementations had been sitting behind that dead dlsym probe long enough to drift apart and hardcode different table names, and a third — in register.c — was a table of function pointers that nothing ever assigned. A branch that can never execute is where bugs go to hide, so prefer deleting it to keeping it as a fallback that never fires.
  • Check what a database says its schema is. content_db.c finds the app table through sqlite_master and PRAGMA table_info rather than naming it, because the two dead implementations disagreed about the name and there was no way to tell which was right.