mirror of
https://github.com/SirHumza/orbisRPC.git
synced 2026-10-06 12:00:39 +02:00
251 lines
8.7 KiB
C
251 lines
8.7 KiB
C
/* updater.c - self-updater. Pure logic (host-tested) plus PS4 HTTPS.
|
|
*
|
|
* Flow, once per daemon boot when enabled:
|
|
* GET https://api.github.com/repos/<repo>/releases/latest
|
|
* -> tag_name + asset browser_download_urls
|
|
* if tag newer than ORBISRPC_VERSION:
|
|
* download each known asset (cap 4MB), validate ELF magic,
|
|
* write <target>.new, rename over target.
|
|
* Plugin .prx takes effect on next game launch; payload .bin on next
|
|
* injection. Never deletes, never writes unvalidated bytes.
|
|
*/
|
|
#include "updater.h"
|
|
#include "clock.h"
|
|
#include "version.h"
|
|
#include "jsonlite.h"
|
|
#include "log.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
/* ---- PS4 HTTPS transport (mirrors ws.c/tmdb.c bring-up) ---- */
|
|
#include "tls.h"
|
|
#ifdef ORBISRPC_SDK_PAYLOAD
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <fcntl.h>
|
|
#else
|
|
#include <orbis/Net.h>
|
|
#include <orbis/Sysmodule.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#define UPD_HOST "api.github.com"
|
|
#define UPD_DEADLINE_S 15
|
|
#define UPD_BODY_MAX (4u*1024u*1024u)
|
|
|
|
#ifndef SO_NBIO
|
|
#define SO_NBIO 0x2000 /* same local define as ws.c; absent from SDK headers */
|
|
#endif
|
|
|
|
static int32_t s_upool = -1;
|
|
static int upd_net(void){
|
|
static int ready = 0;
|
|
if(ready) return 0;
|
|
#ifdef ORBISRPC_SDK_PAYLOAD
|
|
ready = 1;
|
|
return 0;
|
|
#else
|
|
uint32_t ur = sceSysmoduleLoadModuleInternal(ORBIS_SYSMODULE_INTERNAL_NET);
|
|
if((int)ur < 0) return -1;
|
|
if(sceNetInit() < 0) return -1;
|
|
s_upool = (int32_t)sceNetPoolCreate("upd", 128*1024, 0);
|
|
if(s_upool < 0) return -1;
|
|
ready = 1;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int upd_connect(const char *host, int port){
|
|
if(upd_net() < 0) return -1;
|
|
#ifdef ORBISRPC_SDK_PAYLOAD
|
|
struct addrinfo hints, *res = NULL;
|
|
memset(&hints, 0, sizeof hints);
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
{
|
|
char portbuf[16];
|
|
snprintf(portbuf, sizeof portbuf, "%d", port);
|
|
if(getaddrinfo(host, portbuf, &hints, &res) != 0 || !res){
|
|
log_msg("updater: dns fail");
|
|
return -1;
|
|
}
|
|
}
|
|
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if(fd < 0){ freeaddrinfo(res); return -1; }
|
|
struct timeval tv = { .tv_sec = UPD_DEADLINE_S, .tv_usec = 0 };
|
|
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
|
|
if(connect(fd, res->ai_addr, res->ai_addrlen) < 0){
|
|
close(fd); freeaddrinfo(res); return -1;
|
|
}
|
|
freeaddrinfo(res);
|
|
{
|
|
int fl = fcntl(fd, F_GETFL, 0);
|
|
if(fl >= 0) fcntl(fd, F_SETFL, fl | O_NONBLOCK);
|
|
}
|
|
return fd;
|
|
#else
|
|
OrbisNetInAddr in;
|
|
memset(&in, 0, sizeof in);
|
|
int32_t rid = sceNetResolverCreate("updR", (uint32_t)s_upool, 0);
|
|
if(rid < 0) return -1;
|
|
int32_t rr = sceNetResolverStartNtoa(rid, host, &in, 8000000, 3, 0);
|
|
sceNetResolverDestroy(rid);
|
|
if(rr < 0){ log_msg("updater: dns fail"); return -1; }
|
|
int fd = sceNetSocket("upd", ORBIS_NET_AF_INET, ORBIS_NET_SOCK_STREAM, 0);
|
|
if(fd < 0) return -1;
|
|
struct timeval tv = { .tv_sec = UPD_DEADLINE_S, .tv_usec = 0 };
|
|
sceNetSetsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
|
|
OrbisNetSockaddr sa;
|
|
memset(&sa, 0, sizeof sa);
|
|
sa.sa_family = 2;
|
|
*(uint16_t*)sa.sa_data = sceNetHtons((uint16_t)port);
|
|
memcpy(sa.sa_data+2, &in.s_addr, 4);
|
|
if(sceNetConnect(fd, &sa, sizeof sa) < 0){
|
|
sceNetSocketClose(fd); return -1;
|
|
}
|
|
int nb = 1;
|
|
sceNetSetsockopt(fd, SOL_SOCKET, SO_NBIO, &nb, sizeof nb);
|
|
return fd;
|
|
#endif
|
|
}
|
|
|
|
/* HTTPS GET, returns heap body (caller frees) with out_len/out_status. */
|
|
static char *https_get(const char *host, const char *path,
|
|
size_t cap, int *out_status, size_t *out_len){
|
|
if(out_status) *out_status = 0;
|
|
if(out_len) *out_len = 0;
|
|
int fd = upd_connect(host, 443);
|
|
if(fd < 0) return NULL;
|
|
tls_ctx_t *t = tls_start(fd, host);
|
|
if(!t){
|
|
#ifdef ORBISRPC_SDK_PAYLOAD
|
|
close(fd);
|
|
#else
|
|
sceNetSocketClose(fd);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
char req[512];
|
|
int rl = snprintf(req, sizeof req,
|
|
"GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: orbisRPC/%s\r\nConnection: close\r\n\r\n",
|
|
path, host, ORBISRPC_VERSION);
|
|
if(rl <= 0 || rl >= (int)sizeof req){ tls_free(t); return NULL; }
|
|
if(tls_write(t, req, (size_t)rl) < 0){ tls_free(t); return NULL; }
|
|
char *body = (char*)malloc(cap);
|
|
if(!body){ tls_free(t); return NULL; }
|
|
size_t bl = 0, hl = 0;
|
|
static char hb[2048];
|
|
int hdr_done = 0, status = 0;
|
|
int64_t dl = orbis_mono_s() + UPD_DEADLINE_S + 20;
|
|
for(;;){
|
|
char tmp[2048];
|
|
int r = tls_read(t, tmp, sizeof tmp);
|
|
if(r < 0) break;
|
|
if(r == 0){
|
|
if(orbis_mono_s() > dl) break;
|
|
usleep(20000);
|
|
continue;
|
|
}
|
|
size_t off = 0;
|
|
if(!hdr_done){
|
|
size_t room = sizeof hb - 1 - hl;
|
|
size_t cp = (size_t)r < room ? (size_t)r : room;
|
|
memcpy(hb+hl, tmp, cp); hl += cp; hb[hl] = 0;
|
|
char *e = strstr(hb, "\r\n\r\n");
|
|
if(e){
|
|
hdr_done = 1;
|
|
if(!strncmp(hb, "HTTP/1.", 7)) status = atoi(hb+9);
|
|
off = (size_t)(e + 4 - hb) - (hl - cp);
|
|
if(off > (size_t)r) off = (size_t)r;
|
|
} else if(hl >= sizeof hb - 1){
|
|
free(body); tls_free(t); return NULL;
|
|
} else continue;
|
|
}
|
|
while(off < (size_t)r && bl < cap - 1) body[bl++] = tmp[off++];
|
|
if(bl >= cap - 1 || orbis_mono_s() > dl) break;
|
|
}
|
|
tls_free(t);
|
|
body[bl] = 0;
|
|
if(out_status) *out_status = status;
|
|
if(out_len) *out_len = bl;
|
|
if(status != 200 || bl == 0){ free(body); return NULL; }
|
|
return body;
|
|
}
|
|
|
|
static int stage_file(const char *target, const unsigned char *data, size_t n){
|
|
char tmp[192];
|
|
snprintf(tmp, sizeof tmp, "%s.new", target);
|
|
FILE *f = fopen(tmp, "wb");
|
|
if(!f){ log_msg("updater: cannot write %s", tmp); return -1; }
|
|
int ok = (fwrite(data, 1, n, f) == n);
|
|
if(fflush(f) != 0) ok = 0;
|
|
if(ok){ int fd = fileno(f); if(fd >= 0 && fsync(fd) != 0) ok = 0; }
|
|
if(fclose(f) != 0) ok = 0;
|
|
if(!ok){ remove(tmp); return -1; }
|
|
if(rename(tmp, target) != 0){ remove(tmp); return -1; }
|
|
return 0;
|
|
}
|
|
|
|
int updater_check_and_stage(void){
|
|
char path[160];
|
|
snprintf(path, sizeof path, "/repos/%s/releases/latest", ORBISRPC_REPO);
|
|
size_t rl = 0;
|
|
int status = 0;
|
|
char *js = https_get(UPD_HOST, path, 65536, &status, &rl);
|
|
if(!js){ log_msg("updater: release check failed (status=%d)", status); return -1; }
|
|
jl_val_t *r = jl_parse(js, rl);
|
|
free(js);
|
|
if(!r){ log_msg("updater: release parse failed"); return -1; }
|
|
const jl_val_t *tag = jl_obj_get(r, "tag_name");
|
|
const jl_val_t *assets = jl_obj_get(r, "assets");
|
|
int updated = 0;
|
|
if(tag && tag->type == JL_STRING && tag->str[0] &&
|
|
updater_cmp(tag->str, ORBISRPC_VERSION) > 0){
|
|
log_msg("updater: %s available (local %s)", tag->str, ORBISRPC_VERSION);
|
|
if(assets && assets->type == JL_ARRAY){
|
|
for(size_t i = 0; ; i++){
|
|
const jl_val_t *a = jl_arr_at(assets, i);
|
|
if(!a) break;
|
|
const jl_val_t *nm = jl_obj_get(a, "name");
|
|
const jl_val_t *dl = jl_obj_get(a, "browser_download_url");
|
|
if(!nm || nm->type != JL_STRING || !dl || dl->type != JL_STRING) continue;
|
|
const char *target = NULL;
|
|
if(!strcmp(nm->str, "orbisrpc.bin")) target = "/data/GoldHEN/payloads/orbisrpc.bin";
|
|
else if(!strcmp(nm->str, "orbisrpc_plugin.prx")) target = "/data/GoldHEN/plugins/orbisrpc_plugin.prx";
|
|
else continue;
|
|
/* download URLs are full https://... : split host/path */
|
|
const char *url = dl->str;
|
|
const char *h0 = strstr(url, "://");
|
|
if(!h0) continue;
|
|
h0 += 3;
|
|
const char *p0 = strchr(h0, '/');
|
|
if(!p0 || (size_t)(p0-h0) >= 128) continue;
|
|
char host[128];
|
|
memcpy(host, h0, (size_t)(p0-h0)); host[p0-h0] = 0;
|
|
size_t al = 0;
|
|
char *bin = https_get(host, p0, UPD_BODY_MAX, &status, &al);
|
|
if(!bin || !updater_elf_ok((unsigned char*)bin, al)){
|
|
log_msg("updater: asset %s failed validation", nm->str);
|
|
free(bin);
|
|
continue;
|
|
}
|
|
if(stage_file(target, (unsigned char*)bin, al) == 0){
|
|
log_msg("updater: staged %s (%zu bytes)", target, al);
|
|
updated = 1;
|
|
}
|
|
free(bin);
|
|
}
|
|
}
|
|
}
|
|
jl_free(r);
|
|
return updated ? 1 : 0;
|
|
}
|