Files
H4zeyaf--Ghostcontrol-PS5-U…/payload/shellui_pad.c
T
ItsDeidara 62e229775e xbox series S support, official NX controllers + refactoring
added support for official Nintendo Switch pro controllers(not just 8bito ultimate 2) and xbox series S
2026-06-08 07:55:15 -04:00

3933 lines
161 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* SPDX-License-Identifier: GPL-3.0-or-later
* shellui_pad.c — Inject a pad-forwarding thread into a system process
*
* Injects a stub thread into the first attachable system process that has
* libScePad loaded and can create/bind a virtual controller through the
* SceShellCore/SceShellUI path. Once running, 60 Hz pad updates arrive via
* mdbg_copyin when a valid write handle exists.
*
* == Phase ordering (fixes PS5 freeze) ==
* OLD (broken): write code cave → PT_ATTACH (fails) → INT3 left in live code
* NEW (fixed): PT_ATTACH first → only write if attach succeeds → PT_DETACH
*
* == Code cave (Phase 5) ==
* Use the library init/fini section directly as the conservative injection
* path. Target-side mmap experimentation proved less stable on retail.
*
* == Injection targets (Phase 4) ==
* SceShellUI cannot be ptraced (EINVAL, authid protected). Candidates are
* tried in order; the first that accepts PT_ATTACH is used.
*/
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <machine/reg.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/sysctl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#ifdef __PROSPERO__
#include <ps5/kernel.h>
#include <ps5/klog.h>
#include <ps5/mdbg.h>
#include <ps5/nid.h>
#endif
#ifdef __ORBIS__
#include <ps4/kernel.h>
#include <ps4/klog.h>
#include <ps4/mdbg.h>
#include <dlfcn.h>
#endif
#include "shellui_pad.h"
/* Route shellui_pad diagnostics through the persistent status logger as well
* as klog. main.c owns ghostpad_status_log(), which keeps klog_printf behavior
* and appends the same line to /data/ghostpad/ghostpad_status.log. */
#define klog_printf ghostpad_status_log
#ifdef __ORBIS__
extern int kernel_set_vmem_protection(pid_t pid, intptr_t addr, size_t size, int prot);
#endif
#define GHOSTPAD_ASSIGNMENT_SCREEN_RET ((int32_t)0x803B0006u)
#define GHOSTPAD_AUTO_DISMISS_ACTIVE ((int32_t)0x44534D31u) /* "DSM1" */
#define GHOSTPAD_AUTO_DISMISS_DONE ((int32_t)0x44534D32u) /* "DSM2" */
#ifndef GHOSTPAD_ALLOW_UNSAFE_VDA_PATCH
#define GHOSTPAD_ALLOW_UNSAFE_VDA_PATCH 0
#endif
#ifndef GHOSTPAD_ALLOW_UNSAFE_SETPRIV_HOOK
#define GHOSTPAD_ALLOW_UNSAFE_SETPRIV_HOOK 0
#endif
#ifndef GHOSTPAD_ENABLE_KNOWN_VDA_PATCH
#define GHOSTPAD_ENABLE_KNOWN_VDA_PATCH 1
#endif
#define GHOSTPAD_VDA_PS4_LIBSCEPAD_VDA_OFF 0x5b40u
#define GHOSTPAD_VDA_PS4_HASH256 0xbb22d8acd843d81eull
#define GHOSTPAD_VDA_PS4_HASH4K 0x346f2b8071895f89ull
#define GHOSTPAD_VDA_PS4_CALL_OFF 0x0c0u
#define GHOSTPAD_VDA_PS4_AFTER_CALL_OFF 0x0c5u
#define GHOSTPAD_VDA_PS4_BRANCH_OFF 0x0cdu
#define GHOSTPAD_VDA_PS4_CAVE_OFF 0x0dd2u
#define GHOSTPAD_VDA_PS4_CAVE_LEN 14u
static uint64_t
ghostpad_fnv1a64(const uint8_t *buf, size_t len)
{
uint64_t h = 1469598103934665603ull;
if (!buf) return 0;
for (size_t i = 0; i < len; i++) {
h ^= (uint64_t)buf[i];
h *= 1099511628211ull;
}
return h;
}
static int
ghostpad_all_byte(const uint8_t *buf, size_t len, uint8_t value)
{
if (!buf || len == 0) return 0;
for (size_t i = 0; i < len; i++) {
if (buf[i] != value) return 0;
}
return 1;
}
/* PT_IO fallback for shellui_pad_update — always enabled */
/* sys_ptrace — elevate credentials for ptrace, then restore */
static int
sys_ptrace(int request, pid_t pid, caddr_t addr, int data)
{
uint8_t privcaps[16] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};
pid_t mypid = getpid();
uint8_t caps[16];
uint64_t authid;
int ret;
if (!(authid = kernel_get_ucred_authid(mypid))) return -1;
if (kernel_get_ucred_caps(mypid, caps)) return -1;
if (kernel_set_ucred_authid(mypid, 0x4800000000010003l)) return -1;
if (kernel_set_ucred_caps(mypid, privcaps)) return -1;
ret = (int)__syscall(SYS_ptrace, request, pid, addr, data);
kernel_set_ucred_authid(mypid, authid);
kernel_set_ucred_caps(mypid, caps);
return ret;
}
/* find_pids — locate processes by thread name via sysctl (ki_pid@72, ki_tdname@447) */
static size_t
find_pids(const char *name, pid_t *pids, size_t max_pids)
{
int mib[4] = {1, 14, 8, 0};
pid_t mypid = getpid();
size_t buf_size;
uint8_t *buf;
size_t count = 0;
if (!pids || max_pids == 0) return 0;
if (sysctl(mib, 4, NULL, &buf_size, NULL, 0)) return 0;
if (!(buf = malloc(buf_size))) return 0;
if (sysctl(mib, 4, buf, &buf_size, NULL, 0)) { free(buf); return 0; }
for (uint8_t *ptr = buf; ptr < (buf + buf_size);) {
int ki_structsize = *(int *)ptr;
pid_t ki_pid = *(pid_t *)&ptr[72];
char *ki_tdname = (char *)&ptr[447];
size_t pi;
int seen = 0;
ptr += ki_structsize;
if (strcmp(name, ki_tdname) || ki_pid == mypid) {
continue;
}
for (pi = 0; pi < count; pi++) {
if (pids[pi] == ki_pid) {
seen = 1;
break;
}
}
if (seen || count >= max_pids) {
continue;
}
pids[count++] = ki_pid;
}
for (size_t i = 1; i < count; i++) {
pid_t pid = pids[i];
size_t j = i;
while (j > 0 && pids[j - 1] > pid) {
pids[j] = pids[j - 1];
j--;
}
pids[j] = pid;
}
free(buf);
return count;
}
/* resolve_sym — look up a symbol in a remote process library */
static intptr_t
resolve_sym(pid_t pid, uint32_t lib_handle, const char *sym)
{
intptr_t addr = kernel_dynlib_dlsym(pid, lib_handle, sym);
if (addr) return addr;
#ifdef __PROSPERO__
char nid[12];
nid_encode(sym, nid);
addr = kernel_dynlib_resolve(pid, lib_handle, nid);
return addr;
#else
return 0;
#endif
}
/* get_lib — wrapper around kernel_dynlib_handle with logging */
static int
get_lib(pid_t pid, const char *name, uint32_t *handle)
{
*handle = 0;
int ret = kernel_dynlib_handle(pid, name, handle);
if (ret != 0 || *handle == 0) {
char sprx[64];
snprintf(sprx, sizeof(sprx), "%s.sprx", name);
ret = kernel_dynlib_handle(pid, sprx, handle);
}
klog_printf("[Ghostpad] dynlib_handle(%s) -> ret=%d handle=0x%x\n",
name, ret, *handle);
return (*handle != 0) ? 0 : -1;
}
/* pt_io_write — write process memory via PT_IO (process must be stopped) */
static int
pt_io_write(pid_t pid, intptr_t dst, const void *src, size_t len)
{
struct ptrace_io_desc iod;
iod.piod_op = PIOD_WRITE_D;
iod.piod_offs = (void *)dst;
iod.piod_addr = (void *)src;
iod.piod_len = len;
return sys_ptrace(PT_IO, pid, (caddr_t)&iod, 0);
}
typedef struct {
int valid;
int attached;
pid_t pid;
intptr_t args_kaddr;
intptr_t trap_rip;
intptr_t fn_setpriv;
intptr_t fn_setloginuser;
intptr_t fn_setusernumber;
intptr_t fn_setfocus;
intptr_t fn_usleep;
intptr_t fn_gethandle;
intptr_t fn_gethandle_ext;
intptr_t fn_open;
intptr_t fn_open_ext;
intptr_t fn_open_ext2;
intptr_t fn_insert;
intptr_t fn_vdi;
int32_t pad_handle;
int32_t use_insert;
} ShellUiDirectState;
static ShellUiDirectState g_shellui_direct_state = {0};
static int32_t g_shellui_direct_last_stage = 0;
static int64_t g_shellui_direct_last_value = 0;
/* ── Original bytes saved for unpatching ── */
static uint8_t g_orig_gethandle[5];
static int g_gethandle_hooked = 0;
static uint8_t g_orig_setpriv[5];
static int g_setpriv_hooked = 0;
static uint8_t g_orig_vdi_128[128];
static int g_vdi_hooked = 0;
static uint8_t g_orig_vda_call[5];
static uint8_t g_orig_vda_cave[8];
static int g_vda_patched = 0;
static pid_t g_vda_patched_pid = -1;
static uint8_t g_orig_self_vda_call[5];
static uint8_t g_orig_self_vda_cave[8];
static int g_self_vda_patched = 0;
/* Saved injection state for stub relaunch — populated by shellui_pad_inject */
static pid_t g_relaunch_pid = -1;
static intptr_t g_relaunch_args_kaddr = 0;
static intptr_t g_relaunch_stub_fn = 0; /* stub function addr in target */
static intptr_t g_relaunch_thread_storage = 0; /* pthread_t storage in target */
static intptr_t g_relaunch_pthread_fn = 0; /* pthread_create addr in target*/
static intptr_t g_relaunch_trap_rip = 0; /* INT3 addr for pt_call */
static intptr_t g_relaunch_malloc_fn = 0; /* malloc addr in target — for new stub alloc */
static void
shellui_pad_direct_set_last_status(int32_t stage, int64_t value)
{
g_shellui_direct_last_stage = stage;
g_shellui_direct_last_value = value;
}
void
shellui_pad_direct_get_last_status(int32_t *stage, int64_t *value)
{
if (stage) {
*stage = g_shellui_direct_last_stage;
}
if (value) {
*value = g_shellui_direct_last_value;
}
}
static int
shellui_pad_direct_context_usable(pid_t shellui_pid, intptr_t args_kaddr)
{
return g_shellui_direct_state.valid &&
g_shellui_direct_state.pid == shellui_pid &&
g_shellui_direct_state.args_kaddr == args_kaddr;
}
/* Fast single-attempt PT_IO write — no retries, no sleep.
* PT_ATTACH stops SceShellCore briefly to write pad_data+seq.
* The stub background thread then resumes and calls VDI independently. */
static int
shellui_pad_ptrace_update(pid_t shellui_pid, intptr_t args_kaddr,
const void *pad_data, uint32_t pad_data_len,
uint32_t new_seq)
{
intptr_t data_field = args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, pad_data);
intptr_t seq_field = args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, seq);
if (sys_ptrace(PT_ATTACH, shellui_pid, 0, 0) != 0)
return -1; /* busy — skip this packet, next one will try again */
waitpid(shellui_pid, NULL, 0);
if (pt_io_write(shellui_pid, data_field, pad_data, pad_data_len) ||
pt_io_write(shellui_pid, seq_field, &new_seq, sizeof(new_seq))) {
sys_ptrace(PT_DETACH, shellui_pid, (caddr_t)1, 0);
return -1;
}
sys_ptrace(PT_DETACH, shellui_pid, (caddr_t)1, 0);
return 0;
}
/* pt_call — call fn(a1..a6) inside a stopped process via INT3; returns RAX */
static int64_t
pt_call(pid_t pid, intptr_t fn, intptr_t trap_rip,
uint64_t a1, uint64_t a2, uint64_t a3,
uint64_t a4, uint64_t a5, uint64_t a6)
{
struct reg regs, saved;
int status;
if (sys_ptrace(PT_GETREGS, pid, (caddr_t)&regs, 0)) return -1;
memcpy(&saved, &regs, sizeof(regs));
/* Skip x86-64 red zone (128 bytes below RSP), align to 16 bytes. */
intptr_t new_rsp = (regs.r_rsp - 256) & ~(intptr_t)0xf;
if (pt_io_write(pid, new_rsp, &trap_rip, 8)) return -1;
regs.r_rsp = new_rsp;
regs.r_rip = fn;
regs.r_rdi = a1;
regs.r_rsi = a2;
regs.r_rdx = a3;
regs.r_rcx = a4;
regs.r_r8 = a5;
regs.r_r9 = a6;
if (sys_ptrace(PT_SETREGS, pid, (caddr_t)&regs, 0)) return -1;
if (sys_ptrace(PT_CONTINUE, pid, (caddr_t)1, 0)) return -1;
/* Wait for our SIGTRAP; forward other signals so the process stays healthy.
* Use WNOHANG + 1 ms sleep so we never block forever if the INT3 doesn't fire
* (e.g. if the write failed silently or the process runs past trap_rip).
* SIGCHLD (17) is suppressed rather than forwarded: forwarding it while the
* main thread is executing injected code (pthread_create, pad IPC calls) has
* been observed to cause a kernel panic when SceShellUI's SIGCHLD handler
* runs concurrently with thread-creation internals. */
int got_trap = 0;
for (int total_ms = 0; total_ms < 5000; ) {
int r = waitpid(pid, &status, WNOHANG);
if (r < 0) {
klog_printf("[Ghostpad] pt_call: waitpid error errno=%d\n", errno);
break;
}
if (r == 0) {
usleep(1000); /* 1 ms — process hasn't stopped yet */
total_ms++;
continue;
}
/* Process stopped */
if (!WIFSTOPPED(status)) {
klog_printf("[Ghostpad] pt_call: process exited status=0x%x\n", status);
sys_ptrace(PT_SETREGS, pid, (caddr_t)&saved, 0);
return -1;
}
int sig = WSTOPSIG(status);
if (sig == SIGTRAP) { got_trap = 1; break; }
/* Suppress SIGCHLD — forwarding it during injected execution causes panics */
int fwd = (sig == 17) ? 0 : sig;
if (fwd != sig)
klog_printf("[Ghostpad] pt_call: suppressing SIGCHLD\n");
else
klog_printf("[Ghostpad] pt_call: forwarding sig=%d\n", sig);
sys_ptrace(PT_CONTINUE, pid, (caddr_t)1, fwd);
}
if (!got_trap) {
klog_printf("[Ghostpad] pt_call: timed out waiting for SIGTRAP fn=0x%lx\n", fn);
sys_ptrace(PT_SETREGS, pid, (caddr_t)&saved, 0);
return -1;
}
if (sys_ptrace(PT_GETREGS, pid, (caddr_t)&regs, 0)) return -1;
int64_t retval = (int64_t)regs.r_rax;
klog_printf("[Ghostpad] pt_call: fn=0x%lx rip=0x%lx rax=0x%lx\n",
fn, (uint64_t)regs.r_rip, (uint64_t)retval);
sys_ptrace(PT_SETREGS, pid, (caddr_t)&saved, 0);
return retval;
}
static int64_t
pt_call_with_copy(pid_t pid, intptr_t fn, intptr_t trap_rip,
uint64_t a1, const void *buf, size_t len)
{
struct reg regs, saved;
int status;
int got_trap = 0;
uint64_t retval = (uint64_t)-1;
size_t copy_len = (len + 15) & ~(size_t)15;
intptr_t ret_rsp;
intptr_t buf_addr;
if (sys_ptrace(PT_GETREGS, pid, (caddr_t)&regs, 0)) return -1;
memcpy(&saved, &regs, sizeof(regs));
ret_rsp = (regs.r_rsp - 256) & ~(intptr_t)0xf;
buf_addr = ret_rsp - (intptr_t)copy_len;
if (pt_io_write(pid, buf_addr, buf, len)) return -1;
if (pt_io_write(pid, ret_rsp, &trap_rip, 8)) return -1;
regs.r_rsp = ret_rsp;
regs.r_rip = fn;
regs.r_rdi = a1;
regs.r_rsi = (uint64_t)buf_addr;
regs.r_rdx = 0;
regs.r_rcx = 0;
regs.r_r8 = 0;
regs.r_r9 = 0;
if (sys_ptrace(PT_SETREGS, pid, (caddr_t)&regs, 0)) return -1;
if (sys_ptrace(PT_CONTINUE, pid, (caddr_t)1, 0)) return -1;
for (int total_ms = 0; total_ms < 5000; ) {
int r = waitpid(pid, &status, WNOHANG);
if (r < 0) {
klog_printf("[Ghostpad] pt_call_with_copy: waitpid error errno=%d\n", errno);
break;
}
if (r == 0) {
usleep(1000);
total_ms++;
continue;
}
if (!WIFSTOPPED(status)) {
klog_printf("[Ghostpad] pt_call_with_copy: process exited status=0x%x\n", status);
sys_ptrace(PT_SETREGS, pid, (caddr_t)&saved, 0);
return -1;
}
{
int sig = WSTOPSIG(status);
if (sig == SIGTRAP) {
got_trap = 1;
break;
}
sys_ptrace(PT_CONTINUE, pid, (caddr_t)1, (sig == 17) ? 0 : sig);
}
}
if (!got_trap) {
klog_printf("[Ghostpad] pt_call_with_copy: timed out waiting for SIGTRAP fn=0x%lx\n", fn);
sys_ptrace(PT_SETREGS, pid, (caddr_t)&saved, 0);
return -1;
}
if (sys_ptrace(PT_GETREGS, pid, (caddr_t)&regs, 0)) {
sys_ptrace(PT_SETREGS, pid, (caddr_t)&saved, 0);
return -1;
}
retval = (uint64_t)regs.r_rax;
sys_ptrace(PT_SETREGS, pid, (caddr_t)&saved, 0);
return (int64_t)retval;
}
/* ============================================================
* THE STUB — runs as a thread inside the target process.
*
* DESIGN: VDA is called from THIS running thread, not from pt_call.
* Testing confirmed that VDA always returns 0x803b0001 when called via
* pt_call (stopped-process main-thread context), but succeeds when called
* from a running injected thread. The injector (pt_call) only calls
* scePadSetProcessPrivilege(1) — a process-level flag safe to set from
* the stopped main thread. Everything else runs here.
*
* Thread sequence:
* 1. Sleep 500ms (TLS, signal masks, stack guard fully initialized)
* 2. deleteDevice(0..15) — clean up any orphan from a previous run
* 3. fp_vda(&param, 3) — create virtual DualSense; exit on failure
* 4. Auto-press Cross — dismiss "who is using this controller?" dialog
* 5. Insert loop — forward pad data from main process at 60 Hz
* 6. fp_del(handle) — delete virtual device on exit so next run is clean
*
* Rules: position-independent, no direct library calls, no globals/statics.
* All calls go through fp_* pointers in ShellUiPadArgs.
* ============================================================ */
extern void shellui_stub(void *arg);
extern void shellui_stub_end(void);
__attribute__((noinline, section(".text.stub")))
void shellui_stub(void *arg)
{
ShellUiPadArgs *a = (ShellUiPadArgs *)arg;
int32_t assignment_hint = 0;
int32_t handle_from_vda_token = 0;
int32_t initial_pad_handle = a->pad_handle;
/* Early marker so the injector can distinguish "thread never ran" from
* "thread started and died before reaching ready=1". */
a->rc_log[15] = (int32_t)0x53545542u; /* "STUB" */
/* pad_handle semantics set by injector (shellui_pad_inject Step 6.5):
* >= 0 injector obtained a handle via pt_call (stopped process)
* -1 SceShellCore (server-side) — thread libScePad calls are safe
* -2 client process (SceShellUI etc.) — ANY libScePad IPC deadlocks */
int32_t vda_handle = a->pad_handle;
int32_t use_insert = 0;
if (vda_handle >= 0) {
/* ── FAST PATH ────────────────────────────────────────────────────────
* The injector already resolved a handle via pt_call while SceShellUI's
* main thread was stopped. Skip ALL libScePad IPC here: SceShellUI's
* main thread owns the IPC socket and concurrent access from this thread
* deadlocks → kernel panic.
*
* scePadVirtualDeviceInsertData writes to a shared-memory ring buffer
* and is not an IPC round-trip, so it is safe
* to call from a secondary thread while the main thread uses the socket.
* ───────────────────────────────────────────────────────────────────── */
a->rc_log[0] = vda_handle;
/* Injector encoded use_insert hint in seq (0=VDI first, 1=fp_insert first).
* Reset seq to 0 so the insert loop starts clean. */
use_insert = (int32_t)a->seq;
a->seq = 0;
/* In legacy client targets, establish an explicit pad context before the
* assignment-screen follow-up. The main payload-side context setters
* are not authoritative for the client process, so do the same setup
* inside the recovered process before probing for the post-ASGN path. */
if (a->fp_setloginuser) {
a->rc_log[4] = a->fp_setloginuser(1);
}
if (a->fp_setusernumber) {
a->rc_log[5] = a->fp_setusernumber(1);
}
if (a->fp_setfocus) {
a->rc_log[6] = a->fp_setfocus(1, 0, 0, 0, 0, 0);
}
/*
* Assignment-screen attempt from a running client-process thread.
* If this succeeds, switch over to the virtual handle and drive it via
* VDI so the PS5 can show the assignment UI and then accept our Cross
* dismiss pulse.
*/
if (a->fp_vda) {
int32_t uid_try[3];
int32_t ui;
uid_try[0] = 1;
uid_try[1] = a->userId;
uid_try[2] = 0x10000000;
for (ui = 0; ui < 3; ui++) {
struct { int32_t f[8]; } vdp;
int32_t vi;
int32_t vda_ret;
for (vi = 0; vi < 8; vi++) vdp.f[vi] = 0;
vdp.f[0] = 32;
vdp.f[1] = uid_try[ui];
vda_ret = a->fp_vda(&vdp, 3);
a->rc_log[ui] = vda_ret;
if (ui == 0) {
a->rc_log[8] = vdp.f[0];
a->rc_log[9] = vdp.f[1];
a->rc_log[10] = vdp.f[2];
a->rc_log[11] = vdp.f[3];
a->rc_log[12] = vdp.f[4];
a->rc_log[13] = vdp.f[5];
a->rc_log[14] = vdp.f[6];
a->rc_log[15] = vdp.f[7];
}
if (vda_ret >= 0) {
vda_handle = vda_ret;
use_insert = 0;
a->pad_handle = vda_handle;
a->rc_log[6] = (int32_t)0x60000001;
a->rc_log[7] = vda_handle;
break;
}
for (vi = 2; vi < 8; vi++) {
if (vdp.f[vi] != 0 &&
vdp.f[vi] != -1) {
vda_handle = vdp.f[vi];
use_insert = 0;
assignment_hint = 0;
a->pad_handle = vda_handle;
a->rc_log[6] = (int32_t)0x60000002;
a->rc_log[7] = vda_handle;
break;
}
}
if (!use_insert) {
break;
}
if ((uint32_t)vda_ret == (uint32_t)GHOSTPAD_ASSIGNMENT_SCREEN_RET) {
assignment_hint = 1;
a->rc_log[7] = 0x4153474Eu; /* "ASGN" marker: assignment screen branch observed */
a->fp_usleep(300000);
}
}
}
if (assignment_hint && (a->fp_gethandle || a->fp_gethandle_ext)) {
int32_t uid_try[3];
int32_t attempt;
int32_t ui;
uid_try[0] = 1;
uid_try[1] = a->userId;
uid_try[2] = 0x10000000;
for (attempt = 0; attempt < 30 && use_insert; attempt++) {
for (ui = 0; ui < 3 && use_insert; ui++) {
int32_t idx;
for (idx = 0; idx < 8 && use_insert; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], 3, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], 3, idx);
a->rc_log[3] = gh;
a->rc_log[6] = (attempt << 8) | (idx & 0xff);
if (gh >= 0) {
vda_handle = gh;
use_insert = 0;
a->rc_log[7] = 0x56444930; /* "VDI0" marker: recovered virtual handle after ASGN */
}
}
}
if (use_insert) {
a->fp_usleep(150000);
}
}
}
} else {
/* ── SLOW PATH (SceShellCore -1, or client fallback -2) ─────────────
* Only reach here from server-side processes or as a last resort.
* 500ms sleep: let thread fully initialize (TLS, signal masks, stack). */
a->fp_usleep(500000);
/* setPriv: safe only for SceShellCore (-1); client (-2) would deadlock */
if (vda_handle == -1 && a->fp_setpriv) {
a->rc_log[2] = a->fp_setpriv(1);
}
if (vda_handle == -1) {
if (a->fp_setloginuser) {
a->rc_log[4] = a->fp_setloginuser(1);
}
if (a->fp_setusernumber) {
a->rc_log[5] = a->fp_setusernumber(1);
}
if (a->fp_setfocus) {
a->rc_log[6] = a->fp_setfocus(1, 0, 0, 0, 0, 0);
}
}
if (vda_handle == -2) {
a->rc_log[7] = (int32_t)-2; /* marker: -2 sentinel received */
vda_handle = -1;
} else if (a->fp_vda) {
/* SceShellCore: server-side, thread VDA causes no IPC loop */
int32_t uid_try[3];
int32_t ui;
uid_try[0] = 1;
uid_try[1] = a->userId;
uid_try[2] = 0x10000000;
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
struct { int32_t f[8]; } vdp;
int32_t vi;
for (vi = 0; vi < 8; vi++) vdp.f[vi] = 0;
vdp.f[0] = 32;
vdp.f[1] = uid_try[ui];
int32_t vda_ret = a->fp_vda(&vdp, 3);
a->rc_log[ui] = vda_ret;
if (ui == 0) {
a->rc_log[8] = vdp.f[0];
a->rc_log[9] = vdp.f[1];
a->rc_log[10] = vdp.f[2];
a->rc_log[11] = vdp.f[3];
a->rc_log[12] = vdp.f[4];
a->rc_log[13] = vdp.f[5];
a->rc_log[14] = vdp.f[6];
a->rc_log[15] = vdp.f[7];
}
if (vda_ret >= 0) {
vda_handle = vda_ret;
a->pad_handle = vda_handle;
a->rc_log[6] = (int32_t)0x60000001;
} else {
for (vi = 2; vi < 8 && vda_handle < 0; vi++) {
if (vdp.f[vi] != 0 &&
vdp.f[vi] != -1) {
vda_handle = vdp.f[vi];
handle_from_vda_token = 1;
a->pad_handle = vda_handle;
a->rc_log[6] = (int32_t)0x60000002;
a->rc_log[7] = vda_handle;
}
}
}
if ((uint32_t)vda_ret == (uint32_t)GHOSTPAD_ASSIGNMENT_SCREEN_RET) {
assignment_hint = 1;
a->rc_log[7] = 0x4153474Eu;
a->fp_usleep(300000);
}
}
}
if (handle_from_vda_token && (a->fp_gethandle || a->fp_gethandle_ext)) {
int32_t uid_try[3];
int32_t ui;
uid_try[0] = 1;
uid_try[1] = a->userId;
uid_try[2] = 0x10000000;
for (ui = 0; ui < 3 && handle_from_vda_token; ui++) {
int32_t idx;
for (idx = 0; idx < 8 && handle_from_vda_token; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], 3, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], 3, idx);
a->rc_log[3] = gh;
if (gh >= 0) {
vda_handle = gh;
a->pad_handle = gh;
a->rc_log[5] = (int32_t)0x70000001;
a->rc_log[7] = gh;
handle_from_vda_token = 0;
}
}
}
}
if (handle_from_vda_token && (a->fp_open || a->fp_open_ext || a->fp_open_ext2)) {
int32_t uid_try[3];
int32_t ui;
uid_try[0] = 1;
uid_try[1] = a->userId;
uid_try[2] = 0x10000000;
for (ui = 0; ui < 3 && handle_from_vda_token; ui++) {
int32_t oh = a->fp_open_ext2
? a->fp_open_ext2(uid_try[ui], 3, 0, (void *)0, 0, 0)
: (a->fp_open_ext
? a->fp_open_ext(uid_try[ui], 3, 0, (void *)0, 0, 0)
: (a->fp_open ? a->fp_open(uid_try[ui], 3, 0, (void *)0) : -1));
a->rc_log[4] = oh;
if (oh >= 0) {
vda_handle = oh;
a->pad_handle = oh;
a->rc_log[5] = (int32_t)0x70000002;
a->rc_log[7] = oh;
handle_from_vda_token = 0;
}
}
}
if (handle_from_vda_token) {
vda_handle = -1;
}
/* Fallback: GetHandle(type=3) existing virtual DualSense */
if (vda_handle < 0 && (a->fp_gethandle || a->fp_gethandle_ext)) {
int32_t uid_try[3];
int32_t ui;
uid_try[0] = 1;
uid_try[1] = a->userId;
uid_try[2] = 0x10000000;
if (assignment_hint) {
int32_t attempt;
for (attempt = 0; attempt < 60 && vda_handle < 0; attempt++) {
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
int32_t idx;
for (idx = 0; idx < 8 && vda_handle < 0; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], 3, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], 3, idx);
a->rc_log[3] = gh;
a->rc_log[6] = (attempt << 8) | (idx & 0xff);
if (gh >= 0) vda_handle = gh;
}
}
if (vda_handle < 0) {
a->fp_usleep(150000);
}
}
} else {
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
int32_t idx;
for (idx = 0; idx < 8 && vda_handle < 0; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], 3, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], 3, idx);
a->rc_log[3] = gh;
if (gh >= 0) vda_handle = gh;
}
}
}
}
if (vda_handle < 0 && (a->fp_gethandle || a->fp_gethandle_ext) && initial_pad_handle == -1 && assignment_hint) {
int32_t uid_try[3];
int32_t ui;
int32_t attempt;
uid_try[0] = 1;
uid_try[1] = a->userId;
uid_try[2] = 0x10000000;
for (attempt = 0; attempt < 40 && vda_handle < 0; attempt++) {
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
int32_t idx;
for (idx = 0; idx < 8 && vda_handle < 0; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], 0, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], 0, idx);
a->rc_log[5] = gh;
a->rc_log[6] = 0x4000 | ((attempt & 0xff) << 4) | (idx & 0xf);
if (gh >= 0) { vda_handle = gh; use_insert = 1; }
}
}
if (vda_handle < 0) {
a->fp_usleep(150000);
}
}
}
/* Fallback: GetHandle(type=0) physical DualSense */
if (vda_handle < 0 && (a->fp_gethandle || a->fp_gethandle_ext) && initial_pad_handle != -1) {
int32_t uid_try[3];
int32_t ui;
uid_try[0] = 1;
uid_try[1] = a->userId;
uid_try[2] = 0x10000000;
if (assignment_hint) {
int32_t attempt;
for (attempt = 0; attempt < 20 && vda_handle < 0; attempt++) {
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
int32_t idx;
for (idx = 0; idx < 4 && vda_handle < 0; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], 0, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], 0, idx);
a->rc_log[5] = gh;
a->rc_log[6] = 0x3000 | ((attempt & 0xff) << 4) | (idx & 0xf);
if (gh >= 0) { vda_handle = gh; use_insert = 1; }
}
}
if (vda_handle < 0) {
a->fp_usleep(150000);
}
}
} else {
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
int32_t idx;
for (idx = 0; idx < 4 && vda_handle < 0; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], 0, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], 0, idx);
a->rc_log[5] = gh;
if (gh >= 0) { vda_handle = gh; use_insert = 1; }
}
}
}
}
if (vda_handle < 0) {
a->ready = -1;
return;
}
}
a->pad_handle = vda_handle;
a->ready = 1;
a->rc_log[7] = use_insert ? 0x494e5331 : (a->rc_log[7] ? a->rc_log[7] : 0x56444930);
/* Auto-press Cross to dismiss the "who is using this controller?" dialog.
* Only needed for VDA virtual devices (type=3).
* ScePadData byte layout:
* bytes [0..3] buttons LE — Cross = 0x00004000 → byte[1] = 0x40
* byte [4] leftStick.x center=128
* byte [5] leftStick.y center=128
* byte [6] rightStick.x center=128
* byte [7] rightStick.y center=128
* bytes [24..27] quat.w = 1.0f = 0x3F800000 LE
* byte [76] connected = 1
*/
if (!use_insert) {
uint8_t ap[SHELLUI_PAD_DATA_SIZE];
int32_t ai;
for (ai = 0; ai < SHELLUI_PAD_DATA_SIZE; ai++) ap[ai] = 0;
ap[1] = 0x40;
ap[4] = 128;
ap[5] = 128;
ap[6] = 128;
ap[7] = 128;
ap[24] = 0x00;
ap[25] = 0x00;
ap[26] = 0x80;
ap[27] = 0x3F;
ap[76] = 1;
a->fp_usleep(1000000); /* 1000 ms: wait for PS5 UI dialog to render */
int32_t apr = a->fp_vdi(vda_handle, ap);
a->rc_log[5] = apr; /* log first VDI result */
a->fp_usleep(200000); /* 200 ms hold */
ap[1] = 0; /* release Cross */
a->fp_vdi(vda_handle, ap);
a->fp_usleep(100000);
}
/* Insert loop: poll seq, inject pad data when main process increments seq.
*
* On the first injection attempt we log VDI's return code in rc_log[6].
* Legacy insert fallback is disabled in current builds. */
uint32_t last_seq = 0;
int32_t probed = 0; /* 0=not yet, 1=done */
while (!a->stop) {
uint32_t cur = a->seq;
if (cur != last_seq) {
if (!probed) {
/* First data: try the preferred function and log the result. */
int32_t r_ins = -1, r_vdi = -1;
if (use_insert && a->fp_insert) {
r_ins = a->fp_insert(vda_handle, (const void *)a->pad_data);
a->rc_log[5] = r_ins;
if (r_ins < 0 && a->fp_vdi) {
/* Legacy insert rejected handle; try VDI. */
r_vdi = a->fp_vdi(vda_handle, (const void *)a->pad_data);
a->rc_log[6] = r_vdi;
if (r_vdi >= 0) use_insert = 0; /* switch to VDI */
}
} else if (a->fp_vdi) {
r_vdi = a->fp_vdi(vda_handle, (const void *)a->pad_data);
a->rc_log[6] = r_vdi;
if (r_vdi < 0 && a->fp_insert) {
/* Legacy insert fallback is disabled. */
r_ins = a->fp_insert(vda_handle, (const void *)a->pad_data);
a->rc_log[5] = r_ins;
if (r_ins >= 0) use_insert = 1;
}
}
probed = 1;
} else {
/* Subsequent data: use whichever function worked (or keep trying). */
if (use_insert && a->fp_insert)
a->fp_insert(vda_handle, (const void *)a->pad_data);
else if (a->fp_vdi)
a->fp_vdi(vda_handle, (const void *)a->pad_data);
}
last_seq = cur;
}
a->fp_usleep(500);
}
/* Delete virtual device on clean exit so next run doesn't get 0x803b0001.
* Legacy insert handles (use_insert) are not used in current builds. */
if (!use_insert && a->fp_del) a->fp_del(vda_handle);
}
__attribute__((noinline, section(".text.stub")))
void shellui_stub_end(void) { }
extern void shellui_stub_force_vda(void *arg);
extern void shellui_stub_force_vda_end(void);
__attribute__((noinline, section(".text.stubvda")))
void shellui_stub_force_vda(void *arg)
{
ShellUiPadArgs *a = (ShellUiPadArgs *)arg;
int32_t assignment_hint = 0;
int32_t fallback_handle = a->rc_log[0];
int32_t fallback_use_insert = a->rc_log[1];
int32_t pad_type = (a->virtual_device_type >= 0) ? a->virtual_device_type : 3;
int32_t vda_handle = -1;
int32_t handle_from_vda_token = 0;
int32_t use_insert = 0;
int32_t button_probe_done = 0;
int32_t uid_try[3];
int32_t ui;
if (fallback_handle < 0) fallback_handle = -1;
if (fallback_use_insert < 0) fallback_use_insert = 0;
a->rc_log[15] = (int32_t)0x53545542u; /* "STUB" */
if (!a->fp_usleep) {
a->ready = -1;
return;
}
a->fp_usleep(500000);
if (a->fp_setpriv) {
a->rc_log[4] = a->fp_setpriv(1);
}
if (a->fp_setloginuser) {
a->rc_log[5] = a->fp_setloginuser(1);
}
if (a->fp_setusernumber) {
a->rc_log[6] = a->fp_setusernumber(1);
}
if (a->fp_setfocus) {
a->rc_log[7] = a->fp_setfocus(1, 0, 0, 0, 0, 0);
}
/* Try real logged-in userId FIRST: if the pad daemon pre-assigns the device
* to a known user, GetHandle(userId, type=3) works immediately and we skip
* the assignment screen. uid=1 is the fallback "anonymous" slot. */
uid_try[0] = a->userId;
uid_try[1] = 0x10000000;
uid_try[2] = 1;
if (a->fp_vda) {
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
struct { int32_t f[8]; } vdp;
int32_t vi;
int32_t vda_ret;
for (vi = 0; vi < 8; vi++) {
vdp.f[vi] = 0;
}
vdp.f[0] = 32;
vdp.f[1] = uid_try[ui];
vda_ret = a->fp_vda(&vdp, pad_type);
a->rc_log[ui] = vda_ret;
if (ui == 0) {
a->rc_log[8] = vdp.f[0];
a->rc_log[9] = vdp.f[1];
a->rc_log[10] = vdp.f[2];
a->rc_log[11] = vdp.f[3];
a->rc_log[12] = vdp.f[4];
a->rc_log[13] = vdp.f[5];
a->rc_log[14] = vdp.f[6];
a->rc_log[15] = vdp.f[7];
}
if (vda_ret == 0) {
a->rc_log[8] = vdp.f[0];
a->rc_log[9] = vdp.f[1];
a->rc_log[10] = vdp.f[2];
a->rc_log[11] = vdp.f[3];
a->rc_log[12] = vdp.f[4];
a->rc_log[13] = vdp.f[5];
a->rc_log[14] = vdp.f[6];
a->rc_log[15] = vdp.f[7];
for (vi = 2; vi < 8 && vda_handle < 0; vi++) {
if (vdp.f[vi] != 0 && vdp.f[vi] != -1) {
vda_handle = vdp.f[vi];
handle_from_vda_token = 1;
a->pad_handle = vda_handle;
a->rc_log[6] = (int32_t)0x60000002;
a->rc_log[7] = vda_handle;
}
}
if (vda_handle >= 0) {
break;
}
/* The surgical libScePad patch forces the IPC dispatch path to
* return 0 after creating the device. That is success for
* creation, but it is not a usable VDI write handle. */
assignment_hint = 1;
a->rc_log[6] = (int32_t)0x60000000;
a->rc_log[7] = (int32_t)0x56444130u; /* "VDA0" */
break;
}
if (vda_ret > 0) {
vda_handle = vda_ret;
a->pad_handle = vda_handle;
a->rc_log[6] = (int32_t)0x60000001;
a->rc_log[7] = vda_handle;
break;
}
for (vi = 2; vi < 8 && vda_handle < 0; vi++) {
if (vdp.f[vi] != 0 && vdp.f[vi] != -1) {
vda_handle = vdp.f[vi];
handle_from_vda_token = 1;
a->pad_handle = vda_handle;
a->rc_log[6] = (int32_t)0x60000002;
a->rc_log[7] = vda_handle;
}
}
if ((uint32_t)vda_ret == (uint32_t)GHOSTPAD_ASSIGNMENT_SCREEN_RET) {
assignment_hint = 1;
a->rc_log[7] = 0x4153474Eu; /* "ASGN" */
a->fp_usleep(300000);
}
}
}
if (handle_from_vda_token && (a->fp_gethandle_ext || a->fp_gethandle)) {
for (ui = 0; ui < 3 && handle_from_vda_token; ui++) {
int32_t idx;
for (idx = 0; idx < 8 && handle_from_vda_token; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], pad_type, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], pad_type, idx);
a->rc_log[3] = gh;
if (gh >= 0) {
vda_handle = gh;
a->pad_handle = gh;
a->rc_log[5] = (int32_t)0x70000001;
a->rc_log[7] = gh;
handle_from_vda_token = 0;
}
}
}
}
if (handle_from_vda_token && (a->fp_open_ext2 || a->fp_open_ext || a->fp_open)) {
for (ui = 0; ui < 3 && handle_from_vda_token; ui++) {
int32_t oh = a->fp_open_ext2
? a->fp_open_ext2(uid_try[ui], pad_type, 0, (void *)0, 0, 0)
: (a->fp_open_ext
? a->fp_open_ext(uid_try[ui], pad_type, 0, (void *)0, 0, 0)
: (a->fp_open ? a->fp_open(uid_try[ui], pad_type, 0, (void *)0) : -1));
a->rc_log[4] = oh;
if (oh >= 0) {
vda_handle = oh;
a->pad_handle = oh;
a->rc_log[5] = (int32_t)0x70000002;
a->rc_log[7] = oh;
handle_from_vda_token = 0;
}
}
}
if (vda_handle < 0 && (a->fp_gethandle_ext || a->fp_gethandle)) {
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
int32_t idx;
for (idx = 0; idx < 8 && vda_handle < 0; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], pad_type, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], pad_type, idx);
a->rc_log[3] = gh;
if (gh >= 0) {
vda_handle = gh;
a->pad_handle = gh;
a->rc_log[7] = gh;
}
}
}
}
/* NOTE: removed the insert-based fallback (use_insert=1 + fp_insert with
* fallback_handle). The old insert function treated arg1 as an
* object pointer, not an integer handle ID. Using a type=0 handle from
* scePadOpen here causes a page fault at the handle value — confirmed by
* two SIGSEGV crashes. If VDA produced no handle, leave vda_handle=-1
* so the check below returns ready=-1 safely. */
if (vda_handle < 0 && assignment_hint && (a->fp_gethandle_ext || a->fp_gethandle)) {
/* FIRST: probe with userId=0xffffffff and userId=0 — the VDA-created
* device has userId=0xffffffff before assignment. GetHandle with the
* assigned userIds (1, a->userId, 0x10000000) always fails until the
* user dismisses the assignment screen, but the unassigned userId may
* be directly queryable. */
int32_t special_uid[2] = {(int32_t)0xffffffff, 0};
int32_t sui, sidx;
for (sui = 0; sui < 2 && vda_handle < 0; sui++) {
for (sidx = 0; sidx < 8 && vda_handle < 0; sidx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(special_uid[sui], pad_type, sidx, 0, 0, 0)
: a->fp_gethandle(special_uid[sui], pad_type, sidx);
a->rc_log[3] = gh;
a->rc_log[6] = (int32_t)(0x5000 | (sui << 4) | (sidx & 0xf));
if (gh >= 0) {
vda_handle = gh;
a->pad_handle = gh;
a->rc_log[5] = (int32_t)0x70000007;
a->rc_log[7] = (int32_t)0x56444931u; /* "VDI1" unassigned path */
}
}
}
/* THEN: also try Open(userId=0xffffffff, type=3) — in case GetHandle
* rejects 0xffffffff but Open resolves the unassigned slot. */
if (vda_handle < 0 && (a->fp_open_ext2 || a->fp_open_ext || a->fp_open)) {
for (sui = 0; sui < 2 && vda_handle < 0; sui++) {
int32_t oh = a->fp_open_ext2
? a->fp_open_ext2(special_uid[sui], pad_type, 0, (void *)0, 0, 0)
: (a->fp_open_ext
? a->fp_open_ext(special_uid[sui], pad_type, 0, (void *)0, 0, 0)
: a->fp_open(special_uid[sui], pad_type, 0, (void *)0));
a->rc_log[4] = oh;
a->rc_log[6] = (int32_t)(0x5100 | (sui & 0xf));
if (oh >= 0) {
vda_handle = oh;
a->pad_handle = oh;
a->rc_log[5] = (int32_t)0x70000008;
a->rc_log[7] = (int32_t)0x56444932u; /* "VDI2" open-unassigned path */
}
}
}
/* FINALLY: poll with canonical userIds waiting for post-assignment.
* 400 iterations × 150ms = 60 seconds — allows manual user dismiss. */
int32_t attempt;
for (attempt = 0; attempt < 400 && vda_handle < 0; attempt++) {
for (ui = 0; ui < 3 && vda_handle < 0; ui++) {
int32_t idx;
for (idx = 0; idx < 8 && vda_handle < 0; idx++) {
int32_t gh = a->fp_gethandle_ext
? a->fp_gethandle_ext(uid_try[ui], pad_type, idx, 0, 0, 0)
: a->fp_gethandle(uid_try[ui], pad_type, idx);
a->rc_log[3] = gh;
a->rc_log[6] = (int32_t)(0x4100 | ((attempt & 0xff) << 4) | (idx & 0xf));
if (gh >= 0) {
vda_handle = gh;
a->pad_handle = gh;
a->rc_log[5] = (int32_t)0x70000006;
a->rc_log[7] = (int32_t)0x56444930u; /* "VDI0" */
}
}
}
if (vda_handle < 0) {
a->fp_usleep(150000);
}
}
}
/* Do not issue a second VDA call after external Mbus assignment. Live klog
* showed this creates another unassigned virtual pad instead of a write
* handle, which pollutes the assignment state and obscures diagnostics. */
if (vda_handle < 0) {
a->ready = -1;
return;
}
{
uint8_t ap[SHELLUI_PAD_DATA_SIZE];
int32_t ai;
int32_t frame;
int32_t press_ret = 0;
int32_t release_ret = 0;
for (ai = 0; ai < SHELLUI_PAD_DATA_SIZE; ai++) {
ap[ai] = 0;
}
ap[4] = 128;
ap[5] = 128;
ap[6] = 128;
ap[7] = 128;
ap[24] = 0x00;
ap[25] = 0x00;
ap[26] = 0x80;
ap[27] = 0x3F;
ap[76] = 1;
a->rc_log[12] = GHOSTPAD_AUTO_DISMISS_ACTIVE;
a->rc_log[13] = use_insert ? (int32_t)0x494E5331u : (int32_t)0x56444930u;
a->rc_log[14] = 0;
a->rc_log[15] = 0;
a->fp_usleep(1000000);
for (frame = 0; frame < 12; frame++) {
if (use_insert && a->fp_insert) {
a->fp_insert(vda_handle, (const void *)ap);
} else if (a->fp_vdi) {
a->fp_vdi(vda_handle, (const void *)ap);
}
a->fp_usleep(16000);
}
ap[1] = 0x40;
for (frame = 0; frame < 30; frame++) {
if (use_insert && a->fp_insert) {
press_ret = a->fp_insert(vda_handle, (const void *)ap);
} else if (a->fp_vdi) {
press_ret = a->fp_vdi(vda_handle, (const void *)ap);
}
a->fp_usleep(16000);
}
ap[1] = 0x00;
for (frame = 0; frame < 18; frame++) {
if (use_insert && a->fp_insert) {
release_ret = a->fp_insert(vda_handle, (const void *)ap);
} else if (a->fp_vdi) {
release_ret = a->fp_vdi(vda_handle, (const void *)ap);
}
a->fp_usleep(16000);
}
a->rc_log[12] = GHOSTPAD_AUTO_DISMISS_DONE;
a->rc_log[14] = press_ret;
a->rc_log[15] = release_ret;
button_probe_done = 1;
}
a->pad_handle = vda_handle;
a->ready = 1;
if (a->rc_log[7] == 0 || a->rc_log[7] == (int32_t)0x4153474Eu) {
a->rc_log[7] = (int32_t)0x56444930u; /* "VDI0" */
}
{
uint32_t last_seq = 0;
while (!a->stop) {
uint32_t cur = a->seq;
if (cur != last_seq) {
uint32_t buttons = *(const uint32_t *)(const void *)a->pad_data;
{
int32_t active_ret = (int32_t)0xDEADBEEFu;
if (use_insert && a->fp_insert) {
active_ret = a->fp_insert(vda_handle, (const void *)a->pad_data);
} else if (a->fp_vdi) {
active_ret = a->fp_vdi(vda_handle, (const void *)a->pad_data);
} else if (a->fp_insert) {
use_insert = 1;
active_ret = a->fp_insert(vda_handle, (const void *)a->pad_data);
}
if (buttons != 0 && !button_probe_done) {
a->rc_log[12] = (int32_t)buttons;
a->rc_log[13] = use_insert
? (int32_t)0xB7001001u
: (int32_t)0xB7001002u;
a->rc_log[14] = active_ret;
a->rc_log[15] = vda_handle;
button_probe_done = 1;
}
}
last_seq = cur;
}
a->fp_usleep(500);
}
}
if (!use_insert && a->fp_del) {
a->fp_del(vda_handle);
}
}
__attribute__((noinline, section(".text.stubvda")))
void shellui_stub_force_vda_end(void) { }
/* shellui_pad_inject — inject a pad stub into an attachable target process.
* PT_ATTACH happens first; nothing is written until attach succeeds. */
int
shellui_pad_inject(int32_t userId,
int force_virtual_vda,
int32_t virtual_device_type,
pid_t *out_shellui_pid,
intptr_t *out_args_kaddr)
{
/* Candidate processes: SceShellCore first (server-side VDA), SceShellUI as fallback */
static const char *const candidates_vda[] = {
"SceShellCore",
"SceShellUI",
NULL
};
static const char *const candidates_normal[] = {
"SceShellCore",
"SceShellUI",
NULL
};
const char *const *candidates = force_virtual_vda ? candidates_vda : candidates_normal;
for (int i = 0; candidates[i] != NULL; i++) {
pid_t target_pids[16];
size_t target_pid_count = 0;
const char *target_name = candidates[i];
target_pid_count = find_pids(target_name, target_pids, sizeof(target_pids) / sizeof(target_pids[0]));
if (target_pid_count == 0) {
klog_printf("[Ghostpad] %s: not found\n", target_name);
continue;
}
for (size_t pid_index = 0; pid_index < target_pid_count; pid_index++) {
pid_t target_pid = target_pids[pid_index];
uint32_t libpad_h = 0, libkernel_h = 0, libpthread_h = 0, liblibc_h = 0;
intptr_t fn_gethandle = 0, fn_gethandle_ext = 0, fn_open = 0, fn_open_ext = 0, fn_open_ext2 = 0, fn_insert = 0, fn_vdi = 0;
intptr_t fn_vda = 0, fn_del = 0, fn_setpriv = 0, fn_setloginuser = 0;
intptr_t fn_setusernumber = 0, fn_setfocus = 0, fn_usleep = 0, fn_pthread_create = 0, fn_mmap = 0;
intptr_t fn_malloc = 0, fn_free = 0;
size_t stub_code_size = 0, args_offset = 0, alloc_size = 0;
size_t stub_alloc_size = 0, args_alloc_size = 0;
intptr_t stub_mem = 0, trap_mem = 0, args_mem = 0, args_kaddr = 0, thread_storage = 0, stub_fn = 0;
intptr_t init_mem = 0, fini_mem = 0;
const void *stub_src = NULL;
const void *stub_end = NULL;
int is_shellcore = 0;
int is_shellui = 0;
int32_t pt_pad_handle = -1;
int32_t pt_use_insert = 0;
uint8_t int3 = 0xCC;
klog_printf("[Ghostpad] PT_ATTACH(%s pid=%d)...\n", target_name, target_pid);
if (sys_ptrace(PT_ATTACH, target_pid, 0, 0) != 0) {
klog_printf("[Ghostpad] PT_ATTACH(%s): errno=%d\n", target_name, errno);
continue;
}
waitpid(target_pid, NULL, 0);
klog_printf("[Ghostpad] attached to %s (pid=%d) authid=0x%016lx\n",
target_name, target_pid, kernel_get_ucred_authid(target_pid));
if (get_lib(target_pid, "libScePad", &libpad_h) ||
get_lib(target_pid, "libkernel_sys", &libkernel_h)) {
klog_printf("[Ghostpad] library lookup failed in %s\n", target_name);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
continue;
}
get_lib(target_pid, "libpthread", &libpthread_h);
get_lib(target_pid, "libSceLibcInternal", &liblibc_h);
fn_gethandle = resolve_sym(target_pid, libpad_h, "scePadGetHandle");
fn_open = resolve_sym(target_pid, libpad_h, "scePadOpen");
fn_open_ext = resolve_sym(target_pid, libpad_h, "scePadOpenExt");
fn_open_ext2 = resolve_sym(target_pid, libpad_h, "scePadOpenExt2");
fn_insert = 0;
fn_vdi = resolve_sym(target_pid, libpad_h, "scePadVirtualDeviceInsertData");
fn_gethandle_ext = fn_gethandle;
fn_vda = resolve_sym(target_pid, libpad_h, "scePadVirtualDeviceAddDevice");
fn_del = resolve_sym(target_pid, libpad_h, "scePadVirtualDeviceDeleteDevice");
fn_setpriv = resolve_sym(target_pid, libpad_h, "scePadSetProcessPrivilege");
fn_setloginuser = resolve_sym(target_pid, libpad_h, "scePadSetLoginUserNumber");
fn_setusernumber = resolve_sym(target_pid, libpad_h, "scePadSetUserNumber");
fn_setfocus = resolve_sym(target_pid, libpad_h, "scePadSetProcessFocus");
fn_usleep = resolve_sym(target_pid, libkernel_h, "usleep");
fn_pthread_create = resolve_sym(target_pid, libkernel_h, "pthread_create");
fn_mmap = resolve_sym(target_pid, libkernel_h, "mmap");
if (liblibc_h) {
fn_malloc = resolve_sym(target_pid, liblibc_h, "malloc");
fn_free = resolve_sym(target_pid, liblibc_h, "free");
}
if (libpthread_h) {
if (!fn_pthread_create)
fn_pthread_create = resolve_sym(target_pid, libpthread_h, "pthread_create");
if (!fn_usleep)
fn_usleep = resolve_sym(target_pid, libpthread_h, "usleep");
}
klog_printf("[Ghostpad] scePadGetHandle @ 0x%lx\n", fn_gethandle);
klog_printf("[Ghostpad] scePadOpen @ 0x%lx\n", fn_open);
klog_printf("[Ghostpad] scePadOpenExt @ 0x%lx\n", fn_open_ext);
klog_printf("[Ghostpad] scePadOpenExt2 @ 0x%lx\n", fn_open_ext2);
klog_printf("[Ghostpad] scePadVirtualDeviceInsertData @ 0x%lx\n", fn_vdi);
klog_printf("[Ghostpad] scePadVirtualDeviceAddDevice @ 0x%lx\n", fn_vda);
klog_printf("[Ghostpad] scePadVirtualDeviceDeleteDevice @ 0x%lx\n", fn_del);
klog_printf("[Ghostpad] scePadSetProcessPrivilege @ 0x%lx\n", fn_setpriv);
klog_printf("[Ghostpad] scePadSetLoginUserNumber @ 0x%lx\n", fn_setloginuser);
klog_printf("[Ghostpad] scePadSetUserNumber @ 0x%lx\n", fn_setusernumber);
klog_printf("[Ghostpad] scePadSetProcessFocus @ 0x%lx\n", fn_setfocus);
klog_printf("[Ghostpad] usleep @ 0x%lx\n", fn_usleep);
klog_printf("[Ghostpad] pthread_create @ 0x%lx\n", fn_pthread_create);
klog_printf("[Ghostpad] mmap @ 0x%lx\n", fn_mmap);
klog_printf("[Ghostpad] malloc @ 0x%lx\n", fn_malloc);
klog_printf("[Ghostpad] free @ 0x%lx\n", fn_free);
if (force_virtual_vda) {
if (!fn_vda || !fn_vdi || !fn_usleep || !fn_pthread_create) {
klog_printf("[Ghostpad] VDA symbol resolution failed in %s\n", target_name);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
continue;
}
} else if (!fn_gethandle || !fn_vdi || !fn_usleep || !fn_pthread_create) {
klog_printf("[Ghostpad] symbol resolution failed in %s\n", target_name);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
continue;
}
if (force_virtual_vda) {
stub_src = (const void *)shellui_stub_force_vda;
stub_end = (const void *)shellui_stub_force_vda_end;
} else {
stub_src = (const void *)shellui_stub;
stub_end = (const void *)shellui_stub_end;
}
stub_code_size = (size_t)((const char *)stub_end - (const char *)stub_src);
args_offset = (16 + stub_code_size + 15) & ~(size_t)15;
alloc_size = args_offset + sizeof(ShellUiPadArgs) + 16;
klog_printf("[Ghostpad] stub_code=%zu args_off=%zu alloc=%zu force_vda=%d\n",
stub_code_size, args_offset, alloc_size, force_virtual_vda);
init_mem = kernel_dynlib_init_addr(target_pid, libpad_h);
fini_mem = kernel_dynlib_fini_addr(target_pid, libpad_h);
trap_mem = init_mem ? init_mem : fini_mem;
if (!trap_mem) {
klog_printf("[Ghostpad] no code cave available in %s\n", target_name);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
continue;
}
klog_printf("[Ghostpad] init cave @ 0x%lx fini cave @ 0x%lx\n",
init_mem, fini_mem);
stub_alloc_size = (16 + stub_code_size + 15) & ~(size_t)15;
args_alloc_size = sizeof(ShellUiPadArgs) + 16;
if (kernel_set_vmem_protection(target_pid, trap_mem, stub_alloc_size,
PROT_READ | PROT_WRITE | PROT_EXEC)) {
klog_printf("[Ghostpad] kernel_set_vmem_protection(RWX trap) failed in %s\n", target_name);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
continue;
}
klog_printf("[Ghostpad] trap cave @ 0x%lx RWX ok\n", trap_mem);
stub_mem = trap_mem;
args_mem = stub_mem;
pt_io_write(target_pid, stub_mem, &int3, 1);
if (fn_malloc) {
int64_t malloc_ret = pt_call(target_pid, fn_malloc, stub_mem,
(uint64_t)args_alloc_size, 0, 0, 0, 0, 0);
if (malloc_ret > 0) {
args_mem = (intptr_t)malloc_ret;
klog_printf("[Ghostpad] malloc args block @ 0x%lx (%zu bytes)\n",
args_mem, args_alloc_size);
} else {
klog_printf("[Ghostpad] malloc args block failed -> %lld; falling back to code cave storage\n",
(long long)malloc_ret);
}
}
if (args_mem == stub_mem && init_mem && fini_mem && init_mem != fini_mem) {
args_mem = (stub_mem == init_mem) ? fini_mem : init_mem;
if (kernel_set_vmem_protection(target_pid, args_mem, args_alloc_size,
PROT_READ | PROT_WRITE | PROT_EXEC)) {
klog_printf("[Ghostpad] separate args cave protection failed in %s; reusing stub cave\n",
target_name);
args_mem = stub_mem;
}
}
if (args_mem != stub_mem && args_mem != init_mem && args_mem != fini_mem) {
klog_printf("[Ghostpad] conservative stub cave @ 0x%lx heap args block @ 0x%lx\n",
stub_mem, args_mem);
args_kaddr = args_mem;
} else if (args_mem == stub_mem) {
klog_printf("[Ghostpad] conservative code cave @ 0x%lx RWX ok (shared stub/args, mmap disabled)\n",
stub_mem);
args_kaddr = stub_mem + (intptr_t)args_offset;
} else {
klog_printf("[Ghostpad] conservative stub cave @ 0x%lx separate args cave @ 0x%lx\n",
stub_mem, args_mem);
args_kaddr = args_mem + 16;
}
thread_storage = stub_mem + 8;
stub_fn = stub_mem + 16;
klog_printf("[Ghostpad] stub_fn=0x%lx args=0x%lx\n", stub_fn, args_kaddr);
if (fn_setpriv) {
int64_t spriv = pt_call(target_pid, fn_setpriv, stub_mem, 1, 0, 0, 0, 0, 0);
klog_printf("[Ghostpad] scePadSetProcessPrivilege(1) in %s -> %lld\n",
target_name, (long long)spriv);
}
is_shellcore = (strcmp(target_name, "SceShellCore") == 0);
is_shellui = (strcmp(target_name, "SceShellUI") == 0);
if (!force_virtual_vda && !is_shellcore && fn_gethandle) {
int32_t try_users[2];
int tu, ti;
try_users[0] = userId;
try_users[1] = 0x10000000;
for (tu = 0; tu < 2 && pt_pad_handle < 0; tu++) {
for (ti = 0; ti < 4 && pt_pad_handle < 0; ti++) {
int64_t gh = pt_call(target_pid, fn_gethandle, stub_mem,
(uint64_t)try_users[tu], 0, (uint64_t)ti,
0, 0, 0);
klog_printf("[Ghostpad] pt_call GetHandle(0x%x,0,%d) -> 0x%llx\n",
try_users[tu], ti, (unsigned long long)(uint64_t)gh);
if ((int32_t)gh >= 0) { pt_pad_handle = (int32_t)gh; pt_use_insert = 0; }
}
}
}
if (!force_virtual_vda && !is_shellcore && pt_pad_handle < 0 && fn_open) {
int32_t try_users[2];
int tu2;
try_users[0] = userId;
try_users[1] = 0x10000000;
for (tu2 = 0; tu2 < 2 && pt_pad_handle < 0; tu2++) {
int64_t oh = pt_call(target_pid, fn_open, stub_mem,
(uint64_t)try_users[tu2], 0, 0, 0, 0, 0);
klog_printf("[Ghostpad] pt_call scePadOpen(0x%x,0) -> 0x%llx\n",
try_users[tu2], (unsigned long long)(uint64_t)oh);
if ((int32_t)oh >= 0) { pt_pad_handle = (int32_t)oh; pt_use_insert = 1; }
}
}
if (force_virtual_vda && !is_shellcore && pt_pad_handle < 0 && fn_open) {
int32_t try_users[2];
int tu2;
try_users[0] = userId;
try_users[1] = 0x10000000;
for (tu2 = 0; tu2 < 2 && pt_pad_handle < 0; tu2++) {
int64_t oh = pt_call(target_pid, fn_open, stub_mem,
(uint64_t)try_users[tu2], 0, 0, 0, 0, 0);
klog_printf("[Ghostpad] pt_call fallback scePadOpen(0x%x,0) -> 0x%llx\n",
try_users[tu2], (unsigned long long)(uint64_t)oh);
if ((int32_t)oh >= 0) { pt_pad_handle = (int32_t)oh; pt_use_insert = 1; }
}
}
if (force_virtual_vda && !is_shellcore && pt_pad_handle < 0 && (fn_gethandle_ext || fn_gethandle)) {
int32_t try_users[2];
int tu, ti;
try_users[0] = userId;
try_users[1] = 0x10000000;
for (tu = 0; tu < 2 && pt_pad_handle < 0; tu++) {
for (ti = 0; ti < 4 && pt_pad_handle < 0; ti++) {
int64_t gh = fn_gethandle_ext
? pt_call(target_pid, fn_gethandle_ext, stub_mem,
(uint64_t)try_users[tu], 0, (uint64_t)ti,
0, 0, 0)
: pt_call(target_pid, fn_gethandle, stub_mem,
(uint64_t)try_users[tu], 0, (uint64_t)ti,
0, 0, 0);
klog_printf("[Ghostpad] pt_call fallback GetHandle(0x%x,0,%d) -> 0x%llx\n",
try_users[tu], ti, (unsigned long long)(uint64_t)gh);
if ((int32_t)gh >= 0) { pt_pad_handle = (int32_t)gh; pt_use_insert = 1; }
}
}
}
if (force_virtual_vda && !is_shellcore && pt_pad_handle < 0 &&
(fn_open_ext2 || fn_open_ext)) {
int32_t try_users[2];
int tu2;
try_users[0] = userId;
try_users[1] = 0x10000000;
for (tu2 = 0; tu2 < 2 && pt_pad_handle < 0; tu2++) {
int64_t oh = fn_open_ext2
? pt_call(target_pid, fn_open_ext2, stub_mem,
(uint64_t)try_users[tu2], 0, 0, 0, 0, 0)
: (fn_open_ext
? pt_call(target_pid, fn_open_ext, stub_mem,
(uint64_t)try_users[tu2], 0, 0, 0, 0, 0)
: pt_call(target_pid, fn_open, stub_mem,
(uint64_t)try_users[tu2], 0, 0, 0, 0, 0));
klog_printf("[Ghostpad] pt_call fallback scePadOpen*(0x%x,0) -> 0x%llx\n",
try_users[tu2], (unsigned long long)(uint64_t)oh);
if ((int32_t)oh >= 0) { pt_pad_handle = (int32_t)oh; pt_use_insert = 1; }
}
}
if (force_virtual_vda && pt_pad_handle >= 0)
klog_printf("[Ghostpad] force_virtual_vda=1: captured fallback handle=%d use_insert=%d; stub will try VDA first\n",
pt_pad_handle, pt_use_insert);
else if (force_virtual_vda)
klog_printf("[Ghostpad] force_virtual_vda=1: no pt_call fallback handle; stub will try VDA in thread context\n");
else if (pt_pad_handle >= 0)
klog_printf("[Ghostpad] pt_call handle=%d use_insert=%d — stub skips IPC\n",
pt_pad_handle, pt_use_insert);
else if (!is_shellcore)
klog_printf("[Ghostpad] pt_call GetHandle/Open all failed — stub pad_handle=-2\n");
if (is_shellui) {
klog_printf("[Ghostpad] skipping unsafe pthread_create in %s: thread launch remains crash-prone on this firmware\n",
target_name);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
continue;
}
if (!force_virtual_vda && !is_shellcore && pt_pad_handle < 0) {
klog_printf("[Ghostpad] skipping unsafe pthread_create in %s: no usable pt_call pad handle\n",
target_name);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
continue;
}
pt_io_write(target_pid, stub_fn, (void *)stub_src, stub_code_size);
{
ShellUiPadArgs args;
memset(&args, 0, sizeof(args));
args.fp_gethandle = (void *)fn_gethandle;
args.fp_gethandle_ext = (void *)fn_gethandle_ext;
args.fp_open = (void *)fn_open;
args.fp_open_ext = (void *)fn_open_ext;
args.fp_open_ext2 = (void *)fn_open_ext2;
args.fp_insert = (void *)fn_insert;
args.fp_vdi = (void *)fn_vdi;
args.fp_vda = (void *)fn_vda;
args.fp_del = (void *)fn_del;
args.fp_setpriv = (void *)fn_setpriv;
args.fp_setloginuser = (void *)fn_setloginuser;
args.fp_setusernumber = (void *)fn_setusernumber;
args.fp_setfocus = (void *)fn_setfocus;
args.fp_usleep = (void *)fn_usleep;
args.userId = userId;
args.virtual_device_type = virtual_device_type;
args.pad_handle = (pt_pad_handle >= 0) ? pt_pad_handle : ((is_shellcore || force_virtual_vda) ? -1 : -2);
args.seq = (uint32_t)pt_use_insert;
if (force_virtual_vda) {
args.pad_handle = -1;
args.rc_log[0] = pt_pad_handle;
args.rc_log[1] = pt_use_insert;
}
pt_io_write(target_pid, args_kaddr, &args, sizeof(args));
}
{
int64_t pret = pt_call(target_pid, fn_pthread_create, stub_mem,
(uint64_t)thread_storage, 0,
(uint64_t)stub_fn, (uint64_t)args_kaddr, 0, 0);
klog_printf("[Ghostpad] pthread_create(%s) -> %lld\n",
target_name, (long long)pret);
if (pret == 0) {
/* Save relaunch state so post-GBND can restart stub with a known handle */
g_relaunch_pid = target_pid;
g_relaunch_args_kaddr = args_kaddr;
g_relaunch_stub_fn = stub_fn;
g_relaunch_thread_storage = thread_storage;
g_relaunch_pthread_fn = fn_pthread_create;
g_relaunch_trap_rip = stub_mem; /* stub_mem+0 is the INT3 trap */
g_relaunch_malloc_fn = fn_malloc;
klog_printf("[Ghostpad] relaunch state saved: stub_fn=0x%lx pthread=0x%lx trap=0x%lx\n",
stub_fn, fn_pthread_create, stub_mem);
if (pt_pad_handle >= 0 &&
((pt_use_insert && fn_insert) || (!pt_use_insert && fn_vdi))) {
g_shellui_direct_state.valid = 1;
g_shellui_direct_state.attached = 0;
g_shellui_direct_state.pid = target_pid;
g_shellui_direct_state.args_kaddr = args_kaddr;
g_shellui_direct_state.trap_rip = stub_mem;
g_shellui_direct_state.fn_setpriv = fn_setpriv;
g_shellui_direct_state.fn_setloginuser = fn_setloginuser;
g_shellui_direct_state.fn_setusernumber = fn_setusernumber;
g_shellui_direct_state.fn_setfocus = fn_setfocus;
g_shellui_direct_state.fn_usleep = fn_usleep;
g_shellui_direct_state.fn_gethandle = fn_gethandle;
g_shellui_direct_state.fn_gethandle_ext = fn_gethandle_ext;
g_shellui_direct_state.fn_open = fn_open;
g_shellui_direct_state.fn_open_ext = fn_open_ext;
g_shellui_direct_state.fn_open_ext2 = fn_open_ext2;
g_shellui_direct_state.fn_insert = fn_insert;
g_shellui_direct_state.fn_vdi = fn_vdi;
g_shellui_direct_state.pad_handle = pt_pad_handle;
g_shellui_direct_state.use_insert = pt_use_insert ? 1 : 0;
klog_printf("[Ghostpad] cached direct insert path handle=%d use_insert=%d trap=0x%lx\n",
pt_pad_handle, pt_use_insert ? 1 : 0, stub_mem);
} else if (force_virtual_vda) {
g_shellui_direct_state.valid = 1;
g_shellui_direct_state.attached = 0;
g_shellui_direct_state.pid = target_pid;
g_shellui_direct_state.args_kaddr = args_kaddr;
g_shellui_direct_state.trap_rip = stub_mem;
g_shellui_direct_state.fn_setpriv = fn_setpriv;
g_shellui_direct_state.fn_setloginuser = fn_setloginuser;
g_shellui_direct_state.fn_setusernumber = fn_setusernumber;
g_shellui_direct_state.fn_setfocus = fn_setfocus;
g_shellui_direct_state.fn_usleep = fn_usleep;
g_shellui_direct_state.fn_gethandle = fn_gethandle;
g_shellui_direct_state.fn_gethandle_ext = fn_gethandle_ext;
g_shellui_direct_state.fn_open = fn_open;
g_shellui_direct_state.fn_open_ext = fn_open_ext;
g_shellui_direct_state.fn_open_ext2 = fn_open_ext2;
g_shellui_direct_state.fn_insert = fn_insert;
g_shellui_direct_state.fn_vdi = fn_vdi;
g_shellui_direct_state.pad_handle = -1;
g_shellui_direct_state.use_insert = 0;
klog_printf("[Ghostpad] cached direct recovery context trap=0x%lx (no initial handle)\n",
stub_mem);
} else {
memset(&g_shellui_direct_state, 0, sizeof(g_shellui_direct_state));
}
*out_shellui_pid = target_pid;
*out_args_kaddr = args_kaddr;
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
klog_printf("[Ghostpad] detached from %s ret=0\n", target_name);
return 0;
}
klog_printf("[Ghostpad] pthread_create failed in %s\n", target_name);
if (fn_free && args_mem != 0 && args_mem != stub_mem &&
args_mem != init_mem && args_mem != fini_mem) {
int64_t free_ret = pt_call(target_pid, fn_free, stub_mem,
(uint64_t)args_mem, 0, 0, 0, 0, 0);
klog_printf("[Ghostpad] free(args_mem=0x%lx) -> %lld\n",
args_mem, (long long)free_ret);
}
}
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
klog_printf("[Ghostpad] detached from %s ret=-1\n", target_name);
}
}
klog_printf("[Ghostpad] no attachable injection target found\n");
return -1;
}
/* shellui_pad_update — write new pad data at 60 Hz via mdbg_copyin */
int
shellui_pad_update(pid_t shellui_pid, intptr_t args_kaddr,
const void *pad_data, uint32_t pad_data_len)
{
static pid_t cached_pid = -1;
static intptr_t cached_args = 0;
static uint32_t cached_seq = 0;
static int logged_context = 0;
static int logged_data_copy_failure = 0;
static int logged_seq_copy_failure = 0;
if (pad_data_len > SHELLUI_PAD_DATA_SIZE)
pad_data_len = SHELLUI_PAD_DATA_SIZE;
if (shellui_pid != cached_pid || args_kaddr != cached_args) {
uint32_t observed_seq = (uint32_t)mdbg_getint(
shellui_pid,
args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, seq));
cached_pid = shellui_pid;
cached_args = args_kaddr;
/*
* Target-side seq readback can degrade to 0 after startup on the
* working assignment-screen recovery path. Seed from the observed
* value when available, otherwise jump to 1 so the first outbound
* write still advances the stub's last-seen sequence.
*/
cached_seq = (observed_seq != 0) ? observed_seq : 1;
if (!logged_context) {
klog_printf("[Ghostpad] shellui_pad_update context pid=%d args=0x%lx observed_seq=%u cached_seq=%u ready=%d handle=%d\n",
shellui_pid, (unsigned long)args_kaddr, observed_seq,
cached_seq,
(int32_t)mdbg_getint(shellui_pid,
args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, ready)),
(int32_t)mdbg_getint(shellui_pid,
args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, pad_handle)));
logged_context = 1;
}
}
uint32_t new_seq = cached_seq + 1;
intptr_t data_field = args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, pad_data);
/* Elevate to ptrace authid for mdbg_copyin; restore game authid immediately after */
pid_t _mypid = getpid();
uint64_t _saved_au = kernel_get_ucred_authid(_mypid);
if (_saved_au) kernel_set_ucred_authid(_mypid, 0x4800000000010003l);
int copy_ret = mdbg_copyin(shellui_pid, pad_data, data_field, pad_data_len);
if (_saved_au) kernel_set_ucred_authid(_mypid, _saved_au);
if (copy_ret) {
if (!logged_data_copy_failure) {
klog_printf("[Ghostpad] shellui_pad_update data copy failed ret=%d errno=%d pid=%d\n",
copy_ret, errno, shellui_pid);
logged_data_copy_failure = 1;
}
if (shellui_pad_ptrace_update(shellui_pid, args_kaddr, pad_data,
pad_data_len, new_seq) == 0) {
cached_seq = new_seq;
return 0;
}
return -1;
}
if (shellui_pid != cached_pid || args_kaddr != cached_args) {
uint32_t observed_seq = (uint32_t)mdbg_getint(
shellui_pid,
args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, seq));
cached_pid = shellui_pid;
cached_args = args_kaddr;
/*
* Target-side seq readback can degrade to 0 after startup on the
* working assignment-screen recovery path. Seed from the observed
* value when available, otherwise jump to 1 so the first outbound
* write still advances the stub's last-seen sequence.
*/
cached_seq = (observed_seq != 0) ? observed_seq : 1;
if (!logged_context) {
klog_printf("[Ghostpad] shellui_pad_update context pid=%d args=0x%lx observed_seq=%u cached_seq=%u ready=%d handle=%d\n",
shellui_pid, (unsigned long)args_kaddr, observed_seq,
cached_seq,
(int32_t)mdbg_getint(shellui_pid,
args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, ready)),
(int32_t)mdbg_getint(shellui_pid,
args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, pad_handle)));
logged_context = 1;
}
}
intptr_t seq_field = args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, seq);
_saved_au = kernel_get_ucred_authid(_mypid);
if (_saved_au) kernel_set_ucred_authid(_mypid, 0x4800000000010003l);
copy_ret = mdbg_copyin(shellui_pid, &new_seq, seq_field, 4);
if (_saved_au) kernel_set_ucred_authid(_mypid, _saved_au);
if (copy_ret) {
if (!logged_seq_copy_failure) {
klog_printf("[Ghostpad] shellui_pad_update seq copy failed ret=%d errno=%d pid=%d args=0x%lx seq=0x%lx old=%u new=%u\n",
copy_ret, errno, shellui_pid, (unsigned long)args_kaddr,
(unsigned long)seq_field, cached_seq, new_seq);
logged_seq_copy_failure = 1;
}
if (shellui_pad_ptrace_update(shellui_pid, args_kaddr, pad_data,
pad_data_len, new_seq) == 0) {
cached_seq = new_seq;
return 0;
}
return -1;
}
cached_seq = new_seq;
return 0;
}
int
shellui_pad_direct_usable(pid_t shellui_pid, intptr_t args_kaddr)
{
return shellui_pad_direct_context_usable(shellui_pid, args_kaddr) &&
g_shellui_direct_state.pad_handle >= 0 &&
!g_shellui_direct_state.use_insert;
}
int
shellui_pad_direct_mode(pid_t shellui_pid, intptr_t args_kaddr)
{
if (!shellui_pad_direct_context_usable(shellui_pid, args_kaddr) ||
g_shellui_direct_state.pad_handle < 0) {
return -1;
}
return g_shellui_direct_state.use_insert ? 1 : 0;
}
int
shellui_pad_direct_adopt_vdi_handle(pid_t shellui_pid, intptr_t args_kaddr,
int32_t vdi_handle)
{
if (!shellui_pad_direct_context_usable(shellui_pid, args_kaddr) ||
!g_shellui_direct_state.fn_vdi ||
vdi_handle <= 0) {
return -1;
}
g_shellui_direct_state.pad_handle = vdi_handle;
g_shellui_direct_state.use_insert = 0;
shellui_pad_direct_set_last_status(0x7100, vdi_handle);
klog_printf("[Ghostpad] direct_adopt_vdi_handle handle=0x%x pid=%d trap=0x%lx\n",
(uint32_t)vdi_handle, shellui_pid,
(unsigned long)g_shellui_direct_state.trap_rip);
return 0;
}
int
shellui_pad_direct_recover(pid_t shellui_pid, intptr_t args_kaddr, int32_t userId, int32_t altUserId)
{
int32_t try_users[4];
int try_user_count = 0;
int32_t handle = -1;
int use_insert = 0;
int begin_ret = 0;
int64_t setup_ret = 0;
shellui_pad_direct_set_last_status(0x1000, 0);
if (!shellui_pad_direct_context_usable(shellui_pid, args_kaddr)) {
shellui_pad_direct_set_last_status(0x1001, -2);
klog_printf("[Ghostpad] direct_recover context mismatch: requested pid=%d args=0x%lx cached valid=%d pid=%d args=0x%lx handle=%d attached=%d\n",
shellui_pid,
(unsigned long)args_kaddr,
g_shellui_direct_state.valid ? 1 : 0,
g_shellui_direct_state.pid,
(unsigned long)g_shellui_direct_state.args_kaddr,
g_shellui_direct_state.pad_handle,
g_shellui_direct_state.attached ? 1 : 0);
return -2;
}
if (g_shellui_direct_state.pad_handle >= 0) {
shellui_pad_direct_set_last_status(0x1002, g_shellui_direct_state.pad_handle);
klog_printf("[Ghostpad] direct_recover reusing cached handle=%d use_insert=%d\n",
g_shellui_direct_state.pad_handle,
g_shellui_direct_state.use_insert ? 1 : 0);
return 0;
}
begin_ret = shellui_pad_direct_begin(shellui_pid, args_kaddr);
if (begin_ret != 0) {
shellui_pad_direct_set_last_status(0x1003, begin_ret);
klog_printf("[Ghostpad] direct_recover begin failed ret=%d pid=%d args=0x%lx\n",
begin_ret, shellui_pid, (unsigned long)args_kaddr);
return begin_ret;
}
if (g_shellui_direct_state.fn_setpriv) {
setup_ret = pt_call(shellui_pid, g_shellui_direct_state.fn_setpriv, g_shellui_direct_state.trap_rip,
1, 0, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x1101, setup_ret);
klog_printf("[Ghostpad] direct_recover setpriv(1) -> 0x%llx\n",
(unsigned long long)(uint64_t)setup_ret);
}
if (g_shellui_direct_state.fn_setloginuser) {
setup_ret = pt_call(shellui_pid, g_shellui_direct_state.fn_setloginuser, g_shellui_direct_state.trap_rip,
1, 0, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x1102, setup_ret);
klog_printf("[Ghostpad] direct_recover setloginuser(1) -> 0x%llx\n",
(unsigned long long)(uint64_t)setup_ret);
}
if (g_shellui_direct_state.fn_setusernumber) {
setup_ret = pt_call(shellui_pid, g_shellui_direct_state.fn_setusernumber, g_shellui_direct_state.trap_rip,
1, 0, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x1103, setup_ret);
klog_printf("[Ghostpad] direct_recover setusernumber(1) -> 0x%llx\n",
(unsigned long long)(uint64_t)setup_ret);
}
if (g_shellui_direct_state.fn_setfocus) {
setup_ret = pt_call(shellui_pid, g_shellui_direct_state.fn_setfocus, g_shellui_direct_state.trap_rip,
1, 0, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x1104, setup_ret);
klog_printf("[Ghostpad] direct_recover setfocus(1) -> 0x%llx\n",
(unsigned long long)(uint64_t)setup_ret);
}
if (g_shellui_direct_state.fn_usleep) {
setup_ret = pt_call(shellui_pid, g_shellui_direct_state.fn_usleep, g_shellui_direct_state.trap_rip,
150000, 0, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x1105, setup_ret);
klog_printf("[Ghostpad] direct_recover usleep(150000) -> 0x%llx\n",
(unsigned long long)(uint64_t)setup_ret);
}
try_users[try_user_count++] = 1;
if (userId >= 0 && userId != try_users[0]) {
try_users[try_user_count++] = userId;
}
if (0x10000000 != try_users[0] &&
(try_user_count < 2 || 0x10000000 != try_users[1])) {
try_users[try_user_count++] = 0x10000000;
}
if (altUserId >= 0) {
int seen = 0;
for (int ui = 0; ui < try_user_count; ui++) {
if (try_users[ui] == altUserId) {
seen = 1;
break;
}
}
if (!seen && try_user_count < (int)(sizeof(try_users) / sizeof(try_users[0]))) {
try_users[try_user_count++] = altUserId;
}
}
if (g_shellui_direct_state.fn_gethandle_ext || g_shellui_direct_state.fn_gethandle) {
for (int ui = 0; ui < try_user_count && handle < 0; ui++) {
for (int idx = 0; idx < 8 && handle < 0; idx++) {
int64_t gh = g_shellui_direct_state.fn_gethandle_ext
? pt_call(shellui_pid, g_shellui_direct_state.fn_gethandle_ext, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 3, (uint64_t)idx, 0, 0, 0)
: pt_call(shellui_pid, g_shellui_direct_state.fn_gethandle, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 3, (uint64_t)idx, 0, 0, 0);
shellui_pad_direct_set_last_status(0x2000 | (idx & 0xff), gh);
klog_printf("[Ghostpad] direct_recover GetHandle(0x%x,3,%d) -> 0x%llx\n",
try_users[ui], idx, (unsigned long long)(uint64_t)gh);
if ((int32_t)gh >= 0) {
handle = (int32_t)gh;
use_insert = 0;
}
}
}
}
if (handle < 0 &&
g_shellui_direct_state.fn_usleep &&
(g_shellui_direct_state.fn_gethandle_ext || g_shellui_direct_state.fn_gethandle)) {
for (int attempt = 0; attempt < 60 && handle < 0; attempt++) {
for (int ui = 0; ui < try_user_count && handle < 0; ui++) {
for (int idx = 0; idx < 8 && handle < 0; idx++) {
int64_t gh = g_shellui_direct_state.fn_gethandle_ext
? pt_call(shellui_pid, g_shellui_direct_state.fn_gethandle_ext, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 3, (uint64_t)idx, 0, 0, 0)
: pt_call(shellui_pid, g_shellui_direct_state.fn_gethandle, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 3, (uint64_t)idx, 0, 0, 0);
shellui_pad_direct_set_last_status(0x2100 | ((attempt & 0xff) << 4) | (idx & 0xf), gh);
klog_printf("[Ghostpad] direct_recover retry GetHandle(0x%x,3,%d) attempt=%d -> 0x%llx\n",
try_users[ui], idx, attempt, (unsigned long long)(uint64_t)gh);
if ((int32_t)gh >= 0) {
handle = (int32_t)gh;
use_insert = 0;
}
}
}
if (handle < 0) {
int64_t sleep_ret = pt_call(shellui_pid, g_shellui_direct_state.fn_usleep, g_shellui_direct_state.trap_rip,
150000, 0, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x2200 | (attempt & 0xff), sleep_ret);
}
}
}
if (handle < 0 &&
(g_shellui_direct_state.fn_gethandle_ext || g_shellui_direct_state.fn_gethandle)) {
for (int ui = 0; ui < try_user_count && handle < 0; ui++) {
for (int idx = 0; idx < 4 && handle < 0; idx++) {
int64_t gh = g_shellui_direct_state.fn_gethandle_ext
? pt_call(shellui_pid, g_shellui_direct_state.fn_gethandle_ext, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 0, (uint64_t)idx, 0, 0, 0)
: pt_call(shellui_pid, g_shellui_direct_state.fn_gethandle, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 0, (uint64_t)idx, 0, 0, 0);
shellui_pad_direct_set_last_status(0x3000 | (idx & 0xff), gh);
klog_printf("[Ghostpad] direct_recover GetHandle(0x%x,0,%d) -> 0x%llx\n",
try_users[ui], idx, (unsigned long long)(uint64_t)gh);
if ((int32_t)gh >= 0) {
handle = (int32_t)gh;
use_insert = 1;
}
}
}
}
if (handle < 0 && g_shellui_direct_state.fn_open) {
for (int ui = 0; ui < try_user_count && handle < 0; ui++) {
int64_t oh = pt_call(shellui_pid, g_shellui_direct_state.fn_open, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 3, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x5002, oh);
klog_printf("[Ghostpad] direct_recover scePadOpen(0x%x,3) -> 0x%llx\n",
try_users[ui], (unsigned long long)(uint64_t)oh);
if ((int32_t)oh >= 0) {
handle = (int32_t)oh;
use_insert = 0;
}
}
}
if (handle < 0 && g_shellui_direct_state.fn_open) {
for (int ui = 0; ui < try_user_count && handle < 0; ui++) {
int64_t oh = pt_call(shellui_pid, g_shellui_direct_state.fn_open, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 0, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x5000, oh);
klog_printf("[Ghostpad] direct_recover scePadOpen(0x%x,0) -> 0x%llx\n",
try_users[ui], (unsigned long long)(uint64_t)oh);
if ((int32_t)oh >= 0) {
handle = (int32_t)oh;
use_insert = 1;
}
}
}
if (handle < 0 &&
(g_shellui_direct_state.fn_open_ext2 || g_shellui_direct_state.fn_open_ext)) {
for (int ui = 0; ui < try_user_count && handle < 0; ui++) {
int64_t oh = g_shellui_direct_state.fn_open_ext2
? pt_call(shellui_pid, g_shellui_direct_state.fn_open_ext2, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 3, 0, 0, 0, 0)
: pt_call(shellui_pid, g_shellui_direct_state.fn_open_ext, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 3, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x6002, oh);
klog_printf("[Ghostpad] direct_recover scePadOpen*(0x%x,3) -> 0x%llx\n",
try_users[ui], (unsigned long long)(uint64_t)oh);
if ((int32_t)oh >= 0) {
handle = (int32_t)oh;
use_insert = 0;
}
}
}
if (handle < 0 &&
(g_shellui_direct_state.fn_open_ext2 || g_shellui_direct_state.fn_open_ext)) {
for (int ui = 0; ui < try_user_count && handle < 0; ui++) {
int64_t oh = g_shellui_direct_state.fn_open_ext2
? pt_call(shellui_pid, g_shellui_direct_state.fn_open_ext2, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 0, 0, 0, 0, 0)
: pt_call(shellui_pid, g_shellui_direct_state.fn_open_ext, g_shellui_direct_state.trap_rip,
(uint64_t)try_users[ui], 0, 0, 0, 0, 0);
shellui_pad_direct_set_last_status(0x6000, oh);
klog_printf("[Ghostpad] direct_recover scePadOpen*(0x%x,0) -> 0x%llx\n",
try_users[ui], (unsigned long long)(uint64_t)oh);
if ((int32_t)oh >= 0) {
handle = (int32_t)oh;
use_insert = 1;
}
}
}
if (handle >= 0) {
g_shellui_direct_state.pad_handle = handle;
g_shellui_direct_state.use_insert = use_insert ? 1 : 0;
shellui_pad_direct_set_last_status(0x7000 | (use_insert ? 1 : 0), handle);
klog_printf("[Ghostpad] direct_recover cached handle=%d use_insert=%d\n",
handle, use_insert ? 1 : 0);
return 0;
}
{
int32_t last_stage = 0;
int64_t last_value = 0;
shellui_pad_direct_get_last_status(&last_stage, &last_value);
klog_printf("[Ghostpad] direct_recover found no usable handle for pid=%d args=0x%lx user=0x%x last_stage=0x%08x last_value=0x%llx\n",
shellui_pid,
(unsigned long)args_kaddr,
(uint32_t)userId,
(uint32_t)last_stage,
(unsigned long long)(uint64_t)last_value);
}
shellui_pad_direct_end(shellui_pid, args_kaddr);
return -3;
}
int
shellui_pad_direct_begin(pid_t shellui_pid, intptr_t args_kaddr)
{
int attach_errno = 0;
if (!shellui_pad_direct_context_usable(shellui_pid, args_kaddr)) {
return -1;
}
if (g_shellui_direct_state.attached) {
return 0;
}
for (int attempt = 0; attempt < 20; attempt++) {
if (sys_ptrace(PT_ATTACH, shellui_pid, 0, 0) == 0) {
waitpid(shellui_pid, NULL, 0);
g_shellui_direct_state.attached = 1;
if (attempt > 0) {
klog_printf("[Ghostpad] direct_begin PT_ATTACH(pid=%d) succeeded after %d retries\n",
shellui_pid, attempt);
}
return 0;
}
attach_errno = errno;
usleep(50000);
}
klog_printf("[Ghostpad] direct_begin PT_ATTACH(pid=%d) failed errno=%d\n",
shellui_pid, attach_errno);
return -attach_errno;
}
int
shellui_pad_direct_send(pid_t shellui_pid, intptr_t args_kaddr,
const void *pad_data, uint32_t pad_data_len)
{
uint8_t temp[SHELLUI_PAD_DATA_SIZE];
intptr_t fn;
int attached_here = 0;
int ret;
if (!shellui_pad_direct_context_usable(shellui_pid, args_kaddr) ||
g_shellui_direct_state.pad_handle < 0) {
return -1;
}
if (g_shellui_direct_state.use_insert) {
klog_printf("[Ghostpad] direct_send refused unsafe remote insert handle=%d\n",
g_shellui_direct_state.pad_handle);
return -2;
}
memset(temp, 0, sizeof(temp));
if (pad_data_len > SHELLUI_PAD_DATA_SIZE) {
pad_data_len = SHELLUI_PAD_DATA_SIZE;
}
memcpy(temp, pad_data, pad_data_len);
fn = g_shellui_direct_state.use_insert
? g_shellui_direct_state.fn_insert
: g_shellui_direct_state.fn_vdi;
if (!fn) {
return -1;
}
if (!g_shellui_direct_state.attached) {
ret = shellui_pad_direct_begin(shellui_pid, args_kaddr);
if (ret != 0) {
return ret;
}
attached_here = 1;
}
ret = (int)pt_call_with_copy(shellui_pid, fn, g_shellui_direct_state.trap_rip,
(uint64_t)g_shellui_direct_state.pad_handle,
temp, sizeof(temp));
if (attached_here) {
shellui_pad_direct_end(shellui_pid, args_kaddr);
}
return ret;
}
void
shellui_pad_direct_end(pid_t shellui_pid, intptr_t args_kaddr)
{
if (!g_shellui_direct_state.attached ||
!shellui_pad_direct_context_usable(shellui_pid, args_kaddr)) {
return;
}
sys_ptrace(PT_DETACH, shellui_pid, (caddr_t)1, 0);
g_shellui_direct_state.attached = 0;
}
/* ============================================================
* shellui_pad_dismiss_assignment_screen
*
* Called after SceShellCore VDA injection creates the virtual device.
* SceShellUI has already opened the device (visible in klog as
* "Open Pad [deviceId, 0, 0]: ret=HANDLE"). We PT_ATTACH SceShellUI,
* call scePadGetHandle(userId, type=3, idx) via pt_call (the process
* executes normally during the call so the pad IPC can respond), then
* send Cross × 30 frames + release × 18 frames via pt_call_with_copy
* against scePadVirtualDeviceInsertData. VDI writes to shared memory
* so it is safe from the stopped-thread injection context.
* ============================================================ */
int
shellui_pad_dismiss_assignment_screen(int32_t userId, uint64_t virtualDeviceId)
{
pid_t pids[16];
size_t count = find_pids("SceShellUI", pids, 16);
if (count == 0) {
klog_printf("[Ghostpad] dismiss: SceShellUI not found\n");
return -1;
}
pid_t target_pid = pids[0];
klog_printf("[Ghostpad] dismiss: PT_ATTACH(SceShellUI pid=%d)...\n", target_pid);
if (sys_ptrace(PT_ATTACH, target_pid, 0, 0) != 0) {
klog_printf("[Ghostpad] dismiss: PT_ATTACH failed errno=%d\n", errno);
return -1;
}
waitpid(target_pid, NULL, 0);
klog_printf("[Ghostpad] dismiss: attached\n");
uint32_t libpad_h = 0, libkernel_h = 0;
if (get_lib(target_pid, "libScePad", &libpad_h)) {
klog_printf("[Ghostpad] dismiss: libScePad not found in SceShellUI\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
get_lib(target_pid, "libkernel_sys", &libkernel_h);
intptr_t fn_gethandle = resolve_sym(target_pid, libpad_h, "scePadGetHandle");
intptr_t fn_open = resolve_sym(target_pid, libpad_h, "scePadOpen");
intptr_t fn_open_ext = resolve_sym(target_pid, libpad_h, "scePadOpenExt");
intptr_t fn_open_ext2 = resolve_sym(target_pid, libpad_h, "scePadOpenExt2");
intptr_t fn_usleep = libkernel_h ? resolve_sym(target_pid, libkernel_h, "usleep") : 0;
intptr_t fn_vdi = resolve_sym(target_pid, libpad_h, "scePadVirtualDeviceInsertData");
if (!fn_vdi) {
klog_printf("[Ghostpad] dismiss: VDI symbol missing\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
klog_printf("[Ghostpad] dismiss: GH=0x%lx Open=0x%lx VDI=0x%lx usleep=0x%lx\n",
fn_gethandle, fn_open, fn_vdi, fn_usleep);
intptr_t trap_mem = kernel_dynlib_init_addr(target_pid, libpad_h);
if (!trap_mem) trap_mem = kernel_dynlib_fini_addr(target_pid, libpad_h);
if (!trap_mem) {
klog_printf("[Ghostpad] dismiss: no code cave in SceShellUI libScePad\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
if (kernel_set_vmem_protection(target_pid, trap_mem, 16,
PROT_READ | PROT_WRITE | PROT_EXEC)) {
klog_printf("[Ghostpad] dismiss: RWX failed\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
uint8_t int3 = 0xCC;
pt_io_write(target_pid, trap_mem, &int3, 1);
klog_printf("[Ghostpad] dismiss: trap=0x%lx\n", trap_mem);
/* -- Phase 1: find the virtual device handle in SceShellUI ----------------
* The device was created by VDA (userId=0xffffffff). SceShellUI already
* opened it ("Open Pad [deviceId,0,0]: ret=HANDLE" in klog). Try all
* plausible lookups. Log every result so we know which call works. */
int32_t try_uids[4] = {(int32_t)0xffffffff, 0, 1, userId};
int32_t vd_handle = -1;
/* A1: GetHandle(userId, type=0, idx=0..7) — after force_bind, the virtual device
* appears in CIM slot N alongside the physical device. The physical is at idx=0,
* the virtual may be at idx=1 (or higher). Try type=0 first since both show as
* DualSense in the CIM (sub=22). Log EVERY result to determine which idx succeeds. */
if (fn_gethandle) {
for (int i = 0; i < 4 && vd_handle < 0; i++) {
for (int idx = 0; idx < 8; idx++) {
int64_t gh = pt_call(target_pid, fn_gethandle, trap_mem,
(uint64_t)(uint32_t)try_uids[i], 0, (uint64_t)idx,
0, 0, 0);
klog_printf("[Ghostpad] dismiss: GH(uid=0x%x,type=0,idx=%d)->0x%llx\n",
(uint32_t)try_uids[i], idx, (unsigned long long)(uint64_t)gh);
if ((int32_t)gh >= 0 && vd_handle < 0) vd_handle = (int32_t)gh;
}
}
}
/* A2: GetHandle with type=3 as fallback */
if (fn_gethandle && vd_handle < 0) {
for (int i = 0; i < 4 && vd_handle < 0; i++) {
for (int idx = 0; idx < 4 && vd_handle < 0; idx++) {
int64_t gh = pt_call(target_pid, fn_gethandle, trap_mem,
(uint64_t)(uint32_t)try_uids[i], 3, (uint64_t)idx,
0, 0, 0);
klog_printf("[Ghostpad] dismiss: GH(uid=0x%x,type=3,idx=%d)->0x%llx\n",
(uint32_t)try_uids[i], idx, (unsigned long long)(uint64_t)gh);
if ((int32_t)gh >= 0) vd_handle = (int32_t)gh;
}
}
}
/* A3: GetHandle(deviceId_low32, type, idx) — direct device ID based lookup */
if (fn_gethandle && vd_handle < 0 && virtualDeviceId != 0) {
uint32_t dev32 = (uint32_t)(virtualDeviceId & 0xFFFFFFFF);
int types[2] = {0, 3};
for (int t = 0; t < 2 && vd_handle < 0; t++) {
for (int idx = 0; idx < 4 && vd_handle < 0; idx++) {
int64_t gh = pt_call(target_pid, fn_gethandle, trap_mem,
(uint64_t)dev32, (uint64_t)types[t], (uint64_t)idx,
0, 0, 0);
klog_printf("[Ghostpad] dismiss: GH(devId=0x%x,type=%d,idx=%d)->0x%llx\n",
dev32, types[t], idx, (unsigned long long)(uint64_t)gh);
if ((int32_t)gh >= 0) vd_handle = (int32_t)gh;
}
}
}
/* B: scePadOpen* variants (SceShellUI "Open Pad" may use Open not GetHandle) */
if (vd_handle < 0) {
for (int i = 0; i < 4 && vd_handle < 0; i++) {
intptr_t fn = fn_open_ext2 ? fn_open_ext2
: fn_open_ext ? fn_open_ext
: fn_open;
if (!fn) break;
int64_t oh = fn_open_ext2
? pt_call(target_pid, fn, trap_mem,
(uint64_t)(uint32_t)try_uids[i], 3, 0, 0, 0, 0)
: fn_open_ext
? pt_call(target_pid, fn, trap_mem,
(uint64_t)(uint32_t)try_uids[i], 3, 0, 0, 0, 0)
: pt_call(target_pid, fn, trap_mem,
(uint64_t)(uint32_t)try_uids[i], 3, 0, 0, 0, 0);
klog_printf("[Ghostpad] dismiss: Open(0x%x,3)->0x%llx\n",
(uint32_t)try_uids[i], (unsigned long long)(uint64_t)oh);
if ((int32_t)oh >= 0) vd_handle = (int32_t)oh;
}
}
if (vd_handle < 0) {
klog_printf("[Ghostpad] dismiss: no handle found — cannot send Cross\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
klog_printf("[Ghostpad] dismiss: handle=0x%x; sending Cross\n", (uint32_t)vd_handle);
/* -- Phase 2: send Cross via VDI ----------------------------------------
* ScePadData layout (byte view, LE):
* [0..3] buttons — Cross = 0x00004000 → byte[1]=0x40
* [4] LS.x=128, [5] LS.y=128, [6] RS.x=128, [7] RS.y=128
* [26] quat.w low byte 0x80, [27] 0x3F (1.0f LE)
* [76] connected=1 */
uint8_t press[SHELLUI_PAD_DATA_SIZE];
uint8_t release_d[SHELLUI_PAD_DATA_SIZE];
memset(press, 0, SHELLUI_PAD_DATA_SIZE);
memset(release_d, 0, SHELLUI_PAD_DATA_SIZE);
press[1] = 0x40;
press[4] = 128; press[5] = 128; press[6] = 128; press[7] = 128;
press[26] = 0x80; press[27] = 0x3F;
press[76] = 1;
release_d[4] = 128; release_d[5] = 128; release_d[6] = 128; release_d[7] = 128;
release_d[26] = 0x80; release_d[27] = 0x3F;
release_d[76] = 1;
/* Press Cross × 30 frames (~500ms at 60Hz) */
for (int frame = 0; frame < 30; frame++) {
int64_t vret = pt_call_with_copy(target_pid, fn_vdi, trap_mem,
(uint64_t)vd_handle, press, SHELLUI_PAD_DATA_SIZE);
if (frame == 0)
klog_printf("[Ghostpad] dismiss: VDI press frame0 -> %lld\n", (long long)vret);
/* pace at ~16ms between frames if usleep available */
if (fn_usleep)
pt_call(target_pid, fn_usleep, trap_mem, 16000, 0, 0, 0, 0, 0);
}
/* Release × 12 frames */
for (int frame = 0; frame < 12; frame++) {
pt_call_with_copy(target_pid, fn_vdi, trap_mem,
(uint64_t)vd_handle, release_d, SHELLUI_PAD_DATA_SIZE);
if (fn_usleep)
pt_call(target_pid, fn_usleep, trap_mem, 16000, 0, 0, 0, 0, 0);
}
/* -- Phase 3: probe GetHandle post-dismiss --------------------------------
* If assignment succeeded, the device now has userId=0x18c60ea1 and
* GetHandle(userId, type=3, idx) should return the system padHandle. */
if (fn_usleep)
pt_call(target_pid, fn_usleep, trap_mem, 500000, 0, 0, 0, 0, 0); /* 500ms */
int32_t post_handle = -1;
if (fn_gethandle) {
int ptypes[2] = {0, 3};
for (int t = 0; t < 2 && post_handle < 0; t++) {
for (int idx = 0; idx < 8 && post_handle < 0; idx++) {
int64_t ph = pt_call(target_pid, fn_gethandle, trap_mem,
(uint64_t)(uint32_t)userId, (uint64_t)ptypes[t], (uint64_t)idx,
0, 0, 0);
klog_printf("[Ghostpad] dismiss: post GH(uid=0x%x,type=%d,%d)->0x%llx\n",
(uint32_t)userId, ptypes[t], idx, (unsigned long long)(uint64_t)ph);
if ((int32_t)ph >= 0) post_handle = (int32_t)ph;
}
}
}
if (post_handle >= 0)
klog_printf("[Ghostpad] dismiss: post-assign handle=0x%x — assignment SUCCESS\n",
(uint32_t)post_handle);
else
klog_printf("[Ghostpad] dismiss: post-dismiss GH still failed — may need more time\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
klog_printf("[Ghostpad] dismiss: done PT_DETACH\n");
return (post_handle >= 0) ? 0 : 1; /* 0=confirmed, 1=VDI sent but assignment unconfirmed */
}
/* shellui_pad_force_bind — call sceMbusBindDeviceWithUserId in SceShellUI via pt_call */
/* shellui_pad_test_vdi_cross — send a Cross press to a known padHandle via VDI */
static int
pad_test_vdi_cross_in_process(const char *process_name, const char *tag, int32_t pad_handle)
{
pid_t pids[4];
size_t n = find_pids(process_name, pids, 4);
if (n == 0) {
klog_printf("[Ghostpad] %s: %s not found\n", tag, process_name);
return -1;
}
pid_t target_pid = pids[0];
klog_printf("[Ghostpad] %s: pid=%d handle=0x%x\n", tag, target_pid, (uint32_t)pad_handle);
if (pad_handle <= 0) {
klog_printf("[Ghostpad] %s: invalid handle\n", tag);
return -1;
}
if (sys_ptrace(PT_ATTACH, target_pid, 0, 0) != 0) {
klog_printf("[Ghostpad] %s: PT_ATTACH failed errno=%d\n", tag, errno);
return -1;
}
waitpid(target_pid, NULL, 0);
uint32_t libpad_h = 0;
get_lib(target_pid, "libScePad", &libpad_h);
intptr_t fn_vdi = libpad_h ? resolve_sym(target_pid, libpad_h,
"scePadVirtualDeviceInsertData") : 0;
intptr_t trap_mem = libpad_h ? kernel_dynlib_init_addr(target_pid, libpad_h) : 0;
if (!trap_mem && libpad_h) trap_mem = kernel_dynlib_fini_addr(target_pid, libpad_h);
if (!fn_vdi || !trap_mem) {
klog_printf("[Ghostpad] %s: symbol/cave fail vdi=0x%lx trap=0x%lx\n",
tag, fn_vdi, trap_mem);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
kernel_set_vmem_protection(target_pid, trap_mem, 16, PROT_READ | PROT_WRITE | PROT_EXEC);
uint8_t int3 = 0xCC;
pt_io_write(target_pid, trap_mem, &int3, 1);
uint8_t press[SHELLUI_PAD_DATA_SIZE];
uint8_t release_d[SHELLUI_PAD_DATA_SIZE];
memset(press, 0, SHELLUI_PAD_DATA_SIZE);
memset(release_d, 0, SHELLUI_PAD_DATA_SIZE);
press[1] = 0x40; /* Cross */
press[4] = 128; press[5] = 128; press[6] = 128; press[7] = 128;
press[26] = 0x80; press[27] = 0x3F;
press[76] = 1;
release_d[4] = 128; release_d[5] = 128; release_d[6] = 128; release_d[7] = 128;
release_d[26] = 0x80; release_d[27] = 0x3F;
release_d[76] = 1;
int result = 0;
for (int frame = 0; frame < 15; frame++) {
int64_t r = pt_call_with_copy(target_pid, fn_vdi, trap_mem,
(uint64_t)pad_handle, press, SHELLUI_PAD_DATA_SIZE);
if (frame == 0) {
klog_printf("[Ghostpad] %s: VDI press frame0 -> %lld\n", tag, (long long)r);
result = (int)r;
}
}
for (int frame = 0; frame < 8; frame++) {
pt_call_with_copy(target_pid, fn_vdi, trap_mem,
(uint64_t)pad_handle, release_d, SHELLUI_PAD_DATA_SIZE);
}
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
klog_printf("[Ghostpad] %s: done, first VDI ret=%d\n", tag, result);
return result;
}
int
shellui_pad_test_vdi_cross(int32_t pad_handle)
{
return pad_test_vdi_cross_in_process("SceShellUI", "vdi_cross_ui", pad_handle);
}
int
shellcore_pad_test_vdi_cross(int32_t pad_handle)
{
return pad_test_vdi_cross_in_process("SceShellCore", "vdi_cross_core", pad_handle);
}
/* VDI probe with NEUTRAL state (buttons=0) — confirms VDI works without
* sending any input to the UI. Used by GBND handler instead of Cross so
* the UI is not disturbed when no assignment screen is visible.
* Cross is only pressed by the stub when assignment_hint detects the screen. */
int
shellcore_pad_test_vdi_neutral(int32_t pad_handle)
{
pid_t target_pid = -1;
{
pid_t pids[8]; size_t n;
n = find_pids("SceShellCore", pids, 8);
if (n > 0) target_pid = pids[0];
}
if (target_pid < 0) return -1;
if (sys_ptrace(PT_ATTACH, target_pid, 0, 0)) return -1;
waitpid(target_pid, NULL, 0);
uint32_t libpad_h = 0;
get_lib(target_pid, "libScePad", &libpad_h);
intptr_t fn_vdi = libpad_h ? resolve_sym(target_pid, libpad_h, "scePadVirtualDeviceInsertData") : 0;
intptr_t trap_mem = libpad_h ? kernel_dynlib_init_addr(target_pid, libpad_h) : 0;
if (!trap_mem && libpad_h) trap_mem = kernel_dynlib_fini_addr(target_pid, libpad_h);
if (!fn_vdi || !trap_mem) { sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0); return -1; }
kernel_set_vmem_protection(target_pid, trap_mem, 16, PROT_READ | PROT_WRITE | PROT_EXEC);
uint8_t int3 = 0xCC;
pt_io_write(target_pid, trap_mem, &int3, 1);
uint8_t neutral[SHELLUI_PAD_DATA_SIZE];
memset(neutral, 0, SHELLUI_PAD_DATA_SIZE);
neutral[4] = 128; neutral[5] = 128; neutral[6] = 128; neutral[7] = 128;
neutral[26] = 0x80; neutral[27] = 0x3F;
neutral[76] = 1;
int64_t r = pt_call_with_copy(target_pid, fn_vdi, trap_mem,
(uint64_t)pad_handle, neutral, SHELLUI_PAD_DATA_SIZE);
klog_printf("[Ghostpad] vdi_neutral: handle=0x%x ret=%lld\n", (uint32_t)pad_handle, (long long)r);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return (int)r;
}
/* shellui_pad_probe_legacy_disabled — legacy probe, disabled; returns -1 immediately */
/*
* =====================================================================================
* MANIFEST-VERIFIED PS4 VDA PATCH FOR scePadVirtualDeviceAddDevice
* =====================================================================================
*
* This patcher intentionally does not scan live code for generic patterns.
* It applies only to the exact PS4 libScePad fingerprint reported by
* vda_probe_report.txt:
*
* scePadVirtualDeviceAddDevice rel +0x5b40
* hash256 0xbb22d8acd843d81e
* hash4k 0x346f2b8071895f89
* patch call +0x0c0
* code cave +0x0dd2, 14-byte NOP run
*
* Patch shape:
* original call @ +0xc0 -> verified cave @ +0xdd2
* cave: call original dispatcher; xor eax,eax; ret
*
* Returning with RET uses the original call-site return address and resumes at
* +0xc5, so the original canary check and epilogue remain intact. Early
* validation returns are left untouched.
*/
static int
shellui_pad_patch_vda_target(pid_t target, const char *target_name, int dump_only)
{
#if !GHOSTPAD_ENABLE_KNOWN_VDA_PATCH
(void)dump_only;
(void)target;
(void)target_name;
klog_printf("[Ghostpad] patch_vda: known VDA patcher disabled; compile with -DGHOSTPAD_ENABLE_KNOWN_VDA_PATCH=1\n");
return 0;
#elif !defined(__ORBIS__)
(void)dump_only;
(void)target;
(void)target_name;
klog_printf("[Ghostpad] patch_vda: no verified manifest for this platform yet\n");
return 0;
#else
static const uint8_t expected_prologue32[32] = {
0x55,0x48,0x89,0xe5,0x53,0x48,0x83,0xe4,
0xe0,0x48,0x81,0xec,0x80,0x00,0x00,0x00,
0x48,0x8b,0x1d,0xd1,0x64,0x00,0x00,0x48,
0x8b,0x03,0x48,0x89,0x44,0x24,0x60,0xb8
};
static const uint8_t expected_call[5] = {
0xe8,0x33,0xa5,0xff,0xff
};
static const uint8_t expected_after_call[17] = {
0x48,0x8b,0x0b,0x48,0x3b,0x4c,0x24,0x60,
0x75,0x07,0x48,0x8d,0x65,0xf8,0x5b,0x5d,0xc3
};
pid_t mypid = getpid();
uint8_t privcaps[16] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};
uint8_t saved_caps[16];
uint64_t saved_authid = kernel_get_ucred_authid(mypid);
int have_saved_caps = 0;
if (saved_authid && kernel_get_ucred_caps(mypid, saved_caps) == 0) {
have_saved_caps = 1;
kernel_set_ucred_authid(mypid, 0x4800000000010003l);
kernel_set_ucred_caps(mypid, privcaps);
}
if (!target_name) target_name = "target";
klog_printf("[Ghostpad] patch_vda: %s pid=%d dump_only=%d manifest=PS4-libScePad-vda-0xbb22d8acd843d81e\n", target_name,
target, dump_only);
uint32_t libpad_h = 0;
if (get_lib(target, "libScePad", &libpad_h)) {
klog_printf("[Ghostpad] patch_vda: libScePad not found\n");
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return -1;
}
intptr_t libpad_init = kernel_dynlib_init_addr(target, libpad_h);
intptr_t fn_vda = resolve_sym(target, libpad_h, "scePadVirtualDeviceAddDevice");
if (!fn_vda) {
klog_printf("[Ghostpad] patch_vda: scePadVirtualDeviceAddDevice not found\n");
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return -1;
}
klog_printf("[Ghostpad] patch_vda: libScePad init=0x%lx fn_vda=0x%lx rel=0x%lx\n",
(unsigned long)libpad_init,
(unsigned long)fn_vda,
libpad_init ? (unsigned long)(fn_vda - libpad_init) : 0ul);
if (libpad_init && (uint32_t)(fn_vda - libpad_init) != GHOSTPAD_VDA_PS4_LIBSCEPAD_VDA_OFF) {
klog_printf("[Ghostpad] patch_vda: manifest reject: VDA offset 0x%lx != 0x%x\n",
(unsigned long)(fn_vda - libpad_init),
(unsigned)GHOSTPAD_VDA_PS4_LIBSCEPAD_VDA_OFF);
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return 0;
}
static uint8_t buf[4096];
memset(buf, 0, sizeof(buf));
if (mdbg_copyout(target, fn_vda, buf, sizeof(buf)) != 0) {
klog_printf("[Ghostpad] patch_vda: mdbg_copyout failed len=%zu errno=%d\n", sizeof(buf), errno);
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return -1;
}
uint64_t hash256 = ghostpad_fnv1a64(buf, 256);
uint64_t hash4k = ghostpad_fnv1a64(buf, sizeof(buf));
int already_patched = 0;
int32_t patched_call_rel = (int32_t)((intptr_t)GHOSTPAD_VDA_PS4_CAVE_OFF -
(intptr_t)GHOSTPAD_VDA_PS4_AFTER_CALL_OFF);
if (buf[GHOSTPAD_VDA_PS4_CALL_OFF] == 0xe8) {
int32_t cur_rel = (int32_t)((uint32_t)buf[GHOSTPAD_VDA_PS4_CALL_OFF + 1] |
((uint32_t)buf[GHOSTPAD_VDA_PS4_CALL_OFF + 2] << 8) |
((uint32_t)buf[GHOSTPAD_VDA_PS4_CALL_OFF + 3] << 16) |
((uint32_t)buf[GHOSTPAD_VDA_PS4_CALL_OFF + 4] << 24));
already_patched = (cur_rel == patched_call_rel &&
buf[GHOSTPAD_VDA_PS4_CAVE_OFF + 0] == 0xe8 &&
buf[GHOSTPAD_VDA_PS4_CAVE_OFF + 5] == 0x31 &&
buf[GHOSTPAD_VDA_PS4_CAVE_OFF + 6] == 0xc0 &&
buf[GHOSTPAD_VDA_PS4_CAVE_OFF + 7] == 0xc3);
}
if (!already_patched) {
if (memcmp(buf, expected_prologue32, sizeof(expected_prologue32)) != 0) {
klog_printf("[Ghostpad] patch_vda: manifest reject: prologue32 mismatch\n");
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return 0;
}
if (hash256 != GHOSTPAD_VDA_PS4_HASH256 || hash4k != GHOSTPAD_VDA_PS4_HASH4K) {
klog_printf("[Ghostpad] patch_vda: manifest reject: hash256=0x%016llx hash4k=0x%016llx\n",
(unsigned long long)hash256,
(unsigned long long)hash4k);
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return 0;
}
if (memcmp(buf + GHOSTPAD_VDA_PS4_CALL_OFF, expected_call, sizeof(expected_call)) != 0 ||
memcmp(buf + GHOSTPAD_VDA_PS4_AFTER_CALL_OFF, expected_after_call, sizeof(expected_after_call)) != 0 ||
buf[GHOSTPAD_VDA_PS4_BRANCH_OFF] != 0x75) {
klog_printf("[Ghostpad] patch_vda: manifest reject: call/canary bytes mismatch\n");
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return 0;
}
if (!ghostpad_all_byte(buf + GHOSTPAD_VDA_PS4_CAVE_OFF,
GHOSTPAD_VDA_PS4_CAVE_LEN, 0x90)) {
klog_printf("[Ghostpad] patch_vda: manifest reject: cave +0x%x is not the expected NOP run\n",
(unsigned)GHOSTPAD_VDA_PS4_CAVE_OFF);
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return 0;
}
}
klog_printf("[Ghostpad] patch_vda: manifest match: hash256=0x%016llx hash4k=0x%016llx call=+0x%x cave=+0x%x%s\n",
(unsigned long long)hash256,
(unsigned long long)hash4k,
(unsigned)GHOSTPAD_VDA_PS4_CALL_OFF,
(unsigned)GHOSTPAD_VDA_PS4_CAVE_OFF,
already_patched ? " already_patched=1" : "");
if (dump_only || already_patched) {
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return 1;
}
intptr_t call_addr = fn_vda + (intptr_t)GHOSTPAD_VDA_PS4_CALL_OFF;
intptr_t cave_addr = fn_vda + (intptr_t)GHOSTPAD_VDA_PS4_CAVE_OFF;
intptr_t page_call = call_addr & ~(intptr_t)0xfff;
intptr_t page_cave = cave_addr & ~(intptr_t)0xfff;
int protect_call_ok = 0;
int protect_cave_ok = 0;
/* On some PS4/HEN combinations kernel_set_vmem_protection() rejects
* SceShellCore text pages even though mdbg_copyin() can still perform a
* privileged debug write. Do not abort on protection failure: log it, try
* a narrow unaligned range as a fallback, then attempt mdbg_copyin and
* verify by reading the bytes back. The pages are already executable; we
* only need a reliable write primitive. */
if (kernel_set_vmem_protection(target, page_call, 0x1000,
PROT_READ | PROT_WRITE | PROT_EXEC) == 0) {
protect_call_ok = 1;
} else if (kernel_set_vmem_protection(target, call_addr, 5,
PROT_READ | PROT_WRITE | PROT_EXEC) == 0) {
protect_call_ok = 1;
klog_printf("[Ghostpad] patch_vda: RWX call page failed, narrow call range accepted addr=0x%lx\n",
(unsigned long)call_addr);
} else {
klog_printf("[Ghostpad] patch_vda: RWX call page/range failed page=0x%lx addr=0x%lx; trying mdbg_copyin anyway\n",
(unsigned long)page_call, (unsigned long)call_addr);
}
if (page_cave == page_call) {
protect_cave_ok = protect_call_ok;
} else if (kernel_set_vmem_protection(target, page_cave, 0x1000,
PROT_READ | PROT_WRITE | PROT_EXEC) == 0) {
protect_cave_ok = 1;
} else if (kernel_set_vmem_protection(target, cave_addr, GHOSTPAD_VDA_PS4_CAVE_LEN,
PROT_READ | PROT_WRITE | PROT_EXEC) == 0) {
protect_cave_ok = 1;
klog_printf("[Ghostpad] patch_vda: RWX cave page failed, narrow cave range accepted addr=0x%lx\n",
(unsigned long)cave_addr);
} else {
klog_printf("[Ghostpad] patch_vda: RWX cave page/range failed page=0x%lx addr=0x%lx; trying mdbg_copyin anyway\n",
(unsigned long)page_cave, (unsigned long)cave_addr);
}
uint8_t cave_patch[GHOSTPAD_VDA_PS4_CAVE_LEN];
memset(cave_patch, 0x90, sizeof(cave_patch));
int32_t orig_rel32 = (int32_t)((uint32_t)buf[GHOSTPAD_VDA_PS4_CALL_OFF + 1] |
((uint32_t)buf[GHOSTPAD_VDA_PS4_CALL_OFF + 2] << 8) |
((uint32_t)buf[GHOSTPAD_VDA_PS4_CALL_OFF + 3] << 16) |
((uint32_t)buf[GHOSTPAD_VDA_PS4_CALL_OFF + 4] << 24));
intptr_t orig_target_off = (intptr_t)GHOSTPAD_VDA_PS4_AFTER_CALL_OFF + (intptr_t)orig_rel32;
int32_t cave_call_rel32 = (int32_t)(orig_target_off -
((intptr_t)GHOSTPAD_VDA_PS4_CAVE_OFF + 5));
cave_patch[0] = 0xe8;
cave_patch[1] = (uint8_t)(cave_call_rel32 & 0xff);
cave_patch[2] = (uint8_t)((cave_call_rel32 >> 8) & 0xff);
cave_patch[3] = (uint8_t)((cave_call_rel32 >> 16) & 0xff);
cave_patch[4] = (uint8_t)((cave_call_rel32 >> 24) & 0xff);
cave_patch[5] = 0x31;
cave_patch[6] = 0xc0;
cave_patch[7] = 0xc3;
if (target == getpid()) {
memcpy(g_orig_self_vda_call, buf + GHOSTPAD_VDA_PS4_CALL_OFF, 5);
memcpy(g_orig_self_vda_cave, buf + GHOSTPAD_VDA_PS4_CAVE_OFF, 8);
g_self_vda_patched = 1;
klog_printf("[Ghostpad] patch_vda: saved original VDA self patch bytes\n");
} else {
memcpy(g_orig_vda_call, buf + GHOSTPAD_VDA_PS4_CALL_OFF, 5);
memcpy(g_orig_vda_cave, buf + GHOSTPAD_VDA_PS4_CAVE_OFF, 8);
g_vda_patched = 1;
g_vda_patched_pid = target;
klog_printf("[Ghostpad] patch_vda: saved original VDA target patch bytes for pid=%d\n", target);
}
uint8_t call_patch[5];
call_patch[0] = 0xe8;
call_patch[1] = (uint8_t)(patched_call_rel & 0xff);
call_patch[2] = (uint8_t)((patched_call_rel >> 8) & 0xff);
call_patch[3] = (uint8_t)((patched_call_rel >> 16) & 0xff);
call_patch[4] = (uint8_t)((patched_call_rel >> 24) & 0xff);
if (mdbg_copyin(target, cave_patch, cave_addr, sizeof(cave_patch)) != 0) {
klog_printf("[Ghostpad] patch_vda: cave write failed errno=%d\n", errno);
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return -1;
}
if (mdbg_copyin(target, call_patch, call_addr, sizeof(call_patch)) != 0) {
uint8_t nop_restore[GHOSTPAD_VDA_PS4_CAVE_LEN];
memset(nop_restore, 0x90, sizeof(nop_restore));
(void)mdbg_copyin(target, nop_restore, cave_addr, sizeof(nop_restore));
klog_printf("[Ghostpad] patch_vda: call-site write failed errno=%d; cave restored\n", errno);
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return -1;
}
uint8_t verify_cave[GHOSTPAD_VDA_PS4_CAVE_LEN];
uint8_t verify_call[sizeof(call_patch)];
memset(verify_cave, 0, sizeof(verify_cave));
memset(verify_call, 0, sizeof(verify_call));
if (mdbg_copyout(target, cave_addr, verify_cave, sizeof(verify_cave)) != 0 ||
mdbg_copyout(target, call_addr, verify_call, sizeof(verify_call)) != 0 ||
memcmp(verify_cave, cave_patch, sizeof(cave_patch)) != 0 ||
memcmp(verify_call, call_patch, sizeof(call_patch)) != 0) {
klog_printf("[Ghostpad] patch_vda: write verification failed; patch not trusted\n");
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return -1;
}
if (protect_call_ok) {
(void)kernel_set_vmem_protection(target, page_call, 0x1000, PROT_READ | PROT_EXEC);
}
if (protect_cave_ok && page_cave != page_call) {
(void)kernel_set_vmem_protection(target, page_cave, 0x1000, PROT_READ | PROT_EXEC);
}
klog_printf("[Ghostpad] patch_vda: PATCHED %s libScePad VDA call +0x%x -> cave +0x%x; dispatcher rel=0x%08x protect_call=%d protect_cave=%d\n",
target_name,
(unsigned)GHOSTPAD_VDA_PS4_CALL_OFF,
(unsigned)GHOSTPAD_VDA_PS4_CAVE_OFF,
(uint32_t)cave_call_rel32, protect_call_ok, protect_cave_ok);
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return 1;
#endif
}
int
shellui_pad_patch_vda_self(int dump_only)
{
return shellui_pad_patch_vda_target(getpid(), "self", dump_only);
}
int
shellui_pad_patch_vda(int dump_only)
{
#if !GHOSTPAD_ENABLE_KNOWN_VDA_PATCH || !defined(__ORBIS__)
return shellui_pad_patch_vda_target(0, "SceShellCore", dump_only);
#else
pid_t pids[8];
if (find_pids("SceShellCore", pids, 8) == 0) {
klog_printf("[Ghostpad] patch_vda: SceShellCore not found\n");
return -1;
}
return shellui_pad_patch_vda_target(pids[0], "SceShellCore", dump_only);
#endif
}
int32_t
shellui_pad_retry_vda_shellcore(int32_t userId)
{
pid_t pids[8];
if (find_pids("SceShellCore", pids, 8) == 0) {
klog_printf("[Ghostpad] retry_vda: SceShellCore not found\n");
return -1;
}
pid_t target = pids[0];
klog_printf("[Ghostpad] retry_vda: PT_ATTACH(SceShellCore pid=%d)\n", target);
if (sys_ptrace(PT_ATTACH, target, 0, 0) != 0) {
klog_printf("[Ghostpad] retry_vda: PT_ATTACH failed errno=%d\n", errno);
return (int32_t)-errno;
}
waitpid(target, NULL, 0);
uint32_t libpad_h = 0, libkernel_h = 0;
get_lib(target, "libScePad", &libpad_h);
get_lib(target, "libkernel_sys", &libkernel_h);
intptr_t fn_vda = libpad_h ? resolve_sym(target, libpad_h, "scePadVirtualDeviceAddDevice") : 0;
intptr_t trap_mem = libpad_h ? kernel_dynlib_init_addr(target, libpad_h) : 0;
if (!trap_mem && libpad_h) trap_mem = kernel_dynlib_fini_addr(target, libpad_h);
if (!fn_vda || !trap_mem) {
sys_ptrace(PT_DETACH, target, (caddr_t)1, 0);
return -1;
}
kernel_set_vmem_protection(target, trap_mem, 16, PROT_READ | PROT_WRITE | PROT_EXEC);
uint8_t int3 = 0xCC;
pt_io_write(target, trap_mem, &int3, 1);
struct { int32_t f[8]; } vdp = {0};
vdp.f[0] = 32;
vdp.f[1] = userId;
/* Write vdp struct onto the stack of the stopped process via PT_IO */
struct reg regs;
sys_ptrace(PT_GETREGS, target, (caddr_t)&regs, 0);
intptr_t vdp_addr = (regs.r_rsp - 128 - (intptr_t)sizeof(vdp)) & ~(intptr_t)0xf;
pt_io_write(target, vdp_addr, &vdp, sizeof(vdp));
/* pt_call VDA(vdp_addr, 3) via fn_vda */
int64_t vda_ret = pt_call(target, fn_vda, trap_mem,
(uint64_t)vdp_addr, 3, 0, 0, 0, 0);
klog_printf("[Ghostpad] retry_vda: VDA(uid=0x%x, type=3) -> 0x%llx\n",
(uint32_t)userId, (unsigned long long)(uint64_t)vda_ret);
/* Read back vdp struct to see if VDA wrote a handle into f[2..7] */
struct { int32_t f[8]; } vdp_out = {0};
{
struct ptrace_io_desc iod;
iod.piod_op = PIOD_READ_D;
iod.piod_offs = (void *)vdp_addr;
iod.piod_addr = &vdp_out;
iod.piod_len = sizeof(vdp_out);
sys_ptrace(PT_IO, target, (caddr_t)&iod, 0);
}
klog_printf("[Ghostpad] retry_vda: vdp_out f[0..3]=0x%x 0x%x 0x%x 0x%x\n",
(uint32_t)vdp_out.f[0], (uint32_t)vdp_out.f[1],
(uint32_t)vdp_out.f[2], (uint32_t)vdp_out.f[3]);
sys_ptrace(PT_DETACH, target, (caddr_t)1, 0);
return (int32_t)vda_ret;
}
int32_t
shellui_pad_probe_legacy_disabled(int32_t userId)
{
pid_t pids[8];
(void)userId;
klog_printf("[Ghostpad] legacy probe disabled; using SceShellCore/SceShellUI path\n");
return -1;
size_t n = find_pids("LegacyDisabled", pids, 8);
if (n == 0) { klog_printf("[Ghostpad] probe_legacy: target not found\n"); return -1; }
pid_t target = pids[0];
klog_printf("[Ghostpad] probe_legacy: PT_ATTACH(pid=%d)\n", target);
if (sys_ptrace(PT_ATTACH, target, 0, 0) != 0) {
klog_printf("[Ghostpad] probe_rp: PT_ATTACH failed errno=%d\n", errno);
return -1;
}
waitpid(target, NULL, 0);
uint32_t libpad_h = 0, libkernel_h = 0;
get_lib(target, "libScePad", &libpad_h);
get_lib(target, "libkernel_sys", &libkernel_h);
intptr_t fn_gethandle = libpad_h ? resolve_sym(target, libpad_h, "scePadGetHandle") : 0;
intptr_t fn_open = libpad_h ? resolve_sym(target, libpad_h, "scePadOpen") : 0;
intptr_t fn_open_ext = libpad_h ? resolve_sym(target, libpad_h, "scePadOpenExt") : 0;
intptr_t trap_mem = libpad_h ? kernel_dynlib_init_addr(target, libpad_h) : 0;
if (!trap_mem && libpad_h) trap_mem = kernel_dynlib_fini_addr(target, libpad_h);
if (!trap_mem) {
sys_ptrace(PT_DETACH, target, (caddr_t)1, 0);
return -1;
}
kernel_set_vmem_protection(target, trap_mem, 16, PROT_READ | PROT_WRITE | PROT_EXEC);
uint8_t int3 = 0xCC;
pt_io_write(target, trap_mem, &int3, 1);
int32_t found = -1;
int32_t uids[4] = {userId, 0x10000000, 1, (int32_t)0xffffffff};
int types[3] = {0, 3, 16};
/* GetHandle sweeps across all userId × type × idx */
if (fn_gethandle) {
for (int u = 0; u < 4 && found < 0; u++) {
for (int t = 0; t < 3 && found < 0; t++) {
for (int idx = 0; idx < 8 && found < 0; idx++) {
int64_t r = pt_call(target, fn_gethandle, trap_mem,
(uint64_t)(uint32_t)uids[u],
(uint64_t)types[t], (uint64_t)idx, 0, 0, 0);
klog_printf("[Ghostpad] probe_rp: GH(0x%x,t=%d,i=%d)->0x%llx\n",
(uint32_t)uids[u], types[t], idx,
(unsigned long long)(uint64_t)r);
if ((int32_t)r >= 0) found = (int32_t)r;
}
}
}
}
/* scePadOpen sweeps if GetHandle didn't find it */
if (found < 0 && (fn_open || fn_open_ext)) {
for (int u = 0; u < 4 && found < 0; u++) {
for (int t = 0; t < 2 && found < 0; t++) {
for (int idx = 0; idx < 4 && found < 0; idx++) {
intptr_t fn = fn_open_ext ? fn_open_ext : fn_open;
int64_t r = fn_open_ext
? pt_call(target, fn, trap_mem,
(uint64_t)(uint32_t)uids[u], (uint64_t)types[t],
(uint64_t)idx, 0, 0, 0)
: pt_call(target, fn, trap_mem,
(uint64_t)(uint32_t)uids[u], (uint64_t)types[t],
(uint64_t)idx, 0, 0, 0);
klog_printf("[Ghostpad] probe_rp: Open(0x%x,t=%d,i=%d)->0x%llx\n",
(uint32_t)uids[u], types[t], idx,
(unsigned long long)(uint64_t)r);
if ((int32_t)r >= 0) found = (int32_t)r;
}
}
}
}
sys_ptrace(PT_DETACH, target, (caddr_t)1, 0);
klog_printf("[Ghostpad] probe_rp: result=%d\n", found);
return found;
}
int
shellui_pad_disconnect_device(uint64_t physicalDeviceId)
{
pid_t pids[4];
if (find_pids("SceShellUI", pids, 4) == 0) {
klog_printf("[Ghostpad] disconnect: SceShellUI not found, using direct fallback\n");
goto fallback;
}
pid_t target = pids[0];
klog_printf("[Ghostpad] disconnect: PT_ATTACH(SceShellUI pid=%d) dev=0x%llx\n",
target, (unsigned long long)physicalDeviceId);
if (sys_ptrace(PT_ATTACH, target, 0, 0) != 0) {
klog_printf("[Ghostpad] disconnect: PT_ATTACH failed errno=%d, using direct fallback\n", errno);
goto fallback;
}
waitpid(target, NULL, 0);
uint32_t mbus_h = 0;
get_lib(target, "libSceMbus", &mbus_h);
intptr_t fn_disc = mbus_h ? resolve_sym(target, mbus_h, "sceMbusDisconnectDevice") : 0;
klog_printf("[Ghostpad] disconnect: sceMbusDisconnectDevice @ 0x%lx\n", fn_disc);
uint32_t libpad_h = 0;
get_lib(target, "libScePad", &libpad_h);
intptr_t trap_mem = libpad_h ? kernel_dynlib_init_addr(target, libpad_h) : 0;
if (!trap_mem && libpad_h) trap_mem = kernel_dynlib_fini_addr(target, libpad_h);
if (!fn_disc || !trap_mem) {
klog_printf("[Ghostpad] disconnect: symbol/cave fail\n");
sys_ptrace(PT_DETACH, target, (caddr_t)1, 0);
return -1;
}
kernel_set_vmem_protection(target, trap_mem, 16, PROT_READ | PROT_WRITE | PROT_EXEC);
uint8_t int3 = 0xCC;
pt_io_write(target, trap_mem, &int3, 1);
int64_t ret = pt_call(target, fn_disc, trap_mem,
(uint64_t)physicalDeviceId, 0, 0, 0, 0, 0);
klog_printf("[Ghostpad] disconnect: sceMbusDisconnectDevice(0x%llx) -> %lld\n",
(unsigned long long)physicalDeviceId, (long long)ret);
sys_ptrace(PT_DETACH, target, (caddr_t)1, 0);
return (ret == 0) ? 0 : (int)ret;
fallback:
#ifdef __PROSPERO__
klog_printf("[Ghostpad] disconnect: direct fallback not supported on PS5\n");
return -1;
#else
{
klog_printf("[Ghostpad] disconnect: executing direct fallback for 0x%llx\n", (unsigned long long)physicalDeviceId);
void *h = dlopen("/system/common/lib/libSceMbus.sprx", RTLD_LAZY);
if (!h) {
klog_printf("[Ghostpad] disconnect: direct fallback failed to dlopen libSceMbus.sprx\n");
return -1;
}
typedef int (*fn_disconnect)(uint64_t);
fn_disconnect f = (fn_disconnect)dlsym(h, "sceMbusDisconnectDevice");
if (!f) {
klog_printf("[Ghostpad] disconnect: direct fallback failed to dlsym sceMbusDisconnectDevice\n");
dlclose(h);
return -1;
}
/* Elevate to SceShellCore credentials for direct system service call */
pid_t mypid = getpid();
uint64_t saved_authid = kernel_get_ucred_authid(mypid);
if (saved_authid) {
kernel_set_ucred_authid(mypid, 0x4800000000000010l);
}
int r = f(physicalDeviceId);
klog_printf("[Ghostpad] disconnect: direct sceMbusDisconnectDevice(0x%llx) returned %d\n",
(unsigned long long)physicalDeviceId, r);
if (saved_authid) {
kernel_set_ucred_authid(mypid, saved_authid);
}
dlclose(h);
return r;
}
#endif
}
int
shellui_pad_force_bind(uint64_t virtualDeviceId, int32_t userId)
{
pid_t pids[16];
size_t count = find_pids("SceShellUI", pids, 16);
if (count == 0) {
klog_printf("[Ghostpad] force_bind: SceShellUI not found, using direct fallback\n");
goto fallback;
}
pid_t target_pid = pids[0];
klog_printf("[Ghostpad] force_bind: PT_ATTACH(SceShellUI pid=%d) dev=0x%llx user=0x%x\n",
target_pid, (unsigned long long)virtualDeviceId, (uint32_t)userId);
if (sys_ptrace(PT_ATTACH, target_pid, 0, 0) != 0) {
klog_printf("[Ghostpad] force_bind: PT_ATTACH failed errno=%d, using direct fallback\n", errno);
goto fallback;
}
waitpid(target_pid, NULL, 0);
uint32_t libmbus_h = 0;
get_lib(target_pid, "libSceMbus", &libmbus_h);
if (!libmbus_h) {
klog_printf("[Ghostpad] force_bind: libSceMbus not found in SceShellUI\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
intptr_t fn_bind = resolve_sym(target_pid, libmbus_h, "sceMbusBindDeviceWithUserId");
klog_printf("[Ghostpad] force_bind: sceMbusBindDeviceWithUserId @ 0x%lx\n", fn_bind);
if (!fn_bind) {
klog_printf("[Ghostpad] force_bind: symbol not found\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
/* Need a code cave for the INT3 trap. Use libScePad init/fini if available. */
uint32_t libpad_h = 0;
get_lib(target_pid, "libScePad", &libpad_h);
intptr_t trap_mem = libpad_h ? kernel_dynlib_init_addr(target_pid, libpad_h) : 0;
if (!trap_mem && libpad_h) trap_mem = kernel_dynlib_fini_addr(target_pid, libpad_h);
if (!trap_mem) {
klog_printf("[Ghostpad] force_bind: no code cave\n");
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return -1;
}
kernel_set_vmem_protection(target_pid, trap_mem, 16, PROT_READ | PROT_WRITE | PROT_EXEC);
uint8_t int3 = 0xCC;
pt_io_write(target_pid, trap_mem, &int3, 1);
/* sceMbusBindDeviceWithUserId(uint64_t deviceId, uint32_t userId) */
int64_t ret = pt_call(target_pid, fn_bind, trap_mem,
(uint64_t)virtualDeviceId, (uint64_t)(uint32_t)userId,
0, 0, 0, 0);
klog_printf("[Ghostpad] force_bind: sceMbusBindDeviceWithUserId(0x%llx, 0x%x) -> %lld\n",
(unsigned long long)virtualDeviceId, (uint32_t)userId, (long long)ret);
sys_ptrace(PT_DETACH, target_pid, (caddr_t)1, 0);
return (ret == 0) ? 0 : (int)ret;
fallback:
#ifdef __PROSPERO__
klog_printf("[Ghostpad] force_bind: direct fallback not supported on PS5\n");
return -1;
#else
{
klog_printf("[Ghostpad] force_bind: executing direct fallback for 0x%llx -> 0x%x\n",
(unsigned long long)virtualDeviceId, (uint32_t)userId);
void *h = dlopen("/system/common/lib/libSceMbus.sprx", RTLD_LAZY);
if (!h) {
klog_printf("[Ghostpad] force_bind: direct fallback failed to dlopen libSceMbus.sprx\n");
return -1;
}
typedef int (*fn_bind)(uint64_t, uint32_t);
fn_bind f = (fn_bind)dlsym(h, "sceMbusBindDeviceWithUserId");
if (!f) {
klog_printf("[Ghostpad] force_bind: direct fallback failed to dlsym sceMbusBindDeviceWithUserId\n");
dlclose(h);
return -1;
}
/* Elevate to SceShellCore credentials for direct system service call */
pid_t mypid = getpid();
uint64_t saved_authid = kernel_get_ucred_authid(mypid);
if (saved_authid) {
kernel_set_ucred_authid(mypid, 0x4800000000000010l);
}
int r = f(virtualDeviceId, (uint32_t)userId);
klog_printf("[Ghostpad] force_bind: direct sceMbusBindDeviceWithUserId(0x%llx, 0x%x) returned %d\n",
(unsigned long long)virtualDeviceId, (uint32_t)userId, r);
if (saved_authid) {
kernel_set_ucred_authid(mypid, saved_authid);
}
dlclose(h);
return r;
}
#endif
}
/* shellui_pad_relaunch_stub_with_handle — re-launch stub with known VDI handle.
* Stub takes fast path (no VDA); subsequent updates use mdbg_copyin (no lag). */
int
shellui_pad_relaunch_stub_with_handle(int32_t handle)
{
if (!g_relaunch_stub_fn || !g_relaunch_pthread_fn || g_relaunch_pid < 0) {
klog_printf("[Ghostpad] relaunch: no injection state saved — inject first\n");
return -1;
}
pid_t pid = g_relaunch_pid;
intptr_t args_addr = g_relaunch_args_kaddr;
klog_printf("[Ghostpad] relaunch: pid=%d handle=%d args=0x%lx\n",
pid, handle, args_addr);
/* PT_ATTACH, write args via PT_IO, launch stub thread */
if (sys_ptrace(PT_ATTACH, pid, 0, 0) != 0) {
klog_printf("[Ghostpad] relaunch: PT_ATTACH failed errno=%d\n", errno);
return -1;
}
waitpid(pid, NULL, 0);
/* Allocate fresh heap in SceShellCore for shellui_stub via pt_call(malloc) */
size_t reg_stub_len = (size_t)((uintptr_t)shellui_stub_end - (uintptr_t)shellui_stub);
intptr_t new_stub_block = 0;
intptr_t new_trap_rip = g_relaunch_trap_rip; /* fallback: original trap */
intptr_t new_stub_fn = g_relaunch_stub_fn; /* fallback: original cave (unsafe) */
if (g_relaunch_malloc_fn) {
new_stub_block = (intptr_t)pt_call(pid, g_relaunch_malloc_fn, g_relaunch_trap_rip,
(uint64_t)(reg_stub_len + 32), 0, 0, 0, 0, 0);
klog_printf("[Ghostpad] relaunch: malloc(%zu) -> 0x%lx\n", reg_stub_len+32, new_stub_block);
}
if (new_stub_block) {
kernel_set_vmem_protection(pid, new_stub_block, reg_stub_len + 32,
PROT_READ | PROT_WRITE | PROT_EXEC);
uint8_t int3 = 0xCC;
pt_io_write(pid, new_stub_block, &int3, 1); /* INT3 trap at offset 0 */
if (!pt_io_write(pid, new_stub_block + 16, shellui_stub, reg_stub_len)) {
klog_printf("[Ghostpad] relaunch: shellui_stub (%zu bytes) -> 0x%lx ok\n",
reg_stub_len, new_stub_block + 16);
new_trap_rip = new_stub_block;
new_stub_fn = new_stub_block + 16;
} else {
klog_printf("[Ghostpad] relaunch: shellui_stub write failed errno=%d\n", errno);
new_stub_block = 0;
}
}
if (!new_stub_block) {
klog_printf("[Ghostpad] relaunch: no new block — cannot safely run shellui_stub\n");
sys_ptrace(PT_DETACH, pid, (caddr_t)1, 0);
return -1;
}
/* Write handle + reset args */
int32_t v32;
v32 = handle;
if (pt_io_write(pid, args_addr + (intptr_t)offsetof(ShellUiPadArgs, pad_handle), &v32, 4)) {
klog_printf("[Ghostpad] relaunch: PT_IO write handle failed errno=%d\n", errno);
sys_ptrace(PT_DETACH, pid, (caddr_t)1, 0);
return -1;
}
klog_printf("[Ghostpad] relaunch: PT_IO pad_handle=%d ok\n", handle);
v32 = 0;
pt_io_write(pid, args_addr + (intptr_t)offsetof(ShellUiPadArgs, ready), &v32, 4);
pt_io_write(pid, args_addr + (intptr_t)offsetof(ShellUiPadArgs, stop), &v32, 4);
/* seq=1: use_insert hint, fp_vda=NULL: skip VDA (device already bound) */
v32 = 1;
pt_io_write(pid, args_addr + (intptr_t)offsetof(ShellUiPadArgs, seq), &v32, 4);
intptr_t null_fn = 0;
pt_io_write(pid, args_addr + (intptr_t)offsetof(ShellUiPadArgs, fp_vda),
&null_fn, sizeof(null_fn));
/* Launch stub thread with correctly-sized allocation */
int64_t pret = pt_call(pid, g_relaunch_pthread_fn, new_trap_rip,
(uint64_t)g_relaunch_thread_storage, 0,
(uint64_t)new_stub_fn,
(uint64_t)g_relaunch_args_kaddr, 0, 0);
klog_printf("[Ghostpad] relaunch: pthread_create -> %lld\n", (long long)pret);
/* PT_DETACH so SceShellCore resumes and the stub thread can run */
sys_ptrace(PT_DETACH, pid, (caddr_t)1, 0);
klog_printf("[Ghostpad] relaunch: SceShellCore detached — stub thread running\n");
return (pret == 0) ? 0 : -1;
}
/*
* =====================================================================================
* HOOK SceShellCore scePadGetHandle TO BYPASS PID IPC CHECK
* =====================================================================================
*/
int
shellui_pad_hook_gethandle(void)
{
pid_t pids[8];
if (find_pids("SceShellCore", pids, 8) == 0) {
klog_printf("[Ghostpad] hook_gh: SceShellCore not found\n");
return -1;
}
pid_t target = pids[0];
pid_t mypid = getpid();
uint64_t saved_authid = kernel_get_ucred_authid(mypid);
if (saved_authid) {
kernel_set_ucred_authid(mypid, 0x4800000000010003l);
}
uint32_t libpad_h = 0;
if (get_lib(target, "libScePad", &libpad_h)) {
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
intptr_t fn_gethandle = resolve_sym(target, libpad_h, "scePadGetHandle");
intptr_t fn_vdi = resolve_sym(target, libpad_h, "scePadVirtualDeviceInsertData");
intptr_t fn_vda = resolve_sym(target, libpad_h, "scePadVirtualDeviceAddDevice");
if (!fn_gethandle || !fn_vdi || !fn_vda) {
klog_printf("[Ghostpad] hook_gh: symbols not found (gh=%p, vdi=%p, vda=%p)\n",
(void *)fn_gethandle, (void *)fn_vdi, (void *)fn_vda);
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
/* Read original 5 bytes of scePadGetHandle */
if (mdbg_copyout(target, fn_gethandle, g_orig_gethandle, 5) != 0) {
klog_printf("[Ghostpad] hook_gh: failed to read original 5 bytes of gethandle\n");
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
g_gethandle_hooked = 1;
/* Read original 128 bytes of scePadVirtualDeviceInsertData if not already done */
if (!g_vdi_hooked) {
if (mdbg_copyout(target, fn_vdi, g_orig_vdi_128, 128) == 0) {
g_vdi_hooked = 1;
klog_printf("[Ghostpad] hook_gh: captured original 128 bytes of vdi\n");
} else {
klog_printf("[Ghostpad] hook_gh: failed to read original 128 bytes of vdi\n");
}
}
klog_printf("[Ghostpad] hook_gh: original gethandle bytes: %02x %02x %02x %02x %02x\n",
g_orig_gethandle[0], g_orig_gethandle[1], g_orig_gethandle[2], g_orig_gethandle[3], g_orig_gethandle[4]);
/* Construct 128-byte hook block */
uint8_t hook[128];
memset(hook, 0x90, sizeof(hook)); // pad with NOPs
/* 1. cmp edi, 0xdeadbeef */
hook[0] = 0x81; hook[1] = 0xFF;
hook[2] = 0xEF; hook[3] = 0xBE; hook[4] = 0xAD; hook[5] = 0xDE;
/* 2. jne +0x60 (trampoline at offset 104) */
hook[6] = 0x75; hook[7] = 0x60;
/* 3. push rbx */
hook[8] = 0x53;
/* 4. mov ebx, esi */
hook[9] = 0x89; hook[10] = 0xF3;
/* 5. push registers to preserve state */
hook[11] = 0x57; // push rdi
hook[12] = 0x56; // push rsi
hook[13] = 0x52; // push rdx
hook[14] = 0x51; // push rcx
hook[15] = 0x41; hook[16] = 0x50; // push r8
hook[17] = 0x41; hook[18] = 0x51; // push r9
hook[19] = 0x41; hook[20] = 0x52; // push r10
hook[21] = 0x41; hook[22] = 0x53; // push r11
/* 6. sub rsp, 40 */
hook[23] = 0x48; hook[24] = 0x83; hook[25] = 0xEC; hook[26] = 0x28;
/* 7. mov dword ptr [rsp], 32 (vd_param.size) */
hook[27] = 0xC7; hook[28] = 0x04; hook[29] = 0x24;
hook[30] = 32; hook[31] = 0; hook[32] = 0; hook[33] = 0;
/* 8. mov dword ptr [rsp+4], 0x10000000 (vd_param.userId) */
hook[34] = 0xC7; hook[35] = 0x44; hook[36] = 0x24; hook[37] = 0x04;
hook[38] = 0x00; hook[39] = 0x00; hook[40] = 0x00; hook[41] = 0x10;
/* 9. mov qword ptr [rsp+8], 0 */
hook[42] = 0x48; hook[43] = 0xC7; hook[44] = 0x44; hook[45] = 0x24; hook[46] = 0x08;
hook[47] = 0; hook[48] = 0; hook[49] = 0; hook[50] = 0;
/* 10. mov qword ptr [rsp+16], 0 */
hook[51] = 0x48; hook[52] = 0xC7; hook[53] = 0x44; hook[54] = 0x24; hook[55] = 0x10;
hook[56] = 0; hook[57] = 0; hook[58] = 0; hook[59] = 0;
/* 11. mov qword ptr [rsp+24], 0 */
hook[60] = 0x48; hook[61] = 0xC7; hook[62] = 0x44; hook[63] = 0x24; hook[64] = 0x18;
hook[65] = 0; hook[66] = 0; hook[67] = 0; hook[68] = 0;
/* 12. mov rdi, rsp */
hook[69] = 0x48; hook[70] = 0x89; hook[71] = 0xE7;
/* 13. mov esi, ebx */
hook[72] = 0x89; hook[73] = 0xDE;
/* 14. mov rax, fn_vda */
hook[74] = 0x48; hook[75] = 0xB8;
memcpy(&hook[76], &fn_vda, 8);
/* 15. call rax */
hook[84] = 0xFF; hook[85] = 0xD0;
/* 16. add rsp, 40 */
hook[86] = 0x48; hook[87] = 0x83; hook[88] = 0xC4; hook[89] = 0x28;
/* 17. pop r11, r10, r9, r8, rcx, rdx, rsi, rdi, rbx */
hook[90] = 0x41; hook[91] = 0x5B;
hook[92] = 0x41; hook[93] = 0x5A;
hook[94] = 0x41; hook[95] = 0x59;
hook[96] = 0x41; hook[97] = 0x58;
hook[98] = 0x59;
hook[99] = 0x5A;
hook[100] = 0x5E;
hook[101] = 0x5F;
hook[102] = 0x5B;
/* 18. ret */
hook[103] = 0xC3;
/* ---- Trampoline at offset 104 (0x68) ---- */
/* 1. Copy original 5 bytes of scePadGetHandle */
memcpy(&hook[104], g_orig_gethandle, 5);
/* 2. mov rax, fn_gethandle + 5 */
hook[109] = 0x48; hook[110] = 0xB8;
intptr_t ret_addr = fn_gethandle + 5;
memcpy(&hook[111], &ret_addr, 8);
/* 3. jmp rax */
hook[119] = 0xFF; hook[120] = 0xE0;
/* Write the hook block into SceShellCore's scePadVirtualDeviceInsertData */
if (mdbg_copyin(target, hook, fn_vdi, 128) != 0) {
klog_printf("[Ghostpad] hook_gh: failed to write hook block to SceShellCore\n");
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
/* Write 5-byte relative jump at scePadGetHandle */
uint8_t detour[5];
detour[0] = 0xE9;
int32_t jmp_rel32 = (int32_t)(fn_vdi - (fn_gethandle + 5));
memcpy(&detour[1], &jmp_rel32, 4);
if (mdbg_copyin(target, detour, fn_gethandle, 5) != 0) {
klog_printf("[Ghostpad] hook_gh: failed to write detour jump to SceShellCore\n");
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
klog_printf("[Ghostpad] hook_gh: scePadGetHandle HOOKED successfully (detour -> %p)\n", (void *)fn_vdi);
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return 0;
}
/*
* =====================================================================================
* HOOK SceShellCore scePadSetProcessPrivilege FOR IN-PROCESS VDA
* =====================================================================================
*/
int
shellui_pad_hook_setpriv(void)
{
#if !GHOSTPAD_ALLOW_UNSAFE_SETPRIV_HOOK
klog_printf("[Ghostpad] hook_sp: disabled by default; compile with -DGHOSTPAD_ALLOW_UNSAFE_SETPRIV_HOOK=1 to enable\n");
return 0;
#else
pid_t pids[8];
if (find_pids("SceShellCore", pids, 8) == 0) {
klog_printf("[Ghostpad] hook_sp: SceShellCore not found\n");
return -1;
}
pid_t target = pids[0];
pid_t mypid = getpid();
uint64_t saved_authid = kernel_get_ucred_authid(mypid);
if (saved_authid) {
kernel_set_ucred_authid(mypid, 0x4800000000010003l);
}
uint32_t libpad_h = 0;
if (get_lib(target, "libScePad", &libpad_h)) {
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
intptr_t fn_setpriv = resolve_sym(target, libpad_h, "scePadSetProcessPrivilege");
intptr_t fn_vdi = resolve_sym(target, libpad_h, "scePadVirtualDeviceInsertData");
intptr_t fn_vda = resolve_sym(target, libpad_h, "scePadVirtualDeviceAddDevice");
if (!fn_setpriv || !fn_vdi || !fn_vda) {
klog_printf("[Ghostpad] hook_sp: symbols not found (sp=%p, vdi=%p, vda=%p)\n",
(void *)fn_setpriv, (void *)fn_vdi, (void *)fn_vda);
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
/* Read original 5 bytes of scePadSetProcessPrivilege */
if (mdbg_copyout(target, fn_setpriv, g_orig_setpriv, 5) != 0) {
klog_printf("[Ghostpad] hook_sp: failed to read original 5 bytes of setpriv\n");
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
g_setpriv_hooked = 1;
/* Read original 128 bytes of scePadVirtualDeviceInsertData if not already done */
if (!g_vdi_hooked) {
if (mdbg_copyout(target, fn_vdi, g_orig_vdi_128, 128) == 0) {
g_vdi_hooked = 1;
klog_printf("[Ghostpad] hook_sp: captured original 128 bytes of vdi\n");
} else {
klog_printf("[Ghostpad] hook_sp: failed to read original 128 bytes of vdi\n");
}
}
klog_printf("[Ghostpad] hook_sp: original setpriv bytes: %02x %02x %02x %02x %02x\n",
g_orig_setpriv[0], g_orig_setpriv[1], g_orig_setpriv[2], g_orig_setpriv[3], g_orig_setpriv[4]);
/* Construct 128-byte hook block */
uint8_t hook[128];
memset(hook, 0x90, sizeof(hook)); // pad with NOPs
size_t off = 0;
/* 1. cmp edi, 0xdeadbeef */
hook[off++] = 0x81; hook[off++] = 0xFF;
hook[off++] = 0xEF; hook[off++] = 0xBE; hook[off++] = 0xAD; hook[off++] = 0xDE;
/* 2. jne displacement (trampoline at offset 104) */
size_t jne_instr_off = off;
hook[off++] = 0x75;
hook[off++] = 0x00; // placeholder for displacement
/* 3. push registers to preserve state */
hook[off++] = 0x53; // push rbx
hook[off++] = 0x55; // push rbp
hook[off++] = 0x57; // push rdi
hook[off++] = 0x56; // push rsi
hook[off++] = 0x52; // push rdx
hook[off++] = 0x51; // push rcx
hook[off++] = 0x41; hook[off++] = 0x50; // push r8
hook[off++] = 0x41; hook[off++] = 0x51; // push r9
hook[off++] = 0x41; hook[off++] = 0x52; // push r10
hook[off++] = 0x41; hook[off++] = 0x53; // push r11
/* 4. sub rsp, 40 (allocate stack frame) */
hook[off++] = 0x48; hook[off++] = 0x83; hook[off++] = 0xEC; hook[off++] = 0x28;
/* 5. mov dword ptr [rsp], 32 (vd_param.size) */
hook[off++] = 0xC7; hook[off++] = 0x04; hook[off++] = 0x24;
hook[off++] = 32; hook[off++] = 0; hook[off++] = 0; hook[off++] = 0;
/* 6. mov dword ptr [rsp+4], 0x10000000 (vd_param.userId) */
hook[off++] = 0xC7; hook[off++] = 0x44; hook[off++] = 0x24; hook[off++] = 0x04;
hook[off++] = 0x00; hook[off++] = 0x00; hook[off++] = 0x00; hook[off++] = 0x10;
/* 7. mov qword ptr [rsp+8], 0 */
hook[off++] = 0x48; hook[off++] = 0xC7; hook[off++] = 0x44; hook[off++] = 0x24; hook[off++] = 0x08;
hook[off++] = 0; hook[off++] = 0; hook[off++] = 0; hook[off++] = 0;
/* 8. mov qword ptr [rsp+16], 0 */
hook[off++] = 0x48; hook[off++] = 0xC7; hook[off++] = 0x44; hook[off++] = 0x24; hook[off++] = 0x10;
hook[off++] = 0; hook[off++] = 0; hook[off++] = 0; hook[off++] = 0;
/* 9. mov qword ptr [rsp+24], 0 */
hook[off++] = 0x48; hook[off++] = 0xC7; hook[off++] = 0x44; hook[off++] = 0x24; hook[off++] = 0x18;
hook[off++] = 0; hook[off++] = 0; hook[off++] = 0; hook[off++] = 0;
/* 10. mov rdi, rsp */
hook[off++] = 0x48; hook[off++] = 0x89; hook[off++] = 0xE7;
/* 11. mov esi, 3 */
hook[off++] = 0xBE; hook[off++] = 0x03; hook[off++] = 0x00; hook[off++] = 0x00; hook[off++] = 0x00;
/* 12. mov rax, fn_vda */
hook[off++] = 0x48; hook[off++] = 0xB8;
memcpy(&hook[off], &fn_vda, 8);
off += 8;
/* 13. call rax */
hook[off++] = 0xFF; hook[off++] = 0xD0;
/* 14. add rsp, 40 */
hook[off++] = 0x48; hook[off++] = 0x83; hook[off++] = 0xC4; hook[off++] = 0x28;
/* 15. pop registers to restore state */
hook[off++] = 0x41; hook[off++] = 0x5B; // pop r11
hook[off++] = 0x41; hook[off++] = 0x5A; // pop r10
hook[off++] = 0x41; hook[off++] = 0x59; // pop r9
hook[off++] = 0x41; hook[off++] = 0x58; // pop r8
hook[off++] = 0x59; // pop rcx
hook[off++] = 0x5A; // pop rdx
hook[off++] = 0x5E; // pop rsi
hook[off++] = 0x5F; // pop rdi
hook[off++] = 0x5D; // pop rbp
hook[off++] = 0x5B; // pop rbx
/* 16. ret */
hook[off++] = 0xC3;
/* Setup trampoline at offset 104 */
size_t tramp_off = 104;
hook[jne_instr_off + 1] = (uint8_t)(tramp_off - (jne_instr_off + 2));
/* Trampoline: original 5 bytes */
memcpy(&hook[tramp_off], g_orig_setpriv, 5);
/* Trampoline: mov rax, fn_setpriv + 5 */
hook[tramp_off + 5] = 0x48; hook[tramp_off + 6] = 0xB8;
intptr_t ret_addr = fn_setpriv + 5;
memcpy(&hook[tramp_off + 7], &ret_addr, 8);
/* Trampoline: jmp rax */
hook[tramp_off + 15] = 0xFF; hook[tramp_off + 16] = 0xE0;
/* Write hook block to scePadVirtualDeviceInsertData in SceShellCore */
if (mdbg_copyin(target, hook, fn_vdi, 128) != 0) {
klog_printf("[Ghostpad] hook_sp: failed to write hook block to SceShellCore\n");
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
/* Write relative jump in scePadSetProcessPrivilege */
uint8_t detour[5];
detour[0] = 0xE9;
int32_t jmp_rel32 = (int32_t)(fn_vdi - (fn_setpriv + 5));
memcpy(&detour[1], &jmp_rel32, 4);
if (mdbg_copyin(target, detour, fn_setpriv, 5) != 0) {
klog_printf("[Ghostpad] hook_sp: failed to write detour jump to SceShellCore\n");
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return -1;
}
klog_printf("[Ghostpad] hook_sp: scePadSetProcessPrivilege HOOKED successfully (detour -> %p)\n", (void *)fn_vdi);
if (saved_authid) kernel_set_ucred_authid(mypid, saved_authid);
return 0;
#endif /* GHOSTPAD_ALLOW_UNSAFE_SETPRIV_HOOK */
}
/*
* =====================================================================================
* UNPATCH SYSTEM PROCESS HOOKS & TERMINATE STUB
* =====================================================================================
*/
/* Safe memory write with VM protection escalation/restoration */
static void safe_mdbg_restore(pid_t target, void *orig_bytes, intptr_t addr, size_t len) {
intptr_t page1 = addr & ~(intptr_t)0xfff;
intptr_t page2 = (addr + len - 1) & ~(intptr_t)0xfff;
int protect1_ok = (kernel_set_vmem_protection(target, page1, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) == 0);
int protect2_ok = 0;
if (page2 != page1) {
protect2_ok = (kernel_set_vmem_protection(target, page2, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) == 0);
}
mdbg_copyin(target, orig_bytes, addr, len);
if (protect1_ok) {
kernel_set_vmem_protection(target, page1, 0x1000, PROT_READ | PROT_EXEC);
}
if (protect2_ok) {
kernel_set_vmem_protection(target, page2, 0x1000, PROT_READ | PROT_EXEC);
}
}
int
shellui_pad_unpatch(void)
{
pid_t mypid = getpid();
uint64_t saved_authid = kernel_get_ucred_authid(mypid);
uint8_t privcaps[16];
memset(privcaps, 0xff, sizeof(privcaps));
uint8_t saved_caps[16];
int have_saved_caps = 0;
if (saved_authid && kernel_get_ucred_caps(mypid, saved_caps) == 0) {
have_saved_caps = 1;
kernel_set_ucred_authid(mypid, 0x4800000000010003l);
kernel_set_ucred_caps(mypid, privcaps);
}
klog_printf("[Ghostpad] shellui_pad_unpatch: starting unpatching sequence...\n");
/* 1. Stop SceShellUI/SceShellCore stub thread if it is running */
if (g_relaunch_pid > 0 && g_relaunch_args_kaddr != 0) {
klog_printf("[Ghostpad] shellui_pad_unpatch: stopping target stub thread in pid %d...\n", g_relaunch_pid);
int32_t stop_val = 1;
if (mdbg_copyin(g_relaunch_pid, &stop_val, g_relaunch_args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, stop), 4) == 0) {
/* Wait up to 1 second for the thread to exit and clean up */
for (int i = 0; i < 50; i++) {
usleep(20000);
int32_t ready_val = (int32_t)mdbg_getint(g_relaunch_pid, g_relaunch_args_kaddr + (intptr_t)offsetof(ShellUiPadArgs, ready));
if (ready_val == 0 || ready_val == -1) {
klog_printf("[Ghostpad] shellui_pad_unpatch: stub thread exited successfully.\n");
break;
}
}
} else {
klog_printf("[Ghostpad] shellui_pad_unpatch: failed to write stop flag to target stub.\n");
}
}
/* Find SceShellCore PID */
pid_t core_pid = -1;
{
pid_t pids[8];
if (find_pids("SceShellCore", pids, 8) > 0) {
core_pid = pids[0];
}
}
if (core_pid > 0) {
uint32_t libpad_h = 0;
get_lib(core_pid, "libScePad", &libpad_h);
if (libpad_h) {
intptr_t fn_gethandle = resolve_sym(core_pid, libpad_h, "scePadGetHandle");
intptr_t fn_vdi = resolve_sym(core_pid, libpad_h, "scePadVirtualDeviceInsertData");
intptr_t fn_setpriv = resolve_sym(core_pid, libpad_h, "scePadSetProcessPrivilege");
intptr_t fn_vda = resolve_sym(core_pid, libpad_h, "scePadVirtualDeviceAddDevice");
/* Restore scePadGetHandle hook */
if (g_gethandle_hooked && fn_gethandle) {
klog_printf("[Ghostpad] shellui_pad_unpatch: restoring scePadGetHandle (5 bytes)\n");
safe_mdbg_restore(core_pid, g_orig_gethandle, fn_gethandle, 5);
g_gethandle_hooked = 0;
}
/* Restore scePadSetProcessPrivilege hook */
if (g_setpriv_hooked && fn_setpriv) {
klog_printf("[Ghostpad] shellui_pad_unpatch: restoring scePadSetProcessPrivilege (5 bytes)\n");
safe_mdbg_restore(core_pid, g_orig_setpriv, fn_setpriv, 5);
g_setpriv_hooked = 0;
}
/* Restore scePadVirtualDeviceInsertData (128 bytes) */
if (g_vdi_hooked && fn_vdi) {
klog_printf("[Ghostpad] shellui_pad_unpatch: restoring scePadVirtualDeviceInsertData (128 bytes)\n");
safe_mdbg_restore(core_pid, g_orig_vdi_128, fn_vdi, 128);
g_vdi_hooked = 0;
}
/* Restore SceShellCore VDA patch */
if (g_vda_patched && fn_vda && g_vda_patched_pid == core_pid) {
klog_printf("[Ghostpad] shellui_pad_unpatch: restoring SceShellCore VDA patch\n");
safe_mdbg_restore(core_pid, g_orig_vda_call, fn_vda + (intptr_t)GHOSTPAD_VDA_PS4_CALL_OFF, 5);
safe_mdbg_restore(core_pid, g_orig_vda_cave, fn_vda + (intptr_t)GHOSTPAD_VDA_PS4_CAVE_OFF, 8);
g_vda_patched = 0;
}
}
}
/* Restore self VDA patch */
if (g_self_vda_patched) {
uint32_t self_libpad_h = 0;
if (get_lib(mypid, "libScePad", &self_libpad_h) == 0) {
intptr_t self_fn_vda = resolve_sym(mypid, self_libpad_h, "scePadVirtualDeviceAddDevice");
if (self_fn_vda) {
klog_printf("[Ghostpad] shellui_pad_unpatch: restoring self VDA patch\n");
safe_mdbg_restore(mypid, g_orig_self_vda_call, self_fn_vda + (intptr_t)GHOSTPAD_VDA_PS4_CALL_OFF, 5);
safe_mdbg_restore(mypid, g_orig_self_vda_cave, self_fn_vda + (intptr_t)GHOSTPAD_VDA_PS4_CAVE_OFF, 8);
g_self_vda_patched = 0;
}
}
}
klog_printf("[Ghostpad] shellui_pad_unpatch: unpatching sequence complete.\n");
if (have_saved_caps) {
kernel_set_ucred_authid(mypid, saved_authid);
kernel_set_ucred_caps(mypid, saved_caps);
}
return 0;
}