Files
SirHumza--orbisRPC/installer/installer.c
T

262 lines
9.2 KiB
C

/* installer.c - orbisRPC Setup, stripped to the bone:
* install files -> wifi check -> token -> inject -> done.
* One confirm up front; everything after flows forward. */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <time.h>
#include <orbis/libkernel.h>
#include <orbis/SystemService.h>
#include "ui.h"
#include "send.h"
#include "icfg.h"
#include "nettest.h"
#ifndef SETUP_VERSION
#define SETUP_VERSION "1.0.0"
#endif
#define DAEMON_ELF "/app0/assets/daemon.elf"
#define EVICT_ELF "/app0/assets/evict.elf"
#define EVICT_RESULT "/data/orbisRPC/evict.txt"
#define INST_DIR "/data/orbisRPC"
#define INST_BIN "/data/orbisRPC/orbisrpc.elf"
static long file_mtime(const char *p){
struct stat st;
return (stat(p, &st) == 0) ? (long)st.st_mtime : 0;
}
static int copy_file(const char *src, const char *dst){
FILE *in, *out;
static unsigned char buf[65536];
size_t n;
long expect = -1, got;
struct stat st;
in = fopen(src, "rb");
if(!in) return -1;
if(fstat(fileno(in), &st) == 0) expect = (long)st.st_size;
out = fopen(dst, "wb");
if(!out){ fclose(in); return -1; }
while((n = fread(buf, 1, sizeof buf, in)) > 0){
if(fwrite(buf, 1, n, out) != n){ fclose(in); fclose(out); return -1; }
}
fclose(in);
if(fclose(out) != 0) return -1;
/* Read-back proof: a short write must never pass as installed. */
if(expect > 0 && stat(dst, &st) == 0) got = (long)st.st_size;
else got = -1;
return (expect > 0 && got == expect) ? 0 : -1;
}
/* mkdir -p (parents as needed). 0 ok or already there. */
static int mkdirs(const char *path){
char tmp[256];
size_t i, n;
if(!path || !path[0]) return -1;
n = strlen(path);
if(n >= sizeof tmp) return -1;
memcpy(tmp, path, n + 1);
for(i = 1; i < n; i++){
if(tmp[i] == '/'){
tmp[i] = 0;
mkdir(tmp, 0777);
tmp[i] = '/';
}
}
if(mkdir(path, 0777) != 0 && errno != EEXIST) return -1;
return 0;
}
/* Every payload directory any GoldHEN-era tool scans, written + reported
* individually. App sandboxing may deny some; each result is shown so the
* real one is visible instead of guessed. */
static const char *payload_dirs[] = {
"/data/GoldHEN/payloads",
"/data/GoldHEN/bin/elf",
"/data/payloads",
"/data/bin/elf",
"/user/data/payloads",
};
static int step_files(void){
char line[320];
char report[768];
size_t used;
unsigned i;
int bins = 0;
mkdir(INST_DIR, 0777);
if(copy_file(DAEMON_ELF, INST_BIN) != 0){
ui_ok("Install failed: could not write the daemon.\n\nStopping here.");
return -1;
}
used = (size_t)snprintf(report, sizeof report, "Daemon installed to:\n%s", INST_BIN);
for(i = 0; i < sizeof payload_dirs/sizeof payload_dirs[0]; i++){
int ok = -1;
if(mkdirs(payload_dirs[i]) == 0){
snprintf(line, sizeof line, "%s/orbisrpc.bin", payload_dirs[i]);
ok = copy_file(DAEMON_ELF, line);
}
if(ok == 0) bins++;
used += (size_t)snprintf(report + used, sizeof report - used,
"\n%s : %s", payload_dirs[i], ok == 0 ? "OK" : "denied");
if(used >= sizeof report - 64) break;
}
snprintf(report + used, sizeof report - used,
"\n\nWhichever loader lists payloads, one of these is it (%d placed).", bins);
ui_ok(report);
/* Config template when missing; never clobbers learned titles. */
{
char tok[160];
tok[0] = 0;
icfg_token_load(ICFG_PATH, tok, sizeof tok);
if(!token_valid(tok)){
FILE *f = fopen(ICFG_PATH, "rb");
if(!f){
f = fopen(ICFG_PATH, "wb");
if(f){
fputs("{\"schema_version\":1,\"token\":\"SET_ME\",\"presence_state\":\"On PS4\"}", f);
fclose(f);
}
} else {
fclose(f);
}
}
}
return 0;
}
/* 2: wifi. Always continues forward; result is information. */
static void step_wifi(void){
int ok;
if(ui_progress_open("Checking connection...") != 0) return;
ok = net_probe("gateway.discord.gg", 443, 6);
ui_progress_close();
ui_ok(ok ? "WiFi check: OK.\n\nDiscord is reachable from this console."
: "WiFi check: FAILED.\n\nDiscord is unreachable. Presence will not work until the network does.\nContinuing anyway.");
}
/* 3: token. Skips silently when one already validates. */
static void step_token(void){
char tok[160];
int tries, r;
tok[0] = 0;
icfg_token_load(ICFG_PATH, tok, sizeof tok);
if(token_valid(tok)){
ui_ok("A valid token is already saved.\n\nSkipping.");
return;
}
for(tries = 0; tries < 3; tries++){
r = ui_input("Discord token", "paste token, Done to save", tok, sizeof tok);
if(r < 0){ ui_ok("Text input failed. Skipping."); return; }
if(r == 0) return;
if(token_valid(tok)){
r = icfg_token_save(ICFG_PATH, tok);
ui_ok(r == 0 ? "Token saved." : "Could not write config.");
return;
}
ui_ok("That doesn't look like a token.\nCheck it and try again, or cancel to skip.");
}
}
/* 4: inject (evict rotation so reinstalls take over). */
static void step_inject(void){
int port = 0;
char msg[160];
if(ui_progress_open("Starting orbisRPC...") != 0) return;
remove(EVICT_RESULT);
if(send_file_loopback(EVICT_ELF, &port, NULL) == 0){
int waited = 0;
while(waited < 40){
sleep(2);
waited += 2;
if(file_mtime(EVICT_RESULT) != 0) break;
}
}
ui_progress_msg("Starting orbisRPC...");
ui_progress_set(0);
if(send_file_loopback(DAEMON_ELF, &port, ui_progress_set) == 0){
/* Prove it booted: the daemon writes its log within seconds.
* No growth after ~25 s = sent but not running (say so). */
long lsz = file_mtime("/data/orbisRPC/log.txt");
int waited = 0;
int alive = 0;
ui_progress_msg("Waiting for first heartbeat...");
while(waited < 25){
sleep(2);
waited += 2;
if(file_mtime("/data/orbisRPC/log.txt") != lsz){ alive = 1; break; }
}
ui_progress_close();
if(alive)
snprintf(msg, sizeof msg, "orbisRPC is running (loader port %d).\n\nLaunch a game and watch Discord.", port);
else
snprintf(msg, sizeof msg, "Sent via port %d but no heartbeat yet.\n\nGive it a minute; if Discord stays dark, relaunch this app.", port);
ui_ok(msg);
} else {
ui_progress_close();
ui_ok("Start failed on every loader port.\n\nIs GoldHEN's BinLoader on? The files are installed, so just relaunch this app later.");
}
}
/* Status: read-only health snapshot for debugging. Never mutates. */
static void step_status(void){
char out[640];
size_t used = 0;
char tok[160];
struct stat st;
tok[0] = 0;
icfg_token_load(ICFG_PATH, tok, sizeof tok);
used = (size_t)snprintf(out, sizeof out, "orbisRPC status:\n");
used += (size_t)snprintf(out + used, sizeof out - used,
"\ndaemon files: %s", stat(INST_BIN, &st) == 0 ? "installed" : "missing");
used += (size_t)snprintf(out + used, sizeof out - used,
"\nlock: %s", stat("/data/orbisRPC/daemon.lock", &st) == 0 ? "held (may be running)" : "free");
if(stat("/data/orbisRPC/log.txt", &st) == 0){
long age = (long)time(NULL) - (long)st.st_mtime;
used += (size_t)snprintf(out + used, sizeof out - used,
"\nlog: present, last write %lds ago", age < 0 ? 0 : age);
} else {
used += (size_t)snprintf(out + used, sizeof out - used, "\nlog: none yet");
}
used += (size_t)snprintf(out + used, sizeof out - used,
"\ntoken: %s", token_valid(tok) ? "saved" : "missing/invalid");
used += (size_t)snprintf(out + used, sizeof out - used,
"\nlearned titles: %d", icfg_titles_count(ICFG_PATH));
if(used >= sizeof out - 64) out[sizeof out - 64] = 0;
ui_ok(out);
}
int main(void){
int q;
char welcome[256];
if(ui_init() != 0) return 1;
/* The system holds a splash screen over fresh apps: dialogs opened
* under it get auto-dismissed (the half-second flash) or never
* surface. Hide it once the UI layer is ready, then let the
* foreground transition settle before the first dialog. */
sceSystemServiceHideSplashScreen();
sceKernelSleep(3);
snprintf(welcome, sizeof welcome,
"orbisRPC Setup %s\n\nInstalls the daemon, checks WiFi, saves your token, and starts it.",
SETUP_VERSION);
q = ui_confirm(welcome);
if(q != 1){
/* Declined install: offer status, then exit. Forward only. */
if(ui_confirm("Show daemon status instead?") == 1)
step_status();
return 0;
}
if(step_files() != 0) return 1;
step_wifi();
step_token();
step_inject();
ui_ok("Setup " SETUP_VERSION " complete.\n\n"
"After a reboot, re-jailbreak, then open GoldHEN's payload menu\n"
"and enable AutoRun for orbisrpc — it will start itself\n"
"on every jailbreak from then on.");
return 0;
}