installer: wizard, loopback inject, token/net/tweaks, icfg tests

This commit is contained in:
SirHumza committed 2026-09-24 16:43:39 +02:00
1 parent bb51cfcbb3
commit 14c9101174
14 files changed
+617 -12

No files matched your search

+10 -3
View File
@@ -17,7 +17,7 @@ INTDIR := $(PROJDIR)/x64/Debug
CFILES := $(wildcard $(PROJDIR)/*.c)
CPPFILES := $(wildcard $(PROJDIR)/*.cpp)
OBJS := $(patsubst $(PROJDIR)/%.c, $(INTDIR)/%.o, $(CFILES)) $(patsubst $(PROJDIR)/%.cpp, $(INTDIR)/%.o, $(CPPFILES))
OBJS := $(patsubst $(PROJDIR)/%.c, $(INTDIR)/%.o, $(CFILES)) $(patsubst $(PROJDIR)/%.cpp, $(INTDIR)/%.o, $(CPPFILES)) $(INTDIR)/jsonlite.o
# llvmshim (no brew): export PATH="$HOME/llvmshim:$PATH" before make.
LLVM := $(HOME)/llvmshim
@@ -37,8 +37,8 @@ all: $(CONTENT_ID).pkg
$(CONTENT_ID).pkg: installer/pkg.gp4
cd installer && $(TOOLCHAIN)/bin/$(CDIR)/PkgTool.Core pkg_build pkg.gp4 . && mv $(CONTENT_ID).pkg ..
installer/pkg.gp4: installer/eboot.bin installer/sce_sys/about/right.sprx installer/sce_sys/param.sfo installer/sce_sys/icon0.png $(LIBMODULES) $(ASSETS)
cd installer && $(TOOLCHAIN)/bin/$(CDIR)/create-gp4 -out pkg.gp4 --content-id=$(CONTENT_ID) --files "eboot.bin sce_sys/about/right.sprx sce_sys/param.sfo sce_sys/icon0.png sce_module/libSceFios2.prx sce_module/libc.prx $(patsubst installer/%,%,$(ASSETS))"
installer/pkg.gp4: installer/eboot.bin installer/assets/daemon.elf installer/sce_sys/about/right.sprx installer/sce_sys/param.sfo installer/sce_sys/icon0.png $(LIBMODULES) $(ASSETS)
cd installer && $(TOOLCHAIN)/bin/$(CDIR)/create-gp4 -out pkg.gp4 --content-id=$(CONTENT_ID) --files "eboot.bin assets/daemon.elf sce_sys/about/right.sprx sce_sys/param.sfo sce_sys/icon0.png sce_module/libSceFios2.prx sce_module/libc.prx $(patsubst installer/%,%,$(ASSETS))"
installer/sce_sys/param.sfo: installer/Makefile
$(TOOLCHAIN)/bin/$(CDIR)/PkgTool.Core sfo_new $@
@@ -60,6 +60,13 @@ installer/eboot.bin: $(OBJS)
$(INTDIR)/%.o: $(PROJDIR)/%.c
$(CC) $(CFLAGS) -o $@ $<
$(INTDIR)/jsonlite.o: orbisrpc/jsonlite.c
$(CC) $(CFLAGS) -o $@ $<
# Stage the daemon payload into the app image (built first via build_sdk.sh).
installer/assets/daemon.elf: build-sdk/orbisrpc_sdk.elf
cp $< $@
$(INTDIR)/%.o: $(PROJDIR)/%.cpp
$(CCX) $(CXXFLAGS) -o $@ $<
Binary file not shown.
+158
View File
@@ -0,0 +1,158 @@
/* icfg.c - atomic read-modify-write over the daemon config. */
#include "icfg.h"
#include "jsonlite.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define ICFG_MAX (64u*1024u)
int token_valid(const char *t){
size_t n, i;
int dots = 0;
if(!t) return 0;
n = strlen(t);
if(n < 40 || n > 150) return 0;
if(!strcmp(t, "SET_ME") || !strcmp(t, "PUT_TOKEN_HERE")) return 0;
for(i = 0; i < n; i++){
char c = t[i];
if(c == '.'){ dots++; continue; }
if(!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-'))
return 0;
}
return dots >= 2;
}
static jl_val_t *icfg_read(const char *path){
FILE *f = fopen(path, "rb");
long sz;
char *buf;
jl_val_t *r;
if(!f) return NULL;
if(fseek(f, 0, SEEK_END) != 0){ fclose(f); return NULL; }
sz = ftell(f);
if(sz <= 0 || sz > (long)ICFG_MAX){ fclose(f); return NULL; }
if(fseek(f, 0, SEEK_SET) != 0){ fclose(f); return NULL; }
buf = (char *)malloc((size_t)sz + 1);
if(!buf){ fclose(f); return NULL; }
if(fread(buf, 1, (size_t)sz, f) != (size_t)sz){
free(buf); fclose(f); return NULL;
}
fclose(f);
buf[sz] = 0;
r = jl_parse(buf, (size_t)sz);
free(buf);
if(!r || r->type != JL_OBJECT){ if(r) jl_free(r); return NULL; }
return r;
}
static int icfg_write(const char *path, jl_val_t *r){
char tmp[300];
char *s;
FILE *f;
int ok = 1;
if(snprintf(tmp, sizeof tmp, "%s.new", path) <= 0 ||
(size_t)strlen(path) + 5 >= sizeof tmp)
return -1;
s = jl_stringify(r);
if(!s) return -1;
f = fopen(tmp, "wb");
if(!f){ free(s); return -1; }
if(fputs(s, f) < 0) ok = 0;
if(ok && fclose(f) != 0) ok = 0;
free(s);
if(!ok){ remove(tmp); return -1; }
if(rename(tmp, path) != 0){ remove(tmp); return -1; }
return 0;
}
int icfg_token_load(const char *path, char *out, size_t cap){
jl_val_t *r = icfg_read(path);
const jl_val_t *v;
if(!out || cap == 0) return -1;
out[0] = 0;
if(!r) return -1;
v = jl_obj_get(r, "token");
if(v && v->type == JL_STRING && v->str){
strncpy(out, v->str, cap - 1);
out[cap - 1] = 0;
}
jl_free(r);
return 0;
}
int icfg_token_save(const char *path, const char *token){
jl_val_t *r;
int rc;
if(!token_valid(token)) return -1;
r = icfg_read(path);
if(!r){
r = jl_new_object();
if(!r) return -2;
}
jl_obj_set(r, "token", jl_new_string(token));
rc = icfg_write(path, r);
jl_free(r);
return rc == 0 ? 0 : -2;
}
int icfg_set_str(const char *path, const char *key, const char *val){
jl_val_t *r;
int rc;
if(!key || !key[0] || !val) return -1;
r = icfg_read(path);
if(!r){
r = jl_new_object();
if(!r) return -1;
}
jl_obj_set(r, key, jl_new_string(val));
rc = icfg_write(path, r);
jl_free(r);
return rc;
}
int icfg_set_int(const char *path, const char *key, long val){
jl_val_t *r;
int rc;
if(!key || !key[0]) return -1;
r = icfg_read(path);
if(!r){
r = jl_new_object();
if(!r) return -1;
}
jl_obj_set(r, key, jl_new_int(val));
rc = icfg_write(path, r);
jl_free(r);
return rc;
}
int icfg_get_str(const char *path, const char *key, char *out, size_t cap){
jl_val_t *r = icfg_read(path);
const jl_val_t *v;
if(!out || cap == 0) return -1;
out[0] = 0;
if(!r || !key) return -1;
v = jl_obj_get(r, key);
if(v && v->type == JL_STRING && v->str){
strncpy(out, v->str, cap - 1);
out[cap - 1] = 0;
}
jl_free(r);
return 0;
}
int icfg_get_int(const char *path, const char *key, long *out){
jl_val_t *r = icfg_read(path);
const jl_val_t *v;
if(!out) return -1;
if(!r || !key) return -1;
v = jl_obj_get(r, key);
if(v && v->type == JL_NUMBER){
*out = v->num_is_int ? (long)v->inum : (long)v->num;
jl_free(r);
return 0;
}
jl_free(r);
return -1;
}
+25
View File
@@ -0,0 +1,25 @@
/* icfg.h - installer config read/write for /data/orbisRPC/config.json.
* Read-modify-write: preserves learned titles and every other field.
* Pure logic (token_valid) is host-tested. */
#ifndef INSTALLER_ICFG_H
#define INSTALLER_ICFG_H
#include <stddef.h>
#define ICFG_PATH "/data/orbisRPC/config.json"
/* 1 valid user token, 0 not. Tokens are dot-separated base64-ish,
* 40..150 chars. Rejects placeholders and whitespace. */
int token_valid(const char *t);
/* Load current token ("" when missing). 0 ok, -1 unreadable. */
int icfg_token_load(const char *path, char *out, size_t cap);
/* Set token (validated first). 0 saved, -1 invalid, -2 I/O error. */
int icfg_token_save(const char *path, const char *token);
/* Set a string/int key, preserving everything else. 0 saved, -1 I/O. */
int icfg_set_str(const char *path, const char *key, const char *val);
int icfg_set_int(const char *path, const char *key, long val);
/* Read a string/int key. 0 found, -1 missing/unreadable. */
int icfg_get_str(const char *path, const char *key, char *out, size_t cap);
int icfg_get_int(const char *path, const char *key, long *out);
#endif
+146 -6
View File
@@ -1,14 +1,154 @@
/* installer.c - orbisRPC Setup: Inject -> token -> network test -> tweaks.
* Skeleton: proves the app boots on-console with a native dialog. */
*
* NAVIGATION LAW (from the last installer trauma): every No/cancel moves
* FORWARD to the next step. Nothing ever dumps back to the start. The only
* backward motion is none; the only exits are Done or explicit close.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "ui.h"
#include "send.h"
#include "icfg.h"
#include "nettest.h"
#define SETUP_VERSION "0.4.0"
#define DAEMON_ELF "/app0/assets/daemon.elf"
static void step_inject_body(void){
int port = 0;
if(ui_progress_open("Injecting orbisRPC...") != 0){
ui_ok("Could not open progress dialog. Continuing.");
return;
}
ui_progress_set(0);
if(send_file_loopback(DAEMON_ELF, &port, ui_progress_set) == 0){
char msg[128];
ui_progress_close();
snprintf(msg, sizeof msg, "Daemon injected via loader port %d.\n\nIt boots on its own from here.", port);
ui_ok(msg);
} else {
ui_progress_close();
ui_ok("Inject failed on every loader port (9090/9021/9020).\n\nIs GoldHEN's BinLoader running? Continuing anyway.");
}
}
static void step_inject(void){
int q = ui_confirm("Inject the orbisRPC daemon now?\n\nSends it to the console's own loader (native, no PC).");
if(q != 1) return; /* No -> next step, never back. */
step_inject_body();
}
static void step_token(void){
char tok[160];
int tries, r;
int q = ui_confirm("Set your Discord token?\n\nNeeded for presence. Stored only on this console.");
if(q != 1) return;
tok[0] = 0;
icfg_token_load(ICFG_PATH, tok, sizeof tok);
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. Token step skipped."); return; }
if(r == 0) return; /* cancel -> next step */
if(token_valid(tok)){
r = icfg_token_save(ICFG_PATH, tok);
ui_ok(r == 0 ? "Token saved." : "Could not write config. Continuing.");
return;
}
ui_ok("That doesn't look like a token.\nCheck it and try again, or cancel to skip.");
}
}
static void step_nettest(void){
static const struct { const char *label; const char *host; int port; } t[] = {
{ "Discord gateway", "gateway.discord.gg", 443 },
{ "Discord API", "discord.com", 443 },
{ "Google DNS (internet)", "8.8.8.8", 53 },
{ "Sony TMDB (expected: blocked)", "tmdb.np.dl.playstation.net", 80 },
};
char out[512];
size_t used = 0;
unsigned i;
int q = ui_confirm("Run the network test?\n\nChecks what this console can actually reach.");
if(q != 1) return;
if(ui_progress_open("Testing network...") != 0) return;
used = (size_t)snprintf(out, sizeof out, "Network test:\n");
for(i = 0; i < sizeof t/sizeof t[0]; i++){
int ok;
ui_progress_msg(t[i].label);
ok = net_probe(t[i].host, t[i].port, 6);
used += (size_t)snprintf(out + used, sizeof out - used,
"\n%s: %s", t[i].label, ok ? "OK" : "FAIL");
if(used >= sizeof out - 64) break;
ui_progress_set((i + 1) * 100 / 4);
}
ui_progress_close();
ui_ok(out);
}
static void step_tweaks(void){
char buf[256];
long n;
char *end;
/* Presence text */
if(ui_confirm("Change the idle presence text?") == 1){
buf[0] = 0;
icfg_get_str(ICFG_PATH, "presence_state", buf, sizeof buf);
if(ui_input("Idle text", "e.g. On PS4", buf, sizeof buf) == 1 && buf[0]){
if(icfg_set_str(ICFG_PATH, "presence_state", buf) != 0)
ui_ok("Could not save. Continuing.");
}
}
/* Poll interval */
if(ui_confirm("Change the poll interval (seconds)?") == 1){
if(ui_input("Poll interval", "5 to 60", buf, sizeof buf) == 1 && buf[0]){
n = strtol(buf, &end, 10);
if(end != buf && *end == 0 && n >= 5 && n <= 60){
if(icfg_set_int(ICFG_PATH, "poll_interval_s", n) != 0)
ui_ok("Could not save. Continuing.");
} else {
ui_ok("Must be a number 5..60. Keeping current.");
}
}
}
/* Home art */
if(ui_confirm("Change the idle tile art?") == 1){
buf[0] = 0;
icfg_get_str(ICFG_PATH, "home_art", buf, sizeof buf);
if(ui_input("Idle art", "URL, asset key, or empty", buf, sizeof buf) == 1){
if(icfg_set_str(ICFG_PATH, "home_art", buf) != 0)
ui_ok("Could not save. Continuing.");
}
}
/* Debug logging */
{
int d = ui_confirm("Turn debug logging on?");
if(d == 1) icfg_set_int(ICFG_PATH, "debug", 1);
else if(d == 0) icfg_set_int(ICFG_PATH, "debug", 0);
}
ui_ok("Settings saved (where edited).");
}
int main(void){
char msg[256];
snprintf(msg, sizeof msg,
"orbisRPC Setup %s\n\nInstaller skeleton boots.\nWizard lands next.", SETUP_VERSION);
ui_ok(msg);
char tok[160];
int first_run;
tok[0] = 0;
icfg_token_load(ICFG_PATH, tok, sizeof tok);
first_run = !token_valid(tok);
if(!first_run){
/* Returning user: inject? no -> settings? no -> exit. Forward only. */
if(ui_confirm("orbisRPC Setup.\n\nInject the daemon?") == 1){
step_inject_body();
ui_ok("Done.");
return 0;
}
if(ui_confirm("Open settings instead?") == 1)
step_tweaks();
return 0;
}
/* First run wizard: every No skips forward. */
step_inject();
step_token();
step_nettest();
ui_ok("Setup complete.\n\nLaunch a game and watch Discord.");
return 0;
}
+40
View File
@@ -0,0 +1,40 @@
/* nettest.c - connect-only probes. A closed port and a filtered host
* both read as unreachable; that honesty is the point (Sony TMDB from
* the console genuinely fails while Discord succeeds). */
#include "nettest.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
int net_probe(const char *host, int port, int timeout_s){
struct addrinfo hints, *res = NULL, *rp;
char svc[8];
int ok = 0;
if(!host || !host[0] || port <= 0 || port > 65535) return 0;
if(timeout_s < 1) timeout_s = 1;
if(timeout_s > 10) timeout_s = 10;
snprintf(svc, sizeof svc, "%d", port);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if(getaddrinfo(host, svc, &hints, &res) != 0 || !res) return 0;
for(rp = res; rp; rp = rp->ai_next){
int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
struct timeval tv;
if(fd < 0) continue;
tv.tv_sec = timeout_s;
tv.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
if(connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) ok = 1;
close(fd);
if(ok) break;
}
freeaddrinfo(res);
return ok;
}
+8
View File
@@ -0,0 +1,8 @@
/* nettest.h - honest TCP reachability probes (no traffic beyond connect). */
#ifndef INSTALLER_NETTEST_H
#define INSTALLER_NETTEST_H
/* 1 reachable, 0 not. timeout_s clamped 1..10. Host-testable. */
int net_probe(const char *host, int port, int timeout_s);
#endif
+2 -1
View File
@@ -3,7 +3,7 @@
<volume>
<volume_type>pkg_ps4_app</volume_type>
<volume_id>PS4VOLUME</volume_id>
<volume_ts>2026-09-24 16:29:15</volume_ts>
<volume_ts>2026-09-24 16:43:31</volume_ts>
<package content_id="IV0000-ORPC00001_00-ORBISRPCSETUP000" passcode="00000000000000000000000000000000"
storage_type="digital50" app_type="full" />
<chunk_info chunk_count="1" scenario_count="1">
@@ -17,6 +17,7 @@
</volume>
<files img_no="0">
<file targ_path="eboot.bin" orig_path="eboot.bin" />
<file targ_path="assets/daemon.elf" orig_path="assets/daemon.elf" />
<file targ_path="sce_sys/about/right.sprx" orig_path="sce_sys/about/right.sprx" />
<file targ_path="sce_sys/param.sfo" orig_path="sce_sys/param.sfo" />
<file targ_path="sce_sys/icon0.png" orig_path="sce_sys/icon0.png" />
+73
View File
@@ -0,0 +1,73 @@
/* send.c - loopback payload injection: file bytes over TCP to the
* console's own loaders. Chunked send (no whole-file malloc). */
#include "send.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define SEND_CHUNK (64u*1024u)
static const int SEND_PORTS[] = { 9090, 9021, 9020 };
static int send_one(int fd, const char *path, void (*progress)(unsigned)){
FILE *f = fopen(path, "rb");
unsigned char buf[SEND_CHUNK];
long total = 0, sent = 0;
size_t n;
if(!f) return -1;
if(fseek(f, 0, SEEK_END) != 0){ fclose(f); return -1; }
total = ftell(f);
if(total <= 0 || total > 16*1024*1024){ fclose(f); return -1; }
if(fseek(f, 0, SEEK_SET) != 0){ fclose(f); return -1; }
if(progress) progress(0);
while((n = fread(buf, 1, sizeof buf, f)) > 0){
size_t off = 0;
while(off < n){
ssize_t w = send(fd, buf + off, n - off, 0);
if(w <= 0){
if(errno == EAGAIN || errno == EWOULDBLOCK) continue;
fclose(f);
return -1;
}
off += (size_t)w;
}
sent += (long)n;
if(progress) progress((unsigned)(sent * 100 / total));
}
fclose(f);
if(progress) progress(100);
return 0;
}
int send_file_loopback(const char *path, int *port_used, void (*progress)(unsigned)){
unsigned i;
if(!path || !path[0]) return -1;
for(i = 0; i < sizeof SEND_PORTS/sizeof SEND_PORTS[0]; i++){
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in sa;
struct timeval tv = { 5, 0 };
if(fd < 0) continue;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
memset(&sa, 0, sizeof sa);
sa.sin_family = AF_INET;
sa.sin_port = htons((uint16_t)SEND_PORTS[i]);
sa.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(fd, (struct sockaddr *)&sa, sizeof sa) < 0){
close(fd);
continue;
}
if(send_one(fd, path, progress) == 0){
close(fd);
if(port_used) *port_used = SEND_PORTS[i];
return 0;
}
close(fd);
}
return -1;
}
+10
View File
@@ -0,0 +1,10 @@
/* send.h - loopback payload injection (clean-room, standard sockets).
* Tries native GoldHEN BinLoader 9090, then elfldr 9021, then 9020.
* 9020 is one-shot: accepted bytes count as sent. */
#ifndef INSTALLER_SEND_H
#define INSTALLER_SEND_H
/* progress(pct) may be NULL. Returns 0 + port_used set on success. */
int send_file_loopback(const char *path, int *port_used, void (*progress)(unsigned pct));
#endif
+87
View File
@@ -4,9 +4,13 @@
#include "ui.h"
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <orbis/CommonDialog.h>
#include <orbis/MsgDialog.h>
#include <orbis/ImeDialog.h>
#include <orbis/UserService.h>
#include <orbis/Sysmodule.h>
#include <orbis/libkernel.h>
static void base_init(OrbisMsgDialogParam *param){
memset(param, 0, sizeof(*param));
@@ -14,6 +18,7 @@ static void base_init(OrbisMsgDialogParam *param){
param->baseParam.magic =
(uint32_t)(ORBIS_COMMON_DIALOG_MAGIC_NUMBER + (uint64_t)&param->baseParam);
param->size = sizeof(OrbisMsgDialogParam);
param->mode = ORBIS_MSG_DIALOG_MODE_USER_MSG;
}
int ui_ok(const char *msg){
@@ -53,3 +58,85 @@ int ui_confirm(const char *msg){
sceMsgDialogTerminate();
return res.buttonId == ORBIS_MSG_DIALOG_BUTTON_ID_YES ? 1 : 0;
}
static int progress_open = 0;
int ui_progress_open(const char *msg){
OrbisMsgDialogParam param;
OrbisMsgDialogProgressBarParam bar;
if(progress_open) return 0;
sceMsgDialogInitialize();
base_init(&param);
param.mode = ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR;
memset(&bar, 0, sizeof bar);
bar.barType = ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE;
bar.msg = msg;
param.progBarParam = &bar;
if(sceMsgDialogOpen(&param) < 0){ sceMsgDialogTerminate(); return -1; }
progress_open = 1;
return 0;
}
void ui_progress_msg(const char *msg){
if(progress_open && msg) sceMsgDialogProgressBarSetMsg(0, msg);
}
void ui_progress_set(unsigned pct){
if(progress_open) sceMsgDialogProgressBarSetValue(0, pct > 100 ? 100 : pct);
}
void ui_progress_close(void){
if(!progress_open) return;
progress_open = 0;
sceMsgDialogClose();
sceMsgDialogTerminate();
}
int ui_input(const char *title, const char *placeholder, char *out, size_t cap){
static wchar_t wbuf[256];
static wchar_t wtitle[64];
static wchar_t wplace[64];
OrbisImeDialogSetting st;
OrbisDialogResult res;
int32_t uid = 0;
size_t i;
if(!out || cap == 0 || cap > 200) return -1;
/* Prefill with current value (ASCII round-trip). */
for(i = 0; i < cap - 1 && out[i]; i++) wbuf[i] = (wchar_t)(unsigned char)out[i];
wbuf[i] = 0;
for(i = 0; i < 63 && title && title[i]; i++) wtitle[i] = (wchar_t)(unsigned char)title[i];
wtitle[i] = 0;
for(i = 0; i < 63 && placeholder && placeholder[i]; i++) wplace[i] = (wchar_t)(unsigned char)placeholder[i];
wplace[i] = 0;
if(sceUserServiceGetInitialUser(&uid) < 0) uid = 0;
memset(&st, 0, sizeof st);
st.userId = (uint32_t)uid;
st.type = 0;
st.supportedLanguages = 0;
st.enterLabel = ORBIS_BUTTON_LABEL_DEFAULT;
st.inputMethod = 0;
st.filter = 0;
st.option = 0;
st.maxTextLength = (uint32_t)(cap - 1);
st.inputTextBuffer = wbuf;
st.posx = 0;
st.posy = 0;
st.horizontalAlignment = ORBIS_H_CENTER;
st.verticalAlignment = ORBIS_V_CENTER;
st.placeholder = wplace;
st.title = wtitle;
sceImeDialogInit(&st, NULL);
/* Pump until the OSK stops (IME uses its own status enum). */
while(sceImeDialogGetStatus() != ORBIS_DIALOG_STATUS_STOPPED){
/* The dialog system needs no explicit pump; yield briefly. */
sceKernelUsleep(20000);
}
memset(&res, 0, sizeof res);
if(sceImeDialogGetResult(&res) < 0){ sceImeDialogTerm(); return -1; }
sceImeDialogTerm();
if(res.endstatus != ORBIS_DIALOG_OK) return 0;
for(i = 0; i < cap - 1 && wbuf[i]; i++)
out[i] = (wbuf[i] < 128 && wbuf[i] >= 32) ? (char)wbuf[i] : '?';
out[i] = 0;
return 1;
}
+11
View File
@@ -1,10 +1,21 @@
/* ui.h - native dialog helpers for the installer wizard. */
#ifndef INSTALLER_UI_H
#define INSTALLER_UI_H
#include <stddef.h>
/* Info dialog with OK. 0 shown, -1 failed to open. */
int ui_ok(const char *msg);
/* Yes/No dialog, focus on No (safe default). 1 yes, 0 no/closed, -1 error. */
int ui_confirm(const char *msg);
/* Progress dialog. Open once, update, close. 0 ok, -1 error. */
int ui_progress_open(const char *msg);
void ui_progress_msg(const char *msg);
void ui_progress_set(unsigned pct);
void ui_progress_close(void);
/* OSK text input. Prefills with out's current content. Returns 1 + out
* filled on Done, 0 on cancel, -1 on error. */
int ui_input(const char *title, const char *placeholder, char *out, size_t cap);
#endif
+11 -2
View File
@@ -9,18 +9,21 @@ MBEDTLS_DIR := ../third_party/mbedtls/library
MBEDTLS_SRCS := $(filter-out $(MBEDTLS_DIR)/net_sockets.c $(MBEDTLS_DIR)/timing.c $(MBEDTLS_DIR)/entropy_poll.c,$(wildcard $(MBEDTLS_DIR)/*.c))
ORBIS_SRCS := ../orbisrpc/jsonlite.c ../orbisrpc/b64.c ../orbisrpc/sfo.c ../orbisrpc/tmdb_crypto.c ../orbisrpc/updater_util.c ../orbisrpc/art.c ../orbisrpc/log.c ../orbisrpc/health.c ../orbisrpc/manifest.c ../orbisrpc/compat.c ../orbisrpc/cfg.c ../orbisrpc/appdb.c ../orbisrpc/discord.c
INST_SRCS := ../installer/icfg.c ../installer/nettest.c
# SQLite amalgamation: -O0 for host iteration speed (payload uses -O2).
SQLITE_DIR := ../third_party/sqlite/sqlite-amalgamation-3510100
SQLITE_FLAGS := -O0 -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_DEPRECATED -I$(SQLITE_DIR)
ORBIS_OBJS := $(patsubst ../orbisrpc/%.c,$(OBJDIR)/orbis_%.o,$(ORBIS_SRCS))
INST_OBJS := $(patsubst ../installer/%.c,$(OBJDIR)/inst_%.o,$(INST_SRCS))
MBEDTLS_OBJS := $(patsubst $(MBEDTLS_DIR)/%.c,$(OBJDIR)/mbed_%.o,$(MBEDTLS_SRCS))
TEST_OBJS := $(OBJDIR)/test_utils.o $(ORBIS_OBJS) $(MBEDTLS_OBJS) $(OBJDIR)/sqlite3.o
TEST_OBJS := $(OBJDIR)/test_utils.o $(ORBIS_OBJS) $(INST_OBJS) $(MBEDTLS_OBJS) $(OBJDIR)/sqlite3.o
ASAN_FLAGS := -fsanitize=address,undefined -fno-omit-frame-pointer
ASAN_ORBIS_OBJS := $(patsubst ../orbisrpc/%.c,$(ASAN_OBJDIR)/orbis_%.o,$(ORBIS_SRCS))
ASAN_INST_OBJS := $(patsubst ../installer/%.c,$(ASAN_OBJDIR)/inst_%.o,$(INST_SRCS))
ASAN_MBEDTLS_OBJS := $(patsubst $(MBEDTLS_DIR)/%.c,$(ASAN_OBJDIR)/mbed_%.o,$(MBEDTLS_SRCS))
ASAN_OBJS := $(ASAN_OBJDIR)/test_utils.o $(ASAN_ORBIS_OBJS) $(ASAN_MBEDTLS_OBJS) $(ASAN_OBJDIR)/sqlite3.o
ASAN_OBJS := $(ASAN_OBJDIR)/test_utils.o $(ASAN_ORBIS_OBJS) $(ASAN_INST_OBJS) $(ASAN_MBEDTLS_OBJS) $(ASAN_OBJDIR)/sqlite3.o
.PHONY: all clean test asan
@@ -48,6 +51,9 @@ $(OBJDIR)/test_utils.o: test_utils.c | $(OBJDIR)
$(OBJDIR)/orbis_%.o: ../orbisrpc/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/inst_%.o: ../installer/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -I../installer -c -o $@ $<
$(OBJDIR)/mbed_%.o: $(MBEDTLS_DIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -Wno-unused-parameter -c -o $@ $<
@@ -63,6 +69,9 @@ $(ASAN_OBJDIR)/test_utils.o: test_utils.c | $(ASAN_OBJDIR)
$(ASAN_OBJDIR)/orbis_%.o: ../orbisrpc/%.c | $(ASAN_OBJDIR)
$(CC) $(CFLAGS) $(ASAN_FLAGS) -c -o $@ $<
$(ASAN_OBJDIR)/inst_%.o: ../installer/%.c | $(ASAN_OBJDIR)
$(CC) $(CFLAGS) $(ASAN_FLAGS) -I../installer -c -o $@ $<
$(ASAN_OBJDIR)/mbed_%.o: $(MBEDTLS_DIR)/%.c | $(ASAN_OBJDIR)
$(CC) $(CFLAGS) $(ASAN_FLAGS) -Wno-unused-parameter -c -o $@ $<
+36
View File
@@ -10,6 +10,8 @@
#include "../orbisrpc/appdb.h"
#include "../orbisrpc/discord.h"
#include "../orbisrpc/detect.h"
#include "../installer/icfg.h"
#include "../installer/nettest.h"
#include "sqlite3.h"
#include <string.h>
@@ -583,6 +585,39 @@ static void test_cfg_learn(void) {
assert(cfg_learn(&c, "CUSA00001", "Full Game") == 0);
}
static void test_installer_cfg(void) {
char dir[64], path[96];
assert(make_tmpdir(dir, sizeof dir) == 0);
snprintf(path, sizeof path, "%s/config.json", dir);
/* token validation */
assert(token_valid("MTIz.ABCdef_0123456789-xYz.ABCDEFGHijklmnopQRSTUVwxyz") == 1);
assert(token_valid("short") == 0);
assert(token_valid("SET_ME") == 0);
assert(token_valid("has space.in.it.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") == 0);
assert(token_valid("nodots") == 0);
assert(token_valid(NULL) == 0);
/* save creates, preserves, round-trips */
assert(icfg_token_save(path, "MTIz.ABCdef_0123456789-xYz.ABCDEFGHijklmnopQRSTUVwxyz") == 0);
assert(icfg_set_str(path, "presence_state", "On PS4") == 0);
assert(icfg_set_int(path, "poll_interval_s", 12) == 0);
/* invalid token refused, file untouched */
assert(icfg_token_save(path, "junk") == -1);
char tok[160], st[64];
long n = 0;
assert(icfg_token_load(path, tok, sizeof tok) == 0);
assert(!strcmp(tok, "MTIz.ABCdef_0123456789-xYz.ABCDEFGHijklmnopQRSTUVwxyz"));
assert(icfg_get_str(path, "presence_state", st, sizeof st) == 0);
assert(!strcmp(st, "On PS4"));
assert(icfg_get_int(path, "poll_interval_s", &n) == 0 && n == 12);
/* missing file: loads fail soft, save still works */
assert(icfg_get_str("/nonexistent/x.json", "k", st, sizeof st) != 0);
/* net probes fail soft on garbage */
assert(net_probe(NULL, 443, 2) == 0);
assert(net_probe("", 443, 2) == 0);
assert(net_probe("127.0.0.1", 1, 1) == 0);
assert(net_probe("nonexistent.invalid", 443, 1) == 0);
}
int main(void) {
test_json();
test_gateway_op_spoof();
@@ -599,6 +634,7 @@ int main(void) {
test_manifest_sig();
test_cfg_titles();
test_cfg_learn();
test_installer_cfg();
test_appdb();
test_discord_builder();
puts("utility tests passed");