From 43246c3ab320723dff0629e2d3ae43fa3cc120d7 Mon Sep 17 00:00:00 2001 From: SirHumza <204067870+SirHumza@users.noreply.github.com> Date: Sun, 27 Sep 2026 15:31:08 +0200 Subject: [PATCH] god-tier: diag census, v1.1.0, arch docs, CI version gate - daemon writes diag.json at boot (fw, kinfo-table hit, msgbuf / sandbox / shellcore reachability) for the firmware matrix - focus_msgbuf_ok() reachability probe + host test - version 1.1.0 (version.h + installer Makefile) - ARCHITECTURE.md: binaries, detection cascade, gateway states, no-exit policy, on-disk state, installer, compat baseline - CI consistency job: version.h vs Makefile parity gate --- .github/workflows/ci.yml | 11 +++++ ARCHITECTURE.md | 86 ++++++++++++++++++++++++++++++++++++++++ installer/Makefile | 2 +- orbisrpc/daemon.c | 50 +++++++++++++++++++++++ orbisrpc/focus.c | 8 ++++ orbisrpc/focus.h | 3 ++ orbisrpc/version.h | 6 +-- tests/test_utils.c | 2 + 8 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 ARCHITECTURE.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd68a20..7d5799b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,3 +38,14 @@ jobs: echo "$NEEDED" | grep -q libSceNet.sprx || { echo "missing libSceNet"; exit 1; } if echo "$NEEDED" | grep -q 'libkernel\.so'; then echo "FORBIDDEN: app-world libkernel.so"; exit 1; fi echo "linkage OK" + consistency: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: version parity (version.h vs installer Makefile) + run: | + V_H=$(grep -oE '#define ORBISRPC_VERSION "[^"]+"' orbisrpc/version.h | cut -d'"' -f2) + V_MK=$(grep -oE '^VERSION[[:space:]]*:=.*' installer/Makefile | sed 's/.*:= *//') + echo "version.h=$V_H Makefile=$V_MK" + [ -n "$V_H" ] && [ "$V_H" = "$V_MK" ] || { echo "VERSION MISMATCH"; exit 1; } + echo "versions match" diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..b3db0ce --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,86 @@ +# orbisRPC Architecture + +Discord Rich Presence for jailbroken PS4. No PC at runtime: the daemon +talks to the Discord gateway directly. + +## Binaries + +| Binary | Built by | Runs as | +|---|---|---| +| `orbisrpc.elf` | `scripts/build.sh` (OpenOrbis) | BinLoader / Payload Guest payload | +| `orbisrpc_sdk.elf` | `scripts/build_sdk.sh` (ps4-payload-sdk) | PKG `assets/daemon.elf`, copied to `/data/payloads/orbisrpc.bin` | +| Installer PKG (`ORPC00001`) | `installer/Makefile` | Setup app: SDL2 UI, token entry, file staging | +| GoldHEN plugin | frozen | not shipped; payload is the supported path | + +Both payload builds share `orbisrpc/*.c`. `ORBISRPC_SDK_PAYLOAD` +selects toolchain/libc differences only — detection strategy is +identical (probe-first, see below). + +## Detection cascade (ordered, first signal wins) + +1. **msgbuf events** (`focus.c`): `AppFocusChanged [...]` in + kern.msgbuf. Event-driven, sees system screens (settings/browser), + resolves multi-app ambiguity. Unreadable buffer = skip. +2. **ShellCoreUtil dlopen** (`detect.c` `scu_init`): `sceShellCoreUtilIsAppLaunched`. + Firmware-independent (libkernel exports dlopen/dlsym). Fail = skip. +3. **sysctl eboot scan** with the verified kinfo table (`fw.c`): + exact offset only on firmwares in the table (currently 9.00); + everywhere else a bounded in-record scan. Never reads past recsz, + never assumes. +4. **Sandbox/save fallbacks**: `/mnt/sandbox/_000`, freshest + savedata / app dir / `app.pkg` atime. + +New title needs 2 consecutive polls (~2s debounce). Same title +returning within 10 min resumes its timer; cross-restart resume seeds +from `session.json` (validated: 9-char id, sane epoch). + +## Gateway + +Explicit states, logged on transition: `DOWN -> CONNECTING -> +HELLO_WAIT -> IDENTIFYING -> READY`. Fresh IDENTIFY every connect +(no RESUME). Close 4004 (bad token) is a warning + backoff + config +reload — never an exit. Reconnect: exponential backoff with +deterministic jitter, 60s cap for 10 fails, then 10-min cadence. +Presence re-posts after every (re)connect plus a 15-min reconcile. + +## No-exit policy + +The daemon never exits on transient failure: no token (waits, FTP +edit lands without reboot), network drops, corrupt session file, +detection misses. Exits only on: stop request, lock held by a peer +(stands down), superseded generation, fatal `/data` unwritable. + +Crash safety: consecutive unclean boots (never marked healthy after +`HEALTH_STABLE_SECS`) enter safe mode. Single-writer lock +(`/data/orbisRPC/lock`, O_EXCL + pid liveness). `daemon.gen` +generation protocol: installer bumps, older daemons exit cleanly. + +## On-disk state (`/data/orbisRPC/`) + +| File | What | +|---|---| +| `config.json` | token, prefs, learned titles. Atomic save, `0600` | +| `session.json` | live session for timer resume. Validated on load | +| `status.json` | heartbeat (state/title/version/ts), every 60s | +| `diag.json` | boot census: fw, kinfo-table hit, msgbuf/sandbox/shellcore reachability | +| `daemon.gen` | install generation for supersede | +| `install.log` | installer stage log (errno + sizes per step) | +| `log.txt` | daemon log, mirrored to klog | +| `playtime.log` | append-only ledger, readers total it | + +## Installer + +Thin layer: preflight assets, `copy_file` with FNV read-back proof +(no stat — sandbox lies), config preserved on reinstall, 3-try IME +token entry with FTP fallback, gen bump, done screen reports live +daemon state from `status.json`. Every step logs to `install.log`; +crash guard turns faults into log lines. UI is custom SDL2 +(`installer/ui.c`, same `ui.h` API); font is build-time rasterized +(`scripts/genfont.py` -> `installer/font.h`). + +## Compatibility + +Verified baseline: **9.00**. All other firmwares: probe-and-degrade +by design, `diag.json` proves per-box signal availability. The +firmware matrix fills in from user-submitted `diag.json` files — +see `SUPPORT.md` and `docs/`. diff --git a/installer/Makefile b/installer/Makefile index 9f11c20..daef28b 100644 --- a/installer/Makefile +++ b/installer/Makefile @@ -2,7 +2,7 @@ # Flow: daemon payload -> token -> done (gen bump supersedes old daemon). # Payload Guest reads /data/payloads/ on this console. TITLE := orbisRPC Setup -VERSION := 1.0.0 +VERSION := 1.1.0 TITLE_ID := ORPC00001 CONTENT_ID := IV0000-ORPC00001_00-ORBISRPCSETUP000 diff --git a/orbisrpc/daemon.c b/orbisrpc/daemon.c index afce0ab..9411724 100644 --- a/orbisrpc/daemon.c +++ b/orbisrpc/daemon.c @@ -17,11 +17,15 @@ #include "ws.h" #include "discord.h" #include "detect.h" +#include "focus.h" +#include "fw.h" #include "updater.h" #include "version.h" #include "jsonlite.h" #include "art.h" #include <sys/stat.h> +#include <dirent.h> +#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> @@ -181,6 +185,51 @@ static void pres_set(pres_state_t *cur, pres_state_t next){ log_msg("STATE: presence %s -> %s", pres_name(*cur), pres_name(next)); *cur = next; } +/* diag.json: one boot-time signal census for the firmware matrix. + * Users paste this single file instead of "it doesn't work": daemon + * version, firmware, and which of the four focus signals this box + * actually has. Read-only probes, never fatal. */ +extern void *dlopen(const char *filename, int flags); +extern int dlclose(void *handle); +static void diag_write(void){ + char fw[16] = ""; + int noff = 0, mrec = 0; + fw_version(fw, sizeof fw); + int kinfo_known = (fw_kinfo(&noff, &mrec) == 0); + int msgbuf = focus_msgbuf_ok(); + int sandbox = 0; + { + DIR *d = opendir("/mnt/sandbox"); + if(d){ sandbox = 1; closedir(d); } + } + int scu = 0; + { + void *h = dlopen("libSceShellCoreUtil.sprx", 0); + if(h){ scu = 1; dlclose(h); } + } + jl_val_t *r = jl_new_object(); + if(!r) return; + jl_obj_set(r, "version", jl_new_string(ORBISRPC_VERSION)); + jl_obj_set(r, "fw", jl_new_string(fw)); + jl_obj_set(r, "kinfo_table", jl_new_number((double)kinfo_known)); + jl_obj_set(r, "msgbuf", jl_new_number((double)msgbuf)); + jl_obj_set(r, "sandbox", jl_new_number((double)sandbox)); + jl_obj_set(r, "shellcore", jl_new_number((double)scu)); + jl_obj_set(r, "ts", jl_new_number((double)time(NULL))); + char *s = jl_stringify(r); + jl_free(r); + if(!s) return; + FILE *f = fopen("/data/orbisRPC/diag.json.new", "wb"); + if(f){ + int ok = (fputs(s, f) >= 0) && (fflush(f) == 0); + if(ok){ int fd = fileno(f); if(fd < 0 || fsync(fd) != 0) ok = 0; } + if(fclose(f) != 0) ok = 0; + if(ok) rename("/data/orbisRPC/diag.json.new", + "/data/orbisRPC/diag.json"); + else remove("/data/orbisRPC/diag.json.new"); + } + free(s); +} /* fixed_game_name != NULL -> post presence for that game only, no detection. * NULL -> poll the foreground app like the payload daemon does. * Returns 0 normal stop, 1 config error, 2 auth-fatal (bad token). */ @@ -204,6 +253,7 @@ int daemon_run(const char *fixed_game_name){ } log_init(LOG_PATH); log_msg("orbisRPC daemon start — build %s %s", __DATE__, __TIME__); + diag_write(); /* Single writer: a second launch (or a stale pileup from repeated * injections) stands down instead of fighting over the gateway. */ { diff --git a/orbisrpc/focus.c b/orbisrpc/focus.c index 899c104..d151d01 100644 --- a/orbisrpc/focus.c +++ b/orbisrpc/focus.c @@ -133,3 +133,11 @@ int detect_system_screen(char *out_tid, size_t cap){ } return FOCUS_UNKNOWN; } + +int focus_msgbuf_ok(void){ + for(int i = 0; kPaths[i]; i++){ + int fd = open(kPaths[i], O_RDONLY); + if(fd >= 0){ close(fd); return 1; } + } + return 0; +} diff --git a/orbisrpc/focus.h b/orbisrpc/focus.h index c88c5fe..541123d 100644 --- a/orbisrpc/focus.h +++ b/orbisrpc/focus.h @@ -22,4 +22,7 @@ int focus_classify(const char *tid); * Returns FOCUS_* class, writes tid when parsed. Never crashes, never * blocks: unreadable buffer just means FOCUS_UNKNOWN. */ int detect_system_screen(char *out_tid, size_t cap); +/* Reachability probe for the census: 1 if any candidate msgbuf path + * opens, 0 otherwise. No parsing, no logging, no sticky state. */ +int focus_msgbuf_ok(void); #endif diff --git a/orbisrpc/version.h b/orbisrpc/version.h index 9055d48..fb6df6a 100644 --- a/orbisrpc/version.h +++ b/orbisrpc/version.h @@ -1,8 +1,8 @@ /* version.h - single source of truth for the release version. - * Bump on every GitHub release; the on-console updater compares this - * against the latest release tag. */ + * Bump on every GitHub release; status.json/diag.json report this + * so support can tell which build a log came from. */ #ifndef ORBISRPC_VERSION_H #define ORBISRPC_VERSION_H -#define ORBISRPC_VERSION "1.0.0" +#define ORBISRPC_VERSION "1.1.0" #define ORBISRPC_REPO "SirHumza/orbisRPC" #endif diff --git a/tests/test_utils.c b/tests/test_utils.c index b88b772..d0df208 100644 --- a/tests/test_utils.c +++ b/tests/test_utils.c @@ -673,6 +673,8 @@ static void test_focus(void) { assert(focus_classify("NPXS20001") == FOCUS_SYSTEM); assert(focus_classify("XXXX00000") == FOCUS_UNKNOWN); assert(focus_classify(NULL) == FOCUS_UNKNOWN); + /* no msgbuf device on the host: reachability must report 0, no crash */ + assert(focus_msgbuf_ok() == 0); } static void test_fw(void) {