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
+#include
+#include
#include
#include
#include
@@ -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) {