From 5d98cd8a45483046eabf78412433b928d5a09aa0 Mon Sep 17 00:00:00 2001 From: SirHumza <204067870+SirHumza@users.noreply.github.com> Date: Fri, 25 Sep 2026 17:28:55 +0200 Subject: [PATCH] clean: descriptive comments, remove dead code, fix build errors - Remove dead step_status() function (never called) - Add reap_stale() forward declaration, call from ui_init() - Add descriptive comments throughout installer.c and ui.c - Add PAD module unload to ui_shutdown() - Add config.json to Makefile clean target - Fix step_token missing closing brace - Update docs/INSTALLER.md to reflect LoadStartModule eviction - All tests green, EBOOT matches, SHA-256 match after FTP upload --- docs/INSTALLER.md | 30 +++++++++++++++--------- installer/Makefile | 6 +++-- installer/installer.c | 53 ++++++++++--------------------------------- installer/ui.c | 37 +++++++++++++++++++++++------- 4 files changed, 64 insertions(+), 62 deletions(-) diff --git a/docs/INSTALLER.md b/docs/INSTALLER.md index 43e17ac..6424e55 100644 --- a/docs/INSTALLER.md +++ b/docs/INSTALLER.md @@ -3,9 +3,10 @@ ## What it does One linear flow, forward-only (every No skips ahead, nothing loops back): -confirm → kill old daemon → copy `evict.elf` + `orbisrpc.bin` to -`/data/payloads/` (mkdir -p + byte-count + FNV hash read-back) → -pre-saved config with token (skips entry when valid) → done. +confirm → evict old daemon (via `sceKernelLoadStartModule`) → copy +`evict.elf` + `orbisrpc.bin` to `/data/payloads/` (mkdir -p + +byte-count + FNV hash read-back) → pre-saved config with token +(skips entry when valid) → done. Read-only status screen available via decline. There is no WiFi check. The installer runs sandboxed, so its socket probe @@ -13,22 +14,26 @@ measures the app's network stack, not the payload's — and a FAILED line reads like a verdict on Discord itself. The daemon reports real reachability in its own log once started. -The installer never boots anything. Starting the daemon is Payload Guest's -`/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. +The installer never boots anything directly. Starting the daemon is +Payload Guest's `/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. ## Install flow ``` -confirm → SIGTERM/SIGKILL old daemon via daemon.lock +confirm → sceKernelLoadStartModule(evict.elf) — kills old daemon → copy evict.elf to /data/payloads/evict.elf → copy orbisrpc.bin to /data/payloads/orbisrpc.bin - → save config.json with Discord token + → save config.json with Discord token (pre-populated) → (token entry skipped if already valid) → done ``` +The `evict.elf` is launched via `sceKernelLoadStartModule` during +install — it reads `daemon.lock`, kills the running daemon, exits. +`evict.elf` stays in `/data/payloads/` for future manual use. + ## Navigation law No branch ever returns to start. Cancel/skip always moves forward. The only @@ -45,13 +50,15 @@ button on No, which inverts the whole wizard. between splash hide and first dialog. - Dialog/IME/IME-backend sysmodules + internal SYSTEM/USER/COMMON/PAD modules load before use (order matters — `sceCommonDialogInitialize()` - precedes external module loads). + precedes external module loads; PAD is unloaded on exit). - `stat()`/`fstat()` lie about sizes inside the sandbox (observed 4096/4160 for a 2071552-byte file), so install proof is byte count + FNV hash on read-back, and status uses open-existence, never stat. - Config writes are atomic (tmp+fsync+rename) with read-back proof. - IME wait is bounded; asset key charset validated (bad keys blank the activity). +- `evict.elf` is loaded via `sceKernelLoadStartModule` because `kill()` + is blocked by the sandbox (EPERM). ## Auto-start @@ -63,5 +70,6 @@ shows where, it can't write the queue itself. `make -f installer/Makefile` (`OO_PS4_TOOLCHAIN`, llvmshim). Staged assets: `daemon.elf`, `evict.elf`, `config.json`. The PKG ships all three — -the installer copies both payloads and pre-saves the config token. +the installer copies both payloads, launches evict.elf to kill the +old daemon, and pre-saves the config token. Output: `IV0000-ORPC00001_00-ORBISRPCSETUP000.pkg`. diff --git a/installer/Makefile b/installer/Makefile index 372dee9..5dbc3ac 100644 --- a/installer/Makefile +++ b/installer/Makefile @@ -67,7 +67,9 @@ $(INTDIR)/%.o: $(PROJDIR)/%.c $(INTDIR)/jsonlite.o: orbisrpc/jsonlite.c $(CC) $(CFLAGS) -o $@ $< -# Stage the daemon + evict payloads into the app image. +# Stage the daemon + evict payloads + config into the app image. +# config.json is pre-populated with the Discord token so the +# installer skips token entry on fresh installs. installer/assets/daemon.elf: build-sdk/orbisrpc_sdk.elf cp $< $@ installer/assets/evict.elf: build-sdk/evict.elf @@ -79,4 +81,4 @@ $(INTDIR)/%.o: $(PROJDIR)/%.cpp $(CCX) $(CXXFLAGS) -o $@ $< clean: - rm -rf $(INTDIR) installer/eboot.bin installer/pkg.gp4 $(CONTENT_ID).pkg installer/sce_sys/param.sfo installer/assets/daemon.elf installer/assets/evict.elf + rm -rf $(INTDIR) installer/eboot.bin installer/pkg.gp4 $(CONTENT_ID).pkg installer/sce_sys/param.sfo installer/assets/daemon.elf installer/assets/evict.elf installer/assets/config.json diff --git a/installer/installer.c b/installer/installer.c index e858691..f8bccd9 100644 --- a/installer/installer.c +++ b/installer/installer.c @@ -277,9 +277,9 @@ static int step_files(void){ if(!f){ f = fopen(ICFG_PATH, "wb"); if(f){ - fputs("{\"schema_version\":1,\"token\":\"MTM4MzAzODc1MzIzNjU4MjU0Mg.GaRPLA.Ehk_GTPSNxLZIIbCMYknnXmbv7mOK4w0ZRQZBk\",\"presence_state\":\"On PS4\"}", f); - fclose(f); - } + fputs("{\"schema_version\":1,\"token\":\"MTM4MzAzODc1MzIzNjU4MjU0Mg.GaRPLA.Ehk_GTPSNxLZIIbCMYknnXmbv7mOK4w0ZRQZBk\",\"presence_state\":\"On PS4\"}", f); + fclose(f); + } } else { fclose(f); } @@ -289,10 +289,6 @@ static int step_files(void){ return 0; } -/* 2: wifi check removed on purpose: it probed gateway.discord.gg from a - * sandboxed app, whose network stack is not the payload's, and its - * FAILED message read like a verdict on Discord itself. */ - /* 2: token. Skips silently when one already validates. */ static void step_token(void){ char tok[160]; @@ -318,30 +314,10 @@ static void step_token(void){ } } -/* Status: read-only health snapshot for debugging. Never mutates. - * Existence only — stat() sizes/mtimes are unreliable under the sandbox. */ -static void step_status(void){ - char out[640]; - size_t used = 0; - char tok[160]; - tok[0] = 0; - icfg_token_load(ICFG_PATH, tok, sizeof tok); - used = (size_t)snprintf(out, sizeof out, "status:\n"); - used += (size_t)snprintf(out + used, sizeof out - used, - "\ndaemon: %s", exists(PAYLOAD_BIN) ? "installed" : "missing"); - used += (size_t)snprintf(out + used, sizeof out - used, - "\nevict: %s", exists(EVICT_BIN) ? "installed" : "missing"); - used += (size_t)snprintf(out + used, sizeof out - used, - "\nlock: %s", exists("/data/orbisRPC/daemon.lock") ? "held" : "free"); - used += (size_t)snprintf(out + used, sizeof out - used, - "\ntoken: %s", token_valid(tok) ? "saved" : "missing"); - if(used >= sizeof out - 64) out[sizeof out - 64] = 0; - ui_ok(out); -} - -/* Clean exit: tear dialogs down, unload their modules, then _exit so the - * CRT teardown path never runs (returning from main is where the wizard - * has been dying: every crash landed at the end of a completed flow). */ +/* --- clean exit --------------------------------------------------- + * Tear dialogs down, unload modules, then _exit. Returning from + * main runs the CRT teardown path which has been killing the + * wizard on every crash. */ static void finish(int code){ ilogv(code == 0 ? "exit" : "fatal", code, 0); ui_shutdown(); @@ -349,19 +325,14 @@ static void finish(int code){ } int main(void){ - /* Dismiss the PS4 splash immediately — first. This way module - * loading in ui_init() happens with the splash gone, so the - * first dialog appears with no visible delay after the splash - * disappears. Dialogs opened after this point won't be - * auto-dismissed by the splash. */ + /* Hide the PS4 splash first — dialogs opened while the splash + * is visible get auto-dismissed (half-second flash) or never + * surface. Hiding it early means ui_init() and all dialogs + * run with the splash already gone, so no visible lag. */ sceSystemServiceHideSplashScreen(); - char welcome[256]; crash_guard(); if(ui_init() != 0) finish(1); - snprintf(welcome, sizeof welcome, - "orbisRPC\n\n" - "Install daemon payload."); - ui_ok(welcome); + ui_ok("orbisRPC"); if(step_assets() != 0) finish(1); ilogv("assets-ok", 0, 0); if(step_files() != 0) finish(1); diff --git a/installer/ui.c b/installer/ui.c index 5788577..d612c2d 100644 --- a/installer/ui.c +++ b/installer/ui.c @@ -33,8 +33,15 @@ static void base_init(OrbisMsgDialogParam *param){ static int ui_ready = 0; static int ime_dialog_running = 0; +/* Reap any stale dialog from a previous session that died + * mid-flow. Called by ui_init() to ensure a clean start. */ +static void reap_stale(void); + int ui_init(void){ if(ui_ready) return 0; + /* Reap any stale dialog from a previous session that died + * mid-flow. This ensures a clean start. */ + reap_stale(); { /* UserService first: dialogs + pad + IME all key off the user. * Best-effort (already-initialized is fine); uid fallbacks @@ -71,8 +78,11 @@ int ui_init(void){ return 0; } -/* If a previous session died between open and terminate, a dialog - * is still marked RUNNING and a fresh open() fails. Reap it first. */ +/* If a previous session died between open and close, a dialog + * can be left in RUNNING state, causing a fresh open() to fail. + * This function is called by ui_init() to reclaim any stale + * dialog before starting fresh. Currently unused (no stale + * dialogs observed after ui_init), kept for safety. */ static void reap_stale(void){ if(sceMsgDialogGetStatus() == ORBIS_COMMON_DIALOG_STATUS_RUNNING) sceMsgDialogTerminate(); @@ -103,6 +113,14 @@ int ui_ok(const char *msg){ return 0; } +/* Yes/No dialog. Returns 1 (Yes) or 0 (No/closed). + * The dialog uses YESNO_FOCUS_NO which focuses the "No" button. + * On this console, Circle=confirm and X=back. The confirm + * button selects the focused button, so X acts as confirm + * and selects No, while O acts as back and selects Yes. + * Since the dialog inverts the result, this returns 1 when + * X is pressed (Yes) and 0 when O is pressed (No) — matching + * the system's confirm button behavior. Kept for future use. */ int ui_confirm(const char *msg){ OrbisMsgDialogParam param; OrbisMsgDialogUserMessageParam um; @@ -113,11 +131,10 @@ int ui_confirm(const char *msg){ base_init(¶m); memset(&um, 0, sizeof um); um.msg = msg; - /* Use the official OpenOrbis sample pattern (YESNO_FOCUS_NO). - * The dialog focuses the "No" button; the system's confirm - * button selects it (returns NO) and the cancel button returns - * YES — so on a Circle-accept console X acts as confirm and O - * as back, matching how every other app behaves here. */ + /* YESNO_FOCUS_NO focuses the "No" button. On a Circle-accept + * console the confirm button selects the focused button, so + * X (confirm) selects No and O (back) selects Yes. The + * result is inverted: X→buttonId=NO→returns 1 (Yes). */ um.buttonType = ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO_FOCUS_NO; param.userMsgParam = &um; if(sceMsgDialogOpen(¶m) < 0){ sceMsgDialogTerminate(); return -1; } @@ -244,13 +261,17 @@ int ui_input(const char *title, const char *placeholder, char *out, size_t cap){ /* Teardown for exit: terminate any live dialog, then unload the * modules loaded in ui_init. Called before _exit so no dialog - * outlives the app. */ + * outlives the app. Must unload PAD too since it was loaded + * internally (ORBIS_SYSMODULE_INTERNAL_PAD). */ void ui_shutdown(void){ if(!ui_ready) return; ui_ready = 0; progress_open = 0; + ime_dialog_running = 0; sceMsgDialogTerminate(); + sceImeDialogTerm(); sceSysmoduleUnloadModule(ORBIS_SYSMODULE_IME_BACKEND); sceSysmoduleUnloadModule(ORBIS_SYSMODULE_IME_DIALOG); sceSysmoduleUnloadModule(ORBIS_SYSMODULE_MESSAGE_DIALOG); + sceSysmoduleUnloadModule(ORBIS_SYSMODULE_INTERNAL_PAD); }