mirror of
https://github.com/SirHumza/orbisRPC.git
synced 2026-10-06 10:00:36 +02:00
plugin: run orbisRPC as a GoldHEN plugin (native auto-start)
/data/GoldHEN/payloads/ is not an auto-load folder (PPPwn only loads goldhen.bin; folder auto-load is unimplemented GoldHEN feature #296), so the ELF payload never ran regardless of reboots. Replace that route with the GoldHEN plugin loader, which auto-starts into the game process at every boot via plugins.ini [default] with no PC involvement. - daemon.c/h: extract the payload main() loop into a shared daemon_run() usable from both the ELF entry (main.c thin wrapper) and the plugin - detect.c/h: add detect_name_for_title() for the known-titleid plugin mode - plugin/plugin.c: plugin_load() reads procInfo.titleid via sys_sdk_proc_info, skips non-game (NPXS/system) titles, starts daemon thread; plugin_unload() stops it cleanly - plugin/Makefile: builds orbisrpc_plugin.prx against the GoldHEN SDK (libGoldHEN_Hook.a + crtprx.o) - deploy/SETUP.md: rewrite for the plugin route; add plugins.ini [default] (plugin/plugins.ini.ps4) - scripts/build.sh: include daemon.c
This commit is contained in:
1 parent
c1565c3144
commit
d5d1957668
11 files changed
+429
-152
No files matched your search
@@ -2,3 +2,5 @@ build/
|
||||
*.o
|
||||
*.err
|
||||
/tmp/
|
||||
config/config.ps4.json
|
||||
plugin/build/
|
||||
+69
-45
@@ -1,45 +1,56 @@
|
||||
# orbisRPC — Deploy + setup guide
|
||||
|
||||
Target: GoldHEN 2.2 jailbroken PS4 (FW 9.00), daemon route.
|
||||
Target: GoldHEN 2.3+ jailbroken PS4, **GoldHEN plugin route** (the native,
|
||||
auto-starting mechanism — same one the GTA mod menu uses).
|
||||
|
||||
## 1. Copy the payload to the console
|
||||
**Why a plugin and not a payload:** `/data/GoldHEN/payloads/` is NOT an
|
||||
auto-load folder (PPPwn stage2 only loads `goldhen.bin` from there; payload
|
||||
auto-loading from a folder is an unimplemented GoldHEN feature request #296).
|
||||
The only auto-start mechanism that works on-boot with zero PC involvement is
|
||||
the GoldHEN **plugin loader**, driven by `/data/GoldHEN/plugins.ini`.
|
||||
|
||||
Via FTP (the console must be awake and online). GoldHEN's `/data/GoldHEN/payloads/`
|
||||
auto-loader takes a **raw ELF** payload named `.bin`, so deploy `orbisrpc.elf`
|
||||
(the real ELF — `build/orbisrpc.bin` is the fself tool's eboot output, not this):
|
||||
## 1. Copy the plugin to the console
|
||||
|
||||
Via FTP (console must be awake and online), build it first:
|
||||
|
||||
```
|
||||
put build/orbisrpc.elf -> /data/GoldHEN/payloads/orbisrpc.bin
|
||||
OO_PS4_TOOLCHAIN=~/PS4Toolchain/OpenOrbis/PS4Toolchain \
|
||||
GOLDHEN_SDK=/tmp/ghsdk LLD=/Users/mac/lldbuild/build/bin/ld.lld \
|
||||
make -C plugin
|
||||
# -> plugin/build/orbisrpc_plugin.prx
|
||||
```
|
||||
|
||||
GoldHEN auto-loads payloads from `/data/GoldHEN/payloads/` at boot and keeps
|
||||
them running in the background while games run in the foreground.
|
||||
Then deploy:
|
||||
|
||||
Also make the config dir + seed the config:
|
||||
```
|
||||
put plugin/build/orbisrpc_plugin.prx -> /data/GoldHEN/plugins/orbisrpc_plugin.prx
|
||||
```
|
||||
|
||||
## 2. plugins.ini
|
||||
|
||||
`/data/GoldHEN/plugins.ini` must list the plugin. The `[default]` section loads
|
||||
it for ANY game title, so presence auto-starts every time a game launches:
|
||||
|
||||
```
|
||||
[default]
|
||||
/data/GoldHEN/plugins/orbisrpc_plugin.prx=true
|
||||
```
|
||||
|
||||
Existing per-title sections (e.g. the GTA menu `[CUSA00411]`) are untouched.
|
||||
A ready-made file lives at `plugin/plugins.ini.ps4`.
|
||||
|
||||
## 3. Seed the config
|
||||
|
||||
```
|
||||
mkdir /data/orbisRPC
|
||||
put config/config.json -> /data/orbisRPC/config.json
|
||||
```
|
||||
|
||||
## 2. First boot check
|
||||
The plugin reads `/data/orbisRPC/config.json` (cfg.h: `CFG_PATH`), logs to
|
||||
`/data/orbisRPC/log.txt` (`LOG_PATH`), and caches per-game state in
|
||||
`/data/orbisRPC/.lastgame/`.
|
||||
|
||||
Reboot (or run the payload from the GoldHEN loader) and check:
|
||||
|
||||
```
|
||||
get /data/orbisRPC/log.txt
|
||||
```
|
||||
|
||||
Expected lines:
|
||||
|
||||
```
|
||||
[..] config not found at /data/orbisRPC/config.json; defaults applied (only first run)
|
||||
[..] FATAL: set client_id + auth_code (or refresh) in /data/orbisRPC/config.json
|
||||
```
|
||||
|
||||
The FATAL exit is expected until you supply Discord credentials (step 3).
|
||||
|
||||
## 3. One-time Discord app setup
|
||||
## 4. One-time Discord app setup
|
||||
|
||||
1. Open https://discord.com/developers/applications -> New Application -> name it
|
||||
(e.g. `orbisRPC`) -> Create.
|
||||
@@ -63,30 +74,27 @@ The FATAL exit is expected until you supply Discord credentials (step 3).
|
||||
refused is fine — just copy the `code=XXXX` part).
|
||||
7. Paste that code into `config.json` as `auth_code`.
|
||||
|
||||
## 4. Reboot + validate
|
||||
## 5. Launch a game + validate
|
||||
|
||||
Reboot the console. `log.txt` should show:
|
||||
Launch any game (e.g. GTA V). The plugin loads into the game process, reads the
|
||||
title id via the GoldHEN SDK, and starts the daemon. Check:
|
||||
|
||||
```
|
||||
get /data/orbisRPC/log.txt
|
||||
```
|
||||
|
||||
Expected lines:
|
||||
|
||||
```
|
||||
[..] token refreshed, expires_in=604799s
|
||||
[..] gateway connected, hb=41250ms
|
||||
```
|
||||
|
||||
Play a game. After the poll interval (`poll_interval_s`, default 12 s):
|
||||
|
||||
```
|
||||
[..] detect: active name=<Game> tid=CUSAxxxxx
|
||||
[..] presence: <Game>
|
||||
```
|
||||
|
||||
Your Discord profile now shows **Playing <Game>** with a timer. Exit to the
|
||||
home screen and presence clears:
|
||||
Your Discord profile now shows **Playing <Game>** with a timer. Exit to the home
|
||||
screen and presence clears (the plugin unloads with the game process).
|
||||
|
||||
```
|
||||
[..] presence: (cleared)
|
||||
```
|
||||
|
||||
## 5. Token lifecycle
|
||||
## 6. Token lifecycle
|
||||
|
||||
The daemon exchanges the `auth_code` once for an access + refresh token
|
||||
(`token_expires_at`, `refresh_token` in config). It refreshes automatically
|
||||
@@ -98,12 +106,13 @@ Config lives on the PS4 at `/data/orbisRPC/config.json` — no PC needed at runt
|
||||
|
||||
| Symptom | Cause / fix |
|
||||
|---|---|
|
||||
| `FATAL: set client_id` | config missing / `SET_ME` placeholder. Fill in step 3. |
|
||||
| `oauth failed rc=401` | wrong client_id/secret, or the auth_code expired (10 min). Re-do step 3.6. |
|
||||
| no `log.txt` at all after launching a game | plugin didn't load: check `plugins.ini` has the `[default]` section + correct path, and GoldHEN has `[PluginLoader] PluginLoader_Enabled=1` in its config.ini. |
|
||||
| `FATAL: set client_id` | config missing / `SET_ME` placeholder. Fill in step 4. |
|
||||
| `oauth failed rc=401` | wrong client_id/secret, or the auth_code expired (10 min). Re-do step 4.6. |
|
||||
| `no 101:` in log | Discord blocked the TLS handshake — check clock / network. |
|
||||
| `gateway dropped` every poll | WS keepalive mismatch; the reconnect loop is automatic. |
|
||||
| presence shows but clears instantly | foreground detection returned active=0 (home screen). |
|
||||
| game name shows `CUSAxxxxx` | app.xml/app.db lookup didn't find a human name; titleId fallback. |
|
||||
| plugin loads into a system app | `plugin.c` filters non-game titleids (NPXS...) and skips them. |
|
||||
|
||||
## Build on Mac (if rebuilding)
|
||||
|
||||
@@ -111,5 +120,20 @@ Config lives on the PS4 at `/data/orbisRPC/config.json` — no PC needed at runt
|
||||
OO_PS4_TOOLCHAIN=~/PS4Toolchain/OpenOrbis/PS4Toolchain \
|
||||
LLD=/Users/mac/lldbuild/build/bin/ld.lld \
|
||||
./scripts/build.sh
|
||||
# -> build/orbisrpc.fself
|
||||
# -> build/orbisrpc.elf (payload ELF, BinLoader route — not auto-loaded)
|
||||
|
||||
OO_PS4_TOOLCHAIN=~/PS4Toolchain/OpenOrbis/PS4Toolchain \
|
||||
GOLDHEN_SDK=/tmp/ghsdk LLD=/Users/mac/lldbuild/build/bin/ld.lld \
|
||||
make -C plugin
|
||||
# -> plugin/build/orbisrpc_plugin.prx (the deployed artifact)
|
||||
```
|
||||
|
||||
## Repo layout
|
||||
|
||||
```
|
||||
orbisrpc/daemon.c shared daemon loop (payload + plugin use it)
|
||||
orbisrpc/detect.c foreground detection + titleid->name resolution
|
||||
plugin/plugin.c GoldHEN plugin entry (plugin_load / plugin_unload)
|
||||
plugin/Makefile builds orbisrpc_plugin.prx against GoldHEN SDK
|
||||
plugin/plugins.ini.ps4 ready-to-deploy plugins.ini with [default] section
|
||||
```
|
||||
@@ -0,0 +1,136 @@
|
||||
/* daemon.c - orbisRPC core loop, usable from the ELF payload (main.c) or from a
|
||||
* GoldHEN plugin. When `fixed_game_name` is non-NULL the daemon posts presence
|
||||
* for that game unconditionally (the plugin runs inside the game process and
|
||||
* already knows the title); otherwise it detects the foreground game.
|
||||
*
|
||||
* A `stop` flag is polled so a plugin_unload() can shut the loop down cleanly.
|
||||
*/
|
||||
#include "cfg.h"
|
||||
#include "log.h"
|
||||
#include "http.h"
|
||||
#include "ws.h"
|
||||
#include "discord.h"
|
||||
#include "detect.h"
|
||||
#include <orbis/UserService.h>
|
||||
#include <orbis/libkernel.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
extern cfg_t g_cfg;
|
||||
|
||||
static volatile int s_stop = 0;
|
||||
|
||||
/* Tells a running daemon_run() to shut down (called from plugin_unload). */
|
||||
void daemon_request_stop(void){ s_stop = 1; }
|
||||
void daemon_clear_stop(void){ s_stop = 0; }
|
||||
|
||||
static int ensure_token(void){
|
||||
int64_t now=time(NULL);
|
||||
if(g_cfg.access_token[0] && now < g_cfg.token_expires_at - 120) return 0; /* fresh */
|
||||
char at[256]="", rt[256]=""; int64_t exp=0;
|
||||
int rc = http_oauth_token(g_cfg.client_id, g_cfg.client_secret,
|
||||
g_cfg.auth_code,
|
||||
g_cfg.refresh_token[0]?g_cfg.refresh_token:NULL,
|
||||
at, sizeof at, g_cfg.refresh_token[0]?rt:NULL, g_cfg.refresh_token[0]?sizeof g_cfg.refresh_token:0,
|
||||
&exp);
|
||||
if(rc==0 && at[0]){
|
||||
strncpy(g_cfg.access_token, at, sizeof g_cfg.access_token-1);
|
||||
if(rt[0]) strncpy(g_cfg.refresh_token, rt, sizeof g_cfg.refresh_token-1);
|
||||
g_cfg.token_expires_at = exp;
|
||||
memset(g_cfg.auth_code,0,sizeof g_cfg.auth_code); /* one-time use */
|
||||
cfg_save(CFG_PATH, &g_cfg);
|
||||
log_msg("token refreshed, expires_in=%lds", (long)(exp-now));
|
||||
return 0;
|
||||
}
|
||||
log_msg("oauth failed rc=%d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void set_game_presence(discord_t *d, const char *name){
|
||||
if(!name||!*name){ discord_clear_presence(d); return; }
|
||||
discord_set_presence(d,
|
||||
g_cfg.presence_state[0]?g_cfg.presence_state:"On PS4", /* state */
|
||||
name, /* details = game name */
|
||||
1, 1, /* party */
|
||||
"orbisrpc:playing", /* large_image key (asset) */
|
||||
name); /* large_text */
|
||||
log_msg("presence: %s", name);
|
||||
}
|
||||
|
||||
/* fixed_game_name != NULL -> post presence for that game only, no detection.
|
||||
* NULL -> poll the foreground app like the original payload daemon. */
|
||||
int daemon_run(const char *fixed_game_name){
|
||||
s_stop = 0;
|
||||
mkdir(DATA_DIR, 0777); /* plugins/payloads may create their own dir */
|
||||
log_init(LOG_PATH);
|
||||
cfg_load(CFG_PATH, &g_cfg);
|
||||
if(!g_cfg.enabled){ log_msg("disabled in config; exiting"); log_close(); return 0; }
|
||||
if(!g_cfg.client_id[0] || strcmp(g_cfg.client_id,"SET_ME")==0){
|
||||
log_msg("FATAL: set client_id + auth_code (or refresh) in %s", CFG_PATH);
|
||||
log_close();
|
||||
return 1;
|
||||
}
|
||||
discord_t dc;
|
||||
/* keep the daemon alive: transient OAuth/gateway failures retry instead
|
||||
* of exiting (a GoldHEN plugin only runs while the game process lives) */
|
||||
for(;;){ /* outer: token refresh + fresh connect */
|
||||
if(s_stop) break;
|
||||
if(ensure_token()!=0){
|
||||
log_msg("no valid token; retry in %ds", g_cfg.poll_interval_s);
|
||||
sleep((unsigned)g_cfg.poll_interval_s);
|
||||
continue;
|
||||
}
|
||||
if(discord_connect(&dc, g_cfg.access_token, 0)!=0){
|
||||
log_msg("gateway connect failed; retry in %ds", g_cfg.poll_interval_s);
|
||||
sleep((unsigned)g_cfg.poll_interval_s);
|
||||
continue;
|
||||
}
|
||||
char last_name[128]="";
|
||||
int64_t last_change = 0;
|
||||
while(1){ /* inner: live session */
|
||||
if(s_stop) break;
|
||||
char name[128]=""; char path[128]="";
|
||||
if(fixed_game_name){
|
||||
strncpy(name, fixed_game_name, sizeof name-1);
|
||||
}else{
|
||||
int active = detect_foreground_active();
|
||||
if(active && detect_current_game(name,sizeof name,path,sizeof path)==0 && name[0]){
|
||||
/* ok */
|
||||
}else{
|
||||
name[0]=0;
|
||||
}
|
||||
}
|
||||
if(name[0]){
|
||||
int64_t now=time(NULL);
|
||||
if(strcmp(name,last_name)!=0 || now-last_change > 300){
|
||||
set_game_presence(&dc, name);
|
||||
strncpy(last_name, name, sizeof last_name-1);
|
||||
last_change = now;
|
||||
}
|
||||
} else {
|
||||
if(last_name[0] || fixed_game_name==NULL){
|
||||
set_game_presence(&dc, NULL); /* clear when nothing foreground */
|
||||
last_name[0]=0; last_change=time(NULL);
|
||||
}
|
||||
}
|
||||
if(discord_tick(&dc) != 0){
|
||||
log_msg("gateway dropped; reconnecting...");
|
||||
if(discord_reconnect(&dc)!=0){
|
||||
ws_close(&dc.ws);
|
||||
break; /* outer loop: full retry (token + connect) */
|
||||
}
|
||||
last_name[0]=0; /* force re-push of the current game */
|
||||
}
|
||||
int iv = g_cfg.poll_interval_s;
|
||||
for(int i=0; i<iv && !s_stop; i++) usleep(1000000); /* interruptible poll */
|
||||
}
|
||||
ws_close(&dc.ws);
|
||||
}
|
||||
discord_clear_presence(&dc);
|
||||
ws_close(&dc.ws);
|
||||
log_close();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* daemon.h - shared orbisRPC daemon loop (payload ELF and GoldHEN plugin). */
|
||||
#ifndef DAEMON_H
|
||||
#define DAEMON_H
|
||||
/* fixed_game_name != NULL: post presence for that game only (plugin mode).
|
||||
* NULL: poll the foreground app (payload mode). Returns 0 on clean exit. */
|
||||
int daemon_run(const char *fixed_game_name);
|
||||
void daemon_request_stop(void);
|
||||
void daemon_clear_stop(void);
|
||||
#endif
|
||||
@@ -155,4 +155,16 @@ int detect_current_game(char *out_name, size_t cap, char *out_path, size_t p_cap
|
||||
}
|
||||
if(out_path){ snprintf(out_path, p_cap, "/data/orbisRPC/.lastgame/%s", titleId[0]?titleId:"unknown"); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Resolve a display name for a KNOWN title id (plugin mode: the plugin is
|
||||
* loaded into the game process and knows the titleid from the GoldHEN SDK,
|
||||
* so we skip the foreground-app heuristics entirely). */
|
||||
int detect_name_for_title(const char *titleId, char *out_name, size_t cap){
|
||||
if(!titleId || !titleId[0] || !out_name || cap==0) return -1;
|
||||
if(appxml_title(titleId, out_name, cap)==0) return 0;
|
||||
appdb_title(titleId, out_name, cap);
|
||||
if(out_name[0]) return 0;
|
||||
strncpy(out_name, titleId, cap-1); out_name[cap-1]=0;
|
||||
return 0;
|
||||
}
|
||||
@@ -6,4 +6,6 @@
|
||||
int detect_current_game(char *out_name, size_t cap, char *out_path, size_t p_cap);
|
||||
/* Returns nonzero if a foreground user app is running */
|
||||
int detect_foreground_active(void);
|
||||
/* Resolve a display name for a KNOWN title id (plugin mode). */
|
||||
int detect_name_for_title(const char *titleId, char *out_name, size_t cap);
|
||||
#endif
|
||||
+5
-106
@@ -1,110 +1,9 @@
|
||||
/* main.c - orbisRPC daemon: detect game -> Discord presence. Runs as GoldHEN payload. */
|
||||
#include "cfg.h"
|
||||
#include "log.h"
|
||||
#include "http.h"
|
||||
#include "ws.h"
|
||||
#include "discord.h"
|
||||
#include "detect.h"
|
||||
#include "jsonlite.h"
|
||||
#include "b64.h"
|
||||
#include <orbis/UserService.h>
|
||||
#include <orbis/libkernel.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
extern cfg_t g_cfg;
|
||||
|
||||
static int ensure_token(void){
|
||||
int64_t now=time(NULL);
|
||||
if(g_cfg.access_token[0] && now < g_cfg.token_expires_at - 120) return 0; /* fresh */
|
||||
char at[256]="", rt[256]=""; int64_t exp=0;
|
||||
int rc = http_oauth_token(g_cfg.client_id, g_cfg.client_secret,
|
||||
g_cfg.auth_code,
|
||||
g_cfg.refresh_token[0]?g_cfg.refresh_token:NULL,
|
||||
at, sizeof at, g_cfg.refresh_token[0]?rt:NULL, g_cfg.refresh_token[0]?sizeof g_cfg.refresh_token:0,
|
||||
&exp);
|
||||
if(rc==0 && at[0]){
|
||||
strncpy(g_cfg.access_token, at, sizeof g_cfg.access_token-1);
|
||||
if(rt[0]) strncpy(g_cfg.refresh_token, rt, sizeof g_cfg.refresh_token-1);
|
||||
g_cfg.token_expires_at = exp;
|
||||
memset(g_cfg.auth_code,0,sizeof g_cfg.auth_code); /* one-time use */
|
||||
cfg_save(CFG_PATH, &g_cfg);
|
||||
log_msg("token refreshed, expires_in=%lds", (long)(exp-now));
|
||||
return 0;
|
||||
}
|
||||
log_msg("oauth failed rc=%d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void set_game_presence(discord_t *d, const char *name){
|
||||
if(!name||!*name){ discord_clear_presence(d); return; }
|
||||
discord_set_presence(d,
|
||||
g_cfg.presence_state[0]?g_cfg.presence_state:"On PS4", /* state */
|
||||
name, /* details = game name */
|
||||
1, 1, /* party */
|
||||
"orbisrpc:playing", /* large_image key (asset) */
|
||||
name); /* large_text */
|
||||
log_msg("presence: %s", name);
|
||||
}
|
||||
/* main.c - orbisRPC payload entry: detect game -> Discord presence.
|
||||
* Runs as a GoldHEN payload (BinLoader push) or standalone ELF. */
|
||||
#include "daemon.h"
|
||||
#include <stddef.h>
|
||||
|
||||
int main(int argc, char **argv){
|
||||
(void)argc;(void)argv;
|
||||
mkdir(DATA_DIR, 0777); /* payloads may create their own dir */
|
||||
log_init(LOG_PATH);
|
||||
cfg_load(CFG_PATH, &g_cfg);
|
||||
if(!g_cfg.enabled){ log_msg("disabled in config; exiting"); return 0; }
|
||||
if(!g_cfg.client_id[0] || strcmp(g_cfg.client_id,"SET_ME")==0){
|
||||
log_msg("FATAL: set client_id + auth_code (or refresh) in %s", CFG_PATH);
|
||||
return 1;
|
||||
}
|
||||
discord_t dc;
|
||||
/* keep the daemon alive: transient OAuth/gateway failures retry instead
|
||||
* of exiting (a GoldHEN payload only runs once per boot) */
|
||||
for(;;){ /* outer: token refresh + fresh connect */
|
||||
if(ensure_token()!=0){
|
||||
log_msg("no valid token; retry in %ds", g_cfg.poll_interval_s);
|
||||
sleep((unsigned)g_cfg.poll_interval_s);
|
||||
continue;
|
||||
}
|
||||
if(discord_connect(&dc, g_cfg.access_token, 0)!=0){
|
||||
log_msg("gateway connect failed; retry in %ds", g_cfg.poll_interval_s);
|
||||
sleep((unsigned)g_cfg.poll_interval_s);
|
||||
continue;
|
||||
}
|
||||
char last_name[128]="";
|
||||
int64_t last_change = 0;
|
||||
while(1){ /* inner: live session */
|
||||
char name[128]=""; char path[128]="";
|
||||
int active = detect_foreground_active();
|
||||
if(active && detect_current_game(name,sizeof name,path,sizeof path)==0 && name[0]){
|
||||
int64_t now=time(NULL);
|
||||
if(strcmp(name,last_name)!=0 || now-last_change > 300){
|
||||
set_game_presence(&dc, name);
|
||||
strncpy(last_name, name, sizeof last_name-1);
|
||||
last_change = now;
|
||||
}
|
||||
} else {
|
||||
if(last_name[0] || !active){
|
||||
set_game_presence(&dc, NULL); /* clear when nothing foreground */
|
||||
last_name[0]=0; last_change=time(NULL);
|
||||
}
|
||||
}
|
||||
if(discord_tick(&dc) != 0){
|
||||
log_msg("gateway dropped; reconnecting...");
|
||||
if(discord_reconnect(&dc)!=0){
|
||||
ws_close(&dc.ws);
|
||||
break; /* outer loop: full retry (token + connect) */
|
||||
}
|
||||
last_name[0]=0; /* force re-push of the current game */
|
||||
}
|
||||
usleep((useconds_t)(g_cfg.poll_interval_s * 1000000));
|
||||
}
|
||||
}
|
||||
discord_clear_presence(&dc);
|
||||
ws_close(&dc.ws);
|
||||
log_close();
|
||||
return 0;
|
||||
return daemon_run(NULL);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# orbisRPC GoldHEN plugin build (plugin/).
|
||||
# Builds plugin.prx — a GoldHEN plugin that runs the orbisRPC daemon inside the
|
||||
# game process, so presence auto-starts at every boot with no PC involvement.
|
||||
|
||||
DEBUG_FLAGS = -D__FINAL__=1
|
||||
TYPE = final
|
||||
|
||||
OUTPUT_PRX := orbisrpc_plugin
|
||||
BUILD_DIR := build
|
||||
TARGET := $(BUILD_DIR)/$(OUTPUT_PRX).prx
|
||||
TARGET_ELF := $(BUILD_DIR)/$(OUTPUT_PRX).elf
|
||||
|
||||
# Root vars
|
||||
TOOLCHAIN := $(OO_PS4_TOOLCHAIN)
|
||||
GH_SDK := $(GOLDHEN_SDK)
|
||||
ROOT := ..
|
||||
SRCDIR := .
|
||||
ORBISRPC := $(ROOT)/orbisrpc
|
||||
INTDIR := $(BUILD_DIR)
|
||||
|
||||
# Our daemon sources (payload main.c is NOT part of the plugin; plugin.c is the entry)
|
||||
DAEMON_SRC := $(ORBISRPC)/log.c $(ORBISRPC)/cfg.c $(ORBISRPC)/jsonlite.c $(ORBISRPC)/b64.c \
|
||||
$(ORBISRPC)/http.c $(ORBISRPC)/ws.c $(ORBISRPC)/discord.c $(ORBISRPC)/detect.c \
|
||||
$(ORBISRPC)/daemon.c
|
||||
PLUGIN_SRC := $(SRCDIR)/plugin.c
|
||||
CFILES := $(PLUGIN_SRC) $(DAEMON_SRC)
|
||||
OBJS := $(patsubst %.c,$(INTDIR)/%.o,$(notdir $(CFILES)))
|
||||
|
||||
LIBS := -lSceLibcInternal -lSceNet -lSceNetCtl -lSceLibreSSL -lSceSsl -lSceHttp \
|
||||
-lSceSysmodule -lSceUserService -lSceAppInstUtil -lSceAppContent \
|
||||
-lGoldHEN_Hook -lkernel
|
||||
|
||||
EXTRAFLAGS := $(DEBUG_FLAGS) -std=gnu11 -Wall -Wno-unused \
|
||||
-Wno-int-conversion -Wno-incompatible-pointer-types \
|
||||
-Wno-address-of-packed-member
|
||||
|
||||
CFLAGS := --target=x86_64-pc-freebsd12-elf -fPIC -funwind-tables -c $(EXTRAFLAGS) \
|
||||
-isysroot $(TOOLCHAIN) -isystem $(TOOLCHAIN)/include \
|
||||
-I$(GH_SDK)/include -I$(ORBISRPC) -I$(SRCDIR)
|
||||
LDFLAGS := -m elf_x86_64 -pie --script $(TOOLCHAIN)/link.x -e _init --eh-frame-hdr \
|
||||
-L$(TOOLCHAIN)/lib -L$(GH_SDK) $(LIBS)
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
CC := /usr/local/opt/llvm/bin/clang
|
||||
LD := $(LLD)
|
||||
CDIR := macos
|
||||
FSUFFIX := -macos
|
||||
else
|
||||
CC := clang
|
||||
LD := ld.lld
|
||||
CDIR := linux
|
||||
FSUFFIX :=
|
||||
endif
|
||||
|
||||
_unused := $(shell mkdir -p $(INTDIR))
|
||||
|
||||
$(INTDIR)/%.o: $(ORBISRPC)/%.c
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(INTDIR)/plugin.o: $(SRCDIR)/plugin.c
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(LD) $(GH_SDK)/build/crtprx.o $(OBJS) -o $(TARGET_ELF) $(LDFLAGS)
|
||||
$(TOOLCHAIN)/bin/$(CDIR)/create-fself$(FSUFFIX) -in=$(TARGET_ELF) -out=$(TARGET_ELF).oelf \
|
||||
--lib=$(TARGET) --paid 0x3800000000000011
|
||||
|
||||
.PHONY: clean
|
||||
.DEFAULT_GOAL := all
|
||||
|
||||
all: $(TARGET)
|
||||
@echo "plugin -> $(TARGET)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
@@ -0,0 +1,92 @@
|
||||
/* plugin.c - orbisRPC as a GoldHEN plugin (.prx loaded into the game process).
|
||||
*
|
||||
* GoldHEN loads plugins per game title via /data/GoldHEN/plugins.ini. A
|
||||
* plugin runs inside the game's process, so:
|
||||
* - we KNOW which game is running: sys_sdk_proc_info() -> procInfo.titleid
|
||||
* - the game has full network access (this is the whole point)
|
||||
* - it auto-starts at every boot/jailbreak with zero PC involvement
|
||||
*
|
||||
* plugin_load() resolves the titleid to a display name and starts the shared
|
||||
* daemon thread (daemon.c) which handles token refresh, the Discord gateway
|
||||
* and presence. plugin_unload() stops it cleanly.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <orbis/libkernel.h>
|
||||
#include "Common.h"
|
||||
#include "daemon.h"
|
||||
#include "detect.h"
|
||||
|
||||
/* Only post presence for real game titles (CUSA/PPSA/etc). System apps
|
||||
* (NPXS..., shell) launching would otherwise post bogus "playing" state. */
|
||||
static int is_game_titleid(const char *t){
|
||||
static const char *pfx[] = { "CUSA", "PPSA", "PCSE", "PCJS", "EPSA", "PCSB", NULL };
|
||||
for(int i=0; pfx[i]; i++) if(strncmp(t, pfx[i], 4)==0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define attr_public __attribute__((visibility("default")))
|
||||
#define attr_module_hidden __attribute__((weak)) __attribute__((visibility("hidden")))
|
||||
|
||||
attr_public const char *g_pluginName = "orbisrpc";
|
||||
attr_public const char *g_pluginDesc = "Discord Rich Presence for PS4";
|
||||
attr_public const char *g_pluginAuth = "orbisRPC";
|
||||
attr_public uint32_t g_pluginVersion = 0x00000100; /* 1.00 */
|
||||
|
||||
struct proc_info procInfo;
|
||||
|
||||
static void *daemon_thread(void *args){
|
||||
/* args = display name of the game we are loaded into */
|
||||
char name[128];
|
||||
strncpy(name, (const char *)args, sizeof name - 1);
|
||||
name[sizeof name - 1] = 0;
|
||||
daemon_clear_stop();
|
||||
daemon_run(name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t attr_public plugin_load(int32_t argc, const char *argv[]){
|
||||
(void)argc; (void)argv;
|
||||
klog("[orbisrpc] <%s\\Ver.0x%08x> %s\n", g_pluginName, g_pluginVersion, __func__);
|
||||
klog("[orbisrpc] Plugin Author(s): %s\n", g_pluginAuth);
|
||||
|
||||
if(sys_sdk_proc_info(&procInfo) != 0){
|
||||
klog("[orbisrpc] sys_sdk_proc_info failed; not starting\n");
|
||||
return 0;
|
||||
}
|
||||
klog("[orbisrpc] loaded into process: pid=%d name=%s titleid=%s\n",
|
||||
procInfo.pid, procInfo.name, procInfo.titleid);
|
||||
|
||||
if(!is_game_titleid(procInfo.titleid)){
|
||||
klog("[orbisrpc] not a game title (system app?); not starting daemon\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Resolve a display name for the title we are loaded into. */
|
||||
static char game_name[128];
|
||||
detect_name_for_title(procInfo.titleid, game_name, sizeof game_name);
|
||||
klog("[orbisrpc] game: %s\n", game_name);
|
||||
|
||||
OrbisPthread thread;
|
||||
scePthreadCreate(&thread, NULL, daemon_thread, game_name, "orbisrpc_daemon");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t attr_public plugin_unload(int32_t argc, const char *argv[]){
|
||||
(void)argc; (void)argv;
|
||||
klog("[orbisrpc] <%s\\Ver.0x%08x> %s\n", g_pluginName, g_pluginVersion, __func__);
|
||||
daemon_request_stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t attr_module_hidden module_start(int64_t argc, const void *args){
|
||||
(void)argc; (void)args;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t attr_module_hidden module_stop(int64_t argc, const void *args){
|
||||
(void)argc; (void)args;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
; GoldHEN plugins.ini — GTA V mod menu + orbisRPC Discord presence
|
||||
;
|
||||
; [default] loads plugins for ANY game title, so orbisRPC auto-starts
|
||||
; whenever a game launches (no PC needed).
|
||||
;
|
||||
[default]
|
||||
/data/GoldHEN/plugins/orbisrpc_plugin.prx=true
|
||||
|
||||
; Lotus SPRX (primary mod menu — requires GTA V 1.48 update)
|
||||
; Press R1 + Square in-game to open
|
||||
;
|
||||
[CUSA00411]
|
||||
/data/GoldHEN/plugins/gtaPayload_148.sprx=true
|
||||
|
||||
[CUSA00419]
|
||||
/data/GoldHEN/plugins/gtaPayload_148.sprx=true
|
||||
|
||||
; Scorpion Menu v1.2B (alternate mod menu — backup option)
|
||||
; Uncomment these lines if you prefer Scorpion over Lotus:
|
||||
;
|
||||
;[CUSA00411]
|
||||
;/data/GoldHEN/plugins/ScorpionMenu-v1.2b-148.prx=true
|
||||
;
|
||||
;[CUSA00419]
|
||||
;/data/GoldHEN/plugins/ScorpionMenu-v1.2b-148.prx=true
|
||||
+1
-1
@@ -19,7 +19,7 @@ cd "$ROOT"
|
||||
export OO_PS4_TOOLCHAIN="$SDK"
|
||||
OUT="$ROOT/build"; mkdir -p "$OUT"
|
||||
echo "=== compiling ==="
|
||||
for f in log cfg jsonlite b64 http ws detect discord main; do
|
||||
for f in log cfg jsonlite b64 http ws detect discord daemon main; do
|
||||
"$CC" $CFLAGS -c -o "$OUT/$f.o" "orbisrpc/$f.c" || { echo "compile $f FAILED"; exit 1; }
|
||||
done
|
||||
echo "=== linking ($LD) ==="
|
||||
|
||||
Reference in new issue
Block a user