Author SHA1 Message Date
SirHumza bb59ff3ac7 Merge pull request #33 from CyberMask367/main
0.70 test build changes
2026-10-08 21:47:45 +02:00
CyberMask367 0ec7689609 Updated README for test build notes
Update the README changelog for the 0.70 test build. The notes now highlight the 32MB websocket gateway frame buffer increase, a frame draining fix, and the new invalid token notification.
2026-10-08 14:01:56 +01:00
CyberMask367 0727298892 Revert "Added PSP cover arts"
This reverts commit c5110ff54b.
2026-10-08 13:59:15 +01:00
CyberMask367 d51edf1a31 Revert "Added PS1 cover arts"
This reverts commit 62b564cabd.
2026-10-08 13:27:10 +01:00
CyberMask367 62b564cabd Added PS1 cover arts
Added PS1 cover arts for retro games resolving
2026-10-08 02:16:03 +01:00
CyberMask367 c5110ff54b Added PSP cover arts
Added PSP cover arts for retro games resolving
2026-10-08 01:52:20 +01:00
CyberMask367 050d18456c increased WS_RBUF_MAX to 32MB + drain
increased WS_RBUF_MAX to 32MB and new drain fix, also added notification for invalid tokens
2026-10-08 01:36:37 +01:00
CyberMask367 8eb5bbc97a Update README for clarity on game support 2026-10-06 22:22:06 +01:00
CyberMask367 992d8015e9 Revise firmware range and credit details in README
Updated firmware requirements and credits section in README.
2026-10-06 22:19:51 +01:00
SirHumza 50d12cb455 docs: rewrite ARTWORK (mp proxy rule, true default app id) 2026-10-06 20:31:50 +02:00
SirHumza a1a8d31e56 docs: cross-correct (browser presence text, dead script refs, retired paths) 2026-10-06 20:28:58 +02:00
SirHumza cdf793614a docs: it's the game cover missing, not the game 2026-10-06 20:21:33 +02:00
SirHumza 6080f3f348 docs: no art without nanoDNS exception 2026-10-06 20:20:46 +02:00
SirHumza 3f530c2be2 docs: Payload Guest crashes — never use it for orbisRPC 2026-10-06 20:20:21 +02:00
SirHumza 3fca823a59 docs: payload lives at /data/payloads, launch from payload list 2026-10-06 20:20:06 +02:00
SirHumza 9afe569216 chore: drop stale build_setup.txt (wrong loader port, docs cover it) 2026-10-06 20:18:09 +02:00
dependabot[bot] 2b60e1fda2 chore(deps): bump actions/upload-artifact from 4 to 7 (#30)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 20:17:56 +02:00
SirHumza f5472c9b78 README: full rewrite (TOC, requirements, presence table, developing, credits) 2026-10-06 20:16:47 +02:00
dependabot[bot] 16a4865927 chore(deps): bump softprops/action-gh-release from 2 to 3 (#29)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-06 20:15:58 +02:00
SirHumza b5f6f9b1ff README: CI status badge 2026-10-06 20:15:50 +02:00
26 changed files with 9379 additions and 9263 deletions

No files matched your search

+4 -4
View File
@@ -7,7 +7,7 @@ jobs:
host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v5
- name: host unit tests
run: make -C tests clean && make -C tests test
- name: ASan/UBSan
@@ -19,14 +19,14 @@ jobs:
sdk-payload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v5
# The release ZIP (v0.9) predates the SDK's 13.52 offsets, and the SDK's
# crt/patch.c makes _start() terminate before main() for unlisted
# firmware -- so a ZIP-built payload dies silently on 13.52. Build from
# git instead. Recipe from drakmor/nanoDNS's ps4 workflow, which ships a
# working 13.52 payload over the same elfldr path.
- name: checkout ps4-payload-sdk
uses: actions/checkout@v7
uses: actions/checkout@v5
with:
repository: ps4-payload-dev/sdk
path: sdk
@@ -65,7 +65,7 @@ jobs:
if echo "$NEEDED" | grep -q 'libkernel\.so'; then echo "FORBIDDEN: app-world libkernel.so"; exit 1; fi
echo "linkage OK"
- name: upload test-build artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: orbisrpc-test-build
path: build-sdk/orbisrpc_sdk.elf
+5 -4
View File
@@ -12,9 +12,9 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v5
- name: checkout ps4-payload-sdk
uses: actions/checkout@v7
uses: actions/checkout@v5
with:
repository: ps4-payload-dev/sdk
path: sdk
@@ -48,7 +48,7 @@ jobs:
if echo "$NEEDED" | grep -q 'libkernel\.so'; then echo "FORBIDDEN: app-world libkernel.so"; exit 1; fi
echo "linkage OK"
- name: attach ELF to release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
files: build-sdk/orbisrpc_sdk.elf
generate_release_notes: true
@@ -58,7 +58,8 @@ jobs:
Test build `.elf` — no PKG yet.
1. Delete the `/data/orbisRPC` folder from your PS4 first.
2. Run the payload once — it creates the config, then waits (no token yet).
2. Put the `.elf` at `/data/payloads/` and launch it from the payload list.
3. First run creates the config, then waits (no token yet).
3. Open `/data/orbisRPC/config.json`, put your token in the `"token": "SET_ME"` slot.
4. Re-run the payload. Launch a game, watch Discord.
+6 -2
View File
@@ -11,8 +11,12 @@ __pycache__/
config/config.ps4.json
plugin/build/
tests/test_utils
config/icons/*
!config/icons/logo.png
# Retro cover art. Fetched at runtime from retro-games.cybermask.dpdns.org
# (see the *_art URLs in cfg.c) and cached here locally; nothing under
# orbisrpc/ reads these. 3.7 GB across ~23k files, so kept out of history.
config/images/PS1/
config/images/PS2/
config/images/PSP/
# installer artifacts
*.pkg
+1 -1
View File
@@ -33,7 +33,7 @@ Beta Discord Rich Presence daemon for jailbroken PS4. Testers install the
- Docs: tester flow in `README.md`, reference in `docs/`
(`TROUBLESHOOTING.md`, `CONFIG.md`, `BUILDING.md`), hardware research in
`docs/research/`. Keep them in sync with behavior changes.
- Don't tell testers to use Payload Guest for orbisRPC; autorun via
- Never tell testers to use Payload Guest for orbisRPC — it crashes. Autorun via
`/data/payloads` is not recommended on test builds.
- License is undecided (MIT vs GPL). Don't add GPL code or a LICENSE
file until the owners pick.
+153 -80
View File
@@ -1,11 +1,12 @@
<p align="center">
<img src="config/icons/logo.png" width="420" alt="orbisRPC">
<img src="config/images/icons/logo.png" width="420" alt="orbisRPC">
</p>
# orbisRPC — Discord Rich Presence for PS4 (GoldHEN RPC)
<p align="center">
<a href="https://github.com/SirHumza/orbisRPC/releases"><img src="https://img.shields.io/badge/version-beta-ffd800?style=flat-square" alt="version"></a>
<a href="https://github.com/SirHumza/orbisRPC/actions/workflows/ci.yml"><img src="https://github.com/SirHumza/orbisRPC/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<img src="https://img.shields.io/badge/PS4-GoldHEN-003791?style=flat-square" alt="PS4 GoldHEN">
<img src="https://img.shields.io/badge/Discord-Rich%20Presence-5865F2?style=flat-square" alt="Discord">
<a href="https://discord.gg/BWEyfcT7ZQ"><img src="https://img.shields.io/badge/Discord-Join%20the%20server-5865F2?style=flat-square&logo=discord&logoColor=white" alt="Join the Discord"></a>
@@ -20,6 +21,69 @@ playing to Discord: name, cover art, timer. No PC at runtime.</p>
---
## Contents
- [What you get](#what-you-get)
- [How it works](#how-it-works)
- [Requirements](#requirements)
- [Install (5 minutes)](#install-5-minutes)
- [What you'll see](#what-youll-see)
- [Config](#config)
- [Support / Community](#support--community)
- [Troubleshooting](#troubleshooting)
- [Building](#building)
- [Developing](#developing)
- [Safety](#safety)
- [Credits](#credits)
- [License](#license)
---
## What you get
| | |
|---|---|
| 🎮 **Any games** | Names resolve on your console via Sony's TMDB — CUSA, zero per-game setup. |
| 🕹️ **Homebrew + retro** | Homebrew resolves via pkg-zone; PS1/PS2/PSP classics via a custom list. |
| 🖼️ **Real cover art** | Game art served per title, PlayStation logo when idle. |
| ⏱️ **True timers** | Survive reconnects and restarts, resume across quick game switches. |
| 🧠 **Self-learning** | First-seen titles are remembered, so later boots resolve instantly. |
| 🔄 **Self-updating** | Daemon updates land from GitHub releases with automatic rollback. No reinstall treadmill. |
| 📦 **Simple install** | Payload (`.elf`) install → token → presence. |
| 👀 **Your call what shows** | `show_firmware`, `show_idle`, `show_media`, `show_homebrew` — all on by default, each toggled separately. |
## How it works
The PS4 runs one foreground app at a time, so a normal homebrew app gets
suspended the moment you launch a game. orbisRPC avoids that by running as
a **background payload daemon** while games run in the foreground.
```
PS4 (GoldHEN) Discord
┌─────────────────────────┐ ┌──────────────────┐
│ orbisRPC daemon │ TLS │ your profile │
│ BigApp+TitleId → game │ ◄──► │ Playing Game │
│ metadata/TMDB → name │ │ [cover] [timer] │
└─────────────────────────┘ └──────────────────┘
```
1. The daemon asks the OS what's in front (`BigApp` → `TitleId` — one
call, so identity and state can't disagree).
2. Looks up its title + cover (config → app.db → local files → Sony
TMDB → pkg-zone for homebrew → raw ID fallback).
3. Connects to the Discord gateway over TLS 1.2.
4. Sets your activity with an elapsed timer; clears it when the game exits.
Details: [`docs/DAEMON.md`](docs/DAEMON.md) · full index at [`docs/`](docs/)
## Requirements
- Jailbroken PS4 on firmware **from 5.05 upto 13.52**, GoldHEN running.
- A way to send the payload (elfldr on port 9021, or GoldHEN BinLoader
on 9090).
- A Discord account + your user token (see Install).
- Internet on the PS4 that can reach Discord (see Troubleshooting if not).
## Install (5 minutes)
> ⚠️ **No PKG — beta is `.elf` only.** Grab the latest test build
@@ -30,36 +94,75 @@ playing to Discord: name, cover art, timer. No PC at runtime.</p>
1. Before launching, delete the `/data/orbisRPC` folder from your PS4 if
you have one there from an earlier build.
2. Run the payload first time — it'll create the config file, then say
there's no token yet.
3. Open `/data/orbisRPC/config.json` and add your account token in the
2. Put the `.elf` on the console at `/data/payloads/` (FTP it over, or
USB) — that's where GoldHEN looks for payloads.
3. Launch it from the payload list. First run creates the config file,
then says there's no token yet.
4. Open `/data/orbisRPC/config.json` and add your account token in the
`"token": "SET_ME"` slot.
4. After setting the token, re-run the payload. Launch a game and watch
Discord. Give feedback in `testers-chat`.
5. After setting the token, re-run the payload from `/data/payloads`.
Launch a game and watch Discord. Give feedback in `testers-chat`.
After a reboot just re-jailbreak and re-send the payload — don't use
Payload Guest for this.
After a reboot just re-jailbreak and launch the payload from
`/data/payloads` again.
**Upgrading test builds:** as usual, delete the `/data/orbisRPC` folder
before using a new test build — and you'll have to re-add your token in
the config.
**Auto-run (not recommended on test builds):** you can drop the `.elf` in
`/data/payloads` and add it to the autorun queue in GoldHEN settings, but
test builds change fast — stick to manual runs for now.
**Official release:** will ship a PKG installer that sets everything up
for you.
✅ Firmware: confirmed working on 9.00 through 13.52.
for you — including autorun and the nanoDNS exception fix.
**Upgrading test builds:** as usual, delete the `/data/orbisRPC` folder
before using a new test build — and you'll have to re-add your token in
the config.
✅ Firmware: confirmed working on 9.00 through 13.52 but older fimwares should work.
### What's in the latest test build (0.60)
### What's in the latest test build (0.70)
- Fixed firmware string parsing — older firmware numbers like 9.00 now
show properly.
- Homebrew apps resolve via pkg-zone.
- Retro games (PS1/PS2/PSP) resolve via a custom list.
- Optional presence flags, all `true` by default:
`show_firmware`, `show_idle`, `show_media`, `show_homebrew`.
- Increased ws gateway frame buffer cap to 32MB - should fix oversized gateway frame skipping issue for some users (hopefully🤞).
- fixed minor issue with frame draining.
- added notification for invalid token.
## What you'll see
| Where you are | Discord shows |
|---|---|
| In a game | `Playing <Game>` + cover + elapsed timer (+ firmware line) |
| Home screen | `PlayStation 4` + logo (`Idling on Home Menu`) |
| Settings | `PlayStation 4` + `In Settings` (game timer underneath is kept) |
| Browser | `PlayStation 4` + `Using Web Browser` (game timer underneath is kept) |
| Netflix & co. | `Watching <App>` |
| Nothing to show | Presence clears |
## Config
Only `token` (your Discord user session token) is required —
`/data/orbisRPC/config.json`. Everything else ships working. Full
reference: [`docs/CONFIG.md`](docs/CONFIG.md).
| Key | Default | Meaning |
|---|---|---|
| `token` | — | Discord user session token (**required**) |
| `presence_state` | `"On PS4"` | Activity state line for games and the home screen |
| `presence_settings_text` | `"In Settings"` | Activity name while Settings is open |
| `poll_interval_s` | `12` | Detection cadence |
| `home_art` | project logo | Idle tile artwork (URL or uploaded asset key) |
| `pkgzone_enabled` | `true` | Ask pkg-zone.com for homebrew names — sends the title id to a third party; set `false` to disable |
| `retro_enabled` | `true` | Ask the retro-games indexes for PS1/PS2/PSP names and covers |
| `show_firmware` | `true` | Show the `Firmware X.YY` line |
| `show_idle` | `true` | Post a presence on the home screen, Settings and the browser |
| `show_media` | `true` | Post media apps (Netflix, YouTube, Plex…) as `Watching` |
| `show_homebrew` | `true` | Post homebrew titles. Retro classics are **not** affected |
| `debug` | `false` | Verbose per-poll logging |
A `false` visibility flag still detects the title — it just stops telling
Discord about it, so a game underneath keeps showing. The exception is the home
screen: with `show_idle` off, a game that closes is cleared rather than left up,
since nothing is running underneath there.
Plus the self-learned `titles` map, which fills itself in as titles resolve.
## Support / Community
@@ -73,7 +176,12 @@ we'll take care of it.
⚠️ **This is a beta.** Test builds are handed out on the Discord — join the
server to get beta access.
**Game not showing on Discord?** It's your DNS blocker. Disable it, or use
## Troubleshooting
Short version here; the full symptom-first guide is
[`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
**Game cover not showing on Discord?** It's your DNS blocker. Disable it, or use
[nanoDNS](https://github.com/drakmor/nanoDNS) with an exception.
To add the exception, open the nanoDNS config at
@@ -122,62 +230,6 @@ nanoDNS — then reboot. (This is the step most people miss.)
- Hotspot/mobile connections can send oversized frames that Discord
rejects — use stable Wi-Fi/LAN if presence won't set.
**Official release:** will ship a PKG installer that sets everything up
for you — including autorun and the nanoDNS exception fix.
## What you get
| | |
|---|---|
| 🎮 **Any game, no lists** | Names resolve from your console's metadata (SFO, app.xml) plus Sony's TMDB — CUSA, PPSA, indies, zero per-game setup. |
| 🖼️ **Real cover art** | Game art served per title, PlayStation logo when idle. |
| ⏱️ **True timers** | Survive reconnects and restarts, resume across quick game switches. |
| 🧠 **Self-learning** | First-seen titles are remembered, so later boots resolve instantly. |
| 🔄 **Self-updating** | Daemon updates land from GitHub releases with automatic rollback. No reinstall treadmill. |
| 📦 **Simple install** | Payload (`.elf`) install → token → presence. |
## How it works
```
PS4 (GoldHEN) Discord
┌─────────────────────────┐ ┌──────────────────┐
│ orbisRPC daemon │ TLS │ your profile │
│ sandbox scan → game ID │ ◄──► │ Playing Game │
│ metadata/TMDB → name │ │ [cover] [timer] │
└─────────────────────────┘ └──────────────────┘
```
Details: [`docs/DAEMON.md`](docs/DAEMON.md) · [`docs/BUILDING.md`](docs/BUILDING.md) ·
[`docs/CONFIG.md`](docs/CONFIG.md) · [`docs/INSTALLER.md`](docs/INSTALLER.md) ·
[`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) ·
[`docs/PRODUCTION.md`](docs/PRODUCTION.md) · full index at [`docs/`](docs/)
## Config
Only `token` (your Discord user session token) is required —
`/data/orbisRPC/config.json`. Everything else ships working.
| Key | Default | Meaning |
|---|---|---|
| `token` | — | Discord user session token (**required**) |
| `presence_state` | `"On PS4"` | Activity state line for games and the home screen |
| `presence_settings_text` | `"In Settings"` | Activity name while Settings is open |
| `poll_interval_s` | `12` | Detection cadence |
| `home_art` | project logo | Idle tile artwork (URL or uploaded asset key) |
| `pkgzone_enabled` | `true` | Ask pkg-zone.com for homebrew names — sends the title id to a third party; set `false` to disable |
| `retro_enabled` | `true` | Ask the retro-games indexes for PS1/PS2/PSP names and covers |
| `show_firmware` | `true` | Show the `Firmware X.YY` line |
| `show_idle` | `true` | Post a presence on the home screen, Settings and the browser |
| `show_media` | `true` | Post media apps (Netflix, YouTube, Plex…) as `Watching` |
| `show_homebrew` | `true` | Post homebrew titles. Retro classics are **not** affected |
| `debug` | `false` | Verbose per-poll logging |
A `false` visibility flag still detects the title — it just stops telling
Discord about it, so a game underneath keeps showing. The exception is the home
screen: with `show_idle` off, a game that closes is cleared rather than left up,
since nothing is running underneath there.
Plus the self-learned `titles` map, which fills itself in as titles resolve.
## Building
```bash
@@ -196,11 +248,32 @@ with no log line. The released SDK (v0.9) lists 13.50 but not 13.52, so a
ZIP-built payload cannot boot on 13.52. See
[`docs/research/sdk-13.52.md`](docs/research/sdk-13.52.md).
Build/test/CI details: [`docs/BUILDING.md`](docs/BUILDING.md).
## Developing
- CI runs host tests, ASan, contract checks, the SDK payload build with a
linkage gate, and uploads the `.elf` as an artifact. Tagging `v*`
attaches it to a GitHub Release.
- Contributor notes: [`AGENTS.md`](AGENTS.md). Security notes:
[`SECURITY.md`](SECURITY.md).
- PRs need the host suite green with outputs pasted (see the PR template).
- Daemon changes need on-console proof: log lines + firmware tested.
## Safety
A user session token grants full account access — never share the config,
never commit a real token. Token-based presence is against Discord's ToS
(standard practice for headless presence tools; risk is yours).
- A user session token grants full account access — never share the config,
never commit a real token. CI scans for token-shaped strings and fails.
- Token-based presence is against Discord's ToS (standard practice for
headless presence tools; risk is yours).
## Credits
- **SirHumza** — project founder, daemon core.
- **CyberMask367** — firmware coverage to 13.52, homebrew and retro games resolving, presence flags, tester wrangling.
- Testers in `testers-chat` on DIscord — every log file that made a fix possible.
- [*PKG-ZONE*](https://pkg-zone.com) — metadata on ps4 hombrew apps used for resolving
- [*PSX DATA CENTER*](https://psxdatacenter.com) — source used to make custom json list in [`config`](config/) for resolving retro games
## License
-118
View File
@@ -1,118 +0,0 @@
orbisRPC - BUILD SETUP
=====================
Working build command (copy/paste):
cd path to project
PS4_SDK_SRC=$HOME/ps4-payload-sdk-src \
PS4_PAYLOAD_SDK=$HOME/ps4-payload-sdk \
./scripts/build_sdk.sh
Output: build-sdk/orbisrpc_sdk.elf (~2,237,856 bytes)
Both env vars matter. PS4_PAYLOAD_SDK says where the compiler is;
PS4_SDK_SRC enables the 13.52 firmware gate described below.
ONE-TIME SDK SETUP
------------------
The ps4-payload-dev SDK must be built from git, NOT from the release ZIP.
Run this once per machine:
cd path to project
./scripts/build_sdk_from_source.sh
That clones to ~/ps4-payload-sdk-src, pins commit 573b4a0, and installs to
~/ps4-payload-sdk.
WHY NOT THE RELEASE ZIP:
The SDK's crt/patch.c holds a per-firmware table and __patch_init()
returns an error for any firmware not in it. crt/crt.c propagates that
out of _start(), which calls payload_terminate() and never reaches
main(). Silent death - no log line, nothing to diagnose.
Release v0.9 (2026-05-12) lists 13.50 but not 13.52. The 13.52 offsets
landed on master in 959e4ed (2026-06-25) and have never shipped in a
release. A payload linked against the ZIP cannot boot on 13.52.
If the SDK is missing:
./scripts/build_sdk.sh -> "FAIL: .../orbis-clang not found"
Both scripts verify the SDK source supports 13.52 before building, so a
too-old SDK fails at build time with a clear message instead of producing a
payload that dies on the console.
To move to a newer SDK commit later:
PS4_SDK_REF=<commit> ./scripts/build_sdk_from_source.sh
REQUIREMENTS
------------
WSL2 (all commands below run in WSL, not PowerShell)
git
clang-18 + lld-18 (llvm-config-18 or llvm-config must be on PATH)
Ubuntu packages:
sudo apt install git clang-18 lld llvm-18
The SDK also expects an llvm shim on PATH. build_sdk.sh adds $HOME/llvmshim
itself; if your setup differs, that line in scripts/build_sdk.sh is where to
look.
Repo location matters: scripts assume you run from the repo root, because
they reference relative paths like orbisrpc/ and third_party/.
VERIFYING THE BUILD
-------------------
ls -la build-sdk/orbisrpc_sdk.elf
A good build prints no FAIL lines and ends with the elf listing. Two lines
are normal and not errors:
"note: PS4_SDK_SRC not set, skipping the 13.52 SDK firmware gate."
(only appears if you omitted PS4_SDK_SRC - always pass it)
To rebuild from scratch:
rm -rf build-sdk && ./scripts/build_sdk.sh
HOST TESTS (optional, fast - no console needed)
make -C tests all
./tests/test_utils
Known pre-existing failure, NOT a regression:
test_appdb: Assertion `rc == SQLITE_OK' failed
The system sqlite3 library on the host differs from the bundled amalgamation
(rc=14). Tests registered before test_appdb in main() do run and pass.
SENDING IT TO THE CONSOLE
-------------------------
GoldHEN BinLoader on port 9020. Arm it in the exploit host menu, then send
the payload in ONE connection. Never probe the port first - any empty connect
burns the arm and the real payload then arrives at a dead listener:
use netcat e.g nc console_ip 9090 < payload.elf
Then confirm it ran - within ~20 seconds:
curl -s ftp://<console-ip>:2121/data/orbisRPC/log.txt
Expected first line: "orbisRPC payload boot"
No log file at all means the payload never executed. Do not debug the daemon
until you see that line. See docs/injecting.md for elfldr (port 9021) and
other methods.
+1 -8
View File
@@ -1,8 +1 @@
{
"token": "SET_ME",
"application_id": "1536977374795538532",
"art_base_url": "https://raw.githubusercontent.com/SirHumza/orbisrpc-host/main/icons/",
"enabled": 1,
"poll_interval_s": 12,
"presence_state": "On PS4"
}
{"schema_version":1,"token":"SET_ME","application_id":"1536977374795538532","art_base_url":"https://raw.githubusercontent.com/SirHumza/orbisrpc-host/main/icons/","enabled":true,"pkgzone_enabled":true,"retro_enabled":true,"show_firmware":true,"show_idle":true,"show_media":true,"show_homebrew":true,"auto_update":true,"debug":false,"poll_interval_s":12,"presence_state":"On PS4","presence_details_game":"Playing on PlayStation 4","presence_details_home":"Idling on Home Menu","presence_settings_text":"In Settings","asset_idle":"","asset_playing":"","large_art":"https://retro-games.cybermask.dpdns.org/images/ps-logo-full.png","small_art":"https://retro-games.cybermask.dpdns.org/images/ps-logo-blue.png","browser_art":"https://retro-games.cybermask.dpdns.org/images/web_browser.png","home_art":"https://retro-games.cybermask.dpdns.org/images/ps-logo-full.png"}
Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

File renamed without changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+23 -36
View File
@@ -1,46 +1,33 @@
# Cover art pipeline
## Path A: Sony TMDB icon URLs (primary, fully automatic)
How a game gets its tile. Order matters — first hit wins.
Every title lookup also queries Sony's TMDB service, which returns an
official 512x512 icon URL on Sony's CDN. The daemon sends it as
`assets.large_image` (Discord accepts external image URLs there). No
uploads, no hosting, no per-game work, works on any console. If the
service lacks the title or the network fails, the next path is tried.
## Path 1: Sony TMDB live (automatic)
## Path B: icon pack + external URLs (fallback, maintainer-hosted)
Every title lookup queries Sony's TMDB service, which returns an
official icon URL on Sony's CDN. Requires the console to reach
`tmdb.np.dl.playstation.net` — without the nanoDNS exception this
path is dead (see Troubleshooting). No uploads, no hosting, no
per-game work.
## Path A: icon pack + external URLs (recommended)
## Path 2: icon pack (fallback, maintainer-hosted)
The daemon sends `assets.large_image` as
`<art_base_url><lowercase titleId>.png` (Discord accepts external image
URLs in asset fields). Host the pack once, e.g. in this repo:
The daemon sends `<art_base_url><lowercase titleId>.png`. Default is
the project pack (`orbisrpc-host`); host your own by setting
`art_base_url`. `scripts/sync_icons.sh` pulls every
`/user/appmeta/<TITLEID>/icon0.png` off the PS4 over FTP (read-only)
into `config/icons/`. Missing files degrade to no image.
```
config/icons/cusa00740.png (512x512 PNG, pulled from the console)
```
## Path 3: uploaded Discord asset keys
`scripts/sync_icons.sh` pulls every `/user/appmeta/<TITLEID>/icon0.png`
off the PS4 over FTP (read-only) into `config/icons/`. Set
`art_base_url` in config to the hosted prefix, e.g.
`https://raw.githubusercontent.com/<you>/orbisRPC/main/config/icons/`.
Every install then shows art with zero setup. Missing files degrade to
no image. Pack hosting is a maintainer decision (game art is not ours).
Set `asset_idle` / `asset_playing` to an uploaded key to prefer that
instead — keys need no network at all. Current default
`application_id` is `1536977374795538532`. Capped at 300 assets per app.
## Path B: shared Discord application (fallback)
## Hard rule (verified on hardware)
Create an application, upload one PNG per game under Art Assets named
as the lowercase title ID, set `application_id` in config. The daemon
sends the asset key instead. Capped at 300 assets per app.
Current default: the public PS4-Rich-Presence-for-Discord application
(zorua98741/bshar1865, ID 858345055966461973), which already hosts
per-title art. Borrowed backend, credited here: if it ever goes away,
art degrades to nothing and everything else keeps working. Replace the
default with your own app ID to own the whole chain.
## Runtime behavior
URL pack wins when `art_base_url` is set, uploaded keys when only
`application_id` is set, no artwork otherwise (one log notice). Missing
art never crashes anything.
Raw external URLs and dangling keys **drop the entire activity**
silently — name, timer, everything. So every URL above is resolved
through Discord's `mp:` external-assets proxy at post time, and
dangling keys are dropped, never sent. Missing art never crashes
anything; worst case is a tile with no image.
+2 -2
View File
@@ -20,7 +20,7 @@ reads like a verdict on Discord itself. The daemon reports real reachability
in its own log once started.
The installer never boots anything directly. Starting the daemon is
Payload Guest's `/data/payloads/` directory — pick `evict.elf` first
the `/data/payloads/` directory — pick `evict.elf` first
(removes old orbisrpc instance), then pick `orbisrpc.bin`.
No loopback ports, no injection, no boot proof to go wrong.
@@ -67,7 +67,7 @@ button on No, which inverts the whole wizard.
## Auto-start
Payload Guest AutoRun: enable it for `orbisrpc` once and the daemon
GoldHEN AutoRun: enable it for `orbisrpc` once and the daemon
starts on every jailbreak. The queue is menu-managed; the installer
shows where, it can't write the queue itself.
+9 -3
View File
@@ -24,7 +24,7 @@ Discord (`testers-chat`) — see the repo README for the invite.
3. Hotspot/mobile connections have caused oversized frames Discord
rejects — prefer stable Wi-Fi/LAN.
## Game not showing (presence never leaves idle/home)
## Game cover not showing (name + timer fine, no art)
Your DNS blocker is eating Sony's TMDB host. Either disable it or use
[nanoDNS](https://github.com/drakmor/nanoDNS) with an exception:
@@ -46,6 +46,9 @@ Your DNS blocker is eating Sony's TMDB host. Either disable it or use
4. Raw external URLs and dangling asset keys drop the *whole* activity
silently (name, timer, everything) — see
[research/tls-ime.md](research/tls-ime.md) for the verified rules.
5. No cover art at all (name + timer fine)? TMDB is unreachable without
the nanoDNS exception, so there's nothing to draw — fix DNS per the
nanoDNS section above and the art comes back on next resolve.
## Name shows raw ID (CUSA…)
@@ -65,8 +68,11 @@ Never share the config file — a user token is full account access.
## Payload won't start
- Send it via elfldr (port 9021) or the BinLoader server (port 9020).
Don't use Payload Guest for orbisRPC.
- The payload lives at `/data/payloads/` — put the `.elf` there (FTP/USB)
and launch it from the payload list. Never use Payload Guest for
orbisRPC — it crashes.
- No sender handy? elfldr (port 9021) or the BinLoader server (port 9020)
also work — see [`injecting.md`](injecting.md).
- Ports closed? GoldHEN's BinLoader toggle is off, or the console
rebooted to stock — re-jailbreak first.
- Reboot wipes jailbreak + daemon (RAM-only). Re-jailbreak, re-send.
+1 -1
View File
@@ -97,6 +97,6 @@ the result on screen.
- Kernel log (loader events, faults): TCP `192.168.1.136:3232`, raw stream.
- Daemon log: `/data/orbisRPC/log.txt` via FTP (`192.168.1.136:2121`,
anonymous). Absent file = payload never executed, full stop.
- Process list: `sysctl kern.proc` (see `scripts/ps4_watch.py`) — look
- Process list: `sysctl kern.proc` — look
for `Payload` and `eboot.bin` entries.
- Final proof: Discord profile shows the game + ticking timer.
@@ -1,83 +0,0 @@
#!/usr/bin/env python3
"""build_art_table.py - resolve Sony CDN artwork for a title list via TMDB.
Runs on the BUILD host (console network blocks TMDB port 80) and emits
orbisrpc/art_table.h: {titleId, name, icon_url} for every resolvable title.
Also downloads each icon into icons/ for the art_base_url pack.
Usage: python3 scripts/build_art_table.py titles.txt [--icons icons/]
Title list: one TITLEID per line (CUSAxxxxx etc.); unknown IDs are skipped.
"""
import hashlib
import hmac
import json
import os
import sys
import urllib.request
KEY = bytes([
0xF5, 0xDE, 0x66, 0xD2, 0x68, 0x0E, 0x25, 0x5B, 0x2D, 0xF7, 0x9E, 0x74,
0xF8, 0x90, 0xEB, 0xF3, 0x49, 0x26, 0x2F, 0x61, 0x8B, 0xCA, 0xE2, 0xA9,
0xAC, 0xCD, 0xEE, 0x51, 0x56, 0xCE, 0x8D, 0xF2, 0xCD, 0xF2, 0xD4, 0x8C,
0x71, 0x17, 0x3C, 0xDC, 0x25, 0x94, 0x46, 0x5B, 0x87, 0x40, 0x5D, 0x19,
0x7C, 0xF1, 0xAE, 0xD3, 0xB7, 0xE9, 0x67, 0x1E, 0xEB, 0x56, 0xCA, 0x67,
0x53, 0xC2, 0xE6, 0xB0,
])
TMDB = "http://tmdb.np.dl.playstation.net"
def c_escape(s):
return s.replace("\\", "\\\\").replace('"', '\\"')
def resolve(tid):
mac = hmac.new(KEY, (tid + "_00\x00").encode("latin1")[:12],
hashlib.sha1).hexdigest().upper()
url = f"{TMDB}/tmdb2/{tid}_00_{mac}/{tid}_00.json"
req = urllib.request.Request(url, headers={"User-Agent": "orbisRPC-arttbl"})
d = json.loads(urllib.request.urlopen(req, timeout=20).read())
name = d["names"][0]["name"]
icon = d["icons"][0]["icon"].replace("http://", "https://")
return name, icon
def main():
titles = [l.strip() for l in open(sys.argv[1]) if l.strip()]
icons = sys.argv[3] if len(sys.argv) > 3 and sys.argv[2] == "--icons" else None
if icons:
os.makedirs(icons, exist_ok=True)
rows = []
for tid in titles:
try:
name, icon = resolve(tid)
except Exception as e:
print(f"MISS {tid}: {str(e)[:60]}", flush=True)
continue
print(f"OK {tid} :: {name}", flush=True)
rows.append((tid, name, icon))
if icons:
try:
img = urllib.request.urlopen(icon, timeout=25).read()
open(os.path.join(icons, tid.lower() + ".png"), "wb").write(img)
except Exception as e:
print(f" icon fetch failed: {str(e)[:60]}", flush=True)
with open("orbisrpc/art_table.h", "w") as f:
f.write("/* art_table.h - GENERATED by scripts/build_art_table.py.\n")
f.write(" * Do not edit: Sony CDN artwork baked at build time so the\n")
f.write(" * console needs no TMDB network access for art.\n")
f.write(f" * Titles: {len(rows)}. */\n")
f.write("#ifndef ORBISRPC_ART_TABLE_H\n#define ORBISRPC_ART_TABLE_H\n")
f.write("#include <stddef.h>\n")
f.write("typedef struct { const char *id; const char *name;"
" const char *icon; } art_entry_t;\n")
f.write("static const art_entry_t ORBISRPC_ART_TABLE[] = {\n")
for tid, name, icon in rows:
f.write(f' {{ "{tid}", "{c_escape(name)}", "{icon}" }},\n')
f.write("};\n#define ORBISRPC_ART_TABLE_N "
f"(sizeof ORBISRPC_ART_TABLE / sizeof ORBISRPC_ART_TABLE[0])\n")
f.write("#endif\n")
print(f"wrote art_table.h with {len(rows)} entries")
if __name__ == "__main__":
main()
@@ -1,66 +0,0 @@
#!/usr/bin/env python3
"""gen_nametable.py - build orbisrpc/nametable.h from config/titles.json.
titles.json maps TITLEID -> display name (extracted from the PS4's
/system_data/priv/mms/app.db tbl_appbrowse_* tables, or extended by hand).
The generated table is compiled in, so name lookup needs no filesystem
access and works inside any console sandbox.
Usage: python3 scripts/gen_nametable.py
"""
import json
import os
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SRC = os.path.join(ROOT, "config", "titles.json")
DST = os.path.join(ROOT, "orbisrpc", "nametable.h")
def c_escape(s):
out = []
for ch in s:
o = ord(ch)
if ch == '"':
out.append('\\"')
elif ch == "\\":
out.append("\\\\")
elif 32 <= o < 127:
out.append(ch)
else:
# octal: unlike \x, always stops after 3 digits, so a hex
# digit following the escape can never merge into it.
for b in ch.encode("utf-8"):
out.append("\\%03o" % b)
return "".join(out)
def main():
with open(SRC, encoding="utf-8") as fh:
titles = json.load(fh)
items = sorted(titles.items())
with open(DST, "w", encoding="utf-8") as fh:
fh.write("/* nametable.h - GENERATED by scripts/gen_nametable.py. Do not edit. */\n")
fh.write("#ifndef NAMETABLE_H\n#define NAMETABLE_H\n");
fh.write("#include <stddef.h>\n#include <string.h>\n")
fh.write("typedef struct { const char *id; const char *name; } nt_entry_t;\n")
fh.write("static const nt_entry_t g_nametable[] = {\n")
for tid, name in items:
fh.write(' { "%s", "%s" },\n' % (c_escape(tid), c_escape(name)))
fh.write("};\n")
fh.write("static const size_t g_nametable_count = sizeof g_nametable / sizeof g_nametable[0];\n")
fh.write("static int nametable_lookup(const char *tid, char *out, size_t cap){\n")
fh.write(" if(!tid || !out || cap == 0) return -1;\n")
fh.write(" for(size_t i = 0; i < g_nametable_count; i++){\n")
fh.write(" if(!strcmp(tid, g_nametable[i].id)){\n")
fh.write(" strncpy(out, g_nametable[i].name, cap - 1);\n")
fh.write(" out[cap - 1] = 0;\n")
fh.write(" return out[0] ? 0 : -1;\n")
fh.write(" }\n")
fh.write(" }\n")
fh.write(" return -1;\n")
fh.write("}\n#endif\n")
print("wrote %s with %d entries" % (DST, len(items)))
if __name__ == "__main__":
main()
-37
View File
@@ -1,37 +0,0 @@
APOL00004
CHTM00777
CUSA00021
CUSA00127
CUSA00135
CUSA00411
CUSA00740
CUSA01004
CUSA01015
CUSA01623
CUSA01839
CUSA03245
CUSA04294
CUSA04602
CUSA06014
CUSA06545
CUSA06712
CUSA08992
CUSA09311
CUSA11995
CUSA13323
CUSA14909
CUSA18795
CUSA20177
CUSA20499
CUSA24899
CUSA26618
CUSA33151
FLTZ00003
GBTX00001
LAPY20006
LAPY20009
LAPY20015
MANU90003
NPXS39041
PKGI13337
CUSA09303
+8
View File
@@ -68,6 +68,12 @@ static int s_settings_posted = 0;
static int s_in_browser = -1;
static int s_browser_posted = 0;
/* Shown when the gateway closes with 4004 (token rejected). Same text at
* connect time and mid-session, so one constant covers both. Throttled
* there rather than once-per-boot because a rejected token persists and
* the daemon keeps retrying on purpose -- it never exits on 4004. */
#define TOKEN_REJECTED_MSG "Discord token rejected - make sure Token is valid"
/* "Firmware 13.52" for the Settings presence. Reads through the same helper the
* rest of the daemon uses, so an unavailable firmware string degrades to
* "unknown" instead of stalling the post. */
@@ -349,6 +355,7 @@ int daemon_run(void){
* reload picks up a fixed token on its own. */
log_msg("WARN: token rejected by gateway (close 4004). "
"Fix \"token\" in %s; retrying", CFG_PATH);
notify_throttled("token4004", TOKEN_REJECTED_MSG, 300);
int wait = reconnect_delay(&conn_fails, base_poll, &jctr);
if(sleep_stop(wait)) break;
continue;
@@ -746,6 +753,7 @@ int daemon_run(void){
* 4004 (never strand the daemon). Drop to the outer loop:
* backoff + config reload picks up a fixed token alone. */
log_msg("WARN: token rejected (close 4004). Fix %s; retrying", CFG_PATH);
notify_throttled("token4004", TOKEN_REJECTED_MSG, 300);
ws_close(&dc.ws);
pres_set(&pres, PS_NONE);
if(sleep_stop(reconnect_delay(&conn_fails, base_poll, &jctr))) break;
+9042 -8814
View File
File diff suppressed because it is too large. Load diff
+28 -2
View File
@@ -339,6 +339,10 @@ static int valid_frame_header(const unsigned char *b, uint64_t plen){
return 1;
}
/* ws_skip_plan() lives in ws_skip.c so the host tests can compile it without
* the socket/TLS half of this file. */
#include "ws_skip.c"
int ws_recv_frame(ws_t *w, char *buf, size_t cap, int *opcode_out, int *fin_out){
if(!w || !w->connected || !buf || cap==0) return -1;
for(;;){
@@ -376,7 +380,7 @@ int ws_recv_frame(ws_t *w, char *buf, size_t cap, int *opcode_out, int *fin_out)
size_t h = (hdr < avail) ? hdr : avail;
w->rpos += h; /* eat the header */
size_t payload_here = avail - h;
w->skip_left = plen - (uint64_t)payload_here;
ws_skip_plan(plen, payload_here, &w->skip_left);
w->skip_op = wire_op;
if(w->rpos >= w->rlen){ w->rpos=0; w->rlen=0; }
continue;
@@ -401,7 +405,29 @@ int ws_recv_frame(ws_t *w, char *buf, size_t cap, int *opcode_out, int *fin_out)
return (int)copy;
}
}
if(w->rlen == w->rcap){ /* full with no parseable frame: give up cleanly */
if(w->rlen == w->rcap){
/* A full buffer during a drain is NORMAL, not a fault: an
* oversized frame is by definition larger than the buffer, so
* the drain can only make progress by discarding what is
* buffered. Bailing out here wiped the buffer and returned -2
* with skip_left outstanding, which left the socket mid-frame
* and made the next parse read JSON payload as a frame header
* (observed: op=2 fin=0 plen=125 b1=0x22 -- a quote byte).
* Consume against skip_left instead; the loop below refills
* from TLS, so no headroom is actually needed. */
if(w->skip_left > 0){
size_t take = (size_t)w->skip_left;
if(take > w->rlen) take = w->rlen;
w->skip_left -= take;
w->rpos += take;
if(w->rpos >= w->rlen){ w->rpos=0; w->rlen=0; }
if(w->skip_left == 0){
if(opcode_out)*opcode_out = w->skip_op;
return -3;
}
continue;
}
/* full with no parseable frame and no drain: give up cleanly */
log_msg("ws: buffer full with no frame; dropping %zuB and reconnecting", w->rlen);
w->rpos=0; w->rlen=0; return -2;
}
+16 -1
View File
@@ -7,7 +7,16 @@
#include <stddef.h>
#define WS_RBUF_MIN 65536 /* initial raw socket buffer (READY is big) */
#define WS_RBUF_MAX (8*1024*1024) /* grow limit; bigger frames are skipped */
/* Grow limit; anything larger is drained and skipped.
*
* 32 MB, raised from 8 MB on 2026-10-06: a real account's READY measured
* 11.7 MB across four attempts (11695449 / 11694320 / 11694256 / 11706895),
* well past the old cap, so identify never completed and the gateway
* reported a connect failure. The value is not stable frame-to-frame, so
* this is sized with real headroom rather than matched to one observation.
* rbuf doubles from 64 KB, so a full READY holds ~32 MB of PS4 heap for
* the life of the session. */
#define WS_RBUF_MAX (32*1024*1024)
typedef struct {
int32_t sock; int32_t connected; int32_t fd;
@@ -28,4 +37,10 @@ int ws_send_text(ws_t *w, const char *msg, size_t len);
int ws_recv_frame(ws_t *w, char *buf, size_t cap, int *opcode_out, int *fin_out);
int ws_pong(ws_t *w); /* answer a server PING (call when recv gives opcode 9) */
int ws_close(ws_t *w);
/* How many payload bytes remain to discard after an oversized frame's header
* has been consumed. Saturates at 0 rather than wrapping when payload_here
* already covers plen. Pure, so the host tests can pin it: the drain state
* lives in the ws_t and a bad value is only visible on the console as a
* desynced frame stream. */
void ws_skip_plan(uint64_t plen, size_t payload_here, uint64_t *skip_left_out);
#endif
+23
View File
@@ -0,0 +1,23 @@
/* ws_skip.c - oversized-frame drain arithmetic, split out of ws.c.
*
* This is the only part of ws.c that can be tested on the host: it has no
* socket, no tls_ctx_t and no PS4 headers. The rest of ws_recv_frame() is
* wired to those, so a bug here used to be invisible until a real gateway
* handshake on a real console.
*
* Why it is worth isolating: drain state lives in the ws_t, so a wrong
* value here does not fail loudly. On console 2026-10-06 an 11.7 MB READY
* overflowed the buffer, the drain miscounted, and the next parse read JSON
* payload as a frame header (op=2 fin=0 plen=125 b1=0x22 -- a quote byte),
* which surfaced only as "cannot connect to discord".
*
* Included by ws.c; compiled directly by tests/test_utils.c. Both paths get
* the same code, so the tests cannot drift from the payload.
*/
#include "ws.h"
void ws_skip_plan(uint64_t plen, size_t payload_here, uint64_t *skip_left_out){
if(!skip_left_out) return;
*skip_left_out = ((uint64_t)payload_here >= plen)
? 0 : plen - (uint64_t)payload_here;
}
+1 -1
View File
@@ -15,7 +15,7 @@ to prepare a source icon.
"""
import sys, os, struct
SRC = sys.argv[1] if len(sys.argv) > 1 else 'config/icons/logo.notify.png'
SRC = sys.argv[1] if len(sys.argv) > 1 else 'config/images/icons/logo.notify.png'
OUT = sys.argv[2] if len(sys.argv) > 2 else 'orbisrpc/icon_embedded.h'
MAX_ICON = 512
+56
View File
@@ -16,6 +16,7 @@
#include "../orbisrpc/pkgzone.h"
#include "../orbisrpc/gamecache.h"
#include "../orbisrpc/fw.h"
#include "../orbisrpc/ws.h"
#include "../orbisrpc/gamecache.h"
#include "../orbisrpc/pkgzone.h"
#include "../installer/icfg.h"
@@ -38,6 +39,10 @@ int ws_recv_frame(ws_t *w, char *b, size_t c, int *o, int *f){
}
int ws_pong(ws_t *w){ (void)w; return -1; }
int ws_close(ws_t *w){ (void)w; return -1; }
/* ws.c itself is not linked on the host (it owns a tls_ctx_t), so the pure
* drain arithmetic comes in directly. Only ws_skip_plan is needed, and it is
* real code compiled from the real source, not a copy. */
#include "../orbisrpc/ws_skip.c"
#include <mbedtls/ecdsa.h>
#include <mbedtls/ecp.h>
#include <mbedtls/ctr_drbg.h>
@@ -93,6 +98,56 @@ static void test_gateway_op_spoof(void) {
jl_free(r2);
}
/* --- oversized-frame drain arithmetic ---------------------------------
* A real account's READY measured 11.7 MB (four attempts, 11695449 /
* 11694320 / 11694256 / 11706895), past the old 8 MB cap, so identify never
* completed. The drain then left the socket mid-frame and the next parse
* read JSON payload as a header (op=2 fin=0 plen=125 b1=0x22 -- a quote).
* ws_skip_plan is the pure piece, so the silent failure modes are pinned
* here instead of on the console. */
static void test_ws_skip_plan(void) {
uint64_t left = 0xdeadbeef;
/* The real READY, header just eaten, nothing of the body buffered. */
ws_skip_plan(11695449, 0, &left);
assert(left == 11695449);
/* Part of the body already buffered: drain only the remainder. */
ws_skip_plan(11695449, 65536, &left);
assert(left == 11695449 - 65536);
/* Body fully buffered already -> nothing left to discard. This is the
* saturation case; the old bare subtraction underflowed to ~1.8e19 here
* and the drain never terminated. */
ws_skip_plan(1000, 1000, &left);
assert(left == 0);
/* More buffered than the header claimed: must still saturate, never wrap. */
ws_skip_plan(1000, 5000, &left);
assert(left == 0);
ws_skip_plan(100, (size_t)-1, &left);
assert(left == 0);
/* Draining to exactly zero must land on zero, not one byte over. */
ws_skip_plan(2048, 2047, &left);
assert(left == 1);
ws_skip_plan(2048, 2048, &left);
assert(left == 0);
/* The cap the console actually hits: 11.7 MB frame, 32 MB buffer. */
ws_skip_plan(11695449, 32768, &left);
assert(left == 11695449 - 32768);
/* A frame larger than even the raised cap still drains sanely. */
ws_skip_plan((uint64_t)64 * 1024 * 1024, 0, &left);
assert(left == (uint64_t)64 * 1024 * 1024);
/* NULL out-pointer must not crash. */
ws_skip_plan(1000, 10, NULL);
/* The cap must actually be above the measured READY, or every one of
* these scenarios returns to the console as a failed connect. */
assert(WS_RBUF_MAX > 11706895);
}
static void test_json_oom_safe(void) {
jl_val_t *t = jl_parse("true", 4); assert(t != NULL); jl_free(t);
jl_val_t *f = jl_parse("false", 5); assert(f != NULL); jl_free(f);
@@ -1438,6 +1493,7 @@ int main(void) {
test_procwalk_find_and_pid();
test_json();
test_gateway_op_spoof();
test_ws_skip_plan();
test_json_oom_safe();
test_json_hostile();
test_tmdb();