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
This commit is contained in:
SirHumza committed 2026-09-27 15:31:08 +02:00
1 parent e26d25c47a
commit 43246c3ab3
8 files changed
+164 -4

No files matched your search

+11
View File
@@ -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"
+86
View File
@@ -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/<TITLE>_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/`.
+1 -1
View File
@@ -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
+50
View File
@@ -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. */
{
+8
View File
@@ -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;
}
+3
View File
@@ -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
+3 -3
View File
@@ -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
+2
View File
@@ -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) {