2026-05-23.12-22-28.mp4
Remote-control a jailbroken PS4 or PS5 from any device: PC app, web browser, or real BLE controller — via an ESP32-P4 bridge that forwards HID input over TCP to a virtual DualSense running on the console.
Ghostpad is a payload + bridge + GUI system that creates a virtual DualSense on a jailbroken PS4/PS5 and forwards input over LAN. The shell and games see it as a real controller.
TCP 6967 (GPAD input)
┌──────────┐ ──────────────────────────────────► ┌─────────────────┐
│ PC GUI │ │ PS4/PS5 │
│ (Python) │ ◄─────── klog TCP 3434 ──────────── │ ghostpad.elf │
└──────────┘ └─────────────────┘
│
┌──────────────┐ scePadVirtualDevice
│ Browser │──WiFi──┐ InsertData (VDI)
│ (Web UI) │ │ │
└──────────────┘ │ virtual DualSense
▼ @ slot 0
┌──────────────┐ ┌─────────────────────┐
│ BLE Gamepad │──│ ESP32-P4 Bridge │──USB HID──► PS5
│ (DualSense) │ │ (this repo) │
└──────────────┘ └─────────────────────┘
| Component | Directory | Description |
|---|---|---|
| PS4/PS5 Payload | payload/ |
ELF that runs on jailbroken console, creates virtual DualSense via VDA, listens on TCP 6967 |
| ESP32 Bridge | esp32-ghostpad/ |
ESP32-P4 firmware: WiFi, web UI, BLE HID host, USB HID, subnet scan, klog bridge |
| VDA Probe | tools/vda_probe/ |
Diagnostic tool to fingerprint libScePad VDA byte-pattern and enumerate MBus symbols |
| PC GUI | root | Python/Qt desktop app for keyboard/mouse/gamepad input (Windows/Linux) |
The PS4/PS5 scePadVirtualDeviceAddDevice (VDA) normally requires a human user to confirm the virtual device through the shell UI. Ghostpad bypasses this with a code-cave patch of libScePad.sprx inside SceShellCore:
- Find VDA's IPC dispatch call, redirect through a cave in the function's tail-padding
- The cave calls the original dispatcher, pops the leftover return address, returns 0
- The device is now real; the assignment screen is dismissed via MBus bind:
sceMbusDisconnectDevice(physical) → sceMbusBindDeviceWithUserId(virtual, user) - Every 16-byte GPAD packet received over TCP is forwarded into
scePadVirtualDeviceInsertData
Full technical write-up: virtualDS5research.md.
cd payload
# PS4
make PS4_PAYLOAD_SDK=/path/to/ps4-payload-sdk
# PS5
make PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdkDeploy ghostpad.elf to the console (port 9021). Build flags:
| Flag | Default | Purpose |
|---|---|---|
GHOSTPAD_ENABLE_INTERNAL_KLOG |
1 | In-payload /dev/klog reader + TCP bridge |
GHOSTPAD_KLOG_PORT |
3434 | TCP port for klog streaming |
GHOSTPAD_ENABLE_RUNTIME_VDA_PATCH |
1 | Patch libScePad VDA in self and SceShellCore |
GHOSTPAD_ENABLE_KLOG_AUTOBIND |
0 | Auto-bind virtual device from klog DEVICE_ADDED events |
cd esp32-ghostpad
# Copy and fill in your WiFi credentials
cp sdkconfig.private.example sdkconfig.private.defaults
# Edit sdkconfig.private.defaults with your SSID and password
source ~/esp/esp-idf/export.sh
idf.py set-target esp32p4
idf.py build
idf.py -p /dev/cu.usbmodemXXXX flashThe ESP32 connects to your WiFi and serves a web UI at http://ghostpad.local (mDNS) or via its IP.
- Device Browser — auto-discovers PS4/PS5 on the subnet, shows open ports (6967 GPAD, 3434 Klog, 9021 ELF loader)
- Virtual Controller — on-screen gamepad with keyboard bindings and touch support
- Klog Viewer — live streaming of console kernel log over WebSocket
- BLE Scanner — discover nearby DualSense/DS4 controllers with classification (model, score, appearance, HID flags)
- Settings — WiFi config, payload deploy, firmware info
- Scans for DualSense (0x054C:0x0CE6), DualShock 4 (0x054C:0x09CC/0x05C4), Xbox, Nintendo, 8BitDo controllers
- 30-second active scan window with classification scoring
- Decodes DualSense report 0x01 and DualShock 4 report 0x11
- Stores backlog of klog data for late-connecting dashboard clients
| Mode | Input Source | Output | Use Case |
|---|---|---|---|
| WiFi → PS4/PS5 | Web UI / Browser | TCP 6967 to console | Remote play over LAN |
| BLE → PS4/PS5 | DualSense/DS4 via BLE | TCP 6967 to console | Wireless controller relay |
| USB HID → PS5 | Web UI | TinyUSB HID to PS5 USB | Direct wired control |
- WiFi STA (connect to existing network) with mDNS
- WiFi AP fallback (
Ghostpad-ESP32/ghostpad123) - Subnet scanner with port probing (6967, 3434, 9021, 9090)
- Klog TCP bridge (port 3434) with multi-client broadcast
- Private WiFi credentials via
sdkconfig.private.defaults(gitignored)
Fixed 16-byte packet:
| Offset | Type | Field |
|---|---|---|
| 0..3 | char[4] | "GPAD" magic |
| 4..7 | uint32 BE | Button bitmask |
| 8 | uint8 | Left stick X (0..255, 128 = center) |
| 9 | uint8 | Left stick Y |
| 10 | uint8 | Right stick X |
| 11 | uint8 | Right stick Y |
| 12 | uint8 | L2 analog (0..255) |
| 13 | uint8 | R2 analog |
| 14..15 | uint16 | Reserved |
| Magic | Payload | Effect |
|---|---|---|
GBND |
uint32 userId + uint64 virtualDeviceId + uint64 physicalDeviceId | MBus disconnect + bind |
HVDI |
uint32 padHandle | Diagnostic VDI Cross press |
TYPE |
uint32 type (0=DS4, 3=DualSense) | Reconfigure VDA type |
DISC |
(empty) | Disconnect virtual device |
Browser sends JSON frames at ~30 Hz:
{"buttons": 0, "lx": 128, "ly": 128, "rx": 128, "ry": 128, "l2": 0, "r2": 0,
"target_ip": "192.168.1.x", "seq": 1, "ts": 1717430000000}ESP32 responds with status ACKs (every 30th frame):
{"event": "state", "connected": true, "tx": 1500, "err": 0}| Endpoint | Method | Description |
|---|---|---|
/api/status |
GET | System status (WiFi, controller, scan results) |
/api/scan |
GET | Trigger subnet scan for consoles |
/api/ble/scan |
GET | BLE scan for controllers |
/api/ble/status |
GET | BLE connection state |
/api/ble/connect |
POST | Connect to BLE controller {"addr":"xx:xx:xx:xx:xx:xx"} |
/api/ble/disconnect |
POST | Disconnect BLE controller |
/api/controller/pulse |
GET | Diagnostic Cross press test ?ip=192.168.1.x |
/api/settings/deploy |
POST | Deploy ELF payload to console |
/ws |
WS | WebSocket for controller streaming |
/ws/klog |
WS | WebSocket for klog streaming |
Diagnostic payload for reverse-engineering the VDA patch on PS4/PS5:
- Enumerates libScePad exports to find the VDA dispatch function
- Sweeps MBus symbols (20 functions) to map the binding API
- Reports instruction bytes for pattern-matching in
shellui_pad.c
cd tools/vda_probe
make PS4_PAYLOAD_SDK=/path/to/sdk
# Deploy vda_probe.elf, check klog for results| Key | Button | Key | Button |
|---|---|---|---|
| W A S D | D-pad | I J K L | Triangle / Square / Cross / Circle |
| U / O | L1 / R1 | Q / E | L2 / R2 |
| Enter | Options | Space | PS |
| BackSpace | Share/Create | T | Touchpad |
Requirements:
- ps4-payload-sdk with LLVM toolchain
LLVM_CONFIGpointing to a compatiblellvm-config
cd payload
LLVM_CONFIG=/opt/homebrew/opt/llvm/bin/llvm-config \
PS4_PAYLOAD_SDK=../external/ps4-payload-sdk \
makeOutput: payload/ghostpad.elf
cd payload
PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk makeRequirements:
- ESP-IDF v5.3+
- Waveshare ESP32-P4-WIFI6 (with ESP32-C6 co-processor)
cd esp32-ghostpad
cp sdkconfig.private.example sdkconfig.private.defaults
# edit sdkconfig.private.defaults
source ~/esp/esp-idf/export.sh
idf.py set-target esp32p4
idf.py build
idf.py -p /dev/cu.usbmodemXXXX flashpayload/
├── main.c # PS4/PS5 payload: VDA, klog, TCP server, GBND
├── shellui_pad.c/h # SceShellCore injection, VDA patching, MBus helpers
└── Makefile
esp32-ghostpad/
├── main/
│ ├── main.c # ESP32 entry point: WiFi, web server, USB HID
│ ├── wifi_ap.c/h # WiFi STA/AP with mDNS
│ ├── web_server.c/h # HTTP server, WebSocket, API endpoints
│ ├── web_gui.h # Embedded HTML/JS controller UI
│ ├── ble_hid_host.c/h # BLE HID host: scan, connect, decode reports
│ ├── hid_gamepad.c/h # TinyUSB DualSense HID emulation
│ ├── klog_reader.c/h # /dev/klog TCP reader (legacy, for PS5 payload)
│ └── CMakeLists.txt
├── sdkconfig.defaults # Public ESP-IDF config
├── sdkconfig.private.example # Template for private WiFi credentials
├── sdkconfig.private.defaults # Your WiFi credentials (gitignored)
└── CMakeLists.txt
tools/
└── vda_probe/
└── vda_probe.c # VDA pattern / MBus symbol diagnostic
git-patch/ # Patches applied during development
- Firmware-specific byte patterns. The VDA patch scans for specific instruction sequences in libScePad. Other firmware builds need the scan updated via
vda_probe. - Single virtual device. One virtual controller at a time.
- No haptics / rumble feedback from PS4/PS5 → client.
- BLE-only on ESP32. The ESP32-C6 co-processor is BLE-only; no Bluetooth Classic (BR/EDR).
- DualSense BLE pairing requires manual PS+Create button combo and has a narrow ~30s advertising window.
The Ghostpad-app (Ghostpad-app/) is an Electron + React desktop application for Windows that provides the most complete control experience:
- Input Redirection — on-screen virtual controller, XInput gamepad passthrough, keyboard bindings, mouse look
- Macro Recording — record and replay sequences of button presses, stick movements, and trigger pressure
- Console Manager — add, edit, and auto-scan PS4/PS5 consoles on LAN
- System State — reboot, shutdown, rest mode, and disc eject commands
- Beeper & LED — control PS5 beeper/LED (requires
beeper_server.elf) - Auto-Deploy — automatically deploy the payload ELF when connecting
- Start the ELF loader on your PS4/PS5 (port 9021)
- Open Ghostpad-app
- Go to Settings → add your console IP
- Click Deploy Payload (sends
ghostpad.elf) - Click Connect
cd Ghostpad-app
npm install
npm run build:react
npm run start # development
npm run compile # package as Windows .exe| Key | Button | Key | Button |
|---|---|---|---|
| W A S D | D-pad | I J K L | Triangle / Square / Cross / Circle |
| U / O | L1 / R1 | Q / E | L2 / R2 |
| Enter | Options | Backspace | Share/Create |
| Space | PS button | T | Touchpad |
- Ghostpad-app (
Ghostpad-app/): Electron + React desktop GUI for Windows with virtual controller, XInput passthrough, keyboard/mouse input, macro recording/playback, console manager, auto-deploy, system state controls, beeper/LED, and video capture overlay - Original PS5 payload: VDA research,
scePadVirtualDeviceAddDevicecode-cave patch,sceMbusBindDeviceWithUserId/sceMbusDisconnectDeviceMBus binding flow, credential elevation, process injection into SceShellCore - hidDumper companion tool for reverse-engineering unknown controllers
virtualDS5research.mdtechnical write-up
- ESP32-P4 bridge firmware (
esp32-ghostpad/): WiFi STA/AP with mDNS, embedded web UI, BLE HID host (NimBLE scan, connect, DualSense/DS4 report decoding), USB HID (TinyUSB), subnet scanner with port probing, klog TCP bridge with multi-client broadcast and backlog replay, WebSocket controller streaming, HTTP REST API, controller pulse diagnostics - PS4 payload port (
__ORBIS__target): SceShellCore injection (shellui_pad.c), runtime VDA patching with byte-pattern verification, libSceMbus dlopen/dlsym, VDA type handling (DualSense default, skip duplicate), PS4-specific authid/credential flow - Payload-side klog architecture: always-on capture thread with client pool broadcasting, backlog ring buffer, thread-safe VDA candidate tracking with sequence numbers, GBND prebind loop,
ghostpad_try_klog_candidate_binddirect MBus bind path - VDA probe (
tools/vda_probe/): diagnostic tool that fingerprints libScePad VDA byte-patterns and sweeps 20 MBus symbols - klog parser improvements: multi-key DeviceId extraction,
GetUnassignedDeviceInfodetection, VDA type:4/subType:2 RemotePlay recognition - Native CPP GUI
ps5-payload-sdk/ps4-payload-sdk(John Törnblom et al.) — toolchain, kernel API helpers- ESP-IDF — ESP32-P4 framework
- NimBLE — BLE host stack
- TinyUSB — USB HID device stack
- cJSON — JSON parsing/generation
- The PS4/PS5 dev community
Payload C sources: GPL-3.0-or-later. ESP32 firmware: GPL-3.0-or-later. PC GUI scripts: MIT.