Merge branch 'XC-ios-ht' into rumbleDev

This commit is contained in:
MediaMoots 2025-05-31 11:01:24 +08:00
commit 54ef5018e0
11 changed files with 569 additions and 15 deletions

View file

@ -24,6 +24,7 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
4549A31C2DD8795900EC8D88 /* CocoaAsyncSocket in Frameworks */ = {isa = PBXBuildFile; productRef = 4549A31B2DD8795900EC8D88 /* CocoaAsyncSocket */; };
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
@ -203,6 +204,7 @@
buildActionMask = 2147483647;
files = (
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
4549A31C2DD8795900EC8D88 /* CocoaAsyncSocket in Frameworks */,
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */,
);
@ -300,6 +302,7 @@
name = MeloNX;
packageProductDependencies = (
4EA5AE812D16807500AD0B9F /* SwiftSVG */,
4549A31B2DD8795900EC8D88 /* CocoaAsyncSocket */,
);
productName = MeloNX;
productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */;
@ -391,6 +394,7 @@
minimizedProjectReferenceProxies = 1;
packageReferences = (
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */,
4549A31A2DD8795900EC8D88 /* XCRemoteSwiftPackageReference "CocoaAsyncSocket" */,
);
preferredProjectObjectVersion = 56;
productRefGroup = 4E80A98E2CD6F54500029585 /* Products */;
@ -1569,6 +1573,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
4549A31A2DD8795900EC8D88 /* XCRemoteSwiftPackageReference "CocoaAsyncSocket" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/robbiehanson/CocoaAsyncSocket";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 7.6.5;
};
};
4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mchoe/SwiftSVG";
@ -1580,6 +1592,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
4549A31B2DD8795900EC8D88 /* CocoaAsyncSocket */ = {
isa = XCSwiftPackageProductDependency;
package = 4549A31A2DD8795900EC8D88 /* XCRemoteSwiftPackageReference "CocoaAsyncSocket" */;
productName = CocoaAsyncSocket;
};
4EA5AE812D16807500AD0B9F /* SwiftSVG */ = {
isa = XCSwiftPackageProductDependency;
package = 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */;

View file

@ -1,6 +1,15 @@
{
"originHash" : "fedf09a893a63378a2e53f631cd833ae83a0c9ee7338eb8d153b04fd34aaf805",
"originHash" : "b4a593815773c4e9eedb98cabe88f41620776314bffb6c39d5a41cb743e4d390",
"pins" : [
{
"identity" : "cocoaasyncsocket",
"kind" : "remoteSourceControl",
"location" : "https://github.com/robbiehanson/CocoaAsyncSocket",
"state" : {
"revision" : "dbdc00669c1ced63b27c3c5f052ee4d28f10150c",
"version" : "7.6.5"
}
},
{
"identity" : "swiftsvg",
"kind" : "remoteSourceControl",

View file

@ -0,0 +1,14 @@
//
// BaseController.swift
// MeloNX
//
// Created by MediaMoots on 5/17/2025.
//
// MARK:- Base Controller Protocol
/// Base Controller with motion related functions
protocol BaseController: AnyObject {
func tryRegisterMotion(slot: UInt8)
func tryGetMotionProvider() -> DSUMotionProvider?
}

View file

@ -0,0 +1,178 @@
//
// DSUMotionProviders.swift
//
// Multi-source Cemuhook-compatible DSU server.
// Created by MediaMoots on 5/17/2025.
//
//
import CoreMotion
import GameController // GCController
// MARK:- Providers
/// iPhone / iPad IMU
final class DeviceMotionProvider: DSUMotionProvider {
// DSUMotionProvider conformance
let slot: UInt8
let mac: [UInt8] = [0xAB,0x12,0xCD,0x34,0xEF,0x56]
let connectionType: UInt8 = 2
let batteryLevel: UInt8 = 5
let motionRate: Double = 60.0 // 60 Hz
// Internals
private let mm = CMMotionManager()
// Thread Safety
private let dataLock = NSLock()
private var _latest: CMDeviceMotion?
private var latest: CMDeviceMotion? {
get { dataLock.lock(); defer { dataLock.unlock() }; return _latest }
set { dataLock.lock(); _latest = newValue; dataLock.unlock() }
}
private var orientation: UIDeviceOrientation =
UIDevice.current.orientation == .unknown ? .landscapeLeft : UIDevice.current.orientation
init(slot: UInt8) {
precondition(slot < 8, "DSU only supports slots 0…7")
self.slot = slot
// start Core Motion
mm.deviceMotionUpdateInterval = 1.0 / motionRate
mm.startDeviceMotionUpdates(to: .main) { [weak self] m, _ in
guard let self = self, let m = m else { return }
self.latest = m
if let sample = self.nextSample() {
DSUServer.shared.pushSample(sample, from: self)
}
}
// track orientation changes (ignore flat)
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
NotificationCenter.default.addObserver(
self,
selector: #selector(orientationDidChange),
name: UIDevice.orientationDidChangeNotification,
object: nil
)
}
@objc private func orientationDidChange() {
let o = UIDevice.current.orientation
if o.isFlat { return } // ignore face-up / face-down
orientation = o
}
func nextSample() -> DSUMotionSample? {
guard let m = latest else { return nil }
// Raw values
let gx = Float(m.rotationRate.x)
let gy = Float(m.rotationRate.y)
let gz = Float(m.rotationRate.z)
let ax = Float(m.gravity.x + m.userAcceleration.x)
let ay = Float(m.gravity.y + m.userAcceleration.y)
let az = Float(m.gravity.z + m.userAcceleration.z)
// Rotate axes to match Cemuhook's "landscape-left as neutral" convention
let a: SIMD3<Float>
let g: SIMD3<Float>
switch orientation {
case .portrait:
a = SIMD3( ax, az, -ay)
g = SIMD3( gx, -gz, gy)
case .landscapeRight:
a = SIMD3( ay, az, ax)
g = SIMD3( gy, -gz, -gx)
case .portraitUpsideDown:
a = SIMD3( -ax, az, ay)
g = SIMD3( -gx, -gz, -gy)
case .landscapeLeft, .unknown, .faceUp, .faceDown:
a = SIMD3( -ay, az, -ax)
g = SIMD3( -gy, -gz, gx)
@unknown default:
return nil
}
// Convert gyro rad/s °/s here so the server doesn't have to.
let gDeg = g * (180 / .pi)
return DSUMotionSample(timestampUS: currentUS(),
accel: a,
gyroDeg: gDeg)
}
}
// Any Switch Pro / DualSense controller that exposes `GCMotion`
final class ControllerMotionProvider: DSUMotionProvider {
// DSUMotionProvider
let slot: UInt8
let mac: [UInt8] = [0xAB,0x12,0xCD,0x34,0xEF,0x56]
let connectionType: UInt8 = 2
var batteryLevel: UInt8 {
UInt8((pad.battery?.batteryLevel ?? 0.3) * 5).clamped(to: 0...5)
}
private let pad: GCController
// Thread Safety
private let dataLock = NSLock()
private var _latest: GCMotion?
private var latest: GCMotion? {
get { dataLock.lock(); defer { dataLock.unlock() }; return _latest }
set { dataLock.lock(); _latest = newValue; dataLock.unlock() }
}
init(controller: GCController, slot: UInt8) {
self.pad = controller
self.slot = slot
pad.motion?.sensorsActive = true
pad.motion?.valueChangedHandler = { [weak self] motion in
guard let self = self else { return }
self.latest = motion
if let sample = self.nextSample() {
DSUServer.shared.pushSample(sample, from: self)
}
}
}
func nextSample() -> DSUMotionSample? {
guard let m = latest else { return nil }
// Extract and convert acceleration to SIMD3<Float>
let a = SIMD3<Float>(
Float(m.acceleration.x),
Float(m.acceleration.z),
-Float(m.acceleration.y)
)
// Extract, transform, and convert rotation rate to SIMD3<Float> (in radians/s)
let g = SIMD3<Float>(
Float(m.rotationRate.x),
-Float(m.rotationRate.z),
Float(m.rotationRate.y)
)
// Convert gyro rotation rate from rad/s to degrees/s
let gDeg = g * (180 / .pi)
return DSUMotionSample(
timestampUS: currentUS(),
accel: a,
gyroDeg: gDeg
)
}
}
// MARK:- Helper funcs / ext
private func uint64US(_ time: TimeInterval) -> UInt64 { UInt64(time * 1_000_000) }
private func currentUS() -> UInt64 { uint64US(CACurrentMediaTime()) }
private extension Comparable {
func clamped(to r: ClosedRange<Self>) -> Self { min(max(self, r.lowerBound), r.upperBound) }
}

View file

@ -0,0 +1,217 @@
//
// DSUServer.swift
//
// Multi-source Cemuhook-compatible DSU server.
// Created by MediaMoots on 5/17/2025.
//
//
import Foundation
import CocoaAsyncSocket // GCDAsyncUdpSocket
import zlib // CRC-32
// MARK:- DSU Motion protocol
/// One motion source == one DSU *slot* (0-7).
protocol DSUMotionProvider: AnyObject {
var slot: UInt8 { get } // unique, 0-7
var mac: [UInt8] { get } // 6-byte ID
var connectionType: UInt8 { get } // 0 = USB, 2 = BT
var batteryLevel: UInt8 { get } // 0-5 (Cemuhook)
func nextSample() -> DSUMotionSample?
}
/// Raw motion payload returned by providers.
struct DSUMotionSample {
var timestampUS: UInt64 // µs
var accel: SIMD3<Float> // G's
var gyroDeg: SIMD3<Float> // °/s
}
// MARK:- Server constants
private enum C {
static let port: UInt16 = 26_760
static let protocolVersion: UInt16 = 1_001
static let headerMagic = "DSUS"
}
// MARK:- Server core
final class DSUServer: NSObject {
// Singleton for convenience
static let shared = DSUServer()
private override init() {
serverID = UInt32.random(in: .min ... .max)
super.init()
configureSocket()
}
// MARK: Public API
func register(_ provider: DSUMotionProvider) { providers[provider.slot] = provider }
func unregister(slot: UInt8) { providers.removeValue(forKey: slot) }
/// 🔸 providers push fresh samples here.
func pushSample(_ sample: DSUMotionSample, from provider: DSUMotionProvider) {
guard let addr = lastClientAddress else { return } // no subscriber drop
sendPadData(sample: sample, from: provider, to: addr)
}
// MARK: Private
private let serverID: UInt32
private var socket: GCDAsyncUdpSocket?
private var lastClientAddress: Data?
private var providers = [UInt8 : DSUMotionProvider]() // slotprovider
private var packetNumber = [UInt8 : UInt32]() // per-slot counter
// UDP setup
private func configureSocket() {
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: .main)
do {
try socket?.bind(toPort: C.port)
try socket?.beginReceiving()
//print("🟢 DSU server listening on UDP \(C.port)")
} catch {
//print(" DSU socket error:", error)
}
}
}
// MARK:- UDP delegate
extension DSUServer: GCDAsyncUdpSocketDelegate {
func udpSocket(_ sock: GCDAsyncUdpSocket,
didReceive data: Data,
fromAddress addr: Data,
withFilterContext ctx: Any?) {
lastClientAddress = addr
// Light validation
guard data.count >= 20,
String(decoding: data[0..<4], as: UTF8.self) == C.headerMagic,
data.readUInt16LE(at: 4) == C.protocolVersion
else { return }
let type = data.readUInt32LE(at: 16)
switch type {
case 0x100001: sendPortInfo(to: addr) // client asks for port list
case 0x100002: break // subscription acknowledged
default: break
}
}
func udpSocketDidClose(_ sock: GCDAsyncUdpSocket, withError err: Error?) {
//print("UDP closed:", err?.localizedDescription ?? "nil")
lastClientAddress = nil
}
}
// MARK:- Packet helpers
private extension DSUServer {
// Header (16 bytes)
func appendHeader(into d: inout Data, payloadSize: UInt16) {
d.append(C.headerMagic.data(using: .utf8)!) // "DSUS"
d.append(C.protocolVersion.leData) // Protocol Version
d.append(payloadSize.leData) // Payload Size
d.append(Data(repeating: 0, count: 4)) // CRC-stub
d.append(serverID.leData) // Server ID
}
func patchCRC32(of packet: inout Data) {
let crc = packet.withUnsafeBytes { ptr in
crc32(0, ptr.baseAddress, uInt(packet.count))
}.littleEndian
let crcLE = UInt32(crc).littleEndian
let crcData = withUnsafeBytes(of: crcLE) { Data($0) }
packet.replaceSubrange(8..<12, with: crcData)
}
// 0x100001 DSUSPortInfo
func sendPortInfo(to addr: Data) {
for p in providers.values {
var pkt = Data()
appendHeader(into: &pkt, payloadSize: 12)
pkt.append(UInt32(0x100001).leData)
pkt.append(p.slot)
pkt.append(UInt8(2)) // connected
pkt.append(UInt8(2)) // full gyro
pkt.append(p.connectionType)
pkt.append(p.mac, count: 6)
pkt.append(p.batteryLevel)
pkt.append(UInt8(0)) // padding
patchCRC32(of: &pkt)
socket?.send(pkt, toAddress: addr, withTimeout: -1, tag: 0)
}
}
// 0x100002 DSUSPadDataRsp
func sendPadData(sample s: DSUMotionSample,
from p: DSUMotionProvider,
to addr: Data) {
var pkt = Data()
appendHeader(into: &pkt, payloadSize: 84)
pkt.append(UInt32(0x100002).leData)
pkt.append(p.slot)
pkt.append(UInt8(2)) // connected
pkt.append(UInt8(2)) // full gyro
pkt.append(p.connectionType)
pkt.append(p.mac, count: 6)
pkt.append(p.batteryLevel)
pkt.append(UInt8(1)) // is connected
let num = packetNumber[p.slot, default: 0]
pkt.append(num.leData)
packetNumber[p.slot] = num &+ 1
pkt.append(UInt16(0).leData) // buttons
pkt.append(contentsOf: [0,0]) // HOME / Touch
pkt.append(contentsOf: [128,128,128,128]) // sticks
pkt.append(Data(repeating: 0, count: 12)) // d-pad / face / trig
pkt.append(Data(repeating: 0, count: 12)) // touch 1 & 2
pkt.append(s.timestampUS.leData)
pkt.append(s.accel.x.leData)
pkt.append(s.accel.y.leData)
pkt.append(s.accel.z.leData)
pkt.append(s.gyroDeg.x.leData)
pkt.append(s.gyroDeg.y.leData)
pkt.append(s.gyroDeg.z.leData)
patchCRC32(of: &pkt)
socket?.send(pkt, toAddress: addr, withTimeout: -1, tag: 0)
}
}
// MARK:- Helper funcs / ext
private extension FixedWidthInteger {
var leData: Data {
var v = self.littleEndian
return Data(bytes: &v, count: MemoryLayout<Self>.size)
}
}
private extension Float {
var leData: Data {
var v = self
return Data(bytes: &v, count: MemoryLayout<Self>.size)
}
}
private extension Data {
func readUInt16LE(at offset: Int) -> UInt16 {
self[offset..<offset+2].withUnsafeBytes { $0.load(as: UInt16.self) }.littleEndian
}
func readUInt32LE(at offset: Int) -> UInt32 {
self[offset..<offset+4].withUnsafeBytes { $0.load(as: UInt32.self) }.littleEndian
}
}

View file

@ -8,10 +8,11 @@
import CoreHaptics
import GameController
class NativeController: Hashable {
class NativeController: Hashable, BaseController {
private var instanceID: SDL_JoystickID = -1
private var controller: OpaquePointer?
private var nativeController: GCController
private var controllerMotionProvider: ControllerMotionProvider?
private let controllerHaptics: CHHapticEngine?
private let rumbleController: RumbleController?
@ -43,6 +44,20 @@ class NativeController: Hashable {
cleanup()
}
internal func tryRegisterMotion(slot: UInt8) {
// Setup Motion
let dsuServer = DSUServer.shared
controllerMotionProvider = ControllerMotionProvider(controller: nativeController, slot: slot)
if let provider = controllerMotionProvider {
dsuServer.register(provider)
}
}
internal func tryGetMotionProvider() -> DSUMotionProvider? {
return controllerMotionProvider
}
private func setupHandheldController() {
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))

View file

@ -9,11 +9,12 @@ import Foundation
import CoreHaptics
import UIKit
class VirtualController {
class VirtualController : BaseController {
private var instanceID: SDL_JoystickID = -1
private var controller: OpaquePointer?
private let hapticEngine: CHHapticEngine?
private let rumbleController: RumbleController?
private var deviceMotionProvider: DeviceMotionProvider?
public let controllername = "MeloNX Touch Controller"
@ -37,6 +38,20 @@ class VirtualController {
setupVirtualController()
}
internal func tryRegisterMotion(slot: UInt8) {
// Setup Motion
let dsuServer = DSUServer.shared
deviceMotionProvider = DeviceMotionProvider(slot: slot)
if let provider = deviceMotionProvider {
dsuServer.register(provider)
}
}
internal func tryGetMotionProvider() -> DSUMotionProvider? {
return deviceMotionProvider
}
private func setupVirtualController() {
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))

View file

@ -189,6 +189,7 @@ class Ryujinx : ObservableObject {
public class Arguments : Observable, Codable, Equatable {
var gamepath: String
var inputids: [String]
var inputDSUServers: [String]
var resscale: Float = 1.0
var debuglogs: Bool = false
var tracelogs: Bool = false
@ -217,6 +218,7 @@ class Ryujinx : ObservableObject {
init(gamepath: String = "",
inputids: [String] = [],
inputDSUServers: [String] = [],
debuglogs: Bool = false,
tracelogs: Bool = false,
listinputids: Bool = false,
@ -244,6 +246,7 @@ class Ryujinx : ObservableObject {
) {
self.gamepath = gamepath
self.inputids = inputids
self.inputDSUServers = inputDSUServers
self.debuglogs = debuglogs
self.tracelogs = tracelogs
self.listinputids = listinputids
@ -642,6 +645,17 @@ class Ryujinx : ObservableObject {
}
}
// Append the input dsu servers (limit to 8 (used to be 4) just in case)
if !config.inputDSUServers.isEmpty {
config.inputDSUServers.prefix(8).enumerated().forEach { index, inputDSUServer in
if config.handHeldController {
args.append(contentsOf: ["\(index == 0 ? "--input-dsu-server-handheld" : "--input-dsu-server-\(index + 1)")", inputDSUServer])
} else {
args.append(contentsOf: ["--input-dsu-server-\(index + 1)", inputDSUServer])
}
}
}
args.append(contentsOf: config.additionalArgs)
return args

View file

@ -338,6 +338,25 @@ struct ContentView: View {
}
}
private func registerMotionForMatchingControllers() {
// Loop through currentControllers with index
for (index, controller) in currentControllers.enumerated() {
let slot = UInt8(index)
// Check native controllers
for (_, nativeController) in nativeControllers where nativeController.controllername == String("GC - \(controller.name)") && nativeController.tryGetMotionProvider() == nil {
nativeController.tryRegisterMotion(slot: slot)
continue
}
// Check virtual controller if active
if Ryujinx.shared.virtualController.controllername == controller.name && Ryujinx.shared.virtualController.tryGetMotionProvider() == nil {
Ryujinx.shared.virtualController.tryRegisterMotion(slot: slot)
continue
}
}
}
private func start(displayid: UInt32) {
guard let game else { return }
@ -346,10 +365,17 @@ struct ContentView: View {
configureEnvironmentVariables()
registerMotionForMatchingControllers()
if config.inputids.isEmpty {
config.inputids.append("0")
}
// Local DSU loopback to ryujinx per input id
for _ in config.inputids {
config.inputDSUServers.append("127.0.0.1:26760")
}
do {
try ryujinx.start(with: config)
} catch {

View file

@ -99,6 +99,33 @@ namespace Ryujinx.Headless.SDL2
[Option("input-id-handheld", Required = false, HelpText = "Set the input id in use for the Handheld Player.")]
public string InputIdHandheld { get; set; }
[Option("input-dsu-server-1", Required = false, HelpText = "Set the input DSU server:port in use for Player 1.")]
public string InputDSUServer1 { get; set; }
[Option("input-dsu-server-2", Required = false, HelpText = "Set the input DSU server:port in use for Player 2.")]
public string InputDSUServer2 { get; set; }
[Option("input-dsu-server-3", Required = false, HelpText = "Set the input DSU server:port in use for Player 3.")]
public string InputDSUServer3 { get; set; }
[Option("input-dsu-server-4", Required = false, HelpText = "Set the input DSU server:port in use for Player 4.")]
public string InputDSUServer4 { get; set; }
[Option("input-dsu-server-5", Required = false, HelpText = "Set the input DSU server:port in use for Player 5.")]
public string InputDSUServer5 { get; set; }
[Option("input-dsu-server-6", Required = false, HelpText = "Set the input DSU server:port in use for Player 6.")]
public string InputDSUServer6 { get; set; }
[Option("input-dsu-server-7", Required = false, HelpText = "Set the input DSU server:port in use for Player 7.")]
public string InputDSUServer7 { get; set; }
[Option("input-dsu-server-8", Required = false, HelpText = "Set the input DSU server:port in use for Player 8.")]
public string InputDSUServer8 { get; set; }
[Option("input-dsu-server-handheld", Required = false, HelpText = "Set the input DSU server:port in use for the Handheld Player.")]
public string InputDSUServerHandheld { get; set; }
[Option("enable-keyboard", Required = false, Default = false, HelpText = "Enable or disable keyboard support (Independent from controllers binding).")]
public bool EnableKeyboard { get; set; }

View file

@ -894,7 +894,7 @@ namespace Ryujinx.Headless.SDL2
return gameInfo;
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option)
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, string inputDSUServer, PlayerIndex index, Options option)
{
if (inputId == null)
{
@ -1074,6 +1074,28 @@ namespace Ryujinx.Headless.SDL2
EnableRumble = true,
},
};
// Setup DSU Motion
if (config is StandardControllerInputConfig standardConfig && !string.IsNullOrWhiteSpace(inputDSUServer))
{
var serverString = inputDSUServer.Trim();
var parts = serverString.Split(new[] { ':' }, 2);
if (parts.Length == 2 && int.TryParse(parts[1], out var port))
{
var slot = index == PlayerIndex.Handheld ? 0 : (int)index;
standardConfig.Motion = new CemuHookMotionConfigController
{
MotionBackend = MotionInputBackendType.CemuHook,
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1,
Slot = slot,
DsuServerHost = parts[0],
DsuServerPort = port,
};
}
}
}
}
else
@ -1218,9 +1240,9 @@ namespace Ryujinx.Headless.SDL2
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option)
static void LoadPlayerConfiguration(string inputProfileName, string inputId, string inputDSUServer, PlayerIndex index, Options option)
{
InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index, option);
InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, inputDSUServer, index, option);
if (inputConfig != null)
{
@ -1228,15 +1250,15 @@ namespace Ryujinx.Headless.SDL2
}
}
LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1, option);
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2, option);
LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3, option);
LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4, option);
LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5, option);
LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6, option);
LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7, option);
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8, option);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld, option);
LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, option.InputDSUServer1, PlayerIndex.Player1, option);
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, option.InputDSUServer2, PlayerIndex.Player2, option);
LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, option.InputDSUServer3, PlayerIndex.Player3, option);
LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, option.InputDSUServer4, PlayerIndex.Player4, option);
LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, option.InputDSUServer5, PlayerIndex.Player5, option);
LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, option.InputDSUServer6, PlayerIndex.Player6, option);
LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, option.InputDSUServer7, PlayerIndex.Player7, option);
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, option.InputDSUServer8, PlayerIndex.Player8, option);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, option.InputDSUServerHandheld, PlayerIndex.Handheld, option);
if (_inputConfiguration.Count == 0)
{