mirror of
https://github.com/aydencharles/onionHEN.git
synced 2026-10-22 20:02:59 -05:00
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:
1 parent
c3027f2c67
commit
ece77ea433
32 files changed
+805
-171
No files matched your search
+7
-4
@@ -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
@@ -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 + 三份漂移**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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...");
|
||||
|
||||
@@ -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 $@ $<
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user