mirror of
https://github.com/SirHumza/orbisRPC.git
synced 2026-10-06 14:00:36 +02:00
installer: wizard, loopback inject, token/net/tweaks, icfg tests
This commit is contained in:
1 parent
bb51cfcbb3
commit
14c9101174
14 files changed
+617
-12
No files matched your search
+10
-3
@@ -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.
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)¶m->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(¶m);
|
||||
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(¶m) < 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;
|
||||
}
|
||||
@@ -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
@@ -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 $@ $<
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user