mirror of
https://github.com/SirHumza/orbisRPC.git
synced 2026-10-09 19:01:05 +02:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb59ff3ac7 | ||
|
|
0ec7689609 | ||
|
|
0727298892 | ||
|
|
d51edf1a31 | ||
|
|
62b564cabd | ||
|
|
c5110ff54b | ||
|
|
050d18456c | ||
|
|
8eb5bbc97a | ||
|
|
992d8015e9 | ||
|
|
50d12cb455 | ||
|
|
a1a8d31e56 | ||
|
|
cdf793614a | ||
|
|
6080f3f348 | ||
|
|
3f530c2be2 | ||
|
|
3fca823a59 | ||
|
|
9afe569216 | ||
|
|
2b60e1fda2 | ||
|
|
f5472c9b78 | ||
|
|
16a4865927 | ||
|
|
b5f6f9b1ff |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+28
-2
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user