- tmdb_crypto: self-contained SHA1/HMAC, URL builder, response parse,
all verified against hashlib and Sony's live service in unit tests
- tmdb: plain-HTTP fetch with deadline plus 8-entry cache
- detect: TMDB in both resolve paths; artwork URL plumbed through
detect_last_art into presence assets (official Sony CDN, no uploads)
- remove appdb byte-scan: boundary ambiguity returned wrong names,
worse than raw IDs; also removes the multi-MB game-process read
- discord/daemon: full-URL artwork wins, pack URL next, asset key last
- discord: large_image as <art_base_url><lower titleId>.png when
configured (Discord accepts external URLs in asset fields);
uploaded-asset keys remain as fallback via application_id
- cfg: art_base_url field with load/save support
- scripts/sync_icons.sh: FTP pulls all icon0.png (read-only)
- deploy/ARTWORK.md rewritten around URL-pack-first design
The public Discord gateway rejects OAuth2 access tokens (close 4004),
so v1 could never set presence: switch config to a raw user token and
delete the oauth/http module.
- handle close frames; exit fatal on 4004 instead of looping forever
- fix reversed IPv4 packing in sceNetConnect (wrong host)
- fix SSL_read treating WANT_READ/no-data as disconnect
- keep frame bytes glued to the 101 handshake response (HELLO)
- grow recv buffer for large READY payloads; drain-and-skip >2MB
- mask all client frames incl. control; overflow-safe length checks
- drop RESUME (was dead: connect wiped session_id); fresh IDENTIFY
- clear presence stays online, add elapsed timestamps
- tick gateway every second so heartbeats never land late
- connect backoff 5s..300s; live config reload each cycle
The daemon loaded config once at startup and held it in memory, so an
operator editing auth_code/refresh_token on the console (FTP) while it
ran was ignored. Re-read the disk config each ensure_token() so edits
take effect live without a plugin reload.
/data/GoldHEN/payloads/ is not an auto-load folder (PPPwn only loads
goldhen.bin; folder auto-load is unimplemented GoldHEN feature #296), so the
ELF payload never ran regardless of reboots. Replace that route with the
GoldHEN plugin loader, which auto-starts into the game process at every boot
via plugins.ini [default] with no PC involvement.
- daemon.c/h: extract the payload main() loop into a shared daemon_run()
usable from both the ELF entry (main.c thin wrapper) and the plugin
- detect.c/h: add detect_name_for_title() for the known-titleid plugin mode
- plugin/plugin.c: plugin_load() reads procInfo.titleid via sys_sdk_proc_info,
skips non-game (NPXS/system) titles, starts daemon thread; plugin_unload()
stops it cleanly
- plugin/Makefile: builds orbisrpc_plugin.prx against the GoldHEN SDK
(libGoldHEN_Hook.a + crtprx.o)
- deploy/SETUP.md: rewrite for the plugin route; add plugins.ini [default]
(plugin/plugins.ini.ps4)
- scripts/build.sh: include daemon.c
GoldHEN's /data/GoldHEN/payloads/ auto-loader takes a raw ELF named .bin.
SETUP.md/README told users to deploy the .fself (or the confusingly named
eboot .bin), which would not load. Deploy orbisrpc.elf as orbisrpc.bin;
rename the fself eboot output to orbisrpc-eboot.bin to avoid the trap.
Real desktop clients send the gateway capabilities bitfield and an
initial presence inside IDENTIFY, and native clients do not send an
Origin header on the WebSocket upgrade. Match all three for a closer
desktop-client profile.
- identify properties now present a Windows Discord desktop fingerprint
(os windows, browser 'Discord Client', Electron UA) rather than
advertising os:'PS4', browser:'orbisRPC'
- HTTP user-agent (API calls + WS upgrade) now a desktop-client UA
instead of 'orbisRPC/1.0'
ws: SSL_connect on the non-blocking socket now polls WANT_READ/WANT_WRITE
with a deadline (previously it would fail the very first handshake);
handshake response read in chunks so the 5s deadline can't spuriously
trip; ws_send_text errors on oversized frames instead of silently
truncating; added ws_send_pong for RFC6455 server pings.
discord: heartbeat ack tracking -> reconnect on 2x-interval silence;
handle op 7 (reconnect) and op 9 (invalid session, clears session_id);
parse the sequence from the correct "s" field (was "seq", never
matched, heartbeat always sent d:0); READY captures session_id for
RESUME; reconnect now RESUMEs (op 6) when a session exists and falls
back to IDENTIFY on invalid-session; party size now emitted in the
activity; dropped the unused 'intent' arg; zeroed gateway-fetch buffer.
main: use config presence_state (was hardcoded); reset last_name after
reconnect so the current game is re-pushed immediately.
cfg: clamp poll_interval_s to [5,300]; atomic config save (tmp+rename).
log: seconds in timestamps; rotate log at 512 KB.
jsonlite: jl_obj_get/jl_arr_at return non-const (fixes const-drop
warnings); stringify emits proper "null" for missing pair values.
detect: trim whitespace around app.xml title; drop per-poll log spam.
build.sh defaulted LLD to /usr/local/opt/lld@21/bin/ld.lld which
does not exist (lld 21 is built from source at /Users/mac/lldbuild/
build/bin/ld.lld). Bare ./scripts/build.sh now works with no env vars.
README size claims corrected to 182/188 KiB.
The scaffold Makefile globbed PS4RP/*.c (dir renamed to orbisrpc/),
referenced ps4rp.elf, and needed pkg assets that don't exist — 'make'
would fail. scripts/build.sh is the canonical, verified build path.
1. jsonlite.c: jl_stringify started with out=NULL but emit's growth
check (1 > cap) never triggered realloc, so the first '{' wrote to
NULL -> guaranteed crash on first IDENTIFY/presence build. Pre-allocate.
Also grow the buffer in the no-child fallback path.
2. discord.c: jl_obj_set(dd,"status","online") and "invisible"
passed a const char* where jl_val_t* is expected. Compiles (both are
pointers) but pair->child points at rodata -> jl_free reads garbage
struct fields, double-free/global-buffer-overflow caught by ASan.
Verified with host-side ASan tests: presence payload roundtrips clean,
1000 READY parse/free cycles leak-free, b64 known-answer vectors pass.
The old ws_recv_frame did a blocking SSL_read inside discord_tick, so
when the gateway sent nothing the whole poll loop stalled: detection
froze and heartbeats stopped, and Discord drops the connection after
~90s of silence. Now:
- socket set non-blocking (SO_NBIO=0x2000, SOL_SOCKET=0xffff)
- raw SSL_read bytes buffered in ws_t.rbuf
- try_parse_frame returns a complete unmasked server frame when
available, 0 when partial, -1 on error
- discord_tick treats 0 as 'no data yet' (not a disconnect)
Handshake response read still blocks briefly (server replies
immediately) with a busy-wait for the non-blocking fd.
/api/gateway/bot requires a bot Authorization header; we authenticate
as a user OAuth2 token, so it always 401s. /api/gateway is public.
Remove a no-op ternary that selected the same resource either way.
libSceLibreSSL exports SSL_ctrl but not SSL_set_tls_host directly.
SNI is required for Discord's TLS termination to route the gateway
and token endpoints correctly; without it the handshake can be
rejected. Implemented via SSL_ctrl(s, SSL_CTRL_SET_TLSEXT_HOSTNAME=55,
TLSEXT_NAMETYPE_host_name=0, host) — stable across OpenSSL/LibreSSL.
- Resolve ShellCoreUtil at runtime via dlopen/dlsym (no hard import;
GoldHEN payload loader can't always statically bind it)
- IsAppLaunched returns 0 on the home screen -> presence now clears
correctly when the user exits a game
- Fallback to sceUserServiceGetForegroundUser if ShellCoreUtil
unavailable in the payload context