Update Rumble and Motion to use the iPhones sensors when using Backbone controllers

This commit is contained in:
Stossy11 2025-06-18 16:21:17 +10:00
parent 2348f5f4b1
commit 71056542c4
4 changed files with 32 additions and 41 deletions

View file

@ -28,6 +28,10 @@ MeloNX works on iPhone XS/XR and later and iPad 8th Gen and later. Check out the
- Recommended Device: iPhone 15 Pro or newer. - Recommended Device: iPhone 15 Pro or newer.
- Low-End Recommended Device: iPhone 13 Pro. - Low-End Recommended Device: iPhone 13 Pro.
## Discord Server
We have a discord server!
- https://discord.gg/melonx
## How to install ## How to install
@ -141,12 +145,12 @@ If having Issues installing firmware (Make sure your keys are installed first)
- **GPU** - **GPU**
The GPU emulator emulates the Switch's Maxwell GPU using Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. The GPU emulator emulates the Switch's Maxwell GPU using Metal (via MoltenVK) APIs through a custom build of Silk.NET.
- **Input** - **Input**
We currently have support for keyboard, touch input, JoyCon input support, and nearly all controllers. We currently have support for keyboard, touch input, JoyCon input support, and nearly all MFI controllers.
Motion controls are natively supported in most cases. Motion controls are natively supported in most cases, however JoyCons do not have motion support doe to an iOS limitation.
- **DLC & Modifications** - **DLC & Modifications**
@ -157,14 +161,13 @@ If having Issues installing firmware (Make sure your keys are installed first)
The emulator has settings for enabling or disabling some logging, remapping controllers, and more. The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
## License # License
This software is licensed under the terms of the [MeloNX license (Based on MIT License)](LICENSE.txt). This software is licensed under the terms of the [MeloNX license](LICENSE.txt).
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3. This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details. See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
## Credits # Credits
- [Ryujinx](https://github.com/ryujinx-mirror/ryujinx) is used for the base of this emulator. (link is to ryujinx-mirror since they were supportive) - [Ryujinx](https://github.com/ryujinx-mirror/ryujinx) is used for the base of this emulator. (link is to ryujinx-mirror since they were supportive)
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system. - [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation. - [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.

View file

@ -62,7 +62,6 @@ func allocateTest() -> Bool {
memcpy(jitMemory, code, code.count) memcpy(jitMemory, code, code.count)
if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 { if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 {
return false return false
} }

View file

@ -12,8 +12,7 @@ class NativeController: Hashable, BaseController {
private var instanceID: SDL_JoystickID = -1 private var instanceID: SDL_JoystickID = -1
private var controller: OpaquePointer? private var controller: OpaquePointer?
private var nativeController: GCController private var nativeController: GCController
private var controllerMotionProvider: ControllerMotionProvider? private var controllerMotionProvider: DSUMotionProvider?
private var deviceMotionProvider: DeviceMotionProvider?
private let controllerHaptics: CHHapticEngine? private let controllerHaptics: CHHapticEngine?
private let rumbleController: RumbleController? private let rumbleController: RumbleController?
@ -22,21 +21,21 @@ class NativeController: Hashable, BaseController {
init(_ controller: GCController) { init(_ controller: GCController) {
nativeController = controller nativeController = controller
controllerHaptics = nativeController.haptics?.createEngine(withLocality: .default) var ncontrollerHaptics = nativeController.haptics?.createEngine(withLocality: .default)
let vendorName = nativeController.vendorName ?? "Unknown"
var usesdeviceHaptics = (ncontrollerHaptics == nil || vendorName.lowercased().hasSuffix("backbone") || vendorName.lowercased() == "backbone one")
controllerHaptics = usesdeviceHaptics ? ncontrollerHaptics : try? CHHapticEngine()
// Make sure the haptic engine exists before attempting to start it or initialize the controller. // Make sure the haptic engine exists before attempting to start it or initialize the controller.
if let hapticsEngine = controllerHaptics { if let hapticsEngine = controllerHaptics {
do { do {
try hapticsEngine.start() try hapticsEngine.start()
rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: 2.5) rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: 1.2)
// print("CHHapticEngine started and RumbleController initialized.")
} catch { } catch {
// print("Error starting CHHapticEngine: \(error.localizedDescription)")
rumbleController = nil rumbleController = nil
} }
} else { } else {
// print("CHHapticEngine is nil. Cannot initialize RumbleController.")
rumbleController = nil rumbleController = nil
} }
setupHandheldController() setupHandheldController()
@ -49,27 +48,17 @@ class NativeController: Hashable, BaseController {
internal func tryRegisterMotion(slot: UInt8) { internal func tryRegisterMotion(slot: UInt8) {
// Setup Motion // Setup Motion
let dsuServer = DSUServer.shared let dsuServer = DSUServer.shared
let vendorName = nativeController.vendorName ?? "Unknown"
var usesdevicemotion = (vendorName.lowercased() == "Joy-Con (l/R)".lowercased() || vendorName.lowercased().hasSuffix("backbone") || vendorName.lowercased() == "backbone one")
controllerMotionProvider = usesdevicemotion ? DeviceMotionProvider(slot: slot) : ControllerMotionProvider(controller: nativeController, slot: slot)
if nativeController.vendorName?.lowercased() == "Joy-Con (l/R)".lowercased() {
deviceMotionProvider = DeviceMotionProvider(slot: slot)
if let provider = deviceMotionProvider {
dsuServer.register(provider)
}
} else {
controllerMotionProvider = ControllerMotionProvider(controller: nativeController, slot: slot)
if let provider = controllerMotionProvider { if let provider = controllerMotionProvider {
dsuServer.register(provider) dsuServer.register(provider)
} }
} }
}
internal func tryGetMotionProvider() -> DSUMotionProvider? { internal func tryGetMotionProvider() -> DSUMotionProvider? { return controllerMotionProvider }
if nativeController.vendorName == "Joy-Con (l/R)" {
return deviceMotionProvider
} else {
return controllerMotionProvider
}
}
private func setupHandheldController() { private func setupHandheldController() {
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 { if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
@ -94,38 +83,39 @@ class NativeController: Hashable, BaseController {
}, },
SetPlayerIndex: { userdata, playerIndex in SetPlayerIndex: { userdata, playerIndex in
// print("Player index set to \(playerIndex)") // print("Player index set to \(playerIndex)")
guard let userdata, let player = GCControllerPlayerIndex(rawValue: Int(playerIndex)) else { return }
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
_self.nativeController.playerIndex = player
}, },
Rumble: { userdata, lowFreq, highFreq in Rumble: { userdata, lowFreq, highFreq in
// print("Rumble with \(lowFreq), \(highFreq)")
guard let userdata else { return 0 } guard let userdata else { return 0 }
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue() let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
_self.rumbleController?.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq)) _self.rumbleController?.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
return 0 return 0
}, },
RumbleTriggers: { userdata, leftRumble, rightRumble in RumbleTriggers: { userdata, leftRumble, rightRumble in
// print("Trigger rumble with \(leftRumble), \(rightRumble)")
return 0 return 0
}, },
SetLED: { userdata, red, green, blue in SetLED: { userdata, red, green, blue in
// print("Set LED to RGB(\(red), \(green), \(blue))") guard let userdata else { return 0 }
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
guard let light = _self.nativeController.light else { return 0 }
light.color = .init(red: Float(red), green: Float(green), blue: Float(blue))
return 0 return 0
}, },
SendEffect: { userdata, data, size in SendEffect: { userdata, data, size in
// print("Effect sent with size \(size)")
return 0 return 0
} }
) )
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1) instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)
if instanceID < 0 { if instanceID < 0 {
// print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
return return
} }
controller = SDL_GameControllerOpen(Int32(instanceID)) controller = SDL_GameControllerOpen(Int32(instanceID))
if controller == nil { if controller == nil {
// print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
return return
} }
@ -214,7 +204,6 @@ class NativeController: Hashable, BaseController {
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) { func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
guard controller != nil else { return } guard controller != nil else { return }
// // print("Button: \(button.rawValue) {state: \(state)}")
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) { if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
let value: Int = (state == 1) ? 32767 : 0 let value: Int = (state == 1) ? 32767 : 0