fix: harden IPC transport and process-local settings store

Serialize IPC_Client with a per-target mutex and full-frame send/recv plus
NUL-terminated payloads. Escape reply JSON vars, null-check daemon FS paths,
handle BREW_UTIL_TEST_CONNECTION, and track BREW_LAST_RET from the last reply.

Introduce SettingsStore (mutex snapshot/update), dual-path config mtime gating,
and fan IPC persistence to twin config files. Stop cold-start toolbox delay on
util_booted; fix jailbreak hijack retry to stop only when dead or maxed.

Add host tests for IPC wire helpers, toolbox timing policy, and hijack retry.
This commit is contained in:
Hexwell committed 2026-07-10 17:56:21 +08:00
1 parent c3027f2c67
commit ece77ea433
32 files changed
+805 -171

No files matched your search

+7 -4
View File
@@ -247,10 +247,13 @@ ipc_client.* 注入侧客户端
#### 配置分层
```
orion::Settings 持久化 schema(单文件双路径)
g_settings 各进程持有的 Settings 实例
运行时原子量 仅 util:g_legacy_cmd_server / g_legacy_cmd_server_exit(热路径)
OverlayLayout 仅 shellui:由 overlay_pos 派生的像素坐标
orion::Settings 持久化 schema(双路径 twin:primary + shellui)
orion::SettingsStore 进程内线程安全真相源(mutex + snapshot/store/update)
g_settings daemon/util:SettingsStore;shellui:Settings(UI 线程)
LoadSettings() 统一 bool 契约:刷新 store;缺文件用默认并成功
mtime 门控 settings_config_newest_mtime — 任一 twin 更新即失效
运行时原子量 仅 util:g_legacy_cmd_server / g_legacy_cmd_server_exit(热路径)
OverlayLayout 仅 shellui:由 overlay_pos 派生的像素坐标
```
### 2.8 主机工具(`scripts/`)
+22 -27
View File
@@ -70,13 +70,13 @@ NineS ──► orion_proc(不再自带 proc 副本)
NineS 已避免 per-call authid 翻转;bootstrapper/util attach 仍在翻转。util 金手指走 `pt_attach_proc`(翻转)+ NineS `pt_mmap`(不翻转)——**同一条路径两套策略**。
### 2.2 Settings:单 schema、三份真相
### 2.2 Settings:单 schema、三份真相 — **已落地(2026-07-10)**
- 进程内各有一份 `g_settings`(daemon / util / shellui)——多进程可接受。
- **同一进程内无锁**,IPC 写 + jailbreak 线程读 → 数据竞争。
- Fan IPC 只改内存,不 `settings_save`;与 shellui 磁盘双写 twin 模型不一致。
- util:`void LoadSettings()` 定义 vs 多处 `bool LoadSettings()` 声明(ODR/潜在 UB)。
- daemon mtime 缓存:只盯「第一个存在的 path」,双路径不同步时可能误跳过重载。
- 进程内各有一份 store(daemon / util:`SettingsStore`;shellui:`Settings`)——多进程可接受。
- daemon/util:`SettingsStore` mutex + `snapshot`/`store`/`update`,消除 IPC 写与 worker 读竞争。
- Fan IPC:`update` 后 `settings_save` + `SettingsNoteDiskWritten`(twin 落盘)。
- 统一 `bool LoadSettings()`(缺文件用默认并成功);util ODR 已修。
- mtime 门控:`settings_config_newest_mtime` 取双路径 max,任一 twin 更新即重载。
### 2.3 Ready 标志语义泄漏
@@ -169,28 +169,22 @@ Bootstrapper 只 clear util/kstuff/daemon/toolbox,**不清** `util_booted` / `
- Code cave:`MAP_FIXED` 或按返回 VA 写
- elfldr:`sizeof(Elf64_Shdr)`;mprotect 失败 fail-closed
2. **IPC 硬化**(一处修、全树受益)
- 全帧 recv + 强制 NUL
- cJSON 转义 reply
- path 全 null-check
- 补 `BREW_UTIL_TEST_CONNECTION`;修或删 `BREW_LAST_RET`
- shellui:`IPC_Client` 加 mutex 或每调用独立连接
2. **IPC 硬化** — **已落地(2026-07-10)**
- 全帧 `recv_full`/`send_full` + `ipc_message_force_nul`
- `ipc_json_escape` + compact escaped `ipc_format_reply_body`
- daemon path 全 null-check;`BREW_UTIL_TEST_CONNECTION`;`BREW_LAST_RET` 记 process last error
- `IPC_Client` 实例 mutex + full-frame 收发
3. **Ready / Settings 语义收口**
- rest delay 只绑 rest 信号,不要绑 `util_booted`
- bootstrapper clear `util_booted` + `fps_overlay`
- 统一 `LoadSettings` 契约;fan 走 `settings_save`
- `g_settings` 快照或读写锁
3. **Ready / Settings 语义收口** — 部分已落地
- rest delay:daemon 冷启动不再因 `util_booted` sleep(`orion/toolbox_timing.h`)
- Settings store / fan 落盘 / `LoadSettings` 契约 — 见 §2.2
- bootstrapper sticky flag clear — 仍待
4. **加深模块(架构债)** — **已落地(2026-07-10)**
- 单一 `liborion_elfldr`(合并三份 pt/elfldr,统一 authid 策略)
- 删除 shellui 本地 `if_exists` 前向声明;走 `orion/platform`
- daemon `get_shellui_pid` / `get_game_pid` 收敛到 `orion_proc`
- 单一 `liborion_elfldr`;shellui `orion/platform`;daemon pid → `orion_proc`
5. **补测**
- IPC 组帧/转义/null path 的 host 测试
- ready 冷启动时序测试(util 先 booted → daemon 不应 sleep rest delay)
- jailbreak 重试条件单元测试
5. **补测** — **已落地(2026-07-10)**
- `test_ipc_harden` / `test_toolbox_timing` / `test_hijack_retry`
---
@@ -198,10 +192,11 @@ Bootstrapper 只 clear util/kstuff/daemon/toolbox,**不清** `util_booted` / `
架构骨架(双 daemon、共享 `liborion_*`、util 承载重 IO、ready 协议)是清晰且在向深模块演进的。
当前最危险的不是「缺库」,而是:
当前仍优先关注:
1. **进程标识混用(appid vs pid)**
2. **存活检测条件写反 + 空指针 jailbreak**
1. **进程标识混用(appid vs pid)**(FPS suspend 路径)
2. **Jailbreak retry / null spawn** — 策略已修,实机仍需回归
3. sticky ready flags(bootstrapper clear)
3. **IPC 未组帧/未转义 + shellui 并发单连接**
4. **ready/settings 生命周期与文档不符**
5. **elfldr/pt/detour 失败未 fail-closed + 三份漂移**
+11 -1
View File
@@ -14,9 +14,19 @@ using clientArgs = orion::IpcClientArgs;
extern bool is_handler_enabled;
void LoadSettings();
/**
* Refresh g_settings from twin config paths when either is newer.
* Returns true if store is usable (including defaults / skip-if-current).
* Missing config is not an error.
*/
bool LoadSettings();
/** After settings_save from this process, refresh mtime gate so we don't thrash. */
void SettingsNoteDiskWritten();
void reply(int sender_socket, bool error, std::string out_var = "Nothing");
/** Last reply error for BREW_LAST_RET (0 = success, -1 = error). */
int daemon_last_ipc_error();
bool remount(const char *dev, const char *path, int mnt_flag);
int change_permissions_recursive(const char *path);
bool test_sb_file(const char *filename);
+3 -2
View File
@@ -29,7 +29,8 @@ enum StartOpts {
TOOLBOX,
};
// Single process store (same schema as util / shellui).
extern orion::Settings g_settings;
// Thread-safe process store (same schema as util / shellui).
// Readers: g_settings.snapshot(). Writers: g_settings.store / update.
extern orion::SettingsStore g_settings;
int launchApp(const char *titleId);
+10
View File
@@ -5,6 +5,7 @@
#include <orion/proc_query.h>
#include <orion/ipc_server.hpp>
#include <msg.hpp>
#include <atomic>
#include <string>
#include <cstring>
#include <cstdio>
@@ -190,7 +191,16 @@ bool test_sb_file(const char *filename) {
}
namespace {
std::atomic<int> g_last_ipc_error{0};
} // namespace
int daemon_last_ipc_error() {
return g_last_ipc_error.load(std::memory_order_relaxed);
}
void reply(int sender_socket, bool error, std::string out_var) {
g_last_ipc_error.store(error ? -1 : 0, std::memory_order_relaxed);
orion::ipc_reply(sender_socket, BREW_RETURN_VALUE, error, out_var);
}
+17 -7
View File
@@ -6,6 +6,8 @@
#include <orion/proc_query.h>
#include <orion/ready.h>
#include <orion/settings.hpp>
#include <orion/toolbox_timing.h>
#include "globalconf.hpp"
#include "dbg/dbg.hpp"
#include "elf/elf.hpp"
#include "hijacker/hijacker.hpp"
@@ -27,8 +29,6 @@ extern const unsigned int fps_elf_size;
int _sceApplicationGetAppId(int pid, int *appid);
}
extern orion::Settings g_settings;
static void SuspendApp(pid_t pid)
{
sceKernelPrepareToSuspendProcess(pid);
@@ -272,11 +272,21 @@ bool cmd_enable_toolbox(){
}
OrionHEN_log("Activating toolbox...");
/* util_booted: util has completed at least one full start (typed ready flag). */
if (orion_ready_is_set(ORION_FLAG_UTIL_BOOTED)) {
LoadSettings();
OrionHEN_log("sleeping for %llu", g_settings.rest_mode_delay_seconds);
sleep(g_settings.rest_mode_delay_seconds);
/*
* Rest_Mode_Delay only on rest resume — never on cold start.
* util_booted is true almost immediately after util starts (before this
* inject), so gating on it alone hung first toolbox load for delay seconds.
* Rest re-activation delay lives in util patch_checker / check_addr_change.
*/
LoadSettings();
{
const uint64_t delay = g_settings.snapshot().rest_mode_delay_seconds;
constexpr bool kRestResume = false; /* daemon cold/direct inject path */
if (orion_toolbox_should_apply_rest_delay(kRestResume, delay)) {
OrionHEN_log("rest delay %llu (rest resume path)",
static_cast<unsigned long long>(delay));
sleep(static_cast<unsigned int>(delay));
}
}
/* Prefer kstuff ready marker when present; fall back to short settle. */
+14 -6
View File
@@ -11,6 +11,9 @@
#include <orion/proc_query.h>
#include <orion/platform.h>
#include <orion/ready.h>
#include <orion/settings.hpp>
#include <orion/hijack_retry.h>
#include "globalconf.hpp"
#include <elfldr_remote.h>
#include "../../extern/cJSON/orion_cjson.hpp"
@@ -27,7 +30,6 @@
#include <string.h>
#include <unistd.h>
extern orion::Settings g_settings;
extern bool is_800;
namespace {
@@ -89,8 +91,11 @@ void *fifo_and_dumper_thread(void *args) noexcept {
pthread_mutex_lock(&jb_lock);
if (g_settings.enable_fan_speed)
set_fan_threshold(g_settings.fan_threshold);
{
const orion::Settings cfg = g_settings.snapshot();
if (cfg.enable_fan_speed)
set_fan_threshold(cfg.fan_threshold);
}
int bappid = 0;
if (!Get_Running_App_TID(tid, bappid)) {
@@ -161,18 +166,21 @@ void *fifo_and_dumper_thread(void *args) noexcept {
do {
spawned = Hijacker::getHijacker(reserved_value);
if (!spawned) {
if (++hijack_retries > 30 || isProcessAlive(reserved_value)) {
++hijack_retries;
OrionHEN_log("is null for PID %d (attempt %d)", reserved_value,
hijack_retries);
if (orion_hijack_retry_should_stop(isProcessAlive(reserved_value),
hijack_retries, 30)) {
orion_notify(true, "Jailbreak failed, PID is invaild");
OrionHEN_log("Jailbreak failed, PID is invaild");
break;
}
}
OrionHEN_log("is null for PID %d", reserved_value);
} while (spawned == nullptr);
if (spawned) {
OrionHEN_log("RIGHT Jailbreak command received: jailbreaking...");
if (g_settings.debug_app_jb_msg)
if (g_settings.snapshot().debug_app_jb_msg)
orion_notify(true, "App (PID %i) has been granted a jailbreak", reserved_value);
spawned->jailbreak(true);
+35 -33
View File
@@ -1,65 +1,67 @@
/* Copyright (C) 2025 OrionHEN / LightningMods */
/* Copyright (C) 2025 OrionHEN / LightningMods
*
* Process-local settings store + dual-path mtime reload gate.
*/
#include "daemon_ops.hpp"
#include "globalconf.hpp"
#include <orion/platform.h>
#include <orion/settings.hpp>
#include <sys/stat.h>
#include <sys/stat.h>
namespace {
struct ConfigState {
/** Max mtime of twin paths at last successful store update. */
time_t last_modified = 0;
bool ever_loaded = false;
};
ConfigState config_state;
void LoadSettings() {
struct stat file_stat {};
const char *paths[] = {orion::kConfigPathPrimary, orion::kConfigPathShellui};
} // namespace
// Prefer primary; fall back to shellui path for mtime / create.
const char *config_path = nullptr;
for (const char *p : paths) {
if (stat(p, &file_stat) == 0) {
config_path = p;
break;
}
orion::SettingsStore g_settings;
bool LoadSettings() {
const time_t newest = orion::settings_config_newest_mtime();
// Skip disk I/O when neither twin is newer than the last applied snapshot.
if (config_state.ever_loaded && !orion::settings_config_is_newer_than(
config_state.last_modified)) {
return true;
}
if (!config_path) {
if (newest == 0) {
OrionHEN_log("[Daemon] Config file not found. Creating default schema...");
if (orion::settings_ensure_default()) {
orion_notify(true, "OrionHEN config created! @ /data/OrionHEN/config.ini");
config_state.last_modified = 0;
}
// Apply defaults even if create failed.
orion::Settings s{};
orion::settings_load(&s);
g_settings = s;
return;
}
// Only reload if file has been modified since last load
if (file_stat.st_mtime <= config_state.last_modified) {
return;
}
OrionHEN_log("[Daemon] Loading Settings from shared schema...");
orion::Settings s{};
if (!orion::settings_load(&s)) {
const bool from_file = orion::settings_load(&s);
if (!from_file && newest != 0) {
orion_notify(true, "Failed to Read the Settings file");
return;
return false;
}
OrionHEN_log("[Daemon] Reading Settings from %s",
orion::settings_last_loaded_path());
if (from_file) {
OrionHEN_log("[Daemon] Reading Settings from %s",
orion::settings_last_loaded_path());
} else {
OrionHEN_log("[Daemon] Using default settings (no config file)");
}
OrionHEN_log("fan_threshold: %d", s.fan_threshold);
OrionHEN_log("enable_fan_speed: %d", s.enable_fan_speed ? 1 : 0);
g_settings = s;
g_settings.store(s);
config_state.last_modified = orion::settings_config_newest_mtime();
config_state.ever_loaded = true;
return true;
}
config_state.last_modified = file_stat.st_mtime;
void SettingsNoteDiskWritten() {
config_state.last_modified = orion::settings_config_newest_mtime();
config_state.ever_loaded = true;
}
+61 -11
View File
@@ -5,6 +5,7 @@
#include "daemon_ops.hpp"
#include "ipc.hpp"
#include <orion/platform.h>
#include <orion/settings.hpp>
#include "../../extern/cJSON/orion_cjson.hpp"
#include "globalconf.hpp"
#include <msg.hpp>
@@ -19,7 +20,7 @@
#include <signal.h>
#include <strings.h>
extern orion::Settings g_settings;
bool copyRecursive(const char *source, const char *destination);
bool copyFile(const char *source, const char *destination, bool for_dumper);
@@ -36,7 +37,6 @@ void handleIPC(clientArgs *client, std::string &inputStr,
std::string path_buf, path_buf2, json_path;
const char *path = nullptr, *dest = nullptr;
char size_buf[0x255];
bool last_ipc_error = false;
std::string out_var = "Nothing"; // default send var
@@ -64,7 +64,9 @@ void handleIPC(clientArgs *client, std::string &inputStr,
break;
}
case BREW_LAST_RET: {
reply(sender_app, last_ipc_error, last_ipc_error ? "1" : "0");
/* Tracks last reply() error for this process (not this call's local). */
const int last = daemon_last_ipc_error();
reply(sender_app, last != 0, last != 0 ? "1" : "0");
break;
}
case BREW_UNUSED_DECRYPT_DIR:
@@ -132,6 +134,11 @@ void handleIPC(clientArgs *client, std::string &inputStr,
break;
case BREW_STAT_CMD: {
path = orion_cjson::string_item(my_json.get(), "path");
if (!path || !*path) {
OrionHEN_log("BREW_STAT_CMD: missing path");
reply(sender_app, true);
break;
}
if (stat(path, &buffer) == 0) {
snprintf(size_buf, sizeof(size_buf), "%ld", buffer.st_size);
OrionHEN_log("%s exists | size %s", path, size_buf);
@@ -143,15 +150,27 @@ void handleIPC(clientArgs *client, std::string &inputStr,
break;
}
case BREW_CALC_DIR_SIZE: {
uint64_t size = calculateTotalSize(orion_cjson::string_item(my_json.get(), "path"));
snprintf(size_buf, sizeof(size_buf), "%lu", size);
OrionHEN_log("size %lu", size_buf);
path = orion_cjson::string_item(my_json.get(), "path");
if (!path || !*path) {
OrionHEN_log("BREW_CALC_DIR_SIZE: missing path");
reply(sender_app, true);
break;
}
uint64_t size = calculateTotalSize(path);
snprintf(size_buf, sizeof(size_buf), "%lu",
static_cast<unsigned long>(size));
OrionHEN_log("size %s", size_buf);
reply(sender_app, false, size_buf);
break;
}
case BREW_COPY_FILE: {
path = orion_cjson::string_item(my_json.get(), "path");
dest = orion_cjson::string_item(my_json.get(), "dest");
if (!path || !*path || !dest || !*dest) {
OrionHEN_log("BREW_COPY_FILE: missing path/dest");
reply(sender_app, true);
break;
}
if (copyFile(path, dest, false)) {
reply(sender_app, false);
} else {
@@ -163,7 +182,13 @@ void handleIPC(clientArgs *client, std::string &inputStr,
case BREW_COPY_DIR: {
path = orion_cjson::string_item(my_json.get(), "path");
dest = orion_cjson::string_item(my_json.get(), "dest");
snprintf(size_buf, sizeof(size_buf), "%lu", calculateTotalSize(path));
if (!path || !*path || !dest || !*dest) {
OrionHEN_log("BREW_COPY_DIR: missing path/dest");
reply(sender_app, true);
break;
}
snprintf(size_buf, sizeof(size_buf), "%lu",
static_cast<unsigned long>(calculateTotalSize(path)));
if (copyRecursive(path, dest)) {
reply(sender_app, false, size_buf);
} else {
@@ -174,6 +199,11 @@ void handleIPC(clientArgs *client, std::string &inputStr,
}
case BREW_DELETE_DIR: {
path = orion_cjson::string_item(my_json.get(), "path");
if (!path || !*path) {
OrionHEN_log("BREW_DELETE_DIR: missing path");
reply(sender_app, true);
break;
}
if (rmtree(path)) {
reply(sender_app, false);
} else {
@@ -182,7 +212,13 @@ void handleIPC(clientArgs *client, std::string &inputStr,
break;
}
case BREW_TEST_SB_FILE: {
reply(sender_app, !test_sb_file(orion_cjson::string_item(my_json.get(), "path")));
path = orion_cjson::string_item(my_json.get(), "path");
if (!path || !*path) {
OrionHEN_log("BREW_TEST_SB_FILE: missing path");
reply(sender_app, true);
break;
}
reply(sender_app, !test_sb_file(path));
break;
}
case BREW_DAEMON_PID: {
@@ -206,18 +242,32 @@ void handleIPC(clientArgs *client, std::string &inputStr,
break;
}
g_settings.enable_fan_speed = enabled;
if (!enabled) {
orion_notify(true, "Fan speed adjustment is disabled.");
set_fan_threshold(77);
const orion::Settings saved = g_settings.update([](orion::Settings &s) {
s.enable_fan_speed = false;
});
if (orion::settings_save(saved)) {
SettingsNoteDiskWritten();
} else {
OrionHEN_log("Fan disable: failed to persist settings");
}
reply(sender_app, false);
break;
}
if (set_fan_threshold(speed)) {
orion_notify(true, "Fan threshold adjusted to %i°C.", speed);
g_settings.fan_threshold = speed;
const orion::Settings saved = g_settings.update([speed](orion::Settings &s) {
s.enable_fan_speed = true;
s.fan_threshold = speed;
});
if (orion::settings_save(saved)) {
SettingsNoteDiskWritten();
} else {
OrionHEN_log("Fan enable: memory updated but disk twin save failed");
}
reply(sender_app, false);
} else {
orion_notify(true, "Failed to adjust fan speed.");
+4 -4
View File
@@ -126,7 +126,6 @@ extern "C" {
// Global variables
uint64_t p_syscall = 0;
char _end[1] = {};
orion::Settings g_settings;
int fd = -1;
static constexpr auto DEFAULT_PRIORITY = 256;
uintptr_t kernel_base = 0;
@@ -369,7 +368,8 @@ int main() {
OrionHEN_log("is toolbox only: %s | ver: %x", toolbox_only ? "Yes" : "No",
sys_ver.version);
if (g_settings.toolbox_auto_start) {
const orion::Settings boot_cfg = g_settings.snapshot();
if (boot_cfg.toolbox_auto_start) {
cmd_enable_toolbox();
} else {
orion_notify(true,
@@ -382,11 +382,11 @@ int main() {
OrionHEN_log("StartUp thread created!! - welcome to OrionHEN");
if (const char* uri =
startup_uri_for_option(g_settings.start_option, g_settings.toolbox_auto_start)) {
startup_uri_for_option(boot_cfg.start_option, boot_cfg.toolbox_auto_start)) {
OrionHEN_log("ret %d", ItemzLaunchByUri(uri));
}
if (g_settings.auto_eject_disc)
if (boot_cfg.auto_eject_disc)
sceShellCoreUtilRequestEjectDevice("/dev/cd0");
ipc_supervisor_loop(&msg_thr);
+43
View File
@@ -0,0 +1,43 @@
/* Copyright (C) 2025 OrionHEN / LightningMods
*
* Pure jailbreak hijack-retry policy (host-testable).
*
* After getHijacker(pid) fails:
* - stop if process is dead (nothing left to attach)
* - stop if attempt count exceeded max
* - otherwise retry while the target is still alive
*/
#pragma once
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @param process_alive true if pid still exists
* @param attempt 1-based attempt number after this failure (first fail = 1)
* @param max_attempts stop after this many failed attempts (default policy 30)
* @return true if the retry loop should abort (fail)
*/
static inline bool orion_hijack_retry_should_stop(bool process_alive,
int attempt,
int max_attempts) {
if (max_attempts < 1) {
max_attempts = 1;
}
if (attempt >= max_attempts) {
return true;
}
/* Target gone — no point retrying. */
if (!process_alive) {
return true;
}
return false;
}
#ifdef __cplusplus
}
#endif
+30
View File
@@ -0,0 +1,30 @@
/* Copyright (C) 2025 OrionHEN / LightningMods
*
* Pure policy for toolbox inject rest-mode delay (host-testable).
*
* Cold start: util signals util_booted before daemon injects — that must NOT
* apply Rest_Mode_Delay_Seconds (would hang first toolbox load).
* Rest resume: only then apply configured delay (util patch_checker path).
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @param rest_resume true only when recovering from rest mode (not cold boot)
* @param delay_sec configured Rest_Mode_Delay_Seconds
*/
static inline bool orion_toolbox_should_apply_rest_delay(bool rest_resume,
uint64_t delay_sec) {
return rest_resume && delay_sec > 0;
}
#ifdef __cplusplus
}
#endif
@@ -17,6 +17,7 @@ along with this program; see the file COPYING. If not, see
#pragma once
#include <msg.hpp>
#include <mutex>
#include <string>
// ---------------------------------------------------------------------------
@@ -25,6 +26,8 @@ along with this program; see the file COPYING. If not, see
// Design rules:
// - One connection object per target (crit vs util). Never flip a flag on a
// shared singleton — that races MainDaemonSocket / UtilDaemonSocket.
// - IPCSendCommand is serialized with a per-instance mutex (shellui hooks +
// background threads may share the same singleton).
// - Implementation lives in the .cpp (not header-only).
// - JSON string fields are built with cJSON (escape-safe).
// ---------------------------------------------------------------------------
@@ -102,8 +105,14 @@ private:
bool util_daemon_;
int socket_fd_;
int recv_timeout_ms_;
mutable std::mutex mu_;
const char *socket_path() const;
bool require_util(const char *what) const;
bool require_crit(const char *what) const;
/** Unlocked: full-frame send of IPCMessage. */
int send_frame_unlocked(const IPCMessage &msg);
/** Unlocked: full-frame recv into msg; forces NUL on payload. */
int recv_frame_unlocked(IPCMessage &msg);
};
@@ -17,6 +17,7 @@ along with this program; see the file COPYING. If not, see
#pragma once
#include <msg.hpp>
#include <cstddef>
#include <string>
// ---------------------------------------------------------------------------
@@ -49,20 +50,47 @@ void ipc_server_set_log(IpcServerLogFn fn);
// --- transport primitives ---
int ipc_network_listen(const char *soc_path);
int ipc_network_accept(int socket_fd);
/** Single recv (may be short). Prefer ipc_network_recv_full for frames. */
int ipc_network_recv(int socket_fd, void *buffer, int32_t size);
/**
* Read exactly `size` bytes (loop on short reads / EINTR).
* Returns size on success, 0 on clean EOF before any data, 0 < n < size on
* short EOF, -1 on error.
*/
int ipc_network_recv_full(int socket_fd, void *buffer, int32_t size);
/** Send exactly `size` bytes (loop on short writes / EINTR). */
int ipc_network_send_full(int socket_fd, const void *buffer, int32_t size);
int ipc_network_send(int socket_fd, void *buffer, int32_t size);
int ipc_network_close(int socket_fd);
/**
* Pure: JSON body for daemon/util replies.
* Shape is fixed for injectee parsers: {"res":N, "var":"..."}.
*/
inline std::string ipc_format_reply_body(bool error,
const std::string &out_var) {
return std::string("{\"res\":") + std::to_string(error ? -1 : 0) +
", \"var\":\"" + out_var + "\"}";
// --- pure wire helpers (host-testable) ---
/** True when nbytes matches a full IPCMessage frame. */
inline bool ipc_frame_is_complete(int nbytes) {
return nbytes == static_cast<int>(sizeof(IPCMessage));
}
/** Force trailing NUL on the payload buffer (msg is char[DAEMON_BUFF_MAX]). */
inline void ipc_message_force_nul(IPCMessage &msg) {
msg.msg[sizeof(msg.msg) - 1] = '\0';
}
/**
* Escape a string for inclusion inside a JSON double-quoted value.
* Handles \, ", control chars (as \u00XX). Pure — no I/O.
*/
std::string ipc_json_escape(const std::string &in);
/**
* JSON body for daemon/util replies.
* Shape: {"res":N,"var":"..."} with `var` properly escaped.
*/
std::string ipc_format_reply_body(bool error, const std::string &out_var);
// Build {"res":N,"var":"..."} reply with the process's return command ordinal.
void ipc_reply(int sender_socket, DaemonCommands reply_cmd, bool error,
const std::string &out_var = "Nothing");
+37 -17
View File
@@ -15,6 +15,7 @@ along with this program; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>. */
#include <orion/ipc_client.hpp>
#include <orion/ipc_server.hpp>
#include <cstdarg>
#include <cstdio>
@@ -165,9 +166,22 @@ bool IPC_Client::IPCOpenIfNotConnected() {
return IPCOpenConnection();
}
int IPC_Client::IPCReceiveData(IPCMessage &msg, std::string &ipc_msg) {
bzero(msg.msg, sizeof(msg.msg));
int IPC_Client::send_frame_unlocked(const IPCMessage &msg) {
if (socket_fd_ < 0) {
return -1;
}
const int ret = orion::ipc_network_send_full(
socket_fd_, &msg, static_cast<int32_t>(sizeof(msg)));
if (ret < 0) {
shellui_log("IPCSendData failed: %s", strerror(errno));
} else {
shellui_log("IPCSendData sent %i bytes", ret);
}
return ret;
}
int IPC_Client::recv_frame_unlocked(IPCMessage &msg) {
bzero(msg.msg, sizeof(msg.msg));
if (socket_fd_ < 0) {
return -1;
}
@@ -181,12 +195,25 @@ int IPC_Client::IPCReceiveData(IPCMessage &msg, std::string &ipc_msg) {
shellui_log("Waiting for daemon response (%s)...",
util_daemon_ ? "util" : "crit");
int ret = recv(socket_fd_, reinterpret_cast<void *>(&msg), sizeof(msg),
MSG_NOSIGNAL);
const int ret = orion::ipc_network_recv_full(
socket_fd_, &msg, static_cast<int32_t>(sizeof(msg)));
if (ret < 0) {
shellui_log("recv failed: %s", strerror(errno));
return ret;
}
if (!orion::ipc_frame_is_complete(ret)) {
shellui_log("short IPC frame %d (want %zu)", ret, sizeof(msg));
return -1;
}
orion::ipc_message_force_nul(msg);
return ret;
}
int IPC_Client::IPCReceiveData(IPCMessage &msg, std::string &ipc_msg) {
const int ret = recv_frame_unlocked(msg);
if (ret < 0) {
return ret;
}
shellui_log("Daemon returned: %i", msg.error);
if (msg.error != 0) {
@@ -218,17 +245,7 @@ int IPC_Client::IPCReceiveData(IPCMessage &msg, std::string &ipc_msg) {
}
int IPC_Client::IPCSendData(const IPCMessage &msg) {
if (socket_fd_ < 0) {
return -1;
}
int ret = send(socket_fd_, reinterpret_cast<const void *>(&msg), sizeof(msg),
MSG_NOSIGNAL);
if (ret < 0) {
shellui_log("IPCSendData failed: %s", strerror(errno));
} else {
shellui_log("IPCSendData sent %i bytes", ret);
}
return ret;
return send_frame_unlocked(msg);
}
int IPC_Client::IPCCloseConnection() {
@@ -242,6 +259,7 @@ int IPC_Client::IPCCloseConnection() {
bool IPC_Client::IPCSendCommand(DaemonCommands cmd, std::string &ipc_msg1,
std::string ipc_msg2) {
std::lock_guard<std::mutex> lock(mu_);
std::string json;
#if SHELL_DEBUG == 1
@@ -258,7 +276,8 @@ bool IPC_Client::IPCSendCommand(DaemonCommands cmd, std::string &ipc_msg1,
json = json_kv_string2("mount_src", ipc_msg1.c_str(), "mount_dest",
ipc_msg2.c_str());
} else if (ipc_msg2.empty()) {
if (cmd == BREW_DAEMON_PID || cmd == BREW_UTIL_DAEMON_PID) {
if (cmd == BREW_DAEMON_PID || cmd == BREW_UTIL_DAEMON_PID ||
cmd == BREW_TEST_CONNECTION || cmd == BREW_UTIL_TEST_CONNECTION) {
json = "{\"pid\": 0 }";
} else {
json = "{\"msg_1\": 0}";
@@ -273,8 +292,9 @@ bool IPC_Client::IPCSendCommand(DaemonCommands cmd, std::string &ipc_msg1,
}
snprintf(msg.msg, sizeof(msg.msg), "%s", json.c_str());
orion::ipc_message_force_nul(msg);
if (IPCSendData(msg) < 0) {
if (send_frame_unlocked(msg) < 0) {
shellui_log("Failed to send message to daemon");
maybe_notify("Failed to send message to daemon");
IPCCloseConnection();
+116 -8
View File
@@ -61,10 +61,20 @@ void *client_thread(void *arg) {
logf("[%s] Thread created for Socket %i", tag, client->socket);
IPCMessage ipcMessage{};
int readSize = 0;
while ((readSize = ipc_network_recv(client->socket, &ipcMessage,
sizeof(ipcMessage))) > 0) {
while (true) {
IPCMessage ipcMessage{};
const int readSize = ipc_network_recv_full(client->socket, &ipcMessage,
static_cast<int32_t>(sizeof(ipcMessage)));
if (readSize <= 0) {
break;
}
if (!ipc_frame_is_complete(readSize)) {
logf("[%s][client %i] short frame %d (want %zu)", tag, client->cl_nmb,
readSize, sizeof(ipcMessage));
break;
}
ipc_message_force_nul(ipcMessage);
if (ipcMessage.magic == static_cast<int>(0xDEADBABE)) {
std::string message = ipcMessage.msg;
if (handler) {
@@ -73,7 +83,8 @@ void *client_thread(void *arg) {
} else {
logf("[%s][client %i] Invalid magic number", tag, client->cl_nmb);
ipcMessage.error = -1;
ipc_network_send(client->socket, &ipcMessage, sizeof(ipcMessage));
ipc_network_send_full(client->socket, &ipcMessage,
static_cast<int32_t>(sizeof(ipcMessage)));
}
}
@@ -120,7 +131,9 @@ int ipc_network_listen(const char *soc_path) {
return s;
}
int ipc_network_accept(int socket_fd) { return accept(socket_fd, nullptr, nullptr); }
int ipc_network_accept(int socket_fd) {
return accept(socket_fd, nullptr, nullptr);
}
int ipc_network_recv(int socket_fd, void *buffer, int32_t size) {
int n = recv(socket_fd, buffer, size, 0);
@@ -128,12 +141,106 @@ int ipc_network_recv(int socket_fd, void *buffer, int32_t size) {
return n;
}
int ipc_network_recv_full(int socket_fd, void *buffer, int32_t size) {
if (!buffer || size <= 0) {
return -1;
}
auto *p = static_cast<char *>(buffer);
int32_t got = 0;
while (got < size) {
const int n = recv(socket_fd, p + got, static_cast<size_t>(size - got), 0);
if (n < 0) {
if (errno == EINTR) {
continue;
}
logf("recv_full error: %s", strerror(errno));
return -1;
}
if (n == 0) {
logf("recv_full EOF after %d / %d", got, size);
return got;
}
got += n;
}
logf("got %i bytes (full frame)", got);
return got;
}
int ipc_network_send(int socket_fd, void *buffer, int32_t size) {
return send(socket_fd, buffer, size, MSG_NOSIGNAL);
}
int ipc_network_send_full(int socket_fd, const void *buffer, int32_t size) {
if (!buffer || size <= 0) {
return -1;
}
const auto *p = static_cast<const char *>(buffer);
int32_t sent = 0;
while (sent < size) {
const int n =
send(socket_fd, p + sent, static_cast<size_t>(size - sent), MSG_NOSIGNAL);
if (n < 0) {
if (errno == EINTR) {
continue;
}
return -1;
}
if (n == 0) {
return sent;
}
sent += n;
}
return sent;
}
int ipc_network_close(int socket_fd) { return close(socket_fd); }
std::string ipc_json_escape(const std::string &in) {
std::string out;
out.reserve(in.size() + 8);
for (unsigned char c : in) {
switch (c) {
case '\\':
out += "\\\\";
break;
case '"':
out += "\\\"";
break;
case '\b':
out += "\\b";
break;
case '\f':
out += "\\f";
break;
case '\n':
out += "\\n";
break;
case '\r':
out += "\\r";
break;
case '\t':
out += "\\t";
break;
default:
if (c < 0x20) {
char hex[8];
snprintf(hex, sizeof(hex), "\\u%04x", c);
out += hex;
} else {
out += static_cast<char>(c);
}
break;
}
}
return out;
}
std::string ipc_format_reply_body(bool error, const std::string &out_var) {
/* Compact JSON; var is always a quoted escaped string. */
return std::string("{\"res\":") + std::to_string(error ? -1 : 0) +
",\"var\":\"" + ipc_json_escape(out_var) + "\"}";
}
void ipc_reply(int sender_socket, DaemonCommands reply_cmd, bool error,
const std::string &out_var) {
const std::string body = ipc_format_reply_body(error, out_var);
@@ -144,10 +251,11 @@ void ipc_reply(int sender_socket, DaemonCommands reply_cmd, bool error,
outputMessage.error = error ? -1 : 0;
bzero(outputMessage.msg, sizeof(outputMessage.msg));
strncpy(outputMessage.msg, body.c_str(), sizeof(outputMessage.msg) - 1);
outputMessage.msg[sizeof(outputMessage.msg) - 1] = '\0';
ipc_message_force_nul(outputMessage);
logf("error: %d", outputMessage.error);
ipc_network_send(sender_socket, &outputMessage, sizeof(outputMessage));
ipc_network_send_full(sender_socket, &outputMessage,
static_cast<int32_t>(sizeof(outputMessage)));
}
void *ipc_server_loop(void *options_ptr) {
@@ -16,9 +16,6 @@ along with this program; see the file COPYING. If not, see
#pragma once
#include <cstdint>
#include <string>
// ---------------------------------------------------------------------------
// Single product config schema for daemon / util / shellui.
//
@@ -26,12 +23,22 @@ along with this program; see the file COPYING. If not, see
// primary /data/OrionHEN/config.ini (elevated daemons)
// shellui /user/data/OrionHEN/config.ini (SceShellUI sandbox view)
// Load tries both; save writes all writable targets so the files stay twins.
//
// Process-local store: use SettingsStore for multi-threaded daemons (snapshot
// under mutex). Multi-process copies of Settings are expected and OK.
//
// Force C++ linkage even when a parent includes us under extern "C"
// (e.g. util common_utils.h inside extern "C" blocks).
// ---------------------------------------------------------------------------
// Force C++ linkage even if a parent header included us under extern "C".
#ifdef __cplusplus
extern "C++" {
#endif
#include <cstdint>
#include <ctime>
#include <mutex>
#include <string>
#include <utility>
namespace orion {
@@ -82,6 +89,37 @@ struct Settings {
int schema_version = kSettingsSchemaVersion;
};
// Thread-safe process-local settings (daemon / util IPC + worker threads).
// ShellUI may keep a plain Settings if UI work is single-threaded; prefer this
// store when readers and writers can race.
class SettingsStore {
public:
SettingsStore() = default;
explicit SettingsStore(const Settings &s) : s_(s) {}
Settings snapshot() const {
std::lock_guard<std::mutex> lock(mu_);
return s_;
}
void store(const Settings &s) {
std::lock_guard<std::mutex> lock(mu_);
s_ = s;
}
/** Mutate under lock; returns a copy of the new value. */
template <typename Fn>
Settings update(Fn &&fn) {
std::lock_guard<std::mutex> lock(mu_);
fn(s_);
return s_;
}
private:
mutable std::mutex mu_;
Settings s_{};
};
// Fill `out` with defaults then overlay values from the first readable config path.
// Returns true if a file was loaded; false means defaults only (file missing).
bool settings_load(Settings *out);
@@ -107,8 +145,19 @@ const char *settings_last_loaded_path();
// USB override used historically by daemons.
bool settings_usb_disables_toolbox_auto_start();
/**
* Max st_mtime across both twin config paths (0 if neither exists).
* Use for daemon reload gating so either path's write invalidates the cache.
*/
time_t settings_config_newest_mtime();
/**
* True if any twin path has mtime strictly greater than `since`.
* When no config file exists, returns false (nothing newer on disk).
*/
bool settings_config_is_newer_than(time_t since);
} // namespace orion
#ifdef __cplusplus
} // extern "C++"
#endif
#endif /* __cplusplus */
@@ -243,4 +243,29 @@ bool settings_usb_disables_toolbox_auto_start() {
return stat("/mnt/usb0/toolbox_auto_start", &st) == 0;
}
static time_t path_mtime(const char *path) {
if (!path) {
return 0;
}
struct stat st {};
if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) {
return 0;
}
return st.st_mtime;
}
time_t settings_config_newest_mtime() {
const time_t a = path_mtime(kConfigPathPrimary);
const time_t b = path_mtime(kConfigPathShellui);
return a > b ? a : b;
}
bool settings_config_is_newer_than(time_t since) {
const time_t newest = settings_config_newest_mtime();
if (newest == 0) {
return false;
}
return newest > since;
}
} // namespace orion
+4 -3
View File
@@ -60,19 +60,20 @@ void apply_overlay_layout() {
bool LoadSettings()
{
orion::Settings s{};
const bool loaded = orion::settings_load(&s);
if (!loaded) {
/* false from settings_load means defaults only — not a hard failure. */
if (!orion::settings_load(&s)) {
shellui_log("config.ini missing; using defaults");
} else {
shellui_log("Loaded settings from %s", orion::settings_last_loaded_path());
}
// g_settings is the single store; single store for all consumers.
// Process-local store (UI thread); twin disk paths via settings_load/save.
g_settings = s;
if (g_settings.overlay_fps) {
orion_ready_signal(ORION_FLAG_FPS_OVERLAY);
}
apply_overlay_layout();
/* Always true once defaults-or-file applied (prx boot requires success). */
return true;
}
+9 -3
View File
@@ -92,10 +92,15 @@ typedef struct notify_request
} notify_request_t;
/*================ SETTINGS ==============*/
/* Persisted keys: orion::Settings. Concurrent CMD flags stay atomic. */
/* Persisted keys: orion::SettingsStore. Concurrent CMD flags stay atomic.
* Always C++ linkage even when this header is included under extern "C". */
#ifdef __cplusplus
extern "C++" {
#include <orion/settings.hpp>
extern orion::Settings g_settings;
extern orion::SettingsStore g_settings;
/** Reload twin config into g_settings; missing file applies defaults (true). */
bool LoadSettings();
} /* extern "C++" */
#endif
extern atomic_bool g_legacy_cmd_server;
extern atomic_bool g_legacy_cmd_server_exit;
@@ -153,10 +158,11 @@ bool IniliatizeHTTP(void);
/* download/extract/shutdown: see extern "C" block below */
/*============ Back up JB server ==============*/
int get_ip_address(char *ip_address);
/* Always C linkage (impl in common_utils.c) regardless of include context. */
#ifdef __cplusplus
extern "C" {
#endif
int get_ip_address(char *ip_address);
#include <orion/platform.h>
#include <orion/proc_query.h>
int sceNetCtlInit(void);
+23 -12
View File
@@ -18,6 +18,9 @@ along with this program; see the file COPYING. If not, see
#include <orion/ipc_client.hpp>
#include <orion/ready.h>
#include <orion/proc_query.h>
#include <orion/settings.hpp>
#include <orion/hijack_retry.h>
#include "common_utils.h"
#include <string>
#include <vector>
#include <unistd.h>
@@ -86,9 +89,8 @@ pthread_mutex_t jb_lock = PTHREAD_MUTEX_INITIALIZER;
extern atomic_bool not_connected;
// Function forward declarations
bool LoadSettings();
void check_addr_change(void);
int get_ip_address(char *ip_address);
// get_ip_address: common_utils.h (always C linkage)
// util_toolbox.cpp
bool enable_toolbox();
@@ -135,25 +137,28 @@ void cmd_server(int sock, Command &cmd) {
OrionHEN_log("WRONG Jailbreak command received: jailbreaking...");
{
do {
do {
spawned = Hijacker::getHijacker(cmd.PID);
if (spawned == nullptr) {
if (isProcessAlive(cmd.PID)) {
OrionHEN_log("process died");
break;
}
retries++;
if (retries > 30) {
OrionHEN_log("is null for PID %d (attempt %d)", cmd.PID,
retries);
if (orion_hijack_retry_should_stop(isProcessAlive(cmd.PID),
retries, 30)) {
orion_notify(true, "Jailbreak failed, PID is invaild");
OrionHEN_log("Jailbreak failed, PID is invaild");
break;
}
}
OrionHEN_log("is null for PID %d", cmd.PID);
} while (spawned == nullptr);
retries = 0;
if (!spawned) {
replyError(sock);
break;
}
orion_notify(true, "[Legacy] App has been granted a jailbreak\n\nAn update for "
"this PKG is available");
spawned->jailbreak(true);
@@ -274,8 +279,11 @@ void check_addr_change(void) {
} else if (rest_mode_action && !no_network_patched && !not_connected &&
real_rest_mode_detected) {
LoadSettings();
OrionHEN_log("sleeping for %lld secs", g_settings.rest_mode_delay_seconds);
sleep(g_settings.rest_mode_delay_seconds);
const uint64_t delay =
g_settings.snapshot().rest_mode_delay_seconds;
OrionHEN_log("sleeping for %llu secs",
static_cast<unsigned long long>(delay));
sleep(static_cast<unsigned int>(delay));
orion_notify(true, "Coming out of Rest Mode detected, restarting server(s)");
OrionHEN_log("waiting for logged in user");
@@ -287,7 +295,10 @@ void check_addr_change(void) {
OrionHEN_log("Coming out rest mode, activating patches");
if (g_settings.toolbox_auto_start && !g_settings.disable_toolbox_auto_start_for_rest_mode && !enable_toolbox()) {
const orion::Settings cfg = g_settings.snapshot();
if (cfg.toolbox_auto_start &&
!cfg.disable_toolbox_auto_start_for_rest_mode &&
!enable_toolbox()) {
orion_notify(true, "Failed to inject toolbox");
}
}
+4 -1
View File
@@ -39,7 +39,6 @@ extern atomic_bool g_legacy_cmd_server;
extern atomic_bool g_legacy_cmd_server_exit;
void reply(int sender_socket, bool error, std::string out_var = "Nothing");
bool LoadSettings();
extern "C" {
bool load_plugin(const char *path);
int launchApp(const char *titleId);
@@ -73,6 +72,10 @@ void handleIPC(clientArgs *client, std::string &inputStr,
}
switch (command) {
case BREW_UTIL_TEST_CONNECTION: {
reply(sender_app, false, out_var);
break;
}
case BREW_UTIL_SHELLUI_ON_STANDBY: {
OrionHEN_log("ShellUI on standby");
real_rest_mode_detected = no_network_rest_mode_action = true;
+10 -8
View File
@@ -54,7 +54,7 @@ extern "C" {
extern bool is_handler_enabled;
orion::Settings g_settings;
orion::SettingsStore g_settings;
atomic_bool g_legacy_cmd_server = false;
atomic_bool g_legacy_cmd_server_exit = false;
void start_ip_thread(void);
@@ -78,7 +78,7 @@ void __stack_chk_fail(void) {
puts("Stack smashing detected.");
}
void LoadSettings(void) {
bool LoadSettings() {
orion::Settings s{};
if (!orion::settings_load(&s)) {
OrionHEN_log("config.ini missing; using defaults (path primary=%s)",
@@ -87,9 +87,12 @@ void LoadSettings(void) {
OrionHEN_log("Loaded settings from %s", orion::settings_last_loaded_path());
}
g_settings = s;
g_settings.store(s);
g_legacy_cmd_server = s.legacy_cmd_server;
/* Missing file is not an error — defaults were applied. */
return true;
}
int main(void) {
pthread_t ipc_server = 0;
char tmp_buf[200];
@@ -112,9 +115,6 @@ int main(void) {
kernel_base = args->kdata_base_addr;
set_proc_authid(getpid(), DEBUG_AUTHID);
g_settings.rest_mode_delay_seconds = 0;
g_settings.toolbox_auto_start = true;
g_legacy_cmd_server_exit = false;
unlink("/data/OrionHEN/OrionHEN_util_daemon.log");
@@ -134,7 +134,8 @@ int main(void) {
orion_notify(true, "Failed to initialize the HTTP lib, downloading cheats will not work");
}
if (g_settings.toolbox_auto_start && orion_ready_is_set(ORION_FLAG_UTIL_BOOTED)) {
if (g_settings.snapshot().toolbox_auto_start &&
orion_ready_is_set(ORION_FLAG_UTIL_BOOTED)) {
OrionHEN_log("util already booted once — activating toolbox path");
patch_checker();
}
@@ -143,7 +144,8 @@ int main(void) {
for (;;) {
// for rest mode we wait til we can restart everything
if (g_settings.toolbox_auto_start && get_ip_address(&tmp_buf[0]) < 0) {
if (g_settings.snapshot().toolbox_auto_start &&
get_ip_address(&tmp_buf[0]) < 0) {
sleep(1);
bool fail1 = get_ip_address(&tmp_buf[0]) < 0;
-1
View File
@@ -63,7 +63,6 @@ extern char ip_address[];
extern "C" int launchApp(const char *titleId);
void activate_shellui_patch();
bool LoadSettings();
struct sockaddr_in networkAdress(uint16_t port) {
+6 -5
View File
@@ -5,12 +5,11 @@
#include <orion/ready.h>
#include <orion/ipc_client.hpp>
#include <orion/settings.hpp>
#include "common_utils.h"
#include <atomic>
#include <unistd.h>
#include <cstring>
extern orion::Settings g_settings;
void LoadSettings(void);
extern std::atomic_bool no_network_rest_mode_action;
extern std::atomic_bool no_network_patched;
extern std::atomic_bool real_rest_mode_detected;
@@ -71,12 +70,14 @@ void patch_checker() {
}
LoadSettings();
if(g_settings.disable_toolbox_auto_start_for_rest_mode){
const orion::Settings cfg = g_settings.snapshot();
if (cfg.disable_toolbox_auto_start_for_rest_mode) {
OrionHEN_log("Toolbox auto start for rest mode is disabled");
return;
}
OrionHEN_log("sleeping for %lld secs", g_settings.rest_mode_delay_seconds);
sleep(g_settings.rest_mode_delay_seconds);
OrionHEN_log("sleeping for %llu secs",
static_cast<unsigned long long>(cfg.rest_mode_delay_seconds));
sleep(static_cast<unsigned int>(cfg.rest_mode_delay_seconds));
orion_notify(true, "(No Network) Coming out of Rest Mode detected\nre-activating "
"the OrionHEN toolbox...");
+9
View File
@@ -63,6 +63,7 @@ PROD_CXX_SRCS := \
$(UTIL_ROOT)/source/http_github.cpp \
$(REPO_ROOT)/source/extern/cJSON/cJSON.cpp \
$(REPO_ROOT)/source/liborion_settings/source/settings.cpp \
$(REPO_ROOT)/source/liborion_ipc/source/ipc_server.cpp \
$(REPO_ROOT)/source/shellui/src/ps5_settings_ui.cpp \
$(REPO_ROOT)/source/shellui/src/toolbox_route.cpp
@@ -82,12 +83,15 @@ TEST_C_SRCS := \
test_platform_fs.c \
test_platform_log.c \
test_platform_notify.c \
test_toolbox_timing.c \
test_hijack_retry.c \
stubs/host_stubs.c
TEST_CXX_SRCS := \
test_cheat_parsers.cpp \
test_settings.cpp \
test_msg_protocol.cpp \
test_ipc_harden.cpp \
test_ps5_settings_ui.cpp \
test_toolbox_route.cpp \
test_toolbox_helpers.cpp \
@@ -106,6 +110,7 @@ PROD_C_OBJS += $(BUILD_DIR)/prod/liborion_playtime/playtime.o
PROD_CXX_OBJS := $(patsubst $(UTIL_ROOT)/%.cpp,$(BUILD_DIR)/prod/%.o,$(filter $(UTIL_ROOT)/%,$(PROD_CXX_SRCS)))
PROD_CXX_OBJS += $(BUILD_DIR)/prod/extern/cJSON.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/liborion_settings/settings.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/liborion_ipc/ipc_server.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/shellui/ps5_settings_ui.o
PROD_CXX_OBJS += $(BUILD_DIR)/prod/shellui/toolbox_route.o
@@ -152,6 +157,10 @@ $(BUILD_DIR)/prod/liborion_settings/settings.o: $(REPO_ROOT)/source/liborion_set
@mkdir -p $(dir $@)
$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<
$(BUILD_DIR)/prod/liborion_ipc/ipc_server.o: $(REPO_ROOT)/source/liborion_ipc/source/ipc_server.cpp | $(BUILD_DIR)
@mkdir -p $(dir $@)
$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<
$(BUILD_DIR)/prod/shellui/ps5_settings_ui.o: $(REPO_ROOT)/source/shellui/src/ps5_settings_ui.cpp | $(BUILD_DIR)
@mkdir -p $(dir $@)
$(HOST_CXX) $(CXXFLAGS) -c -o $@ $<
+43
View File
@@ -0,0 +1,43 @@
/* Host tests: jailbreak hijack retry stop condition. */
#include "test_harness.h"
#include <orion/hijack_retry.h>
static int test_alive_retries_until_max(void) {
/* Process alive → keep retrying until attempt reaches max. */
TEST_ASSERT_TRUE(!orion_hijack_retry_should_stop(true, 1, 30));
TEST_ASSERT_TRUE(!orion_hijack_retry_should_stop(true, 29, 30));
TEST_ASSERT_TRUE(orion_hijack_retry_should_stop(true, 30, 30));
TEST_ASSERT_TRUE(orion_hijack_retry_should_stop(true, 31, 30));
return 0;
}
static int test_dead_process_stops_immediately(void) {
/* Dead process → stop even on first attempt. */
TEST_ASSERT_TRUE(orion_hijack_retry_should_stop(false, 1, 30));
TEST_ASSERT_TRUE(orion_hijack_retry_should_stop(false, 5, 30));
return 0;
}
static int test_inverted_legacy_bug_regression(void) {
/*
* Old bug: stop when process_alive (first fail aborts live targets).
* Correct: alive + under max → do NOT stop.
*/
const bool process_alive = true;
const int attempt = 1;
TEST_ASSERT_TRUE(
!orion_hijack_retry_should_stop(process_alive, attempt, 30));
return 0;
}
int test_hijack_retry_suite(void) {
int failures = 0;
failures +=
orion_test_run("hijack_retry.alive_until_max", test_alive_retries_until_max);
failures +=
orion_test_run("hijack_retry.dead_stops", test_dead_process_stops_immediately);
failures += orion_test_run("hijack_retry.alive_not_abort",
test_inverted_legacy_bug_regression);
return failures;
}
+68
View File
@@ -0,0 +1,68 @@
/* Host tests: IPC wire helpers (frame, escape, reply body, null path). */
#include "test_harness.h"
#include <msg.hpp>
#include <orion/ipc_server.hpp>
#include <cstring>
#include <string>
static int test_frame_complete(void) {
TEST_ASSERT_TRUE(orion::ipc_frame_is_complete(static_cast<int>(sizeof(IPCMessage))));
TEST_ASSERT_TRUE(!orion::ipc_frame_is_complete(0));
TEST_ASSERT_TRUE(!orion::ipc_frame_is_complete(4));
TEST_ASSERT_TRUE(
!orion::ipc_frame_is_complete(static_cast<int>(sizeof(IPCMessage)) - 1));
return 0;
}
static int test_force_nul(void) {
IPCMessage m{};
memset(m.msg, 'A', sizeof(m.msg));
orion::ipc_message_force_nul(m);
TEST_ASSERT_EQ_INT(0, m.msg[sizeof(m.msg) - 1]);
return 0;
}
static int test_json_escape_quotes_and_slash(void) {
std::string e = orion::ipc_json_escape(R"(a"b\c)");
TEST_ASSERT_STREQ(R"(a\"b\\c)", e.c_str());
std::string nl = orion::ipc_json_escape("x\ny");
TEST_ASSERT_STREQ(R"(x\ny)", nl.c_str());
return 0;
}
static int test_format_reply_escapes_var(void) {
using orion::ipc_format_reply_body;
std::string ok = ipc_format_reply_body(false, "Nothing");
TEST_ASSERT_STREQ(R"({"res":0,"var":"Nothing"})", ok.c_str());
std::string err = ipc_format_reply_body(true, "fail");
TEST_ASSERT_STREQ(R"({"res":-1,"var":"fail"})", err.c_str());
std::string quoted = ipc_format_reply_body(false, R"(say "hi")");
TEST_ASSERT_TRUE(quoted.find(R"(\"hi\")") != std::string::npos);
TEST_ASSERT_TRUE(quoted.find("\"var\":\"say") != std::string::npos);
/* Must be parseable as JSON-ish: no raw unescaped quote inside var value. */
TEST_ASSERT_TRUE(quoted.find(R"("var":"say \"hi\"")") != std::string::npos);
return 0;
}
static int test_null_path_policy(void) {
/* Mirror daemon handleIPC guards: missing path is hard fail. */
auto missing = [](const char *p) -> bool { return !p || !*p; };
TEST_ASSERT_TRUE(missing(nullptr));
TEST_ASSERT_TRUE(missing(""));
TEST_ASSERT_TRUE(!missing("/data/OrionHEN"));
return 0;
}
extern "C" int test_ipc_harden_suite(void) {
int failures = 0;
failures += orion_test_run("ipc_frame_complete", test_frame_complete);
failures += orion_test_run("ipc_force_nul", test_force_nul);
failures += orion_test_run("ipc_json_escape", test_json_escape_quotes_and_slash);
failures += orion_test_run("ipc_format_reply_escape", test_format_reply_escapes_var);
failures += orion_test_run("ipc_null_path_policy", test_null_path_policy);
return failures;
}
+6
View File
@@ -17,6 +17,9 @@ extern "C" int test_platform_fs_suite(void);
extern "C" int test_platform_log_suite(void);
extern "C" int test_platform_notify_suite(void);
extern "C" int test_msg_protocol_suite(void);
extern "C" int test_ipc_harden_suite(void);
extern "C" int test_toolbox_timing_suite(void);
extern "C" int test_hijack_retry_suite(void);
extern "C" int test_ps5_settings_ui_suite(void);
extern "C" int test_toolbox_route_suite(void);
extern "C" int test_toolbox_helpers_suite(void);
@@ -41,6 +44,9 @@ int main() {
failures += test_platform_log_suite();
failures += test_platform_notify_suite();
failures += test_msg_protocol_suite();
failures += test_ipc_harden_suite();
failures += test_toolbox_timing_suite();
failures += test_hijack_retry_suite();
failures += test_ps5_settings_ui_suite();
failures += test_toolbox_route_suite();
failures += test_toolbox_helpers_suite();
+4 -3
View File
@@ -73,9 +73,10 @@ static int test_ipc_format_reply_body(void) {
std::string ok = ipc_format_reply_body(false, "Nothing");
std::string err = ipc_format_reply_body(true, "fail");
std::string tid = ipc_format_reply_body(false, "CUSA12345");
TEST_ASSERT_STREQ("{\"res\":0, \"var\":\"Nothing\"}", ok.c_str());
TEST_ASSERT_STREQ("{\"res\":-1, \"var\":\"fail\"}", err.c_str());
TEST_ASSERT_STREQ("{\"res\":0, \"var\":\"CUSA12345\"}", tid.c_str());
/* Compact JSON; var is escaped (no space after colon). */
TEST_ASSERT_STREQ("{\"res\":0,\"var\":\"Nothing\"}", ok.c_str());
TEST_ASSERT_STREQ("{\"res\":-1,\"var\":\"fail\"}", err.c_str());
TEST_ASSERT_STREQ("{\"res\":0,\"var\":\"CUSA12345\"}", tid.c_str());
return 0;
}
+39
View File
@@ -202,6 +202,42 @@ static int test_empty_file_loads_defaults(void) {
return 0;
}
static int test_settings_store_snapshot_update(void) {
orion::SettingsStore store;
orion::Settings a = store.snapshot();
TEST_ASSERT_EQ_INT(77, a.fan_threshold);
store.update([](orion::Settings &s) {
s.fan_threshold = 60;
s.enable_fan_speed = true;
});
orion::Settings b = store.snapshot();
TEST_ASSERT_EQ_INT(60, b.fan_threshold);
TEST_ASSERT_TRUE(b.enable_fan_speed);
orion::Settings c{};
c.overlay_fps = true;
store.store(c);
orion::Settings d = store.snapshot();
TEST_ASSERT_TRUE(d.overlay_fps);
TEST_ASSERT_TRUE(d.enable_fan_speed == false); /* full replace */
return 0;
}
static int test_config_mtime_helpers(void) {
/* No twin paths on host test machine → newest is 0, not newer. */
TEST_ASSERT_TRUE(orion::settings_config_newest_mtime() == 0 ||
orion::settings_config_newest_mtime() >= 0);
const time_t n = orion::settings_config_newest_mtime();
if (n == 0) {
TEST_ASSERT_TRUE(!orion::settings_config_is_newer_than(0));
} else {
TEST_ASSERT_TRUE(orion::settings_config_is_newer_than(n - 1));
TEST_ASSERT_TRUE(!orion::settings_config_is_newer_than(n));
}
return 0;
}
extern "C" int test_settings_suite(void) {
int failures = 0;
failures += orion_test_run("settings_defaults_serialize", test_defaults_and_serialize_keys);
@@ -213,5 +249,8 @@ extern "C" int test_settings_suite(void) {
failures += orion_test_run("settings_partial_ini_defaults", test_partial_ini_keeps_defaults);
failures += orion_test_run("settings_serialize_overlay_keys", test_serialize_contains_overlay_keys);
failures += orion_test_run("settings_empty_file_defaults", test_empty_file_loads_defaults);
failures += orion_test_run("settings_store_snapshot_update",
test_settings_store_snapshot_update);
failures += orion_test_run("settings_config_mtime_helpers", test_config_mtime_helpers);
return failures;
}
+44
View File
@@ -0,0 +1,44 @@
/* Host tests: toolbox rest-delay policy (cold start vs rest resume). */
#include "test_harness.h"
#include <orion/toolbox_timing.h>
#include <orion/ready.h>
#include <stdio.h>
#include <unistd.h>
static int test_cold_start_no_delay(void) {
/* util_booted alone must not imply rest delay. */
TEST_ASSERT_TRUE(!orion_toolbox_should_apply_rest_delay(false, 30));
TEST_ASSERT_TRUE(!orion_toolbox_should_apply_rest_delay(false, 0));
return 0;
}
static int test_rest_resume_applies_delay(void) {
TEST_ASSERT_TRUE(orion_toolbox_should_apply_rest_delay(true, 5));
TEST_ASSERT_TRUE(!orion_toolbox_should_apply_rest_delay(true, 0));
return 0;
}
static int test_util_booted_flag_does_not_force_delay(void) {
/* Simulate cold-start ordering: util signals util_booted before daemon inject. */
char name[64];
snprintf(name, sizeof(name), "tb_timing_%d", (int)getpid());
orion_ready_clear(name);
/* Use a private flag name — ready API rejects dots/slashes; ok for smoke. */
(void)orion_ready_signal(ORION_FLAG_UTIL_BOOTED);
/* Policy still requires rest_resume=true; util_booted is orthogonal. */
TEST_ASSERT_TRUE(
!orion_toolbox_should_apply_rest_delay(/*rest_resume=*/false, 10));
return 0;
}
int test_toolbox_timing_suite(void) {
int failures = 0;
failures += orion_test_run("toolbox_timing.cold_no_delay", test_cold_start_no_delay);
failures +=
orion_test_run("toolbox_timing.rest_applies", test_rest_resume_applies_delay);
failures += orion_test_run("toolbox_timing.util_booted_orthogonal",
test_util_booted_flag_does_not_force_delay);
return failures;
}