From c1cb19042da82153ee021dea9c53c0052ec88090 Mon Sep 17 00:00:00 2001 From: ItsDeidara Date: Sun, 7 Jun 2026 21:14:44 -0400 Subject: [PATCH] initial --- Makefile | 30 + ProControllerResearch.md | 267 +++ README.md | 91 +- gc_main.c | 1085 +++++++++++ ghost-control-ps5.elf | Bin 0 -> 282720 bytes othercontrollersGuide.md | 306 +++ shellui_pad.c | 3932 ++++++++++++++++++++++++++++++++++++++ shellui_pad.h | 141 ++ 8 files changed, 5850 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 ProControllerResearch.md create mode 100644 gc_main.c create mode 100644 ghost-control-ps5.elf create mode 100644 othercontrollersGuide.md create mode 100644 shellui_pad.c create mode 100644 shellui_pad.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9739342 --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Ghost-Control: USB HID controller → virtual DualSense on PS5 + +PS5_HOST ?= ps5 +PORT ?= 9021 +TARGET := ghost-control-ps5.elf + +ifndef PS5_PAYLOAD_SDK +$(error PS5_PAYLOAD_SDK is not set) +endif + +include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk + +CFLAGS += -D__PROSPERO__ -Wall -Wextra -g -O2 -fPIC -fno-stack-protector +LDFLAGS += -lScePad -lSceUserService -lpthread -ldl + +SRC := gc_main.c shellui_pad.c + +.PHONY: all clean deploy + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +clean: + rm -f $(TARGET) + +deploy: $(TARGET) + nc -w 5 $(PS5_HOST) $(PORT) < $(TARGET) diff --git a/ProControllerResearch.md b/ProControllerResearch.md new file mode 100644 index 0000000..874a834 --- /dev/null +++ b/ProControllerResearch.md @@ -0,0 +1,267 @@ +# Nintendo Switch Pro Controller USB Protocol Research + +This document captures the complete research into making the 8BitDo Ultimate 2 (Nintendo Switch Pro Controller mode) work on PS5 via raw USB FS access. Derived from 12 versions of `usb_handshake_probe` run on the PS5. + +--- + +## Device Identification + +| Property | Value | +|----------|-------| +| VID:PID (Nintendo mode) | `0x057e:0x2009` | +| VID:PID (Native mode) | `0x2dc8:0x310b` | +| PS5 device node | `/dev/ugen2.2` | +| USB bus | `usbus2`, depth=1 port=2 speed=FS | +| Kernel driver (pre-detach) | `usb_hid0` | +| IN endpoint | `ep=0x81`, wMaxPacketSize=64, bInterval=1ms | +| OUT endpoint | `ep=0x02`, wMaxPacketSize=64 (**NOTE: 0x02 not 0x01 like genuine Nintendo**) | + +--- + +## PS5 USB Access Method + +Direct kernel HID access via `usb_hid0` is blocked (`EPERM`). The working path: + +```c +USB_IFACE_DRIVER_DETACH(iface=0) // detach usb_hid0 +USB_FS_INIT(ep_index_max=N) // allocate N endpoint slots +USB_FS_OPEN(ep_no=0x81, ep_index=0) // IN endpoint +USB_FS_OPEN(ep_no=0x02, ep_index=1) // OUT endpoint +USB_FS_START(ep_index=0) // start interrupt IN transfer +USB_FS_COMPLETE(ep_index=0) // block until data +``` + +### Why Two-Pass Init + +The IN endpoint (0x81) cannot be opened on the first try in some states. A "pass 1" is required: + +```c +// Pass 1: unlock endpoint +FS_INIT(1 slot) → DETACH → FS_OPEN(ep=0x81) → FS_UNINIT → close() + +// Pass 2: actual session +open() → DETACH → FS_INIT(2 slots) → FS_OPEN IN(0x81) + OUT(0x02) +``` + +Pass 1 does nothing functionally — it just exercises the endpoint to unlock it for pass 2. + +--- + +## Critical Finding: Subcmd 0x03 Must Be Sent LAST + +**Wrong order (pipe breaks):** +``` +[80 02] → [80 04] → subcmd 0x03 0x30 ← USB pipe break, controller disconnects +``` + +**Correct order (works):** +``` +[80 02] → [80 04] ← USB handshake only +respond to 0x81 handshake ← controller responds with 0x81 0x01 / 0x81 0x02 +[80 04] again ← handshake ack +subcmd 0x40 0x01 ← enable IMU +subcmd 0x48 0x01 ← enable vibration +subcmd 0x30 0x01 ← set player LED +subcmd 0x03 0x30 ← set input report mode (LAST) +``` + +Sending `subcmd 0x03` first causes a USB pipe break (`EBUSY` on `USB_FS_COMPLETE`) and the controller enters a reconnect loop that requires 2–3 additional handshake rounds to recover. With the correct order, `subcmd 0x03` is accepted cleanly and streaming starts immediately. + +--- + +## Complete Initialization Sequence + +### Phase 1: Two-Pass USB FS Setup + +```c +// Pass 1 (unlock) +fd1 = open("/dev/ugen2.2", O_RDWR); +USB_FS_INIT(fd1, ep_index_max=1); +USB_IFACE_DRIVER_DETACH(fd1, iface=0); +USB_FS_OPEN(fd1, ep_index=0, ep_no=0x81, max_bufsize=64); +USB_FS_UNINIT(fd1); +close(fd1); +usleep(30000); + +// Pass 2 (working session) +fd = open("/dev/ugen2.2", O_RDWR); +USB_IFACE_DRIVER_DETACH(fd, iface=0); +USB_FS_INIT(fd, ep_index_max=2); +USB_FS_OPEN(fd, ep_index=0, ep_no=0x81, max_bufsize=64); // IN +USB_FS_OPEN(fd, ep_index=1, ep_no=0x02, max_bufsize=64); // OUT +``` + +### Phase 2: Initial USB Handshake (Blind) + +```c +send_out([0x80, 0x02]); // handshake start +usleep(30000); +send_out([0x80, 0x04]); // disable USB timeout +usleep(50000); +``` + +### Phase 3: Controller-Initiated Handshake Response + +The controller sends `0x81` handshake packets. Respond **within ~200ms or the controller gives up:** + +``` +Controller sends: 0x81 0x01 → Host responds: [0x80, 0x02] +Controller sends: 0x81 0x02 → Host responds: [0x80, 0x04] +``` + +### Phase 4: Subcmd Init Sequence (MUST be in this order) + +```c +send_subcmd(timer=1, subcmd=0x40, data=0x01); // enable IMU +usleep(50000); +send_subcmd(timer=2, subcmd=0x48, data=0x01); // enable vibration +usleep(50000); +send_subcmd(timer=3, subcmd=0x30, data=0x01); // set player LED +usleep(50000); +send_subcmd(timer=4, subcmd=0x03, data=0x30); // input mode = full push (LAST) +``` + +Subcmd packet format (12 bytes): +```c +uint8_t sc[12] = {0}; +sc[0] = 0x01; // output report ID +sc[1] = timer; // 4-bit counter, must increment each packet +// sc[2..9] = neutral rumble = {0,1,0x40,0x40,0,1,0x40,0x40} +sc[10] = subcmd; +sc[11] = data; +``` + +### Phase 5: Read Loop + +After `subcmd 0x03`, the controller immediately sends streaming data: + +```c +while (1) { + USB_FS_START(ep_index=0); + USB_FS_COMPLETE(ep_index=0, timeout=200ms); + // process pkt[0..63] +} +``` + +--- + +## Streaming Packet Format + +After the init sequence, the controller sends `0x00`-prefixed packets at ~60Hz: + +``` +Byte 0: 0x00 — always 0x00 (report ID stripped by 8BitDo firmware) +Byte 1: timer — 8-bit counter, increments ~6-8 units per frame +Byte 2: 0x70 — battery/connection (0x70 = USB, full charge) +Byte 3: right_btns — Y X B A SR SL R ZR (bits 0-7) +Byte 4: misc_btns — Minus Plus R3 L3 Home Capture (bits 0-5) +Byte 5: left_btns — Down Up Right Left SR SL L ZL (bits 0-7) +Bytes 6-8: left stick (12-bit packed, center ~0x800) +Bytes 9-11: right stick (12-bit packed, center ~0x7FF) +Byte 12: vibration status (0x0b = idle) +Bytes 13-47: IMU data (3 × 6-axis samples, when IMU enabled) +``` + +**Note:** Genuine Nintendo Switch Pro Controllers send `0x30` as byte 0 (the report ID). The 8BitDo sends `0x00`. The rest of the layout is identical. + +### Stick Decoding (12-bit packed) + +```c +uint16_t lx = buf[6] | ((buf[7] & 0x0F) << 8); // 0–4095, center ~2048 +uint16_t ly = (buf[7] >> 4) | ((uint16_t)buf[8] << 4); // 0–4095, center ~2047 +uint16_t rx = buf[9] | ((buf[10] & 0x0F) << 8); +uint16_t ry = (buf[10] >> 4) | ((uint16_t)buf[11] << 4); + +// Map to 0–255 for ScePadData: +uint8_t x = (uint8_t)((v * 255u) / 4095u); +``` + +### Button Bit Mapping + +``` +buf[3] — right face buttons: + bit 0: Y → PS5 Square + bit 1: X → PS5 Triangle + bit 2: B → PS5 Cross + bit 3: A → PS5 Circle + bit 6: R → PS5 R1 + bit 7: ZR → PS5 R2 + +buf[4] — shared buttons: + bit 0: Minus → PS5 Create/Share + bit 1: Plus → PS5 Options + bit 2: R3 → PS5 R3 + bit 3: L3 → PS5 L3 + bit 4: Home → PS5 PS button + +buf[5] — left buttons: + bit 0: Down → PS5 Down + bit 1: Up → PS5 Up + bit 2: Right → PS5 Right + bit 3: Left → PS5 Left + bit 6: L → PS5 L1 + bit 7: ZL → PS5 L2 +``` + +--- + +## Reconnect Handling + +On controller unplug/replug: +- `USB_FS_START` or `USB_FS_COMPLETE` returns `ENXIO` or `ENOTTY` +- Close all endpoints, `USB_FS_UNINIT`, `close(fd)` +- Re-scan `ugen` paths for the device (20s timeout) +- Run the full two-pass init + handshake sequence again + +On reconnect without full power cycle: +- First packet may be all-zeros (`00 00 00 00...`) — discard it +- Then `0x81` handshake packets appear — run Phase 3 and 4 again + +--- + +## USB FS COMPLETE Polling + +`USB_FS_COMPLETE` is non-blocking. Poll at 50ms intervals for up to `timeout_ms + 300ms`: + +```c +int max_polls = (int)((timeout_ms + 300) / 50) + 1; +for (int w = 0; w < max_polls; w++) { + if (ioctl(fd, USB_FS_COMPLETE, &co) == 0) break; + if (errno != EBUSY) { /* error */ break; } + usleep(50000); +} +``` + +**Do not** use 5-retry × 10ms — the USB transfer timeout is 200ms so you'll stop polling before the transfer can complete, leaving the endpoint stuck in `EBUSY`. + +--- + +## Things That Do Not Work + +| Approach | Result | +|----------|--------| +| `USB_GET_REPORT_DESC` | `errno=25 ENOTTY` | +| `read(fd, buf, 64)` without DETACH | `errno=1 EPERM` | +| `libSceHidControl` | Controller not auto-registered; ShellCore not initialized | +| `libSceUsbd` | Not loaded in SceShellCore or SceShellUI | +| Soft-reinit after `[80 04]` | Controller goes silent (wrong approach) | +| Sending `subcmd 0x03` first | USB pipe break → reconnect loop | +| Responding to `0x81 0x02` without subcmds | Controller silent after `[80 04]` | + +--- + +## Research Probe History + +Probes run on PS5 (`usb_handshake_probe v1` through `v12`) at port 6986, reports fetched from FTP `/data/ghostpad/`: + +| Version | Key Discovery | +|---------|--------------| +| v1 | Confirmed soft-reinit after subcmd 0x03 pipe-break works | +| v2–v3 | Fixed COMPLETE polling (50ms intervals needed, not 10ms) | +| v4–v5 | Identified full USB bus-reset after `[80 04]` in second handshake | +| v6 | Second subcmd 0x03 also causes bus-reset — infinite loop | +| v7–v8 | Discovered controller gives up if host takes >200ms to respond to 0x81 | +| v9 | Tight 200ms loop — controller responds within timing window | +| v10 | Added IMU/vibration subcmds — still no streaming | +| v11 | **BREAKTHROUGH**: subcmd 0x03 sent LAST → immediate 0x30 streaming | +| v12 | Full 64-byte packet dump — confirmed format, verified stick decode | diff --git a/README.md b/README.md index 7fa2e59..d075502 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,89 @@ -# Ghostcontrol - PS5 USB Controller Patcher -A PS5 payload to allow other controllers to work via the front USB port +# Ghostcontrol — by StonedModder + +Use third-party USB controllers on PS5. Reads USB HID input from a plugged-in controller and injects it into a virtual DualSense via the PS5's `scePadVirtualDeviceInsertData` path (Ghostpad VDI path). + +**Tested controller:** 8BitDo Ultimate 2 in Nintendo Switch Pro Controller mode (VID=0x057e PID=0x2009) + +--- + +## Features + +- 60Hz input streaming from USB HID controller → virtual DualSense +- PS5 notifications: startup, controller connect/disconnect, detected controller type +- User assignment: virtual DualSense is bound to the foreground user on startup +- Full button mapping: face buttons, triggers, sticks, dpad, L3/R3, PS button +- Auto-reconnect on controller unplug/replug + +--- + +## Requirements + +- PS5 with kernel exploit (tested on jailbroken PS5) +- [ps5-payload-sdk](https://github.com/ps5-payload-dev/sdk) +- USB controller — see supported list below + +--- + +## Supported Controllers + +| Controller | Mode | VID:PID | Status | +|-----------|------|---------|--------| +| 8BitDo Ultimate 2 | Nintendo Switch Pro | 057e:2009 | ✅ Working | +| 8BitDo Ultimate 2 | Native | 2dc8:310b | Untested | + +See `othercontrollersGuide.md` for adding new controllers. + +--- + +## Build + +```sh +export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk +make clean all +``` + +Output: `ghost-control-ps5.elf` + +## Deploy + +```sh +# Deploy to PS5 (replace IP) +nc -w 5 192.168.1.xxx 9021 < ghost-control-ps5.elf +``` + +Or set `PS5_HOST` in your environment: +```sh +make deploy PS5_HOST=192.168.1.xxx +``` + +--- + +## How It Works + +1. **VDA**: Creates a virtual DualSense via `scePadVirtualDeviceAddDevice(type=3)` +2. **klog capture**: Monitors klogsrv TCP to detect the `DEVICE_ADDED` event and get the device handle +3. **force_bind**: Binds the virtual device to the foreground user via ShellUI MBus IPC +4. **USB HID thread**: Detaches `usb_hid0` from the controller, opens raw USB FS endpoints, runs the Nintendo Switch Pro Controller USB handshake, then reads 60Hz input reports +5. **VDI inject**: Parses HID reports into `ScePadData` and calls `scePadVirtualDeviceInsertData` at 60Hz + +See `ProControllerResearch.md` for the full research documentation on the USB HID protocol. + +--- + +## Files + +| File | Description | +|------|-------------| +| `gc_main.c` | Main payload — VDA, VDI, USB HID thread, button parsing | +| `shellui_pad.c` | ShellUI PT_ATTACH helper for force_bind via MBus | +| `shellui_pad.h` | Header for shellui_pad | +| `Makefile` | Build system | +| `ghost-control-ps5.elf` | Pre-compiled payload (deploy directly) | +| `ProControllerResearch.md` | Full USB protocol research for Nintendo Switch Pro Controller | +| `othercontrollersGuide.md` | Guide for adding other USB HID controllers | + +--- + +## License + +GPL-3.0-or-later diff --git a/gc_main.c b/gc_main.c new file mode 100644 index 0000000..49d04b4 --- /dev/null +++ b/gc_main.c @@ -0,0 +1,1085 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * Ghost-Control v3: USB HID controller → virtual DualSense on PS5 + * + * Follows Ghostpad's proven VDA path exactly: + * 1. scePadVirtualDeviceAddDevice(userId=1, type=3) + * 2. Monitor klogsrv:3232 for DEVICE_ADDED [type:1][subType:22] + * 3. shellui_pad_force_bind(vDevId, fgUserId) via PT_ATTACH SceShellUI + * 4. scePadVirtualDeviceInsertData(handle, &padData) directly from this process + * + * USB HID input (new piece in front of Ghostpad's VDI path): + * /dev/ugen2.2 → USB_IFACE_DRIVER_DETACH → USB_FS_OPEN ep=0x81 + * → Nintendo handshake → 0x30/0x3f report loop → parse → VDI + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef __PROSPERO__ +#include +#include +#include +#endif + +#include "shellui_pad.h" + +/* ------------------------------------------------------------------ + * Logging + * ------------------------------------------------------------------ */ + +#define LOG_DIR "/data/ghostpad" +#define LOG_PATH "/data/ghostpad/gc_status.log" +#define PID_PATH "/data/ghostpad/gc_main.pid" +#define LOG_MAX 480 + +static pthread_mutex_t g_log_lock = PTHREAD_MUTEX_INITIALIZER; +static int g_log_fd = -1; + +void ghostpad_status_log_reset(void) { + pthread_mutex_lock(&g_log_lock); + if (g_log_fd >= 0) { close(g_log_fd); g_log_fd = -1; } + mkdir(LOG_DIR, 0755); + g_log_fd = open(LOG_PATH, O_WRONLY|O_CREAT|O_TRUNC, 0600); + pthread_mutex_unlock(&g_log_lock); +} + +void ghostpad_status_log(const char *fmt, ...) { + char buf[LOG_MAX]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf)-1, fmt, ap); + va_end(ap); + buf[LOG_MAX-1] = '\0'; + klog_printf("%s", buf); + pthread_mutex_lock(&g_log_lock); + if (g_log_fd >= 0) { + size_t n = strnlen(buf, sizeof(buf)); + write(g_log_fd, buf, n); + if (n && buf[n-1] != '\n') write(g_log_fd, "\n", 1); + } + pthread_mutex_unlock(&g_log_lock); +} + +#define gp_log(...) ghostpad_status_log("[GC] " __VA_ARGS__) + +/* ------------------------------------------------------------------ + * SCE stubs + * ------------------------------------------------------------------ */ + +extern int32_t sceUserServiceInitialize(void *params); +extern int32_t sceUserServiceGetInitialUser(int32_t *outUserId); +extern int32_t sceUserServiceGetForegroundUser(int32_t *outUserId); +extern int32_t scePadInit(void); +extern int32_t scePadSetProcessPrivilege(int32_t privilege); +extern int32_t scePadGetHandle(int32_t userId, int32_t type, int32_t index); +extern int32_t scePadVirtualDeviceAddDevice(void *param, int32_t deviceType); +extern int32_t scePadVirtualDeviceDeleteDevice(int32_t handle); +extern int32_t scePadVirtualDeviceInsertData(int32_t handle, const void *padData); +extern int32_t sceKernelSendNotificationRequest(int unk0, void *req, size_t size, int unk1); + +/* ------------------------------------------------------------------ + * SCE button constants + * ------------------------------------------------------------------ */ + +#define SCE_PAD_BUTTON_L3 0x00000002u +#define SCE_PAD_BUTTON_R3 0x00000004u +#define SCE_PAD_BUTTON_OPTIONS 0x00000008u +#define SCE_PAD_BUTTON_UP 0x00000010u +#define SCE_PAD_BUTTON_RIGHT 0x00000020u +#define SCE_PAD_BUTTON_DOWN 0x00000040u +#define SCE_PAD_BUTTON_LEFT 0x00000080u +#define SCE_PAD_BUTTON_L2 0x00000100u +#define SCE_PAD_BUTTON_R2 0x00000200u +#define SCE_PAD_BUTTON_L1 0x00000400u +#define SCE_PAD_BUTTON_R1 0x00000800u +#define SCE_PAD_BUTTON_TRIANGLE 0x00001000u +#define SCE_PAD_BUTTON_CIRCLE 0x00002000u +#define SCE_PAD_BUTTON_CROSS 0x00004000u +#define SCE_PAD_BUTTON_SQUARE 0x00008000u +#define SCE_PAD_BUTTON_CREATE 0x00010000u +#define SCE_PAD_BUTTON_PS 0x00010000u +#define SCE_PAD_BUTTON_TOUCH_PAD 0x00100000u + +typedef struct { uint16_t x; uint16_t y; uint8_t finger; uint8_t pad[3]; } ScePadTouch; +typedef struct { + uint8_t fingers; uint8_t pad1[3]; uint32_t pad2; ScePadTouch touch[2]; +} ScePadTouchData; +typedef struct { + uint32_t buttons; + struct { uint8_t x; uint8_t y; } leftStick; + struct { uint8_t x; uint8_t y; } rightStick; + struct { uint8_t l2; uint8_t r2; } analogButtons; + uint16_t padding; + struct { float x, y, z, w; } quat; + struct { float x, y, z; } vel; + struct { float x, y, z; } accel; + ScePadTouchData touchData; + uint8_t connected; + uint8_t _align[3]; + uint64_t timestamp; + uint8_t ext[16]; + uint8_t count; + uint8_t unknown[15]; +} ScePadData; + +#define VIRTUAL_DEVICE_TYPE_DUALSENSE 3 + +/* ------------------------------------------------------------------ + * Shared state + * ------------------------------------------------------------------ */ + +static volatile int32_t g_vdi_handle = -1; +static volatile int g_vdi_ready = 0; +/* Actual foreground userId — used for force_bind */ +static int32_t g_inject_uid = 0x10000000; + +/* klog monitoring: set when DEVICE_ADDED [type:1][subType:22] seen */ +static pthread_mutex_t g_klog_lock = PTHREAD_MUTEX_INITIALIZER; +static volatile uint64_t g_klog_vdev_id = 0; + +/* ------------------------------------------------------------------ + * Notification + * ------------------------------------------------------------------ */ + +typedef struct { char _unk[45]; char message[3075]; } NotifyRequest; +static void notify(const char *fmt, ...) { + NotifyRequest req; va_list ap; + memset(&req, 0, sizeof(req)); + va_start(ap, fmt); + vsnprintf(req.message, sizeof(req.message), fmt, ap); + va_end(ap); + sceKernelSendNotificationRequest(0, &req, sizeof(req), 0); +} + +/* ------------------------------------------------------------------ + * klog line parser — extract virtual DualSense DEVICE_ADDED deviceId + * ------------------------------------------------------------------ */ + +static uint64_t parse_hex_str(const char *s) { + uint64_t v = 0; + while (*s) { + char c = *s++; + if (c >= '0' && c <= '9') v = (v<<4)|(c-'0'); + else if (c >= 'a' && c <= 'f') v = (v<<4)|(c-'a'+10); + else if (c >= 'A' && c <= 'F') v = (v<<4)|(c-'A'+10); + else break; + } + return v; +} + +static void parse_klog_line(const char *line) { + /* Looking for: DEVICE_ADDED ... [type:1][subType:22][capabilityBattery:0] + * That's the virtual DualSense device we created with VDA(type=3). */ + if (!strstr(line, "DEVICE_ADDED")) return; + if (!strstr(line, "subType:22")) return; + if (!strstr(line, "capabilityBattery:0")) return; + + /* Extract DeviceId:0x... or deviceId=0x... */ + const char *p = strstr(line, "DeviceId:0x"); + if (!p) p = strstr(line, "deviceId=0x"); + if (!p) return; + p += 11; /* skip "DeviceId:0x" or "deviceId=0x" */ + + uint64_t dev_id = parse_hex_str(p); + if (!dev_id) return; + + pthread_mutex_lock(&g_klog_lock); + if (!g_klog_vdev_id) { + g_klog_vdev_id = dev_id; + gp_log("klog: VDA device 0x%llx\n", (unsigned long long)dev_id); + } + pthread_mutex_unlock(&g_klog_lock); +} + +/* ------------------------------------------------------------------ + * klog capture thread — connects to klogsrv:3232 (since /dev/klog + * is unavailable to payload processes: errno=16 EBUSY) + * ------------------------------------------------------------------ */ + +static void *klog_capture_thread(void *arg) { + (void)arg; + char buf[512]; + char line[1024]; + size_t line_len = 0; + int fd = -1; + + /* Try /dev/klog first; fall back to klogsrv TCP on port 3232 */ + fd = open("/dev/klog", O_RDONLY); + if (fd < 0) { + gp_log("klog: /dev/klog errno=%d, trying klogsrv TCP 127.0.0.1:3232\n", errno); + struct sockaddr_in sin; + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { gp_log("klog: socket failed errno=%d\n", errno); return NULL; } + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = inet_addr("127.0.0.1"); + sin.sin_port = htons(3232); + for (int retry = 0; retry < 10; retry++) { + if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) == 0) { + fd = sock; + gp_log("klog: connected to klogsrv TCP 127.0.0.1:3232\n"); + break; + } + usleep(500000); + } + if (fd < 0) { + gp_log("klog: could not connect to klogsrv errno=%d\n", errno); + close(sock); + return NULL; + } + } + + while (1) { + ssize_t len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EINTR) continue; + gp_log("klog: read error errno=%d\n", errno); + break; + } + if (len == 0) { usleep(10000); continue; } + + for (ssize_t i = 0; i < len; i++) { + char c = buf[i]; + if (c == '\n' || line_len >= sizeof(line)-1) { + line[line_len] = '\0'; + parse_klog_line(line); + line_len = 0; + } else if (c != '\r') { + line[line_len++] = c; + } + } + } + close(fd); + return NULL; +} + +/* ------------------------------------------------------------------ + * Nintendo input parsing + * ------------------------------------------------------------------ */ + +static uint8_t ntoh_stick(uint16_t v) { + if (v > 4095) v = 4095; + return (uint8_t)((v * 255u) / 4095u); +} + +static uint32_t hat_to_dpad(uint8_t hat) { + switch (hat) { + case 0: return SCE_PAD_BUTTON_UP; + case 1: return SCE_PAD_BUTTON_UP | SCE_PAD_BUTTON_RIGHT; + case 2: return SCE_PAD_BUTTON_RIGHT; + case 3: return SCE_PAD_BUTTON_DOWN | SCE_PAD_BUTTON_RIGHT; + case 4: return SCE_PAD_BUTTON_DOWN; + case 5: return SCE_PAD_BUTTON_DOWN | SCE_PAD_BUTTON_LEFT; + case 6: return SCE_PAD_BUTTON_LEFT; + case 7: return SCE_PAD_BUTTON_UP | SCE_PAD_BUTTON_LEFT; + default: return 0; + } +} + +/* Report 0x30: full 60Hz input after Nintendo handshake + * [3]=right btns [4]=shared [5]=left btns [6-8]=lstick [9-11]=rstick */ +static void parse_0x30(const uint8_t *b, ScePadData *o) { + uint8_t br=b[3], bs=b[4], bl=b[5]; + uint16_t lx=(uint16_t)(b[6]|((b[7]&0xF)<<8)); + uint16_t ly=(uint16_t)((b[7]>>4)|((uint16_t)b[8]<<4)); + uint16_t rx=(uint16_t)(b[9]|((b[10]&0xF)<<8)); + uint16_t ry=(uint16_t)((b[10]>>4)|((uint16_t)b[11]<<4)); + uint32_t btn=0; + if(br&0x04) btn|=SCE_PAD_BUTTON_CROSS; + if(br&0x08) btn|=SCE_PAD_BUTTON_CIRCLE; + if(br&0x01) btn|=SCE_PAD_BUTTON_SQUARE; + if(br&0x02) btn|=SCE_PAD_BUTTON_TRIANGLE; + if(bl&0x40) btn|=SCE_PAD_BUTTON_L1; + if(bl&0x80) btn|=SCE_PAD_BUTTON_L2; + if(br&0x40) btn|=SCE_PAD_BUTTON_R1; + if(br&0x80) btn|=SCE_PAD_BUTTON_R2; + if(bs&0x08) btn|=SCE_PAD_BUTTON_L3; + if(bs&0x04) btn|=SCE_PAD_BUTTON_R3; + if(bs&0x02) btn|=SCE_PAD_BUTTON_OPTIONS; + if(bs&0x01) btn|=SCE_PAD_BUTTON_CREATE; + if(bs&0x10) btn|=SCE_PAD_BUTTON_PS; + if(bs&0x20) btn|=SCE_PAD_BUTTON_TOUCH_PAD; + if(bl&0x02) btn|=SCE_PAD_BUTTON_UP; + if(bl&0x01) btn|=SCE_PAD_BUTTON_DOWN; + if(bl&0x04) btn|=SCE_PAD_BUTTON_RIGHT; + if(bl&0x08) btn|=SCE_PAD_BUTTON_LEFT; + o->buttons=btn; + o->leftStick.x=ntoh_stick(lx); o->leftStick.y=ntoh_stick(ly); + o->rightStick.x=ntoh_stick(rx); o->rightStick.y=ntoh_stick(ry); + o->analogButtons.l2=(bl&0x80)?255:0; + o->analogButtons.r2=(br&0x80)?255:0; + o->connected=1; o->quat.w=1.0f; +} + +/* Report 0x3f: simple input on button change only + * [1]=right btns [2]=shared [3]=HAT [4]=lx [5]=ly [6]=rx [7]=ry [8]=L/ZL */ +static void parse_0x3f(const uint8_t *b, ScePadData *o) { + uint8_t b1=b[1],b2=b[2],hat=b[3],b8=b[8]; + uint32_t btn=0; + if(b1&0x04) btn|=SCE_PAD_BUTTON_CROSS; + if(b1&0x08) btn|=SCE_PAD_BUTTON_CIRCLE; + if(b1&0x01) btn|=SCE_PAD_BUTTON_SQUARE; + if(b1&0x02) btn|=SCE_PAD_BUTTON_TRIANGLE; + if(b8&0x40) btn|=SCE_PAD_BUTTON_L1; + if(b8&0x80) btn|=SCE_PAD_BUTTON_L2; + if(b1&0x40) btn|=SCE_PAD_BUTTON_R1; + if(b1&0x80) btn|=SCE_PAD_BUTTON_R2; + if(b2&0x08) btn|=SCE_PAD_BUTTON_L3; + if(b2&0x04) btn|=SCE_PAD_BUTTON_R3; + if(b2&0x02) btn|=SCE_PAD_BUTTON_OPTIONS; + if(b2&0x01) btn|=SCE_PAD_BUTTON_CREATE; + if(b2&0x10) btn|=SCE_PAD_BUTTON_PS; + if(b2&0x20) btn|=SCE_PAD_BUTTON_TOUCH_PAD; + btn|=hat_to_dpad(hat); + o->buttons=btn; + o->leftStick.x=b[4]; o->leftStick.y=b[5]; + o->rightStick.x=b[6]; o->rightStick.y=b[7]; + o->analogButtons.l2=(b8&0x80)?255:0; + o->analogButtons.r2=(b1&0x80)?255:0; + o->connected=1; o->quat.w=1.0f; +} + +/* ------------------------------------------------------------------ + * ugen device detection + * ------------------------------------------------------------------ */ + +static const char *UGEN_PATHS[] = { + "/dev/ugen2.2","/dev/ugen2.3","/dev/ugen2.4","/dev/ugen2.5", + "/dev/ugen1.2","/dev/ugen0.2","/dev/ugen0.3", +}; +#define N_UGEN_PATHS ((int)(sizeof(UGEN_PATHS)/sizeof(UGEN_PATHS[0]))) + +#define VID_NATIVE 0x2dc8u +#define PID_NATIVE 0x310bu +#define VID_SWITCH 0x057eu +#define PID_SWITCH 0x2009u + +static int ugen_find_target(const char **out_path, uint16_t *out_vid, uint16_t *out_pid) { + for (int i = 0; i < N_UGEN_PATHS; i++) { + /* Try O_RDWR — PS5 ugen requires RDWR for USB ioctls. + * USB_GET_DEVICEINFO returns ENOTTY with O_RDONLY on PS5. + * O_NONBLOCK prevents blocking if the device is busy. */ + int fd = open(UGEN_PATHS[i], O_RDWR | O_NONBLOCK); + if (fd < 0) { + if (errno != ENOENT) + gp_log("ugen_find: open(%s) errno=%d\n", UGEN_PATHS[i], errno); + continue; + } + struct usb_device_info di; + memset(&di, 0, sizeof(di)); + int ok = 0; + int r = ioctl(fd, USB_GET_DEVICEINFO, &di); + if (r != 0) { + if (errno == ENOTTY) { + if (strncmp(UGEN_PATHS[i], "/dev/ugen2.", 11) == 0) { + gp_log("ugen_find: %s USB_GET_DEVICEINFO ENOTTY, trying as Nintendo candidate\n", + UGEN_PATHS[i]); + if (out_vid) *out_vid = VID_SWITCH; + if (out_pid) *out_pid = PID_SWITCH; + if (out_path) *out_path = UGEN_PATHS[i]; + close(fd); + return 1; + } + gp_log("ugen_find: %s USB_GET_DEVICEINFO ENOTTY, ignored non-external bus\n", + UGEN_PATHS[i]); + close(fd); + continue; + } + if (errno != ENOENT) + gp_log("ugen_find: %s USB_GET_DEVICEINFO errno=%d\n", + UGEN_PATHS[i], errno); + close(fd); + continue; + } + if (r == 0) { + gp_log("ugen_find: %s VID=0x%04x PID=0x%04x\n", + UGEN_PATHS[i], di.udi_vendorNo, di.udi_productNo); + if ((di.udi_vendorNo == VID_NATIVE && di.udi_productNo == PID_NATIVE) || + (di.udi_vendorNo == VID_SWITCH && di.udi_productNo == PID_SWITCH)) { + if (out_vid) *out_vid = di.udi_vendorNo; + if (out_pid) *out_pid = di.udi_productNo; + if (out_path) *out_path = UGEN_PATHS[i]; + ok = 1; + } + } else { + /* ENOTTY = PS5 ugen doesn't support this ioctl in current state. + * Try a USB_FS_INIT probe to check if the device accepts ugen FS ops. + * If it does, assume it's our target (only one USB game controller expected). */ + struct usb_fs_endpoint ep_probe; + struct usb_fs_init init_probe; + memset(&ep_probe, 0, sizeof(ep_probe)); + memset(&init_probe, 0, sizeof(init_probe)); + init_probe.pEndpoints = &ep_probe; + init_probe.ep_index_max = 1; + int ir = ioctl(fd, USB_FS_INIT, &init_probe); + gp_log("ugen_find: %s DEVICEINFO errno=%d FS_INIT=%d\n", + UGEN_PATHS[i], errno, ir); + if (ir == 0) { + /* FS_INIT succeeded. Verify endpoint by opening ep=0x81 and + * checking max_pkt_length == 64 (8BitDo Nintendo IN ep is always 64). */ + struct usb_fs_open probe_open; + memset(&probe_open, 0, sizeof(probe_open)); + probe_open.ep_index = 0; + probe_open.ep_no = 0x81; + probe_open.max_bufsize = 64; + probe_open.max_frames = 1; + if (ioctl(fd, USB_FS_OPEN, &probe_open) == 0 && + probe_open.max_packet_length == 64) { + gp_log("ugen_find: %s ep=0x81 max_pkt=%u — 8BitDo!\n", + UGEN_PATHS[i], (unsigned)probe_open.max_packet_length); + struct usb_fs_close pc; memset(&pc,0,sizeof(pc)); pc.ep_index=0; + ioctl(fd, USB_FS_CLOSE, &pc); + if (out_vid) *out_vid = VID_SWITCH; + if (out_pid) *out_pid = PID_SWITCH; + if (out_path) *out_path = UGEN_PATHS[i]; + ok = 1; + } else { + gp_log("ugen_find: %s ep probe failed or wrong pkt size %u\n", + UGEN_PATHS[i], (unsigned)probe_open.max_packet_length); + } + struct usb_fs_uninit un; memset(&un,0,sizeof(un)); + ioctl(fd, USB_FS_UNINIT, &un); + } + } + close(fd); + if (ok) return 1; + } + return 0; +} + +/* ------------------------------------------------------------------ + * Inject pad data via direct VDI (Ghostpad path) + * ------------------------------------------------------------------ */ + +static void inject_pad(const ScePadData *pad) { + int32_t h = g_vdi_handle; + if (h < 0) return; + int vr = scePadVirtualDeviceInsertData(h, pad); + static uint32_t inject_count = 0; + inject_count++; + if (inject_count <= 8 || (inject_count % 300) == 0) { + gp_log("VDI inject #%u ret=0x%08x buttons=0x%08x ls=%u,%u rs=%u,%u lt=%u rt=%u\n", + inject_count, (uint32_t)vr, pad->buttons, + pad->leftStick.x, pad->leftStick.y, + pad->rightStick.x, pad->rightStick.y, + pad->analogButtons.l2, pad->analogButtons.r2); + } + static int logged = 0; + if (vr != 0 && !logged) { + gp_log("VDI error 0x%08x (logged once)\n", (uint32_t)vr); + logged = 1; + } +} + +static int usb_fs_send_out_report(int fd, struct usb_fs_endpoint *ep, + const uint8_t *data, uint32_t len, + const char *tag) { + void *out_buffers[1] = { (void *)data }; + uint32_t out_lengths[1] = { len }; + struct usb_fs_start start; + struct usb_fs_complete complete; + + ep->ppBuffer = out_buffers; + ep->pLength = out_lengths; + ep->nFrames = 1; + ep->timeout = 100; + ep->flags = 0; + ep->aFrames = 0; + ep->status = 0; + + memset(&start, 0, sizeof(start)); + start.ep_index = 1; + if (ioctl(fd, USB_FS_START, &start) != 0) { + int e = errno; + gp_log("OUT %s START failed errno=%d ep_status=%d aFrames=%u len=%u\n", + tag, e, ep->status, ep->aFrames, len); + return -errno; + } + + for (int wait = 0; wait < 20; wait++) { + memset(&complete, 0, sizeof(complete)); + complete.ep_index = 1; + if (ioctl(fd, USB_FS_COMPLETE, &complete) == 0) { + gp_log("OUT %s complete ok wait=%d ep_status=%d aFrames=%u len=%u\n", + tag, wait, ep->status, ep->aFrames, out_lengths[0]); + return 0; + } + if (errno != EBUSY) { + int e = errno; + gp_log("OUT %s COMPLETE failed errno=%d ep_status=%d aFrames=%u len=%u\n", + tag, e, ep->status, ep->aFrames, out_lengths[0]); + return -errno; + } + usleep(50000); + } + + gp_log("OUT %s COMPLETE still busy ep_status=%d aFrames=%u len=%u\n", + tag, ep->status, ep->aFrames, out_lengths[0]); + return -EBUSY; +} + +static int usb_fs_send_out_cmd(int fd, struct usb_fs_endpoint *ep, uint8_t a, uint8_t b) { + uint8_t out_buf[2] = { a, b }; + char tag[16]; + snprintf(tag, sizeof(tag), "%02x %02x", a, b); + return usb_fs_send_out_report(fd, ep, out_buf, sizeof(out_buf), tag); +} + +static int nintendo_send_subcmd(int fd, struct usb_fs_endpoint *ep, + uint8_t *seq, uint8_t subcmd, + const uint8_t *data, uint32_t data_len) { + static const uint8_t neutral_rumble[8] = { + 0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40 + }; + uint8_t out_buf[64]; + char tag[32]; + uint32_t len = 11 + data_len; + + if (len > sizeof(out_buf)) + return -EINVAL; + + memset(out_buf, 0, sizeof(out_buf)); + out_buf[0] = 0x01; + out_buf[1] = *seq & 0x0f; + memcpy(out_buf + 2, neutral_rumble, sizeof(neutral_rumble)); + out_buf[10] = subcmd; + if (data_len) + memcpy(out_buf + 11, data, data_len); + *seq = (uint8_t)((*seq + 1) & 0x0f); + + snprintf(tag, sizeof(tag), "subcmd %02x", subcmd); + return usb_fs_send_out_report(fd, ep, out_buf, len, tag); +} + +static void nintendo_maybe_poll_state(int fd, struct usb_fs_endpoint *ep, + uint8_t *seq, int enabled) { + static struct timeval last_poll; + struct timeval now; + long elapsed_ms; + + if (!enabled) + return; + gettimeofday(&now, NULL); + elapsed_ms = (now.tv_sec - last_poll.tv_sec) * 1000L + + (now.tv_usec - last_poll.tv_usec) / 1000L; + if (last_poll.tv_sec != 0 && elapsed_ms < 33) + return; + last_poll = now; + (void)nintendo_send_subcmd(fd, ep, seq, 0x00, NULL, 0); +} + +/* ------------------------------------------------------------------ + * USB HID reader thread + * ------------------------------------------------------------------ */ + +/* Handshake states for Nintendo Pro Controller USB init */ +#define HS_WAIT_81_01 0 /* waiting for 0x81 sub=0x01 */ +#define HS_WAIT_81_02 1 /* saw 0x81 0x01, sent [80 02], waiting for 0x81 0x02 */ +#define HS_STREAMING 2 /* subcmds sent, reading 0x00/0x30 data */ + +static void *usb_hid_thread(void *arg) { + (void)arg; + const char *dev_path = NULL; + uint16_t vid=0, pid=0; + struct usb_fs_endpoint eps[2]; + struct usb_fs_init init; + struct usb_fs_open fs_open; + struct usb_fs_open fs_out_open; + struct usb_fs_start start; + struct usb_fs_complete complete; + struct usb_fs_stop stop; + struct usb_fs_close fs_close; + struct usb_fs_uninit uninit; + uint8_t buf[64]; + void *buffers[1]; uint32_t lengths[1]; + int fd = -1; + int out_opened = 0; + + gp_log("USB thread started\n"); + +retry_find: + dev_path = NULL; vid = 0; pid = 0; + out_opened = 0; + { int _scan=0; + while (!ugen_find_target(&dev_path, &vid, &pid)) { + if (_scan==0 || _scan==5) gp_log("USB scan #%d: no 8BitDo found\n",_scan); + _scan++; usleep(2000000); + } + } + gp_log("USB candidate at %s VID=0x%04x PID=0x%04x\n", dev_path, vid, pid); + { + const char *ctlr_name = + (vid==VID_SWITCH && pid==PID_SWITCH) ? "8BitDo (Nintendo Pro mode)" : + (vid==VID_NATIVE && pid==PID_NATIVE) ? "8BitDo (Native mode)" : + "Unknown controller"; + notify("Ghostcontrol: Detected %s", ctlr_name); + } + + fd = open(dev_path, O_RDWR); + if (fd < 0) { gp_log("open failed errno=%d\n", errno); goto retry_find; } + + /* Probe-matching two-stage USB FS setup. */ + memset(eps,0,sizeof(eps)); memset(&init,0,sizeof(init)); + init.pEndpoints=eps; init.ep_index_max=1; + if (ioctl(fd,USB_FS_INIT,&init)!=0) { + gp_log("USB_FS_INIT pass1 failed errno=%d\n",errno); + close(fd); goto retry_find; + } + + int iface0 = 0; + int dr0 = ioctl(fd, USB_IFACE_DRIVER_DETACH, &iface0); + int de0 = (dr0 == 0) ? 0 : errno; + int iface1 = 1; + int dr1 = ioctl(fd, USB_IFACE_DRIVER_DETACH, &iface1); + int de1 = (dr1 == 0) ? 0 : errno; + gp_log("USB_IFACE_DRIVER_DETACH pass1(0)=%d errno=%d, pass1(1)=%d errno=%d\n", + dr0, de0, dr1, de1); + + memset(&fs_open,0,sizeof(fs_open)); + fs_open.ep_index=0; fs_open.ep_no=0x81; + fs_open.max_bufsize=sizeof(buf); fs_open.max_frames=1; + if (ioctl(fd,USB_FS_OPEN,&fs_open)!=0) { + gp_log("USB_FS_OPEN pass1 IN failed errno=%d\n",errno); + goto uninit_retry; + } + gp_log("USB_FS_OPEN pass1 IN ok max_pkt=%u\n", + (unsigned)fs_open.max_packet_length); + + memset(&uninit,0,sizeof(uninit)); ioctl(fd,USB_FS_UNINIT,&uninit); + close(fd); fd = -1; + + fd = open(dev_path, O_RDWR); + if (fd < 0) { gp_log("reopen failed errno=%d\n", errno); goto retry_find; } + + iface0 = 0; + dr0 = ioctl(fd, USB_IFACE_DRIVER_DETACH, &iface0); + de0 = (dr0 == 0) ? 0 : errno; + iface1 = 1; + dr1 = ioctl(fd, USB_IFACE_DRIVER_DETACH, &iface1); + de1 = (dr1 == 0) ? 0 : errno; + gp_log("USB_IFACE_DRIVER_DETACH pass2(0)=%d errno=%d, pass2(1)=%d errno=%d\n", + dr0, de0, dr1, de1); + + memset(eps,0,sizeof(eps)); memset(&init,0,sizeof(init)); + init.pEndpoints=eps; init.ep_index_max=2; + if (ioctl(fd,USB_FS_INIT,&init)!=0) { + gp_log("USB_FS_INIT pass2 failed errno=%d\n",errno); + close(fd); goto retry_find; + } + + memset(&fs_open,0,sizeof(fs_open)); + fs_open.ep_index=0; fs_open.ep_no=0x81; + fs_open.max_bufsize=sizeof(buf); fs_open.max_frames=1; + if (ioctl(fd,USB_FS_OPEN,&fs_open)!=0) { + gp_log("USB_FS_OPEN pass2 IN failed errno=%d\n",errno); + goto uninit_retry; + } + gp_log("USB_FS_OPEN pass2 IN ok max_pkt=%u\n", + (unsigned)fs_open.max_packet_length); + if (fs_open.max_packet_length != 64) { + gp_log("USB_FS_OPEN IN max_pkt=%u is not controller HID, rejecting %s\n", + (unsigned)fs_open.max_packet_length, dev_path); + goto reinit; + } + + buffers[0]=buf; lengths[0]=sizeof(buf); + eps[0].ppBuffer=buffers; eps[0].pLength=lengths; + eps[0].nFrames=1; eps[0].timeout=200; + eps[0].flags=USB_FS_FLAG_SINGLE_SHORT_OK|USB_FS_FLAG_MULTI_SHORT_OK; + + out_opened = 0; + memset(&fs_out_open,0,sizeof(fs_out_open)); + fs_out_open.ep_index=1; fs_out_open.ep_no=0x02; + fs_out_open.max_bufsize=64; fs_out_open.max_frames=1; + if (ioctl(fd,USB_FS_OPEN,&fs_out_open)==0) { + out_opened = 1; + gp_log("USB_FS_OPEN OUT ep=0x02 ok max_pkt=%u\n", + (unsigned)fs_out_open.max_packet_length); + if (pid == PID_SWITCH && fs_out_open.max_packet_length != 64) { + gp_log("USB_FS_OPEN OUT max_pkt=%u is not Nintendo controller OUT, rejecting %s\n", + (unsigned)fs_out_open.max_packet_length, dev_path); + goto reinit; + } + } else { + gp_log("USB_FS_OPEN OUT ep=0x02 failed errno=%d\n", errno); + } + + /* Proven init sequence (from usb_handshake_probe v11): + * [80 02]+[80 04] blindly, then respond to 0x81 0x01→[80 02] and + * 0x81 0x02→[80 04]+subcmds(0x40,0x48,0x30,0x03 LAST). */ + uint8_t nintendo_seq = 1; + int hs_state = HS_WAIT_81_01; + + if (pid == PID_SWITCH && out_opened) { + int c0 = usb_fs_send_out_cmd(fd, &eps[1], 0x80, 0x02); + usleep(30000); + int c1 = usb_fs_send_out_cmd(fd, &eps[1], 0x80, 0x04); + gp_log("Nintendo blind [80 02]=%d [80 04]=%d; handshake state machine starting\n", c0, c1); + usleep(50000); + } else if (pid == PID_SWITCH) { + gp_log("Nintendo OUT not open — skipping handshake, trying native read\n"); + hs_state = HS_STREAMING; + } + + uint32_t pkt_count = 0; + int usb_ready_notified = 0; + + while (1) { + memset(buf,0,sizeof(buf)); + lengths[0]=sizeof(buf); eps[0].aFrames=0; eps[0].status=0; + + memset(&start,0,sizeof(start)); start.ep_index=0; + if (ioctl(fd,USB_FS_START,&start)!=0) { + if (errno==EBUSY) { + memset(&stop,0,sizeof(stop)); stop.ep_index=0; + ioctl(fd,USB_FS_STOP,&stop); usleep(5000); + } else if (errno==ENXIO||errno==ENOTTY) { + gp_log("START errno=%d — device gone, reinit\n",errno); + goto reinit; + } else { + gp_log("START fatal errno=%d\n",errno); goto reinit; + } + continue; + } + + int complete_ok = 0; + int complete_errno = 0; + int complete_wait = 0; + /* Poll COMPLETE for up to 300ms (timeout=200ms + margin) */ + for (complete_wait = 0; complete_wait < 10; complete_wait++) { + memset(&complete,0,sizeof(complete)); complete.ep_index=0; + if (ioctl(fd,USB_FS_COMPLETE,&complete)==0) { + complete_ok = 1; + break; + } + complete_errno = errno; + if (complete_errno==ENXIO||complete_errno==ENOTTY) { + gp_log("COMPLETE errno=%d — device gone, reinit\n",complete_errno); + goto reinit; + } + if (complete_errno != EBUSY) break; + usleep(50000); + } + if (!complete_ok) { + static uint32_t compl_err=0; compl_err++; + if (compl_err==1||compl_err%20==0) + gp_log("COMPLETE failed errno=%d wait=%d count=%u\n", + complete_errno,complete_wait,compl_err); + memset(&stop,0,sizeof(stop)); stop.ep_index=0; + ioctl(fd,USB_FS_STOP,&stop); + continue; + } + + if (lengths[0] < 1) continue; + uint8_t rid = buf[0]; + uint32_t len = lengths[0]; + + if (pkt_count < 10 || (pkt_count % 300 == 0)) { + gp_log("PKT #%u hs=%d id=0x%02x len=%u: " + "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + pkt_count, hs_state, rid, len, + buf[0],buf[1],buf[2],buf[3],buf[4],buf[5], + buf[6],buf[7],buf[8],buf[9],buf[10],buf[11]); + } + pkt_count++; + + ScePadData pad; + memset(&pad, 0, sizeof(pad)); + pad.quat.w = 1.0f; + + /* Data packets: rid=0x00 (8BitDo streaming format, same layout as 0x30) + * or rid=0x30 (first transition packet after subcmd 0x03). + * Require buf[1]!=0 (non-zero timer) — all-zeros is a USB buffer + * artifact after reconnect, not real data. */ + if ((rid == 0x00 || rid == 0x30) && len >= 12) { + if (rid == 0x00 && buf[1] == 0) { + /* All-zeros garbage packet — skip, handshake not done yet */ + continue; + } + if (hs_state != HS_STREAMING) { + gp_log("rid=0x%02x timer=0x%02x — already streaming, skip handshake\n", + rid, buf[1]); + hs_state = HS_STREAMING; + } + if (!usb_ready_notified) { + notify("Ghostcontrol: Controller connected and streaming"); + usb_ready_notified = 1; + } + parse_0x30(buf, &pad); + if (g_vdi_ready) inject_pad(&pad); + continue; + } + + if (rid == 0x3f && len >= 9) { + if (!usb_ready_notified) { + notify("Ghostcontrol: Controller connected and streaming"); + usb_ready_notified = 1; + } + parse_0x3f(buf, &pad); + if (g_vdi_ready) inject_pad(&pad); + continue; + } + + if (rid == 0x21 && len >= 12) { + gp_log("0x21 ACK subcmd=0x%02x hs=%d\n", (buf[12]&0x7f), hs_state); + /* ACK for subcmd 0x03 might carry first input data — try parsing */ + parse_0x30(buf, &pad); + if (g_vdi_ready && hs_state==HS_STREAMING) inject_pad(&pad); + continue; + } + + if (rid == 0x81) { + /* Nintendo handshake. State machine from probe v11: + * 0x81 0x01 → [80 02] → wait for 0x81 0x02 + * 0x81 0x02 → [80 04] → send subcmds 0x40,0x48,0x30,0x03 → streaming + * If 0x81 arrives while HS_STREAMING (reconnect): reset and redo handshake. */ + if (hs_state == HS_STREAMING) { + gp_log("0x81 sub=0x%02x while streaming — reconnect, restarting handshake\n", buf[1]); + hs_state = HS_WAIT_81_01; + usb_ready_notified = 0; + } + if (buf[1] == 0x01 && hs_state == HS_WAIT_81_01) { + int hr = out_opened ? usb_fs_send_out_cmd(fd,&eps[1],0x80,0x02) : -ENODEV; + gp_log("0x81 sub=0x01 → [80 02] ret=%d\n",hr); + hs_state = HS_WAIT_81_02; + } else if (buf[1] == 0x02 && hs_state <= HS_WAIT_81_02) { + /* Complete handshake and send full subcmd init sequence */ + int hr = out_opened ? usb_fs_send_out_cmd(fd,&eps[1],0x80,0x04) : -ENODEV; + gp_log("0x81 sub=0x02 → [80 04] ret=%d; sending subcmds\n",hr); + usleep(50000); + if (out_opened) { + uint8_t d1[]={0x01}; nintendo_send_subcmd(fd,&eps[1],&nintendo_seq,0x40,d1,1); usleep(50000); + uint8_t d2[]={0x01}; nintendo_send_subcmd(fd,&eps[1],&nintendo_seq,0x48,d2,1); usleep(50000); + uint8_t d3[]={0x01}; nintendo_send_subcmd(fd,&eps[1],&nintendo_seq,0x30,d3,1); usleep(50000); + uint8_t d4[]={0x30}; nintendo_send_subcmd(fd,&eps[1],&nintendo_seq,0x03,d4,1); + gp_log("Subcmds sent (0x40,0x48,0x30,0x03) — expecting 0x00 stream\n"); + } + hs_state = HS_STREAMING; + } else { + gp_log("0x81 sub=0x%02x hs=%d (ignored)\n",buf[1],hs_state); + } + continue; + } + + /* Unknown packet — log and skip */ + if (pkt_count < 20) { + gp_log("UNKNOWN rid=0x%02x len=%u hs=%d\n",rid,len,hs_state); + } + } + +reinit: + if (usb_ready_notified) { + notify("Ghostcontrol: Controller disconnected"); + usb_ready_notified = 0; + } + memset(&stop,0,sizeof(stop)); stop.ep_index=0; ioctl(fd,USB_FS_STOP,&stop); + if (out_opened) { + memset(&fs_close,0,sizeof(fs_close)); fs_close.ep_index=1; ioctl(fd,USB_FS_CLOSE,&fs_close); + out_opened = 0; + } + memset(&fs_close,0,sizeof(fs_close)); fs_close.ep_index=0; ioctl(fd,USB_FS_CLOSE,&fs_close); +uninit_retry: + memset(&uninit,0,sizeof(uninit)); ioctl(fd,USB_FS_UNINIT,&uninit); + close(fd); gp_log("USB: reinit in 300ms\n"); usleep(300000); + goto retry_find; +} + +/* ------------------------------------------------------------------ + * Credential elevation + * ------------------------------------------------------------------ */ + +static void elevate_credentials(void) { + pid_t mypid = getpid(); + uint8_t caps[16] = { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff + }; + kernel_set_ucred_authid(mypid, 0x3800000000010003l); + kernel_set_ucred_caps(mypid, caps); +} + +/* ------------------------------------------------------------------ + * main — follows Ghostpad's VDA + klog + force_bind + direct VDI path + * ------------------------------------------------------------------ */ + +int main(void) { + int32_t userId = -1, fgUser = -1; + int ret; + + ghostpad_status_log_reset(); + gp_log("Ghost-Control v3 starting\n"); + notify("Ghostcontrol - by StonedModder"); + + /* Kill previous instance via pidfile */ + { + int pfd = open(PID_PATH, O_RDONLY); + if (pfd >= 0) { + char pbuf[16] = {0}; + read(pfd, pbuf, sizeof(pbuf)-1); + close(pfd); + pid_t old = (pid_t)atoi(pbuf); + if (old > 0 && old != getpid()) { + gp_log("Killing previous instance pid=%d\n", old); + kill(old, SIGTERM); + usleep(600000); + } + } + int pfd2 = open(PID_PATH, O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (pfd2 >= 0) { + char pbuf[16]; + snprintf(pbuf, sizeof(pbuf), "%d", getpid()); + write(pfd2, pbuf, strlen(pbuf)); + close(pfd2); + } + } + + /* User service */ + sceUserServiceInitialize(NULL); + sceUserServiceGetInitialUser(&userId); + sceUserServiceGetForegroundUser(&fgUser); + gp_log("userId=0x%08x fgUser=0x%08x\n", (uint32_t)userId, (uint32_t)fgUser); + + /* g_inject_uid: actual foreground user — used for force_bind. + * userId is clamped to [0x10000000,0x1000000F] for direct scePad calls. */ + g_inject_uid = (fgUser > 0) ? fgUser : userId; + if ((uint32_t)userId < 0x10000000u || (uint32_t)userId > 0x1000000Fu) + userId = 0x10000000; + gp_log("inject_uid=0x%08x direct_uid=0x%08x\n", + (uint32_t)g_inject_uid, (uint32_t)userId); + + elevate_credentials(); + + ret = scePadInit(); + gp_log("scePadInit: 0x%08x\n", ret); + ret = scePadSetProcessPrivilege(1); + gp_log("scePadSetProcessPrivilege: 0x%08x\n", ret); + + /* Clean orphaned virtual devices from previous runs */ + for (int dh = 0; dh < 64; dh++) { + int32_t dr = scePadVirtualDeviceDeleteDevice(dh); + if (dr == 0) gp_log("deleteDevice(%d): ok\n", dh); + } + + /* Start klog monitoring BEFORE VDA so we never miss the DEVICE_ADDED event */ + pthread_t klog_tid; + if (pthread_create(&klog_tid, NULL, klog_capture_thread, NULL) == 0) { + pthread_detach(klog_tid); + gp_log("klog thread started\n"); + } + usleep(300000); /* let klog thread connect before VDA fires the event */ + + /* ── VDA: create virtual DualSense ────────────────────────────── + * PS5 VDA uses userId=1 (not the actual user ID). + * Returns 0x803b0006 on PS5 — device IS created despite error code. + * If ret >= 0, it may encode a handle in the return value or param fields. */ + struct { int32_t size; int32_t userId; int32_t pad[6]; } vdp; + const int32_t SENTINEL = (int32_t)0xDEADBEEF; + memset(&vdp, 0, sizeof(vdp)); + vdp.size = (int32_t)sizeof(vdp); + vdp.userId = 1; /* PS5 VDA always uses userId=1 */ + for (int k=0; k<6; k++) vdp.pad[k] = SENTINEL; + + ret = scePadVirtualDeviceAddDevice(&vdp, VIRTUAL_DEVICE_TYPE_DUALSENSE); + gp_log("VDA ret=0x%08x pad[0-3]=0x%08x 0x%08x 0x%08x 0x%08x\n", + (uint32_t)ret, (uint32_t)vdp.pad[0], (uint32_t)vdp.pad[1], + (uint32_t)vdp.pad[2], (uint32_t)vdp.pad[3]); + + /* Check if VDA returned a direct handle (positive return value) */ + int32_t direct_handle = -1; + if (ret > 0) { + direct_handle = ret; + gp_log("VDA direct handle from ret: %d\n", direct_handle); + } + for (int k=0; k<6; k++) { + if (vdp.pad[k] != SENTINEL && vdp.pad[k] > 0) { + gp_log("VDA handle in pad[%d]=%d\n", k, vdp.pad[k]); + if (direct_handle < 0) direct_handle = vdp.pad[k]; + break; + } + } + + /* ── Wait for klog to confirm DEVICE_ADDED (up to 10s) ─────────── */ + gp_log("Waiting for VDA device ID (klog + GetHandle fallback)...\n"); + uint64_t vdev_id = 0; + for (int t = 0; t < 100 && !vdev_id; t++) { + usleep(100000); + pthread_mutex_lock(&g_klog_lock); + vdev_id = g_klog_vdev_id; + pthread_mutex_unlock(&g_klog_lock); + } + + int32_t vdi_handle = -1; + + if (vdev_id) { + gp_log("VDA device from klog: 0x%llx\n", (unsigned long long)vdev_id); + vdi_handle = (int32_t)(vdev_id & 0xffffffffu); + /* force_bind triggers the PS5 user-assignment dialog. + * Without it the PS5 silently auto-assigns with no screen shown. + * The user then presses a button on the controller to confirm. */ + int br = shellui_pad_force_bind(vdev_id, g_inject_uid); + gp_log("force_bind(0x%llx, 0x%08x) ret=%d — dialog should appear\n", + (unsigned long long)vdev_id, (uint32_t)g_inject_uid, br); + + } else if (direct_handle >= 0) { + gp_log("klog timeout — using direct VDA handle %d\n", direct_handle); + vdi_handle = direct_handle; + } else { + /* Last resort: GetHandle scan for existing virtual DualSense */ + gp_log("klog timeout — GetHandle scan...\n"); + static const int32_t uids[] = {1, 0x10000000, (int32_t)0xffffffff}; + for (int ui=0; ui<3 && vdi_handle<0; ui++) + for (int idx=0; idx<8 && vdi_handle<0; idx++) { + vdi_handle = scePadGetHandle(uids[ui], 3, idx); + if (vdi_handle >= 0) + gp_log("GetHandle uid=0x%08x idx=%d h=%d\n", + (uint32_t)uids[ui], idx, vdi_handle); + } + } + + if (vdi_handle >= 0) { + g_vdi_handle = vdi_handle; + g_vdi_ready = 1; + gp_log("VDI READY handle=0x%x\n", (uint32_t)vdi_handle); + notify("Ghostcontrol: Ready — select your user with the controller"); + } else { + gp_log("ERROR: no VDI handle found\n"); + notify("Ghostcontrol: ERROR — no VDI handle"); + } + + /* Start USB HID reader thread */ + pthread_t usb_tid; + if (pthread_create(&usb_tid, NULL, usb_hid_thread, NULL) == 0) { + pthread_detach(usb_tid); + gp_log("USB thread created\n"); + } + + /* Keep-alive + periodic status */ + uint32_t tick = 0; + while (1) { + usleep(1000000); + tick++; + if (tick % 10 == 0) { + gp_log("alive tick=%u vdi_ready=%d handle=0x%x\n", + tick, g_vdi_ready, (uint32_t)g_vdi_handle); + } + } + return 0; +} diff --git a/ghost-control-ps5.elf b/ghost-control-ps5.elf new file mode 100644 index 0000000000000000000000000000000000000000..15ec4c4e52260e9907553de0b4bc91d5823e71ce GIT binary patch literal 282720 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 Scx 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ພ=Vv8^fZFzLLEuXFEv_CD- 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 literal 0 HcmV?d00001 diff --git a/othercontrollersGuide.md b/othercontrollersGuide.md new file mode 100644 index 0000000..5e1ad9b --- /dev/null +++ b/othercontrollersGuide.md @@ -0,0 +1,306 @@ +# Adding Other USB HID Controllers + +This guide provides the baseline for adding support for controllers other than the 8BitDo in Nintendo Switch Pro mode. Everything below is derived from what was confirmed working on PS5. + +--- + +## Architecture Overview + +The payload has two separate paths: + +``` +USB HID Controller → gc_main USB thread → ScePadData → scePadVirtualDeviceInsertData + (parse) (VDI inject) +``` + +To add a new controller you only need to: +1. Add VID:PID detection in `ugen_find_target()` +2. Add an initialization sequence in `usb_hid_thread()` +3. Add a parser function that converts the controller's HID report to `ScePadData` + +--- + +## Step 1: Identify the Controller + +Plug the controller into the PS5 USB port and watch klog: + +``` +ugen2.2: (VID=0xXXXX PID=0xXXXX) at usbus2 +``` + +Also check: +- Which `ugen` path it appears on (`/dev/ugen2.2`, `/dev/ugen1.2`, etc.) +- What kernel driver claims it (`usb_hid0`, `usb_hid1`, etc.) + +Add the VID:PID to `gc_main.c`: + +```c +#define VID_MYCTLR 0xXXXXu +#define PID_MYCTLR 0xXXXXu +``` + +And add detection to `ugen_find_target()`: + +```c +if ((di.udi_vendorNo == VID_MYCTLR && di.udi_productNo == PID_MYCTLR)) { + if (out_vid) *out_vid = di.udi_vendorNo; + if (out_pid) *out_pid = di.udi_productNo; + if (out_path) *out_path = UGEN_PATHS[i]; + ok = 1; +} +``` + +--- + +## Step 2: Find the Endpoints + +After `USB_IFACE_DRIVER_DETACH` and `USB_FS_INIT`, try opening common HID endpoints: + +```c +// Standard HID: IN=0x81, OUT=0x01 (or 0x02 for some — check your device) +USB_FS_OPEN(ep_no=0x81, ep_index=0, max_bufsize=64) // IN +USB_FS_OPEN(ep_no=0x01, ep_index=1, max_bufsize=64) // OUT (try 0x02 if this fails) +``` + +Log `max_packet_length` from the open result — it tells you the expected HID report size. + +**Common endpoint configurations:** + +| Controller Family | IN | OUT | Report Size | +|------------------|----|-----|------------| +| Nintendo Pro / 8BitDo Nintendo mode | 0x81 | 0x02 | 64 bytes | +| Xbox 360 (wired) | 0x81 | 0x02 | 20 bytes (IN), 8 bytes (OUT) | +| Xbox One / Series (wired) | 0x81 | 0x02 | 18 bytes (IN) | +| Generic HID gamepad | 0x81 | 0x01 | varies | + +--- + +## Step 3: Determine Init Sequence + +### Native HID Controllers (no init needed) + +Most generic HID gamepads (XInput-incompatible mode, standard HID descriptor) start sending reports immediately after you open the IN endpoint — no OUT commands needed. + +```c +// No init: just open IN and start reading +USB_FS_OPEN(ep_no=0x81, ep_index=0); +// → start reading, data comes immediately +``` + +Check if `USB_FS_COMPLETE` returns data within 500ms with no OUT sent. If yes: native HID, skip all init. + +### Xbox 360 (Wired) + +Xbox 360 wired controller uses XInput protocol. It requires an init packet on the OUT endpoint before it sends input reports: + +```c +// No handshake needed — just send one OUT "enable" packet +uint8_t enable[] = {0x01, 0x03, 0x0E}; // XInput enable command (3 bytes) +send_out(fd, &eps[1], enable, sizeof(enable)); +// Controller then sends 0x00 input reports at ~125Hz +``` + +Xbox 360 has no USB mode switching — no handshake like Nintendo. See report format below. + +### Xbox One / Series (Wired) + +Xbox One protocol (XInput HID variant) requires: + +```c +// Enable input reports +uint8_t enable[] = {0x05, 0x20, 0x00, 0x01, 0x00}; +send_out(fd, &eps[1], enable, sizeof(enable)); +// Optionally: rumble keepalive every 5s (or controller disconnects from idle) +``` + +### Generic / Unknown + +1. Open IN endpoint (no OUT init) +2. Read for 2 seconds +3. If no data: try sending `[0x00]` or `[0x01]` on OUT endpoint +4. Capture and log whatever comes in — inspect the HID report manually + +--- + +## Step 4: Parse the HID Report + +### Identify Report Structure + +Log the raw bytes while pressing each button individually: + +```c +gp_log("PKT: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7], + buf[8],buf[9],buf[10],buf[11]); +``` + +Press each button, record which bit changes. Common patterns: + +``` +Buttons: one byte per group, each button = 1 bit +Sticks: one byte per axis (0–255 range, center=127–128) + OR two bytes per axis (little-endian 16-bit, center=~32767) +Triggers:one byte per trigger (0–255) + OR in button byte (binary on/off) +Dpad: one nibble (0-8 hat switch) OR four bits in a button byte +``` + +### Xbox 360 Report Format (20 bytes, report ID = 0x00) + +``` +Byte 0: 0x00 — report ID +Byte 1: 0x14 — length (20) +Byte 2: buttons lo — A B X Y LB RB Back Start (bits 4-7: 0000) +Byte 3: buttons hi — LS RS Guide (bits 0-2) +Byte 4: left trigger (0-255) +Byte 5: right trigger (0-255) +Byte 6-7: left stick X (int16 LE, center=0, range -32768..32767) +Byte 8-9: left stick Y (int16 LE, center=0, inverted: up=positive) +Byte 10-11: right stick X +Byte 12-13: right stick Y +Byte 14-19: (padding) +``` + +Dpad is in `buf[2]` bits 0-3: +``` +bit 0: Up, bit 1: Down, bit 2: Left, bit 3: Right +``` + +### Xbox One Report Format (18 bytes, report ID = 0x20) + +``` +Byte 0: 0x20 — report ID +Byte 1: 0x00 — sequence +Byte 2: buttons lo — A B X Y LB RB View Menu (bits 0-7) +Byte 3: buttons hi — LS RS (bits 0-1), dpad (bits 2-5) +Byte 4: left trigger (0-255, maps to 0-1023 internally) +Byte 5: right trigger (0-255) +Byte 6-7: left stick X (int16 LE, center=0) +Byte 8-9: left stick Y (int16 LE) +Byte 10-11: right stick X +Byte 12-13: right stick Y +Byte 14-17: (varies by firmware) +``` + +--- + +## Step 5: Map to ScePadData + +Add a parse function in `gc_main.c` following this pattern: + +```c +static void parse_xbox360(const uint8_t *b, ScePadData *o) { + uint8_t bl = b[2], bh = b[3]; + uint8_t lt = b[4], rt = b[5]; + int16_t lx = (int16_t)(b[6] | (b[7] << 8)); + int16_t ly = (int16_t)(b[8] | (b[9] << 8)); + int16_t rx = (int16_t)(b[10] | (b[11] << 8)); + int16_t ry = (int16_t)(b[12] | (b[13] << 8)); + + uint32_t btn = 0; + /* Face buttons */ + if (bl & 0x10) btn |= SCE_PAD_BUTTON_CROSS; // A + if (bl & 0x20) btn |= SCE_PAD_BUTTON_CIRCLE; // B + if (bl & 0x40) btn |= SCE_PAD_BUTTON_SQUARE; // X + if (bl & 0x80) btn |= SCE_PAD_BUTTON_TRIANGLE; // Y + /* Shoulder */ + if (bh & 0x01) btn |= SCE_PAD_BUTTON_L1; // LB + if (bh & 0x02) btn |= SCE_PAD_BUTTON_R1; // RB + /* Triggers (analog) */ + o->analogButtons.l2 = lt; + o->analogButtons.r2 = rt; + if (lt > 128) btn |= SCE_PAD_BUTTON_L2; + if (rt > 128) btn |= SCE_PAD_BUTTON_R2; + /* Sticks */ + if (bh & 0x20) btn |= SCE_PAD_BUTTON_L3; + if (bh & 0x40) btn |= SCE_PAD_BUTTON_R3; + /* Menu */ + if (bl & 0x04) btn |= SCE_PAD_BUTTON_CREATE; // Back + if (bl & 0x08) btn |= SCE_PAD_BUTTON_OPTIONS; // Start + /* Dpad */ + if (bl & 0x01) btn |= SCE_PAD_BUTTON_UP; + if (bl & 0x02) btn |= SCE_PAD_BUTTON_DOWN; + if (bl & 0x04) btn |= SCE_PAD_BUTTON_LEFT; + if (bl & 0x08) btn |= SCE_PAD_BUTTON_RIGHT; + + o->buttons = btn; + /* Map int16 (-32768..32767) → uint8 (0..255), center=127 */ + o->leftStick.x = (uint8_t)((lx + 32768) >> 8); + o->leftStick.y = (uint8_t)(255 - ((ly + 32768) >> 8)); // Y axis inverted + o->rightStick.x = (uint8_t)((rx + 32768) >> 8); + o->rightStick.y = (uint8_t)(255 - ((ry + 32768) >> 8)); + o->connected = 1; + o->quat.w = 1.0f; +} +``` + +Then in the main read loop, add a branch: + +```c +} else if (pid == PID_XBOX360 && rid == 0x00 && len >= 14) { + parse_xbox360(buf, &pad); + if (g_vdi_ready) inject_pad(&pad); +``` + +--- + +## Step 6: Add Init to USB Thread + +In `usb_hid_thread()`, after the two-pass setup, add a branch for your controller: + +```c +if (pid == PID_SWITCH && out_opened) { + // 8BitDo Nintendo mode — full handshake (existing) + ... +} else if (pid == PID_XBOX360 && out_opened) { + uint8_t enable[] = {0x01, 0x03, 0x0E}; + usb_fs_send_out_report(fd, &eps[1], enable, sizeof(enable), "xbox360_enable"); + hs_state = HS_STREAMING; // Xbox 360 has no handshake +} else { + // Generic HID: no init, just start reading + hs_state = HS_STREAMING; +} +``` + +--- + +## Button Mapping Reference + +All PS5 `SCE_PAD_BUTTON_*` values used in `gc_main.c`: + +```c +SCE_PAD_BUTTON_L3 = 0x00000002 +SCE_PAD_BUTTON_R3 = 0x00000004 +SCE_PAD_BUTTON_OPTIONS = 0x00000008 // Options / Start / Menu +SCE_PAD_BUTTON_UP = 0x00000010 +SCE_PAD_BUTTON_RIGHT = 0x00000020 +SCE_PAD_BUTTON_DOWN = 0x00000040 +SCE_PAD_BUTTON_LEFT = 0x00000080 +SCE_PAD_BUTTON_L2 = 0x00000100 +SCE_PAD_BUTTON_R2 = 0x00000200 +SCE_PAD_BUTTON_L1 = 0x00000400 +SCE_PAD_BUTTON_R1 = 0x00000800 +SCE_PAD_BUTTON_TRIANGLE = 0x00001000 +SCE_PAD_BUTTON_CIRCLE = 0x00002000 +SCE_PAD_BUTTON_CROSS = 0x00004000 +SCE_PAD_BUTTON_SQUARE = 0x00008000 +SCE_PAD_BUTTON_CREATE = 0x00010000 // Share / Back / View +SCE_PAD_BUTTON_PS = 0x00010000 // PS button / Guide / Home +SCE_PAD_BUTTON_TOUCH_PAD = 0x00100000 +``` + +`ScePadData` stick fields: `uint8_t x, y` — range 0–255, center = 127–128. +`ScePadData` trigger fields: `uint8_t l2, r2` — range 0–255. + +--- + +## Checklist for a New Controller + +- [ ] VID:PID added to constants and `ugen_find_target()` +- [ ] Controller type notification added +- [ ] Init sequence determined (native / Xbox / custom handshake) +- [ ] IN endpoint confirmed working (`max_packet_length` logged) +- [ ] Raw bytes logged per button press — report format mapped +- [ ] Parser function written and tested +- [ ] Read loop handles new `pid` and `rid` correctly +- [ ] Reconnect path tested (unplug and replug while payload running) diff --git a/shellui_pad.c b/shellui_pad.c new file mode 100644 index 0000000..54cec19 --- /dev/null +++ b/shellui_pad.c @@ -0,0 +1,3932 @@ +/* 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef __PROSPERO__ +#include +#include +#include +#include +#endif + +#ifdef __ORBIS__ +#include +#include +#include +#include +#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)®s, 0)) return -1; + memcpy(&saved, ®s, 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)®s, 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)®s, 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)®s, 0)) return -1; + memcpy(&saved, ®s, 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)®s, 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)®s, 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(¶m, 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)®s, 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; +} + diff --git a/shellui_pad.h b/shellui_pad.h new file mode 100644 index 0000000..4ca1794 --- /dev/null +++ b/shellui_pad.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later + * shellui_pad.h — System process pad injection interface + * + * Injects through the confirmed SceShellCore/SceShellUI path: SceShellCore + * creates the virtual controller, and SceShellUI performs Mbus binding plus + * VDI diagnostics. + * + * Key safety rule: PT_ATTACH is attempted FIRST. Nothing is written to the + * target process until attach succeeds, preventing the PS5 freeze caused by + * leaving an INT3 in live code when attach fails. + */ +#pragma once +#include + +/* Persistent status log exported by main.c. It mirrors critical logs to + * /data/ghostpad/ghostpad_status.log so diagnostics survive when /dev/klog + * or the TCP klog bridge are unavailable. */ +void ghostpad_status_log(const char *fmt, ...); +void ghostpad_status_log_reset(void); + +/* Maximum size of ScePadData we forward to the stub (padded for alignment) */ +#define SHELLUI_PAD_DATA_SIZE 256 + +/* Shared state between our process and the stub running in SceShellUI. + * We write pad_data + increment seq; the stub polls seq and calls InsertData. */ +typedef struct { + /* ── function pointers resolved in the target process's address space ── */ + int32_t (*fp_gethandle)(int32_t userId, int32_t type, int32_t index); + int32_t (*fp_gethandle_ext)(int32_t userId, int32_t type, int32_t index, uint64_t a4, uint64_t a5, uint64_t a6); + int32_t (*fp_open)(int32_t userId, int32_t type, int32_t index, void *param); + int32_t (*fp_open_ext)(int32_t userId, int32_t type, int32_t index, void *param, uint64_t a5, uint64_t a6); + int32_t (*fp_open_ext2)(int32_t userId, int32_t type, int32_t index, void *param, uint64_t a5, uint64_t a6); + int32_t (*fp_insert)(int32_t handle, const void *data); /* unused legacy slot */ + int32_t (*fp_vdi)(int32_t handle, const void *data); /* scePadVirtualDeviceInsertData */ + int32_t (*fp_vda)(void *param, int32_t type); /* scePadVirtualDeviceAddDevice */ + int32_t (*fp_del)(int32_t handle); /* scePadVirtualDeviceDeleteDevice */ + int32_t (*fp_setpriv)(int32_t privilege); /* scePadSetProcessPrivilege */ + int32_t (*fp_setloginuser)(int32_t loginUserNumber); /* scePadSetLoginUserNumber */ + int32_t (*fp_setusernumber)(int32_t userNumber); /* scePadSetUserNumber */ + int32_t (*fp_setfocus)(int32_t focus, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6); /* scePadSetProcessFocus */ + void (*fp_usleep)(unsigned int usec); + + /* ── parameters ── */ + int32_t userId; + int32_t virtual_device_type; + + /* ── shared pad state (written by our process, read by stub) ── */ + volatile uint32_t seq; /* increment to trigger injection */ + uint8_t pad_data[SHELLUI_PAD_DATA_SIZE]; /* ScePadData bytes */ + + /* ── stub status (written by stub, read by our process) ── */ + volatile int32_t pad_handle; /* set by stub after scePadGetHandle */ + volatile int32_t ready; /* 1=running, -1=error */ + volatile int32_t stop; /* set to 1 to ask stub to exit */ + + /* ── diagnostic: first-iteration return codes from each probe ── */ + volatile int32_t rc_log[16]; +} ShellUiPadArgs; + + +/* Inject the pad stub into SceShellUI. + * + * On success returns 0 and sets: + * *out_shellui_pid — PID of SceShellUI + * *out_args_kaddr — address of ShellUiPadArgs inside SceShellUI's VA space + * (use mdbg_copyin to update pad_data + seq each frame) + * + * Returns -1 on failure (see klog for details). + */ +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); + +/* Update pad data inside SceShellUI's stub (call at up to 60 Hz). + * pad_data must point to a ScePadData struct (at most SHELLUI_PAD_DATA_SIZE bytes). + * This uses mdbg_copyin — no ptrace attach needed. + */ +int shellui_pad_update(pid_t shellui_pid, intptr_t args_kaddr, + const void *pad_data, uint32_t pad_data_len); + +int shellui_pad_direct_usable(pid_t shellui_pid, intptr_t args_kaddr); +int shellui_pad_direct_mode(pid_t shellui_pid, intptr_t args_kaddr); +int shellui_pad_direct_adopt_vdi_handle(pid_t shellui_pid, intptr_t args_kaddr, + int32_t vdi_handle); +int shellui_pad_direct_recover(pid_t shellui_pid, intptr_t args_kaddr, int32_t userId, int32_t altUserId); +void shellui_pad_direct_get_last_status(int32_t *stage, int64_t *value); +/* Re-launch stub thread with pre-set VDI handle — one-time pt_call, then + * use shellui_pad_update() (mdbg_copyin) for all packets. No per-packet + * ptrace freeze. Requires ptrace authid set permanently in caller. */ +int shellui_pad_relaunch_stub_with_handle(int32_t handle); +int shellui_pad_direct_begin(pid_t shellui_pid, intptr_t args_kaddr); +int shellui_pad_direct_send(pid_t shellui_pid, intptr_t args_kaddr, + const void *pad_data, uint32_t pad_data_len); +void shellui_pad_direct_end(pid_t shellui_pid, intptr_t args_kaddr); + +/* PT_ATTACH SceShellUI, find virtual device handle via pt_call GetHandle, + * then send Cross × 30 frames + release × 18 frames via pt_call VDI to + * dismiss the assignment screen. Call this ~3s after SceShellCore VDA + * injection so the assignment screen has time to render. */ +/* virtualDeviceId: pass the MBUS deviceId (from DEVICE_ADDED klog) so + * dismiss can try GetHandle(deviceId, type, idx) in addition to userId-based + * lookups. SceShellUI's "Open Pad [deviceId,0,0]" suggests GetHandle accepts + * deviceId as first arg when it is small (deviceId << userId range). */ +int shellui_pad_dismiss_assignment_screen(int32_t userId, uint64_t virtualDeviceId); + +/* PT_ATTACH SceShellUI, resolve sceMbusBindDeviceWithUserId from libSceMbus, + * and call it to directly assign virtualDeviceId to userId without going + * through the assignment screen UI. Returns 0 on success. */ +int shellui_pad_force_bind(uint64_t virtualDeviceId, int32_t userId); + +/* PT_ATTACH SceShellUI, call sceMbusDisconnectDevice(physicalDeviceId) to evict the + * current physical controller from the user's slot. After eviction the virtual device + * becomes the sole device at slot 0. Returns 0 on success. */ +int shellui_pad_disconnect_device(uint64_t physicalDeviceId); + +/* PT_ATTACH SceShellUI and call scePadVirtualDeviceInsertData(pad_handle, cross_data) + * directly, bypassing scePadGetHandle. padHandle comes from the CIM log. */ +int shellui_pad_test_vdi_cross(int32_t pad_handle); +int shellcore_pad_test_vdi_cross(int32_t pad_handle); +int shellcore_pad_test_vdi_neutral(int32_t pad_handle); /* buttons=0, no UI input */ + +/* PT_ATTACH SceShellCore and call VDA(userId, type=3) via pt_call after + * assignment. Returns VDA return value; if >= 0 it is the VDI write handle. */ +int32_t shellui_pad_retry_vda_shellcore(int32_t userId); + +/* Manifest-verified SceShellCore VDA patch. + * + * PS4 firmware fingerprint from vda_probe_report: + * libScePad:scePadVirtualDeviceAddDevice hash256=0xbb22d8acd843d81e + * hash4k=0x346f2b8071895f89, VDA offset +0x5b40 + * + * The patch only applies if prologue/hash/callsite/cave all match. It detours + * the dispatcher call at VDA+0xc0 into the verified NOP cave at VDA+0xdd2, + * calls the original dispatcher, forces eax=0, and returns to the original + * canary/epilogue path. Early validation errors are left intact. + * Returns 1 when applicable/applied/already applied, 0 on safe non-match. */ +int shellui_pad_patch_vda(int dump_only); +int shellui_pad_patch_vda_self(int dump_only); +int shellui_pad_hook_setpriv(void); +int shellui_pad_unpatch(void); +