1 Commits
Author SHA1 Message Date
Sub f29a1de877 padbridge 1.2.0: pad2c renamed, README rewritten (#5)
* padbridge 1.2.0: pad2c renamed, README rewritten

pad2c is no longer about one controller: it reads the Ultimate 2C dongle,
the 8BitDo USB Wireless Adapter 2 with whatever is paired with it, and
wired XInput pads. Hence padbridge.

- padbridge-<version>.elf, padbridge-probe.elf; notifications and the
  settings page read "padbridge by Sub"; files in /data/padbridge.
- From pad2c: on the first start a running pad2c is asked to stop (its
  own stop flag), and /data/pad2c moves to /data/padbridge with the
  remembered users and settings; the log is renamed.
- The log names, once per device, every USB device plugged in that
  padbridge does not read (a controller in another mode: the GameSir G8
  on its cable in Switch mode is 057e:2009 "Pro Controller").
- README rewritten: compatibility tables (adapter, dongle, cable, ports),
  the settings page with a screenshot, the adapter's modes, upgrading
  from pad2c. The old 1.0 photos are gone.
- dist/ ignored as a whole (two .sha256 files had slipped in).

Tested on the PS5: the move from a running pad2c 1.1.0; the Ultimate 2C
on a USB cable (2dc8:310a) in the USB-A and USB-C ports; the G8 on its
cable in Switch mode logged once as not read.

* CHANGELOG: the log of devices not read; the 2C on a cable

* README: no mention of the G8 on a cable

* README: no section on cables and ports
2026-10-22 18:25:37 -03:00
27 changed files with 287 additions and 217 deletions

No files matched your search

+4 -4
View File
@@ -1,6 +1,6 @@
---
name: Controller report
about: Tried pad2c with a controller (working or not), or want support for a new one
about: Tried padbridge with a controller (working or not), or want support for a new one
labels: controller
---
@@ -10,13 +10,13 @@ labels: controller
**What happened** (works / partly / not detected):
**pad2c log** (`/data/pad2c/pad2c.log`, the lines from the last start):
**padbridge log** (`/data/padbridge/padbridge.log`, the lines from the last start):
```
```
**For a controller that is not supported yet:** send `pad2c-probe.elf` with the controller plugged in, press every
button one at a time while it runs, and paste `/data/pad2c/probe.log` here.
**For a controller that is not supported yet:** send `padbridge-probe.elf` with the controller plugged in, press every
button one at a time while it runs, and paste `/data/padbridge/probe.log` here.
```
```
+1 -2
View File
@@ -1,4 +1,3 @@
build/
dist/*.elf
!dist/.keep
dist/
release/
+10
View File
@@ -1,5 +1,15 @@
# Changelog
## 1.2.0
- **pad2c is now padbridge**: it is no longer about one controller. The payloads are `padbridge-<version>.elf` and
`padbridge-probe.elf`, notifications read "padbridge by Sub", and its files live in `/data/padbridge`. On its first
start it asks a running pad2c to stop and moves `/data/pad2c` there, remembered users and settings included.
- The log names, once per device, every USB device plugged in that padbridge does not read: a controller in another
mode shows up there (the GameSir G8 on its cable in Switch mode is `057e:2009` "Pro Controller").
- Tested: the Ultimate 2C on a USB cable (XInput, `2dc8:310a`), in the USB-A and USB-C ports.
- README rewritten around what is supported and tested: the Ultimate 2C dongle, the USB Wireless Adapter 2 and the
controllers tested through it, controllers on a USB cable, and the settings page (with a screenshot).
## 1.1.0
- The **8BitDo USB Wireless Adapter 2** (#1): any controller it pairs with (tested: DualSense, Ultimate 2C, GameSir G8
Plus Galileo) becomes a virtual DualSense. Plugged in, the adapter tries several identities and keeps the one whose
+5 -5
View File
@@ -1,11 +1,11 @@
# pad2c: the 8BitDo dongle as a second DualSense on a jailbroken PS5.
# padbridge: the 8BitDo dongle as a second DualSense on a jailbroken PS5.
#
# make test the mapping test, on this machine
# make the payloads into dist/ (needs PS5_PAYLOAD_SDK)
VERSION := $(shell sed -n 's/^\#define PAD2C_VERSION "\(.*\)"/\1/p' src/version.h)
VERSION := $(shell sed -n 's/^\#define PADBRIDGE_VERSION "\(.*\)"/\1/p' src/version.h)
all: dist/pad2c-$(VERSION).elf dist/pad2c-probe.elf
all: dist/padbridge-$(VERSION).elf dist/padbridge-probe.elf
test:
@mkdir -p build
@@ -27,11 +27,11 @@ LDLIBS := -lScePad -lSceUserService -ldl
SRCS := src/main.c src/usbpad.c src/usbdesc.c src/map8bitdo.c src/mapxinput.c src/config.c src/web.c src/ps5_vpad.c src/ps5_power.c src/lock.c src/log.c src/util.c
dist/pad2c-$(VERSION).elf: $(SRCS) $(wildcard src/*.h)
dist/padbridge-$(VERSION).elf: $(SRCS) $(wildcard src/*.h)
@mkdir -p dist
$(CC) $(CFLAGS) -o $@ $(SRCS) $(LDLIBS)
dist/pad2c-probe.elf: src/probe.c src/usbdesc.c src/usbdesc.h
dist/padbridge-probe.elf: src/probe.c src/usbdesc.c src/usbdesc.h
@mkdir -p dist
$(CC) $(CFLAGS) -o $@ src/probe.c src/usbdesc.c
+152 -139
View File
@@ -1,166 +1,179 @@
# pad2c
# padbridge
**Use an [8BitDo Ultimate 2C Wireless Controller](https://www.8bitdo.com/ultimate-2c-wireless-controller/), or
almost any Bluetooth controller through the [8BitDo USB Wireless Adapter 2](https://www.8bitdo.com/usb-wireless-adapter-2/),
on a jailbroken PS5, next to your DualSense.**
**Use controllers the PS5 does not accept, on a jailbroken PS5, as extra DualSense controllers.**
pad2c is a payload for a jailbroken PS5. It reads the 8BitDo 2.4G dongle or the 8BitDo USB Wireless Adapter 2
plugged into the console's USB port and shows each controller to the system and to games as a **virtual
DualSense**, up to four at once. The console's Bluetooth is not involved, so it never touches your DualSense's
pairing. A settings page, opened from a phone or a computer, shows the controllers live.
padbridge is a payload for a jailbroken PS5. It reads USB controllers and receivers plugged into the console and
shows each controller to the system and to games as a **virtual DualSense**, up to four at once, next to your own
DualSense:
## Screenshots
- the **8BitDo Ultimate 2C Wireless** through its 2.4G dongle;
- almost **any Bluetooth controller** (DualSense, DualShock 4, Xbox, Switch Pro, 8BitDo, GameSir...) through the
**8BitDo USB Wireless Adapter 2**;
- **wired XInput (Xbox 360) controllers** on a USB cable.
Switching the 8BitDo on and off, with pad2c running on the console:
The console's own Bluetooth is never touched, so your DualSense stays paired. A **settings page**, opened from a
phone or a computer, shows each controller live and lets you fix controllers with the Nintendo button layout.
Up to 1.1.0 the project was called **pad2c**; see [Upgrading from pad2c](#upgrading-from-pad2c).
<p align="center">
<img src="docs/images/connected.jpg" width="45%" alt="The PS5 shows 'pad2c: 8BitDo connected' and asks who is using the controller">
&nbsp;
<img src="docs/images/disconnected.jpg" width="45%" alt="The PS5 shows 'pad2c: 8BitDo disconnected'">
<img src="docs/images/settings-page.png" width="70%" alt="The padbridge settings page: the Ultimate 2C waiting on its dongle, and a controller connected through the USB Wireless Adapter 2 with the Nintendo layout on">
</p>
<p align="center"><em>Left: switched on; the console asks who is using the controller. Right: switched off.</em></p>
<p align="center"><em>The settings page: the Ultimate 2C on its dongle, and a GameSir G8 through the USB Wireless
Adapter 2 with the Nintendo layout on.</em></p>
## Which controller
## Compatibility
| Controller | Supported |
Everything below was tested on a **PS5 Slim (CFI-2114), firmware 13.60**.
### Through the 8BitDo USB Wireless Adapter 2
[8BitDo USB Wireless Adapter 2](https://www.8bitdo.com/usb-wireless-adapter-2/), firmware 1.10. Plug it in, pair a
controller with it, switch the controller on: nothing to set on the adapter.
| Controller | How it pairs with the adapter | Result |
|---|---|---|
| DualSense | its normal pairing (Create + PS) | ✅ all buttons, touchpad click (DInput mode) |
| 8BitDo Ultimate 2C Wireless | Switch mode (Home + Y) | ✅ all buttons |
| GameSir G8 Plus Galileo | Switch mode (its only mode the adapter accepts) | ✅ with **Nintendo layout** on (it sends A/B and X/Y swapped) |
| Anything else the adapter accepts (DualShock 4, Xbox One / Series, Switch Pro, Joy-Con, Wii U Pro, 8BitDo) | | expected to work, untested: please report |
### 8BitDo Ultimate 2C Wireless on its own dongle
| Connection | Result |
|---|---|
| [8BitDo Ultimate 2C **Wireless** Controller](https://www.8bitdo.com/ultimate-2c-wireless-controller/) (the one sold for PC / Android, with a 2.4G USB dongle), through its dongle | ✅ |
| 8BitDo Ultimate 2C **Bluetooth** Controller (the Switch edition, no dongle) | ❌ different product |
| The Ultimate 2C Wireless over Bluetooth, without the dongle | ❌ not yet |
| [8BitDo USB Wireless Adapter 2](https://www.8bitdo.com/usb-wireless-adapter-2/), with any controller it pairs with | ✅ tested with a DualSense, the Ultimate 2C (Switch mode) and a GameSir G8 Plus Galileo (Switch mode, with "Nintendo layout") |
| Wired XInput (Xbox 360) controllers on a USB cable | ✅ tested with a GameSir G8 Plus Galileo |
| Other 8BitDo 2.4G dongles | untested: run `pad2c-probe.elf` and open an issue with its output |
| 2.4G dongle, DInput mode (`2dc8:301c`) | ✅ all buttons, L4/R4 as the touchpad click |
| 2.4G dongle, XInput mode (`2dc8:310a`) | ✅ all buttons except L4/R4 (the controller does not send them in this mode) |
| USB cable (`2dc8:310a`, XInput) | ✅ all buttons except L4/R4 |
## Why this controller, and why the dongle
The 8BitDo Ultimate 2C **Bluetooth** Controller (the Switch edition, without a dongle) is a different product and is
not supported directly; it works through the USB Wireless Adapter 2.
**The controller** is simply the one I own: pad2c was made to play with it on my PS5, next to the DualSense. It is
also a cheap way to get a second controller: a DualSense costs at least twice as much as the Ultimate 2C Wireless,
which still has Hall effect sticks and triggers.
### Not yet
**The dongle instead of Bluetooth**, on purpose:
- **No conflicts.** The PS5 has one Bluetooth chip, and the system uses it for the DualSense. Sharing it from a
payload risks taking over or unpairing the DualSense, and the chip is laid out differently from one console model
to another. The dongle is a plain USB device: pad2c never touches the console's Bluetooth.
- **Simpler.** No pairing, no Bluetooth stack to keep alive, no reconnection logic: the dongle and the controller
already talk to each other, and pad2c only reads what the dongle sends.
- **Practical.** Plug the dongle into a USB port and switch the controller on. That is all.
## FAQ
**Can I use an 8BitDo controller on a PS5?** Not on a stock PS5: it only accepts its own and a few licensed
controllers. On a jailbroken PS5, pad2c makes the 8BitDo Ultimate 2C appear as a DualSense.
**Does it work in games, or only in the menus?** In games too. Give the controller its own console user (the console
asks when you press Home) and it is player 2, like a second DualSense.
**Do I lose my DualSense?** No. Both work at the same time. pad2c does not use the console's Bluetooth at all.
**Can I use my DualSense, an Xbox or a Switch Pro controller through the adapter?** Yes: the USB Wireless Adapter 2
pairs with them over its own Bluetooth, and pad2c reads the adapter. Pairing a DualSense with the adapter unpairs it
from the console; plug it into the console with its cable and press PS to pair it back.
**A/B and X/Y are swapped on my controller.** Some controllers in Switch mode send the Nintendo layout (A on the
right). Tick **Nintendo layout** for it on the settings page.
**Which firmware?** Tested on 13.60 (PS5 Slim). It needs a console that already runs a payload loader such as elfldr
or Payload Manager; it does not jailbreak anything itself.
## Status
| | |
|---|---|
| Tested on | PS5 Slim (CFI-2114), firmware **13.60**, elfldr 0.26, kstuff-lite 1.11, Payload Manager 0.5.2 |
| Controllers | 8BitDo Ultimate 2C Wireless, 2.4G dongle, DInput (`2dc8:301c`) and XInput (`2dc8:310a`) modes; 8BitDo USB Wireless Adapter 2, firmware 1.10, XInput (`2dc8:3106`) and DInput (`2dc8:3105`) modes |
| Works | all buttons, sticks, analog triggers, d-pad, Home = PS, console menus and games, sleep and wake, the console's "who is using this controller" screen, remembers its user, "turn off" from the console's menu, up to four controllers at once |
| Not yet | vibration, light bar, motion sensors, audio |
Other firmwares and console models are untested.
The controller has two modes on its dongle, both work. It keeps the one last chosen:
- **DInput** (the default): with the controller on, hold **B + Home** until it switches off, then **B + Home** again
until it switches on.
- **XInput** (seen by a PC as an Xbox controller): the same with **X + Home**. L4/R4 send nothing in this mode.
### The 8BitDo USB Wireless Adapter 2
Plug it in and switch the controller on: nothing to set. The adapter picks its mode from the host it sees; pad2c
answers it as Windows does, so it settles in **XInput** by itself, and pad2c hides the keyboard and mouse the adapter
adds in that mode, so the console lists only the controller.
**DInput** works too and also passes a DualSense's touchpad click: with the controller connected, hold **View
(Create on a DualSense) + Left** for 3 seconds (the adapter's light blinks twice). To let the adapter choose by itself
again, hold its button until its light goes off, then pair the controller again. Its DualShock 4 mode does not work
(it stops sending after a moment); pad2c's answer keeps it from ending there.
Notes:
- The 8BitDo has no touchpad: its extra **L4 / R4** buttons press the touchpad.
- "Turn off" in the console's menu disconnects the controller from the console, as with a DualSense, and Home
connects it again. The 8BitDo itself stays on until it goes to sleep by itself (the dongle's commands to switch
it off are not known).
- Vibration: PS5 games send DualSense haptics as audio to the controller, and a virtual DualSense has no known way
to receive them yet.
## Button map
| 8BitDo | DualSense |
|---|---|
| A / B / X / Y | ✕ / ○ / □ / △ (by position) |
| LB / RB / LT / RT | L1 / R1 / L2 / R2 (analog) |
| View / Menu | Create / Options |
| Home | PS |
| L3 / R3 | L3 / R3 |
| L4 / R4 | touchpad click |
Vibration, light bar, motion sensors, audio. PS5 games send DualSense haptics as audio to the controller, and a
virtual DualSense has no known way to receive them yet.
## Install
1. Plug the dongle (or the USB Wireless Adapter 2) into a USB port of the PS5 and switch the controller on.
2. Send `pad2c-<version>.elf` to your payload loader (elfldr listens on port 9021), or add it to Payload Manager
and to its autoload list, so that it starts with the console.
3. A notification says it is running, with the address of the settings page. Press **Home** on the controller: the
console asks who is using it.
For a second player, pick a **second user** (a game takes one controller per user). pad2c remembers the choice
and gives the controller back to that user next time, as a DualSense does.
1. Plug the dongle, the adapter or the controller's cable into a USB port of the PS5.
2. Send `padbridge-<version>.elf` to your payload loader (elfldr listens on port 9021), or add it to Payload
Manager and to its autoload list, so that it starts with the console.
3. A notification says it is running, with the address of the settings page.
4. Switch the controller on and press **Home** (or PS): the console asks who is using it. For a second player, pick a
**second user** (a game takes one controller per user). padbridge remembers the choice per controller and gives
it back to that user next time, as a DualSense does.
Files, all in `/data/pad2c/`:
| File | |
|---|---|
| `pad2c.log` | the log |
| `stop` | create it to stop pad2c (it also stops by itself before rest mode) |
| `owner`, `owner.1`... | the user the controller in each slot was last given to (delete to forget) |
| `config` | the settings page's choices (`swap <controller name>` for Nintendo layout) |
| `bind_user` | optional: `main`, `other` or a hexadecimal user id, to give the controller to that user at once |
It needs a console that already runs a payload loader; padbridge does not jailbreak anything itself.
## Settings page
Open **http://&lt;console IP&gt;:8090** in a browser on the same network (the address is in the notification when
pad2c starts). For each controller: its name, mode, slot and user, the **Nintendo layout** switch (A/B and X/Y
swapped, applied at once and kept), and its buttons, sticks and triggers live, to check a mapping. The page is open
to the local network, without a password.
Open **http://&lt;console IP&gt;:8090** in a browser on the same network (phone, computer, the console's own); the
address is in the notification when padbridge starts. For each controller:
- its name, mode (DInput or XInput), slot and the user it is given to;
- **Nintendo layout**: A/B and X/Y swapped, applied at once and kept, for controllers that send the Nintendo layout
(A on the right), such as the GameSir G8 in Switch mode;
- its buttons, sticks and triggers, live, to check a mapping.
The page is open to the local network, without a password. Through the adapter, a setting belongs to the adapter:
it holds for whatever controller is paired with it.
## The 8BitDo USB Wireless Adapter 2
The adapter shows a different USB identity for each mode and picks its mode from the host it sees. padbridge answers
it as Windows does, so it settles in **XInput** by itself, and hides the keyboard and mouse the adapter adds in that
mode, so the console's Accessories list shows only the controller.
| Mode | How | Notes |
|---|---|---|
| XInput (`2dc8:3106`) | automatic | everything but a DualSense's touchpad |
| DInput (`2dc8:3105`) | hold **View + Left** (Create + Left on a DualSense) for 3 s; the light blinks twice | passes a DualSense's touchpad click |
| back to automatic | hold the adapter's button until its light goes off, then pair the controller again | |
| DualShock 4 (`054c:05c4`) | where it ends on a PS5 without padbridge's answer | does not work: it stops sending after a moment |
Pairing a DualSense with the adapter unpairs it from the console: plug it into the console with its cable and press
PS to pair it back. The adapter takes one controller at a time.
## The Ultimate 2C's two dongle modes
The controller keeps the last mode chosen. With the controller on:
- **DInput** (the default): hold **B + Home** until it switches off, then **B + Home** again until it switches on.
- **XInput**: the same with **X + Home**.
## Button map
| Controller | DualSense |
|---|---|
| A / B / X / Y (Xbox layout) | ✕ / ○ / □ / △ (by position) |
| LB / RB / LT / RT | L1 / R1 / L2 / R2 (analog) |
| View / Menu (Back / Start, - / +) | Create / Options |
| Home / Guide | PS |
| L3 / R3 | L3 / R3 |
| Ultimate 2C L4 / R4 (DInput), DualSense touchpad (adapter, DInput) | touchpad click |
## Notes
- "Turn off" in the console's menu disconnects the controller from the console, as with a DualSense, and Home
connects it again. The controller itself stays on until it goes to sleep by itself.
- Every notification reads "padbridge by Sub", then the controller, its mode and what happened.
## Files
All in `/data/padbridge/` on the console:
| File | |
|---|---|
| `padbridge.log` | the log |
| `stop` | create it to stop padbridge (it also stops by itself before rest mode) |
| `owner`, `owner.1`... | the user the controller in each slot was last given to (delete to forget) |
| `config` | the settings page's choices (`swap <controller name>` for Nintendo layout) |
| `bind_user` | optional: `main`, `other` or a hexadecimal user id, to give controllers to that user at once |
## Upgrading from pad2c
padbridge 1.2.0 is pad2c renamed. On its first start it asks a running pad2c to stop and moves `/data/pad2c` to
`/data/padbridge`, remembered users and settings included. Replace `pad2c-<version>.elf` with
`padbridge-<version>.elf` in Payload Manager's autoload list.
## FAQ
**Can I use a non-Sony controller on a PS5?** Not on a stock PS5: it only accepts its own and a few licensed
controllers. On a jailbroken PS5, padbridge makes them appear as DualSense controllers.
**Does it work in games, or only in the menus?** In games too. Give each controller its own console user (the console
asks when you press Home) and it is player 2, 3 or 4, like another DualSense.
**Do I lose my DualSense?** No. Both work at the same time; padbridge does not use the console's Bluetooth. (Pairing
the DualSense itself with the USB Wireless Adapter 2 is your choice; see above.)
**A/B and X/Y are swapped on my controller.** Tick **Nintendo layout** for it on the settings page.
**My controller does nothing.** `/data/padbridge/padbridge.log` lists every USB device plugged in that padbridge does
not read, with its id and name: a controller in another mode (Switch, DS4) shows up there.
**My controller is not listed.** Run `padbridge-probe.elf` with it plugged in and open an issue with
`/data/padbridge/probe.log` (the issue template says what to include).
## How it works
- `src/usbpad.c` opens each pad's `/dev/ugenB.A` node and reads its interrupt IN endpoint, up to four pads, each its
own virtual DualSense. The console's own HID driver holds the interface but ignores the device. New pads are
looked for every 100 ms. The internal Bluetooth chip (`ugen0.2`) is never touched.
- The USB Wireless Adapter 2 shows several identities when plugged in (8BitDo XInput, Xbox 360, DualShock 4) and
keeps the one whose host answers like Windows or a Switch. pad2c sends XInput pads what Windows sends first (the
Microsoft OS string and the XUSB request, then the LED), and detaches the console's driver from a boot keyboard or
mouse beside the pad.
- With no controller the dongle shows up as "8BitDo IDLE". When the controller connects, the dongle enumerates again:
in DInput mode it sends HID report 1, turned into DualSense buttons by `src/map8bitdo.c`; in XInput mode it becomes
`2dc8:310a` with an Xbox 360 interface, read by `src/mapxinput.c`. `src/usbdesc.c` picks the interface from the
USB descriptor (XInput first, then the 8BitDo HID, never a keyboard). `make test` checks both against reports and
descriptors captured on consoles. When the controller sleeps, the dongle enumerates again and the open device ends.
- `src/usbpad.c` opens each controller's `/dev/ugenB.A` node and reads its interrupt IN endpoint, up to four, each
its own virtual DualSense. New devices are looked for every 100 ms. The console's own HID driver holds the
interface but ignores the device. The internal Bluetooth chip (`ugen0.2`) is never touched.
- `src/usbdesc.c` picks the interface from the USB descriptor: XInput (`ff/5d/01`) from any vendor first, then an
8BitDo HID interface, never a keyboard. `src/map8bitdo.c` (8BitDo DInput report 1) and `src/mapxinput.c` (Xbox 360
report) turn the reports into DualSense buttons.
- XInput devices get what Windows sends first (the Microsoft OS string and the XUSB vendor request, then the LED):
the USB Wireless Adapter 2 keeps XInput when its host answers like that. A boot keyboard or mouse beside the pad is
detached from the console's driver.
- When a controller sleeps or switches off, the dongle or adapter enumerates again ("IDLE") and the open device ends.
- `src/ps5_vpad.c` creates the virtual DualSense (`scePadVirtualDevice*`), finds its device id in the kernel log,
gives it to a user (`sceMbusBindDeviceWithUserId`) and feeds it. It follows the kernel log while running: the
console's choice of user is remembered, and its "turn off" (`sceMbusDisconnectDevice`) removes the virtual pad.
- `src/web.c` serves the settings page without blocking the pads; `src/config.c` keeps its choices.
- `src/probe.c` is a read-only diagnostic payload: it dumps the USB descriptors and the reports of whatever is
plugged in. Useful to add support for another USB controller.
- `src/web.c` serves the settings page without ever blocking the controllers; `src/config.c` keeps its choices.
- `src/probe.c` is a read-only diagnostic payload: it dumps the USB descriptors, names and reports of whatever is
plugged in. Useful to add support for another controller.
## Build
@@ -170,7 +183,7 @@ Needs [ps5-payload-sdk](https://github.com/ps5-payload-dev/sdk) and clang, lld a
```
. ~/.ps5sdk
make test # button maps, interface choice, settings, status JSON, on the PC
make # dist/pad2c-<version>.elf and dist/pad2c-probe.elf
make # dist/padbridge-<version>.elf and dist/padbridge-probe.elf
```
## Credits
@@ -183,5 +196,5 @@ make # dist/pad2c-<version>.elf and dist/pad2c-probe.elf
GPL-3.0-or-later. See [LICENSE](LICENSE).
No Sony code, keys or firmware are part of this project. pad2c does not jailbreak anything: it needs a console
No Sony code, keys or firmware are part of this project. padbridge does not jailbreak anything: it needs a console
that already runs a payload loader.
-1
View File
@@ -1 +0,0 @@
090d82eab24cdd1af43b82f1610e88d90ba4d6c1335660c3bb953b4ad6dfc50c pad2c-1.0.0.elf
-1
View File
@@ -1 +0,0 @@
9341757afec92e1adc3bf7e3978626548fe2a8dceb6c54544bb4f85b7f704a4f pad2c-probe.elf
Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

+3 -3
View File
@@ -1,4 +1,4 @@
/* pad2c's settings, /data/pad2c/config: a line per setting, set from the
/* padbridge's settings, /data/padbridge/config: a line per setting, set from the
* web page (web.c) or by hand.
*
* swap <controller name> A/B and X/Y exchanged for that controller
@@ -8,8 +8,8 @@
* a swap set there holds for whatever controller is connected to it (a
* Nintendo-layout pad such as the GameSir G8 in Switch mode needs it). Lines
* it does not know are kept as they are. */
#ifndef PAD2C_CONFIG_H
#define PAD2C_CONFIG_H
#ifndef PADBRIDGE_CONFIG_H
#define PADBRIDGE_CONFIG_H
#include "pad.h"
+2 -2
View File
@@ -1,6 +1,6 @@
/* The part of AnyPad PS5's host.h that ps5_vpad.c needs. */
#ifndef PAD2C_HOST_H
#define PAD2C_HOST_H
#ifndef PADBRIDGE_HOST_H
#define PADBRIDGE_HOST_H
#include "pad.h"
+5
View File
@@ -53,6 +53,11 @@ static int lock_is_live(const char *path, long boot)
return kill((pid_t)pid, 0) == 0 || errno == EPERM;
}
int lock_held(const char *path)
{
return lock_is_live(path, lock_boot_time());
}
int lock_take(const char *path)
{
char buf[64];
+1
View File
@@ -11,6 +11,7 @@
/* Returns 1 with the lock held, 0 if another copy is running or the file
* cannot be created. */
int lock_take(const char *path);
int lock_held(const char *path); /* 1 if a running copy holds the lock at path */
void lock_release(void);
/* The boot time of this machine, in seconds since 1970; 0 if unknown. */
+47 -15
View File
@@ -1,18 +1,22 @@
/* pad2c -- the 8BitDo Ultimate 2C 2.4G dongle as a second DualSense on a
* jailbroken PS5, read over USB (no Bluetooth involved).
/* padbridge -- USB controllers as virtual DualSense pads on a jailbroken PS5:
* the 8BitDo Ultimate 2C 2.4G dongle, the 8BitDo USB Wireless Adapter 2 (and
* whatever Bluetooth controller is paired with it) and wired XInput pads,
* up to four at once. The console's Bluetooth is never involved.
*
* usbpad.c the dongle, read from its /dev/ugen node
* ps5_vpad.c the virtual DualSense it drives (from AnyPad PS5, GPL-3.0)
* usbpad.c each pad, read from its /dev/ugen node
* ps5_vpad.c the virtual DualSense pads (from AnyPad PS5, GPL-3.0)
* web.c the settings page, port 8090
*
* A virtual pad exists only while the controller is connected to the dongle:
* it is made on the first controller report and removed when the dongle goes
* A virtual pad exists only while its controller is connected: it is made on
* the first controller report and removed when the dongle or adapter goes
* back to "IDLE" (controller off or asleep) or is unplugged.
*
* "Turn off" from the console's menu removes the virtual pad, as a DualSense
* disconnects; Home (PS) brings it back, as it switches a DualSense on.
*
* Stop: create /data/pad2c/stop. It also stops by itself before rest mode.
* Log: /data/pad2c/pad2c.log
* Stop: create /data/padbridge/stop. It also stops by itself before rest mode.
* Log: /data/padbridge/padbridge.log
* Up to 1.1.0 this was pad2c, in /data/pad2c: moved here on the first start.
*/
#include "host.h"
#include "lock.h"
@@ -33,11 +37,12 @@
#include <string.h>
#include <unistd.h>
#define STATE_DIR "/data/pad2c"
#define LOG_PATH STATE_DIR "/pad2c.log"
#define LOCK_PATH STATE_DIR "/pad2c.lock"
#define STATE_DIR "/data/padbridge"
#define LOG_PATH STATE_DIR "/padbridge.log"
#define LOCK_PATH STATE_DIR "/padbridge.lock"
#define STOP_FLAG STATE_DIR "/stop"
#define CONFIG_PATH STATE_DIR "/config"
#define OLD_DIR "/data/pad2c" /* 1.1.0 and before */
#define T_SCAN 100 /* ms between looks for new pads (the USB Wireless Adapter 2 shows
its XInput identity for under a second) */
#define T_RESEND 50 /* ms: the last state is sent again at least this often */
@@ -150,15 +155,42 @@ static void ctl_poll(int slot, long now)
}
}
/* Up to 1.1.0 this was pad2c. A pad2c still running (from an autoload list)
* is asked to stop, so that two do not read the same pads, and its state
* (remembered users, settings, log) moves here once. Returns what to log. */
static const char *from_pad2c(void)
{
struct stat sb;
int i, asked = 0;
if (stat(OLD_DIR, &sb) != 0) return NULL;
if (lock_held(OLD_DIR "/pad2c.lock")) {
FILE *f = fopen(OLD_DIR "/stop", "w");
if (f) fclose(f);
for (i = 0; i < 50 && lock_held(OLD_DIR "/pad2c.lock"); i++) usleep(100000);
asked = 1;
}
if (stat(STATE_DIR, &sb) == 0) return asked ? "pad2c was running: asked to stop" : NULL;
if (rename(OLD_DIR, STATE_DIR) != 0) return "pad2c's state could not be moved here";
unlink(STATE_DIR "/pad2c.lock");
unlink(STATE_DIR "/stop");
rename(STATE_DIR "/pad2c.log", STATE_DIR "/padbridge.log");
rename(STATE_DIR "/pad2c.log.1", STATE_DIR "/padbridge.log.1");
return asked ? "pad2c was running: stopped, and its state moved here" : "pad2c's state moved here";
}
int main(void)
{
const char *moved;
const char *why = "stop requested";
long last_scan = -T_SCAN, last_power = 0, last_check = 0;
int i;
moved = from_pad2c();
mkdir(STATE_DIR, 0777);
log_open(LOG_PATH);
log_line("pad2c %s", PAD2C_VERSION);
log_line("padbridge %s", PADBRIDGE_VERSION);
if (moved) log_line("%s", moved);
if (!lock_take(LOCK_PATH)) {
log_line("another instance is running");
notify("Already running");
@@ -166,7 +198,7 @@ int main(void)
return 1;
}
unlink(STOP_FLAG);
syscall(SYS_thr_set_name, -1, "pad2c");
syscall(SYS_thr_set_name, -1, "padbridge");
signal(SIGTERM, on_signal);
signal(SIGINT, on_signal);
signal(SIGHUP, on_signal);
@@ -183,9 +215,9 @@ int main(void)
{
char ip[32];
if (web_start(WEB_PORT) && web_local_ip(ip, sizeof ip))
notify("Version %s running\nSettings: http://%s:%d", PAD2C_VERSION, ip, WEB_PORT);
notify("Version %s running\nSettings: http://%s:%d", PADBRIDGE_VERSION, ip, WEB_PORT);
else
notify("Version %s running\nSwitch the controller on with its dongle plugged in", PAD2C_VERSION);
notify("Version %s running\nSwitch the controller on with its dongle plugged in", PADBRIDGE_VERSION);
}
for (i = 0; i < HOST_MAX_PADS; i++) pad_state_reset(&g_ctl[i].st);
+5 -5
View File
@@ -1,4 +1,4 @@
/* pad2c probe -- read-only look at the console's USB devices.
/* padbridge probe -- read-only look at the console's USB devices.
*
* For every /dev/ugenB.A that is not a hub and not the internal Bluetooth
* chip (ugen0.2, left alone on purpose): device descriptor, name, full
@@ -7,7 +7,7 @@
* its interrupt IN endpoints send (press buttons and move the sticks then).
* Also copies what /dev/klog holds, to see why another payload died.
*
* Everything goes to stdout (the loader's socket) and /data/pad2c/probe.log.
* Everything goes to stdout (the loader's socket) and /data/padbridge/probe.log.
* Nothing is written to any device except standard GET_DESCRIPTOR requests
* (configuration, HID report and string descriptors).
*/
@@ -31,7 +31,7 @@
#include <time.h>
#include <unistd.h>
#define DIR_PATH "/data/pad2c"
#define DIR_PATH "/data/padbridge"
#define LOG_PATH DIR_PATH "/probe.log"
#define KLOG_PATH DIR_PATH "/klog.txt"
#ifndef READ_SECONDS
@@ -70,7 +70,7 @@ static void notify(const char *msg)
notify_request req;
memset(&req, 0, sizeof req);
snprintf(req.message, sizeof req.message, "pad2c probe by Sub\n%s", msg);
snprintf(req.message, sizeof req.message, "padbridge probe by Sub\n%s", msg);
sceKernelSendNotificationRequest(0, &req, sizeof req, 0);
}
@@ -398,7 +398,7 @@ int main(void)
mkdir(DIR_PATH, 0777);
g_log = fopen(LOG_PATH, "w");
fw = kernel_get_fw_version();
out("pad2c probe 2, firmware %x.%02x (raw %#x), pid %d", (unsigned)(fw >> 24) & 0xFF,
out("padbridge probe 2, firmware %x.%02x (raw %#x), pid %d", (unsigned)(fw >> 24) & 0xFF,
(unsigned)(fw >> 16) & 0xFF, (unsigned)fw, (int)getpid());
notify("Starting");
copy_klog();
+15 -15
View File
@@ -111,18 +111,18 @@ typedef struct {
pad_state last; /* the pad's latest state, resent while PS is held from the menu */
long ps_until; /* ms; PS is added to the frames until then (0: not held) */
int ps_active; /* a frame with PS went out and the release is still to be sent */
int sys_off; /* pad2c: the system removed it or it refused input ("turned off") */
int sys_off; /* padbridge: the system removed it or it refused input ("turned off") */
} vslot;
static mbus_bind_fn g_bind;
static int g_ready; /* vpad_init succeeded */
static int32_t g_user = -1;
/* pad2c: the user the pad was last given to, from the console's own choice
/* padbridge: the user the pad was last given to, from the console's own choice
* (the "who is using this controller" screen) as the kernel log tells it.
* Kept in /data/pad2c/owner, so the pad goes back to that user next time, as
* Kept in /data/padbridge/owner, so the pad goes back to that user next time, as
* a DualSense does. -1: none yet. */
#define OWNER_PATH "/data/pad2c/owner" /* slot 0; slot n: owner.n */
#define OWNER_PATH "/data/padbridge/owner" /* slot 0; slot n: owner.n */
static int32_t g_owner[HOST_MAX_PADS] = { -1, -1, -1, -1 };
static void owner_path(int slot, char *out, size_t size)
@@ -193,8 +193,8 @@ static size_t g_line_len;
void notify(const char *fmt, ...)
{
/* Every notification: "pad2c by <author>", then what happened. */
static const char head[] = "pad2c by " PAD2C_AUTHOR "\n";
/* Every notification: "padbridge by <author>", then what happened. */
static const char head[] = "padbridge by " PADBRIDGE_AUTHOR "\n";
notify_request req;
va_list ap;
@@ -282,7 +282,7 @@ static uint64_t virtual_device_in(const char *line)
return hex_after(line, "DeviceId");
}
/* pad2c: the source is kept open for as long as the payload runs, so what the
/* padbridge: the source is kept open for as long as the payload runs, so what the
* system does with our pads is seen. Whatever it held before is old news, and
* an old DEVICE_ADDED line must never be taken for ours: drop it all. */
static void log_source_drain(void)
@@ -296,7 +296,7 @@ static void log_source_drain(void)
log_line("vpad: kernel log open (%s), %ld old bytes skipped", g_log_is_dev ? "/dev/klog" : "server on 3232", total);
}
/* pad2c: lines about one of our live pads. The console's choice of user for
/* padbridge: lines about one of our live pads. The console's choice of user for
* it is remembered; its removal by the system (not by us: our own removals
* forget the device first) is the console turning the pad off. Everything
* else said about it is logged, to learn what the console asks of it. */
@@ -396,12 +396,12 @@ int32_t vpad_choose_user(const int32_t login[4], int32_t init, int32_t fore, con
return -1;
}
/* pad2c: whether /data/pad2c/bind_user asks to bind the pad to a user
/* padbridge: whether /data/padbridge/bind_user asks to bind the pad to a user
* ("main" for the default choice, "other", or a hexadecimal id). */
static int bind_wanted(void)
{
char want[40] = "";
FILE *f = fopen("/data/pad2c/bind_user", "r");
FILE *f = fopen("/data/padbridge/bind_user", "r");
if (!f) return 0;
if (!fgets(want, sizeof want, f)) want[0] = '\0';
@@ -420,7 +420,7 @@ static int32_t main_user(void)
if (sceUserServiceGetLoginUserIdList(login) != 0) login[0] = login[1] = login[2] = login[3] = -1;
if (sceUserServiceGetInitialUser(&init) != 0) init = -1;
if (sceUserServiceGetForegroundUser(&fore) != 0) fore = -1;
if ((f = fopen("/data/pad2c/bind_user", "r"))) {
if ((f = fopen("/data/padbridge/bind_user", "r"))) {
if (fgets(want, sizeof want, f)) {
for (i = 0; want[i]; i++) if (want[i] == '\n' || want[i] == '\r' || want[i] == ' ') want[i] = 0;
if (strncmp(want, "0x", 2) == 0) memmove(want, want + 2, strlen(want + 2) + 1);
@@ -652,9 +652,9 @@ static void finish_identification(long now)
memset(v, 0, sizeof *v);
return;
}
/* pad2c: the user, as a DualSense does it: back to the user who last used
/* padbridge: the user, as a DualSense does it: back to the user who last used
* it, if signed in; otherwise nobody, and the console asks who uses it
* when its PS (Home) button is pressed. /data/pad2c/bind_user = main,
* when its PS (Home) button is pressed. /data/padbridge/bind_user = main,
* other or a hexadecimal id binds it to that user instead. */
{
int32_t to = -1;
@@ -679,7 +679,7 @@ void vpad_poll(long now)
if (!g_ready) return;
ps_tick(now);
log_source_read(); /* pad2c: always, to follow what the console does with our pads */
log_source_read(); /* padbridge: always, to follow what the console does with our pads */
if (g_pending >= 0) {
finish_identification(now);
return;
@@ -740,7 +740,7 @@ static void send_frame(int slot, const pad_state *st)
if (scePadVirtualDeviceInsertData(g_slot[slot].handle, &d) != 0) {
/* Stop feeding a device that refuses: it may not be ours any more. */
log_line("slot %d: the virtual pad refused input, stopped feeding it", slot);
g_slot[slot].sys_off = 1; /* pad2c: as if turned off; Home brings a new one */
g_slot[slot].sys_off = 1; /* padbridge: as if turned off; Home brings a new one */
}
}
+3 -3
View File
@@ -41,16 +41,16 @@ void vpad_update(int slot, const pad_state *st);
int vpad_press_ps(int slot, int ms);
void vpad_remove(int slot);
/* pad2c: 1 when the slot's virtual pad is gone on the console's side (turned
/* padbridge: 1 when the slot's virtual pad is gone on the console's side (turned
* off from the console's menu, refused input, or never identified). The
* caller removes it and asks for a new one when the user presses PS (Home). */
int vpad_off(int slot);
/* pad2c: the user the slot's live pad is given to, -1 if none (or not known). */
/* padbridge: the user the slot's live pad is given to, -1 if none (or not known). */
int32_t vpad_user(int slot);
int vpad_user_name(int32_t user, char *out, size_t size); /* 0 if unknown */
/* pad2c: closes the kernel log, held open while running. */
/* padbridge: closes the kernel log, held open while running. */
void vpad_shutdown(void);
/* The user's own DualSense, read only. Returns 0 if it cannot be read;
+3 -3
View File
@@ -1,7 +1,7 @@
/* Which interface of a USB device pad2c reads, from its configuration
/* Which interface of a USB device padbridge reads, from its configuration
* descriptor alone (no device needed, so it runs in the tests too). */
#ifndef PAD2C_USBDESC_H
#define PAD2C_USBDESC_H
#ifndef PADBRIDGE_USBDESC_H
#define PADBRIDGE_USBDESC_H
#include <stddef.h>
#include <stdint.h>
+12
View File
@@ -30,6 +30,9 @@
* Adapter 2 shows its XInput identity for under a second. */
static unsigned char g_held[N_BUS][N_ADDR];
static struct { ino_t ino; long ctime, seen; unsigned char set; } g_not_pad[N_BUS][N_ADDR];
static char g_reject[96]; /* what try_node last found not to be a pad */
static char g_logged[N_BUS][N_ADDR][96]; /* per node: the last one logged (a Switch Pro pad
not set up enumerates again every few seconds) */
#define T_NOT_PAD 2000 /* ms a node found not to be a pad is left alone, unless it changes */
struct usbpad {
@@ -189,6 +192,9 @@ static usbpad *try_node(int bus, int addr, int *not_pad)
if (ioctl(u->fd, USB_GET_DEVICE_DESC, &dd) != 0) goto fail;
u->kind = choose_interface(u->fd, UGETW(dd.idVendor), &pick);
if (u->kind == USBPAD_NONE) {
char prod[48] = "";
read_string(u->fd, dd.iProduct, prod, sizeof prod);
snprintf(g_reject, sizeof g_reject, "%04x:%04x \"%s\"", UGETW(dd.idVendor), UGETW(dd.idProduct), prod);
*not_pad = 1;
goto fail;
}
@@ -307,6 +313,12 @@ usbpad *usbpad_open(void)
g_not_pad[bus][addr].set = 0;
return u;
}
/* Logged once per device: what is plugged in and not read, to
* tell why a controller does nothing (another mode, say). */
if (not_pad && strcmp(g_logged[bus][addr], g_reject) != 0) {
log_line("usb: %s %s: not a controller padbridge reads", path, g_reject);
snprintf(g_logged[bus][addr], sizeof g_logged[bus][addr], "%s", g_reject);
}
g_not_pad[bus][addr].set = (unsigned char)not_pad;
g_not_pad[bus][addr].ino = sb.st_ino;
g_not_pad[bus][addr].ctime = (long)sb.st_ctime;
+2 -2
View File
@@ -10,8 +10,8 @@
* interface is read is chosen in usbdesc.c. Every re-enumeration ends the
* open device, so the caller closes it and looks again.
*/
#ifndef PAD2C_USBPAD_H
#define PAD2C_USBPAD_H
#ifndef PADBRIDGE_USBPAD_H
#define PADBRIDGE_USBPAD_H
#include "pad.h"
+4 -4
View File
@@ -1,7 +1,7 @@
#ifndef PAD2C_VERSION_H
#define PAD2C_VERSION_H
#ifndef PADBRIDGE_VERSION_H
#define PADBRIDGE_VERSION_H
#define PAD2C_VERSION "1.1.0"
#define PAD2C_AUTHOR "Sub" /* every notification starts "pad2c by Sub" */
#define PADBRIDGE_VERSION "1.2.0"
#define PADBRIDGE_AUTHOR "Sub" /* every notification starts "padbridge by Sub" */
#endif
+1 -1
View File
@@ -90,7 +90,7 @@ int web_status_json(const web_pad *pads, int n, char *out, int size)
{
int len, i, first = 1;
len = snprintf(out, (size_t)size, "{\"version\":\"%s\",\"pads\":[", PAD2C_VERSION);
len = snprintf(out, (size_t)size, "{\"version\":\"%s\",\"pads\":[", PADBRIDGE_VERSION);
for (i = 0; i < n && len < size; i++) {
const web_pad *p = &pads[i];
char name[160], user[100];
+3 -3
View File
@@ -1,4 +1,4 @@
/* pad2c's settings page, http://<console>:8090 from any browser on the local
/* padbridge's settings page, http://<console>:8090 from any browser on the local
* network (phone, computer, the console's own).
*
* GET / the page
@@ -7,8 +7,8 @@
*
* Never blocks: web_poll() takes what has arrived and answers what is
* complete, so a slow browser does not delay the pads. */
#ifndef PAD2C_WEB_H
#define PAD2C_WEB_H
#ifndef PADBRIDGE_WEB_H
#define PADBRIDGE_WEB_H
#include "pad.h"
+6 -6
View File
@@ -1,13 +1,13 @@
/* The settings page served by web.c: one file, no outside resources (the
* console may have no internet). It asks /api/status about 10 times a second
* while it is visible. */
#ifndef PAD2C_WEBPAGE_H
#define PAD2C_WEBPAGE_H
#ifndef PADBRIDGE_WEBPAGE_H
#define PADBRIDGE_WEBPAGE_H
static const char WEB_PAGE[] =
"<!doctype html><html lang=en><head><meta charset=utf-8>"
"<meta name=viewport content='width=device-width,initial-scale=1'>"
"<title>pad2c by Sub</title><style>"
"<title>padbridge by Sub</title><style>"
":root{--bg:#0f1115;--card:#181b22;--line:#2a2f3a;--txt:#e8eaf0;--dim:#8a91a0;--on:#3b82f6;--ok:#22c55e}"
"*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--txt);"
"font:15px/1.4 -apple-system,Segoe UI,Roboto,sans-serif}"
@@ -31,9 +31,9 @@ static const char WEB_PAGE[] =
".bar i{display:block;height:100%;background:var(--on);width:0}"
".empty{color:var(--dim);padding:20px}footer{color:var(--dim);font-size:12px;padding:0 20px 20px}"
"</style></head><body>"
"<header><h1>pad2c by Sub</h1><small id=st>connecting...</small></header>"
"<header><h1>padbridge by Sub</h1><small id=st>connecting...</small></header>"
"<main id=m><div class=empty>No controller yet.</div></main>"
"<footer>Settings are kept on the console in /data/pad2c/config.</footer>"
"<footer>Settings are kept on the console in /data/padbridge/config.</footer>"
"<script>"
"var B=[['Cross',0x4000],['Circle',0x2000],['Square',0x8000],['Triangle',0x1000],['L1',0x400],['R1',0x800],"
"['L2',0x100],['R2',0x200],['L3',2],['R3',4],['Create',1],['Options',8],['PS',0x10000],['Touchpad',0x100000],"
@@ -63,7 +63,7 @@ static const char WEB_PAGE[] =
"if(key!==shown){shown=key;document.getElementById('m').innerHTML=j.pads.length?j.pads.map(card).join('')"
":'<div class=empty>No controller yet. Plug in a dongle or adapter and switch the controller on.</div>'}"
"j.pads.forEach(live);setTimeout(tick,100)}).catch(function(){"
"document.getElementById('st').textContent='pad2c is not answering';setTimeout(tick,1000)})}"
"document.getElementById('st').textContent='padbridge is not answering';setTimeout(tick,1000)})}"
"function sw(e){var s=e.getAttribute('data-s');busy[s]=1;"
"fetch('/api/swap?slot='+s+'&on='+(e.checked?1:0),{method:'POST'}).then(function(){"
"setTimeout(function(){busy[s]=0},300)})}"
+1 -1
View File
@@ -31,7 +31,7 @@ int main(void)
/* lines it does not know are kept, a damaged file is no harm */
f = fopen(path, "w");
fputs("# pad2c settings\nfuture-option 7\nswap Some Pad\n\nswap\n", f);
fputs("# padbridge settings\nfuture-option 7\nswap Some Pad\n\nswap\n", f);
fclose(f);
config_load(path);
CHECK(config_swap("Some Pad"), "swap from a hand-written file");
+2 -2
View File
@@ -1,7 +1,7 @@
/* Interface choice and the XInput mapping. Runs on the PC.
*
* The 310a descriptor is the one in issue #2 (pad2c-probe on firmware 13.00):
* the Ultimate 2C receiver in XInput mode. pad2c 0.2.1 read its keyboard. */
* The 310a descriptor is the one in issue #2 (padbridge-probe on firmware 13.00):
* the Ultimate 2C receiver in XInput mode. padbridge 0.2.1 read its keyboard. */
#include "usbdesc.h"
#include "usbpad.h"