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:
SirHumza committed 2026-08-15 00:23:33 +02:00
1 parent c1565c3144
commit d5d1957668
11 files changed
+429 -152

No files matched your search

+2
View File
@@ -2,3 +2,5 @@ build/
*.o
*.err
/tmp/
config/config.ps4.json
plugin/build/
+69 -45
View File
@@ -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
```
+136
View File
@@ -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;
}
+9
View File
@@ -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
+12
View File
@@ -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;
}
+2
View File
@@ -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
View File
@@ -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);
}
+76
View File
@@ -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)
+92
View File
@@ -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;
}
+25
View File
@@ -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
View File
@@ -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) ==="