mirror of
https://github.com/sub0x00001/pad2c.git
synced 2026-10-24 11:03:02 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f29a1de877 |
No files matched your search
@@ -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
@@ -1,4 +1,3 @@
|
||||
build/
|
||||
dist/*.elf
|
||||
!dist/.keep
|
||||
dist/
|
||||
release/
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
<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://<console IP>: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://<console IP>: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.
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
090d82eab24cdd1af43b82f1610e88d90ba4d6c1335660c3bb953b4ad6dfc50c pad2c-1.0.0.elf
|
||||
Vendored
-1
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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];
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
|
||||
Reference in new issue
Block a user