Skip to content

Commit 182e084

Browse files
committed
feat: 添加 DS4/DS5 和增强 Switch Pro 控制器 USB 驱动支持
- 新增 DualShock 4 (DS4) USB 驱动支持 - 新增 DualSense (DS5) USB 驱动支持,包括 PS5 和雷蛇幻影战狼 v2 Pro - 增强 Switch Pro 控制器驱动,添加完整功能: * IMU 数据支持(陀螺仪和加速度计) * 震动反馈支持 * 摇杆校准功能(支持出厂和用户校准) * 完整的初始化流程 - 添加 AbstractDualSenseController 抽象基类,封装 PlayStation 控制器通用逻辑 - 修复多个安全漏洞:数组越界、除零错误、运算符优先级等 参考实现: - https://github.com/Axixi2233/moonlight-android - https://github.com/Ryochan7/DS4Windows - https://www.psdevwiki.com/ps4/DS4-USB 特别感谢 Axixi2233 等开源作者的无私贡献,让这个功能得以实现。
1 parent 762dc71 commit 182e084

File tree

5 files changed

+1348
-107
lines changed

5 files changed

+1348
-107
lines changed
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
package com.limelight.binding.input.driver;
2+
3+
import android.hardware.usb.UsbConstants;
4+
import android.hardware.usb.UsbDevice;
5+
import android.hardware.usb.UsbDeviceConnection;
6+
import android.hardware.usb.UsbEndpoint;
7+
import android.hardware.usb.UsbInterface;
8+
import android.os.SystemClock;
9+
import android.util.Log;
10+
11+
import com.limelight.nvstream.input.ControllerPacket;
12+
import com.limelight.nvstream.jni.MoonBridge;
13+
14+
import java.nio.ByteBuffer;
15+
import java.nio.ByteOrder;
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
19+
public abstract class AbstractDualSenseController extends AbstractController {
20+
protected final UsbDevice device;
21+
protected final UsbDeviceConnection connection;
22+
23+
private Thread inputThread;
24+
private boolean stopped;
25+
26+
protected UsbEndpoint inEndpt, outEndpt;
27+
28+
// IMU data fields
29+
protected float gyroX, gyroY, gyroZ;
30+
protected float accelX, accelY, accelZ;
31+
32+
public AbstractDualSenseController(UsbDevice device, UsbDeviceConnection connection, int deviceId, UsbDriverListener listener) {
33+
super(deviceId, listener, device.getVendorId(), device.getProductId());
34+
this.device = device;
35+
this.connection = connection;
36+
this.type = MoonBridge.LI_CTYPE_PS;
37+
this.capabilities = MoonBridge.LI_CCAP_GYRO | MoonBridge.LI_CCAP_ACCEL | MoonBridge.LI_CCAP_RUMBLE;
38+
this.buttonFlags =
39+
ControllerPacket.A_FLAG | ControllerPacket.B_FLAG | ControllerPacket.X_FLAG | ControllerPacket.Y_FLAG |
40+
ControllerPacket.UP_FLAG | ControllerPacket.DOWN_FLAG | ControllerPacket.LEFT_FLAG | ControllerPacket.RIGHT_FLAG |
41+
ControllerPacket.LB_FLAG | ControllerPacket.RB_FLAG |
42+
ControllerPacket.LS_CLK_FLAG | ControllerPacket.RS_CLK_FLAG |
43+
ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG | ControllerPacket.SPECIAL_BUTTON_FLAG;
44+
this.supportedButtonFlags = this.buttonFlags;
45+
}
46+
47+
private Thread createInputThread() {
48+
return new Thread() {
49+
public void run() {
50+
try {
51+
// Delay for a moment before reporting the new gamepad and
52+
// accepting new input. This allows time for the old InputDevice
53+
// to go away before we reclaim its spot. If the old device is still
54+
// around when we call notifyDeviceAdded(), we won't be able to claim
55+
// the controller number used by the original InputDevice.
56+
Thread.sleep(1000);
57+
} catch (InterruptedException e) {
58+
return;
59+
}
60+
61+
// Report that we're added _before_ reporting input
62+
notifyDeviceAdded();
63+
64+
while (!isInterrupted() && !stopped) {
65+
byte[] buffer = new byte[64];
66+
67+
int res = -1; // Initialize to error state
68+
69+
//
70+
// There's no way that I can tell to determine if a device has failed
71+
// or if the timeout has simply expired. We'll check how long the transfer
72+
// took to fail and assume the device failed if it happened before the timeout
73+
// expired.
74+
//
75+
76+
do {
77+
// Check if we should stop before attempting transfer
78+
if (stopped || isInterrupted()) {
79+
res = -1; // Set to error state before break
80+
break;
81+
}
82+
83+
// Read the next input state packet
84+
long lastMillis = SystemClock.uptimeMillis();
85+
if (connection == null || inEndpt == null) {
86+
Log.w("DualSenseController", "Connection or endpoint is null");
87+
res = -1; // Set to error state before break
88+
break;
89+
}
90+
res = connection.bulkTransfer(inEndpt, buffer, buffer.length, 3000);
91+
92+
// If we get a zero length response, treat it as an error
93+
if (res == 0) {
94+
res = -1;
95+
}
96+
97+
if (res == -1 && SystemClock.uptimeMillis() - lastMillis < 1000) {
98+
Log.d("DualSenseController", "Detected device I/O error");
99+
AbstractDualSenseController.this.stop();
100+
break;
101+
}
102+
} while (res == -1 && !isInterrupted() && !stopped);
103+
104+
if (res == -1 || stopped || isInterrupted()) {
105+
break;
106+
}
107+
108+
if (res > 0 && handleRead(ByteBuffer.wrap(buffer, 0, res).order(ByteOrder.LITTLE_ENDIAN))) {
109+
// Report input if handleRead() returns true
110+
reportInput();
111+
reportMotion();
112+
}
113+
}
114+
}
115+
};
116+
}
117+
118+
private static UsbInterface findInterface(UsbDevice device) {
119+
int count = device.getInterfaceCount();
120+
for (int i = 0; i < count; i++) {
121+
UsbInterface intf = device.getInterface(i);
122+
if (intf.getInterfaceClass() == UsbConstants.USB_CLASS_HID && intf.getEndpointCount() >= 2) {
123+
Log.d("DualSenseController", "Found HID interface: " + i);
124+
return intf;
125+
}
126+
}
127+
return null;
128+
}
129+
130+
private List<UsbInterface> ifaces = new ArrayList<>();
131+
132+
public boolean start() {
133+
ifaces.clear();
134+
Log.d("DualSenseController", "start");
135+
// Force claim all interfaces
136+
for (int i = 0; i < device.getInterfaceCount(); i++) {
137+
UsbInterface iface = device.getInterface(i);
138+
139+
if (!connection.claimInterface(iface, true)) {
140+
Log.d("DualSenseController", "Failed to claim interface: " + i);
141+
return false;
142+
} else {
143+
ifaces.add(iface);
144+
}
145+
}
146+
Log.d("DualSenseController", "getInterfaceCount:" + device.getInterfaceCount());
147+
148+
// Find the endpoints
149+
UsbInterface iface = findInterface(device);
150+
151+
if (iface == null) {
152+
Log.e("DualSenseController", "Failed to find interface");
153+
return false;
154+
}
155+
156+
for (int i = 0; i < iface.getEndpointCount(); i++) {
157+
UsbEndpoint endpt = iface.getEndpoint(i);
158+
if (endpt.getDirection() == UsbConstants.USB_DIR_OUT) {
159+
if (outEndpt != null) {
160+
Log.d("DualSenseController", "Found duplicate OUT endpoint");
161+
return false;
162+
}
163+
outEndpt = endpt;
164+
} else if (endpt.getDirection() == UsbConstants.USB_DIR_IN) {
165+
if (inEndpt != null) {
166+
Log.d("DualSenseController", "Found duplicate IN endpoint");
167+
return false;
168+
}
169+
inEndpt = endpt;
170+
}
171+
}
172+
Log.d("DualSenseController", "inEndpt: " + inEndpt);
173+
Log.d("DualSenseController", "outEndpt: " + outEndpt);
174+
// Make sure the required endpoints were present
175+
if (inEndpt == null || outEndpt == null) {
176+
Log.d("DualSenseController", "Missing required endpoint");
177+
return false;
178+
}
179+
// Run the init function
180+
if (!doInit()) {
181+
return false;
182+
}
183+
// Start listening for controller input
184+
inputThread = createInputThread();
185+
inputThread.start();
186+
return true;
187+
}
188+
189+
public void stop() {
190+
synchronized (this) {
191+
if (stopped) {
192+
return;
193+
}
194+
stopped = true;
195+
}
196+
197+
// Cancel any rumble effects (may fail if device is already disconnected)
198+
try {
199+
rumble((short) 0, (short) 0);
200+
} catch (Exception e) {
201+
Log.d("DualSenseController", "Failed to cancel rumble during stop", e);
202+
}
203+
204+
// Stop the input thread
205+
if (inputThread != null) {
206+
inputThread.interrupt();
207+
try {
208+
inputThread.join(1000);
209+
} catch (InterruptedException e) {
210+
Thread.currentThread().interrupt();
211+
}
212+
inputThread = null;
213+
}
214+
215+
// Release all claimed interfaces
216+
if (connection != null && !ifaces.isEmpty()) {
217+
synchronized (ifaces) {
218+
for (UsbInterface iface : ifaces) {
219+
try {
220+
connection.releaseInterface(iface);
221+
} catch (Exception e) {
222+
Log.w("DualSenseController", "Failed to release interface", e);
223+
}
224+
}
225+
ifaces.clear();
226+
}
227+
}
228+
229+
// Close the USB connection
230+
if (connection != null) {
231+
try {
232+
connection.close();
233+
} catch (Exception e) {
234+
Log.w("DualSenseController", "Failed to close connection", e);
235+
}
236+
}
237+
238+
// Report the device removed
239+
notifyDeviceRemoved();
240+
}
241+
242+
protected void reportMotion() {
243+
// Report gyroscope data (in deg/s)
244+
notifyControllerMotion(MoonBridge.LI_MOTION_TYPE_GYRO, gyroX, gyroY, gyroZ);
245+
// Report accelerometer data (in m/s²)
246+
notifyControllerMotion(MoonBridge.LI_MOTION_TYPE_ACCEL, accelX, accelY, accelZ);
247+
}
248+
249+
protected abstract boolean handleRead(ByteBuffer buffer);
250+
251+
protected abstract boolean doInit();
252+
253+
protected abstract void sendCommand(byte[] data);
254+
}
255+

0 commit comments

Comments
 (0)