- Unify userland exploit runner, ROP primitives, and payload pipeline on ps5-webkit-remote-loader - Drop legacy umtx2 (FW <= 5.50) - Improve Relapse stability
11 KiB
WebKit Autoloader architecture
This repository installs an offline homescreen app and bundles a standalone HTML
page built by third_party/ps5-webkit-remote-loader. Remote-loader owns WebKit,
firmware offsets, the ROP worker, Poops, Relapse, and the standalone runtime.
It also owns the shared native installer helpers and SDK Docker recipe. This
repository provides include/installer_config.h for its app name, title ID,
assets and log prefix. Remote-loader has no dependency on this repository.
The installer has a small exploit chooser and cache progress bar. The exploit
runs directly in the standalone page, without an iframe or generated-code patches.
Runtime
tools/build_standalone.py builds one standalone page per chain, and each
page carries three ordinary payloads. The loader first prepares the WebKit memory
primitive and ROP worker, then runs this queue:
elfldr-check.jsconnects to127.0.0.1:9021without sending data. If a loader accepts the connection, it requestsapi.stopQueue(), closes its socket, and skips both remaining payloads. A refused connection allows the queue to proceed; an unexpected probe or cleanup error stops it instead of retrying a kernel exploit blindly. No kernel access is required for this check.poops.js(orrelapse.js) runs unmodified from the submodule. It establishes kernel access and brings up elfldr/kexp itself on port 9021.autoload.jsthen stages the bundled ELF into an anonymous mapping and streams it to elfldr. It selects nothing: the page is already built for one chain.
The payload queue runs in order: [1/3] elfldr-check.js, [2/3] poops.js
(or relapse.js), then [3/3] autoload.js. Each payload runs directly in the page
context without wrappers.
The installation flow is choose, then cache:
- The installer page loads with no
?exploit=. On firmware that supports both chains it shows the prompt, records nothing, and — importantly — registers no AppCache handlers, so nothing downloads behind the buttons. - The server withholds
manifest="/cache.appcache"from the entry page until the request carries a supported?exploit=, because AppCache starts on that attribute and JS runs too late to stop it. Firmware with only one chain needs no question, so it is served with the attribute immediately. - Choosing records
localStorage.wkal_pendingand reloads with the choice. That reload is what starts the cache. - Once the cache completes, the page checks the
__complete__marker and calls/install. Only on success iswkal_pendingpromoted towkal_exploitand cleared.
Two keys, so the one the pointer reads means "an install completed". A choice is
never written over the installed one on the way in, so a failed install leaves the
previous autoloader intact and launchable; only a finished /install changes what
is installed. This is also what makes "Currently installed: X" in the prompt
truthful rather than "whatever was last clicked".
Both chain pages are cached once caching starts, so switching later costs nothing and never re-downloads anything.
The server's part is decided strictly per request, from the query string and the User-Agent, without maintaining server-side state or remembered choices.
Both chain pages are always cached (~9.4 MB uncompressed). The cached bytes therefore do not depend on the choice, so switching chains changes one storage key and touches nothing in the AppCache — there is no cache to invalidate. This is deliberate and load bearing: caching both pages at once makes subsequent chain switches instant and offline, with zero network or cache invalidation risk.
The server's only selection logic is deciding per request whether to include the
manifest="/cache.appcache" attribute on /index.html. It stores no selection state
in memory or C variables.
The upstream firmware table rejects unsupported firmware before running WebKit. Supported versions are the remote-loader offset tables in the 7.00–13.60 range. Poops works without an active network interface; Relapse needs a connected local network interface, but no Internet access.
Build
Makefile calls the upstream builders without transforming their output:
make page: verify/download dependencies, then calltools/build_standalone.pyonce per chain withpayloads/elfldr-check.js, that chain's exploit, thenpayloads/autoload.js::<payload_elf>, plus--elfldrand--embed <payload_elf>=<file>. Outputs:build/autoloader/{poops,relapse}.html. The exploits are submodule paths, resolved relative to the submodule root.make all: stage the installer page, stable pointer and generated HTML; generate a compressed C registry and AppCache manifest; compileinstaller.elfwith the PS5 SDK. Both installers compile remote-loader'sinstaller/commonsources; product-specific main loops and HTTP routing stay in their own repositories. Docker provides the SDK and libmicrohttpd from remote-loader's Docker recipe.make host HOST_PAYLOAD=<installer.elf>: build standalone setup pages for both chains (Relapse and Poops) embedding that installer, then package them via remote-loader'stools/build_host.pyintowebkit-autoloader-host.py. At runtime, the host serves Relapse by default and automatically routes firmwares incompatible with Relapse (9.05, 11.40) to Poops. Use a versioned ELF path outside the nativeinstaller.elftarget when building the host on a machine without the SDK.make dev: serve the generated chain pages on localhost:8123.make clean: remove generated pages, registry and unversioned artifacts; retain verified downloads inbuild/deps/for offline rebuilds.
PAGE_CSS and PAGE_JS default to frontend/autoloader/style.css and app.js,
providing the log panel, progress bar, and status footer on both chain pages and the
PC setup page. The script moves the existing console into the panel and observes
its log lines for progress; it never wraps the exploit or replaces its APIs.
Completion waits for the queue summary, including payload cleanup. An existing
ELF loader is shown as already running, and failures stay visible in the log.
tools/gen_version.py --page-config supplies the footer's version and build time
as an inline startup script. No additional network resources are needed.
Override PAGE_CSS and PAGE_JS to pass other files to the upstream HTML builder.
CSS is embedded after the default styles in the
head. Each JavaScript file runs in its own async function before module boot and
the exploit; use top-level await or return a promise for work that must finish
first. Multiple files run in argument order, and startup errors stop boot.
make page PAGE_CSS=path/to/theme.css PAGE_JS=path/to/setup.js
# Or build the upstream plain console:
make page PAGE_CSS= PAGE_JS=
The variables accept whitespace-separated lists of paths relative to the working
directory. Startup scripts can prepare the DOM and share state through window;
they have no payload api. Inline assets needed during offline startup. This
keeps appearance changes in CSS/JS files here instead of rewriting generated HTML
or changing the exploit sources.
tools/download_deps.sh downloads the pinned itsPLK elfldr and unified-autoloader
release assets, caching verified digest sidecars. kexp is the binary bundled by
remote-loader. Submodule initialization is non-recursive: remote-loader's nested
copy of this repository is not a build input.
build_release.sh pins BUILD_VERSION once across Docker, HTML/cache generation,
and PC host packaging, then writes versioned ELF/Python artifacts. GitHub Actions
also packages the Python host as a Windows executable.
Remote-loader changes live on refactor/minimal-autoloader: --embed URL=FILE
adds inline fetch resources, --elfldr [URL=]FILE overrides the loader binary without
editing generated HTML, and the host builder accepts --page and --name.
Its probe verifies embedded file sizes and input digests, including external assets.
The target payload ELF (ps5-unified-autoloader for offline autoloader pages, or
the installer ELF for the PC setup host) is embedded via --embed.
Offline installation
The installer serves on 127.0.0.1:18181 only. On dual-chain firmwares, its browser
page prompts the user to select Poops or Relapse before starting caching. Choosing
reloads with ?exploit=<chain>, allowing the HTTP server to include the manifest
attribute. The page displays AppCache download progress and calls /install once
caching completes (cached, noupdate, or updateready). Upon successful install,
wkal_pending is promoted to wkal_exploit in localStorage. The installer validates
the version marker before creating the shortcut. Cache errors leave the installer
running and offer a user-confirmed WebKit-data clear; the native installer then
reopens the browser to retry.
The cache layout is:
/index.html installer/cache entry
/logo.svg installer logo asset
/cache.appcache generated manifest
/app/index.html stable homescreen pointer
/app/<version>/poops.html chain page: elfldr-check.js, poops.js, then autoload.js
/app/<version>/relapse.html chain page: elfldr-check.js, relapse.js, then autoload.js
/app/<version>/__complete__ version marker, last in CACHE
The pointer checks the marker before navigating, then sends the user to the chain page they chose, so partial cache downloads cannot start the app. The HTTP server serves the standalone chain pages verbatim. It withholds the installer's manifest attribute until a supported choice is present, as described above.
SIMULATE=1|2 and tools/build_cache_corruption_test_elfs.sh are available for
hardware testing of failed-cache repair.
Verification
make test-native runs remote-loader's common native helper tests on Linux (or
inside the SDK container). PS5 services are stubbed and all /user filesystem
operations are redirected into a temporary tree. The tests cover installation
and updates with two app configurations, embedded resources, service errors,
notifications, browser launch, foreground-user cleanup, long-log truncation and
raw DEFLATE. Native ELF compilation still uses the PS5 SDK.
make test runs the four offline suites. tools/test_elfldr_check.js drives the
real check payload through the upstream queue, verifying accepted/refused sockets,
descriptor cleanup, and skipping the remaining payloads on a stop or unexpected
probe failure. tools/test_autoload_payload.js covers
the third payload: ELF staging into the mapping, the hand-off to elfldr, short
socket writes, the missing-krw refusal and cleanup on failure.
tools/test_installer.js checks the installer's cache events, the marker gate,
choice gating in both orders, error handling, repair confirmation and the pointer.
tools/test_selection_flow.js checks that a pick is stored on the origin and that
the pointer routes to the matching chain page, that lone-chain firmware neither
prompts nor leaves the choice unset, and that both chain pages are staged and
cached so a switch never invalidates anything.
Upstream third_party/ps5-webkit-remote-loader/tools/probe_standalone.js and
tools/boot_probe.js check the generated HTML, inline resources, modules, firmware
offsets and boot order. SIMULATE=1|2 builds and
tools/build_cache_corruption_test_elfs.sh cover failed-cache repair.
None of this can establish exploit reliability; that needs a PS5 hardware run.