diff --git a/README.md b/README.md index cf186e833..640527850 100644 --- a/README.md +++ b/README.md @@ -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. - Low-End Recommended Device: iPhone 13 Pro. +## Discord Server + +We have a discord server! + - https://discord.gg/melonx ## How to install @@ -141,12 +145,12 @@ If having Issues installing firmware (Make sure your keys are installed first) - **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** - We currently have support for keyboard, touch input, JoyCon input support, and nearly all controllers. - Motion controls are natively supported in most cases. + We currently have support for keyboard, touch input, JoyCon input support, and nearly all MFI controllers. + Motion controls are natively supported in most cases, however JoyCons do not have motion support doe to an iOS limitation. - **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. -## 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. 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) - [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system. - [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation. diff --git a/src/MeloNX/MeloNX.xcconfig b/src/MeloNX/MeloNX.xcconfig index 42fd383c3..2adb67484 100644 --- a/src/MeloNX/MeloNX.xcconfig +++ b/src/MeloNX/MeloNX.xcconfig @@ -8,4 +8,4 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 -VERSION = 2.0 +VERSION = 2.0.1 diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index f0485b609..b4c4648c6 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -788,6 +788,9 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); GCC_OPTIMIZATION_LEVEL = z; GENERATE_INFOPLIST_FILE = YES; @@ -1039,6 +1042,10 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = "$(VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; @@ -1197,6 +1204,9 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); GCC_OPTIMIZATION_LEVEL = z; GENERATE_INFOPLIST_FILE = YES; @@ -1448,6 +1458,10 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = "$(VERSION)"; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate index 00f7f4a41..b4c024fa9 100644 Binary files a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate and b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX.xcscheme b/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX.xcscheme index ae7c58c0b..f79f4ed54 100644 --- a/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX.xcscheme +++ b/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX.xcscheme @@ -58,6 +58,7 @@ buildConfiguration = "Release" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" @@ -68,7 +69,8 @@ allowLocationSimulation = "YES" queueDebuggingEnabled = "No" consoleMode = "0" - structuredConsoleMode = "2"> + structuredConsoleMode = "2" + disablePerformanceAntipatternChecker = "YES"> #include + #ifdef __cplusplus extern "C" { #endif + struct GameInfo { long FileSize; char TitleName[512]; @@ -41,6 +43,10 @@ struct DlcNcaList { struct DlcNcaListItem* items; }; +typedef void (^SwiftCallback)(NSString *result); + +void RegisterCallback(NSString *identifier, SwiftCallback callback); + extern struct GameInfo get_game_info(int, char*); extern struct DlcNcaList get_dlc_nca_list(const char* titleIdPtr, const char* pathPtr); diff --git a/src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift b/src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift index 385457516..fd5b7c68e 100644 --- a/src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift +++ b/src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift @@ -62,7 +62,6 @@ func allocateTest() -> Bool { memcpy(jitMemory, code, code.count) - if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 { return false } diff --git a/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/NativeController.swift b/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/NativeController.swift index fae70410c..d0cdc47e3 100644 --- a/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/NativeController.swift +++ b/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/NativeController.swift @@ -12,8 +12,7 @@ class NativeController: Hashable, BaseController { private var instanceID: SDL_JoystickID = -1 private var controller: OpaquePointer? private var nativeController: GCController - private var controllerMotionProvider: ControllerMotionProvider? - private var deviceMotionProvider: DeviceMotionProvider? + private var controllerMotionProvider: DSUMotionProvider? private let controllerHaptics: CHHapticEngine? private let rumbleController: RumbleController? @@ -22,21 +21,21 @@ class NativeController: Hashable, BaseController { init(_ controller: GCController) { 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. if let hapticsEngine = controllerHaptics { do { try hapticsEngine.start() - rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: 2.5) - - // print("CHHapticEngine started and RumbleController initialized.") + rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: 1.2) } catch { - // print("Error starting CHHapticEngine: \(error.localizedDescription)") rumbleController = nil } } else { - // print("CHHapticEngine is nil. Cannot initialize RumbleController.") rumbleController = nil } setupHandheldController() @@ -49,27 +48,17 @@ class NativeController: Hashable, BaseController { internal func tryRegisterMotion(slot: UInt8) { // Setup Motion 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") - 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 { - dsuServer.register(provider) - } + controllerMotionProvider = usesdevicemotion ? DeviceMotionProvider(slot: slot) : ControllerMotionProvider(controller: nativeController, slot: slot) + + if let provider = controllerMotionProvider { + dsuServer.register(provider) } } - internal func tryGetMotionProvider() -> DSUMotionProvider? { - if nativeController.vendorName == "Joy-Con (l/R)" { - return deviceMotionProvider - } else { - return controllerMotionProvider - } - } + internal func tryGetMotionProvider() -> DSUMotionProvider? { return controllerMotionProvider } private func setupHandheldController() { if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 { @@ -94,38 +83,39 @@ class NativeController: Hashable, BaseController { }, SetPlayerIndex: { userdata, playerIndex in // print("Player index set to \(playerIndex)") + guard let userdata, let player = GCControllerPlayerIndex(rawValue: Int(playerIndex)) else { return } + let _self = Unmanaged.fromOpaque(userdata).takeUnretainedValue() + _self.nativeController.playerIndex = player }, Rumble: { userdata, lowFreq, highFreq in - // print("Rumble with \(lowFreq), \(highFreq)") guard let userdata else { return 0 } let _self = Unmanaged.fromOpaque(userdata).takeUnretainedValue() _self.rumbleController?.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq)) return 0 }, RumbleTriggers: { userdata, leftRumble, rightRumble in - // print("Trigger rumble with \(leftRumble), \(rightRumble)") return 0 }, SetLED: { userdata, red, green, blue in - // print("Set LED to RGB(\(red), \(green), \(blue))") + guard let userdata else { return 0 } + let _self = Unmanaged.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 }, SendEffect: { userdata, data, size in - // print("Effect sent with size \(size)") 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 { - // print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))") return } controller = SDL_GameControllerOpen(Int32(instanceID)) if controller == nil { - // print("Failed to create virtual controller: \(String(cString: SDL_GetError()))") return } @@ -214,7 +204,6 @@ class NativeController: Hashable, BaseController { func setButtonState(_ state: Uint8, for button: VirtualControllerButton) { guard controller != nil else { return } -// // print("Button: \(button.rawValue) {state: \(state)}") if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) { let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT let value: Int = (state == 1) ? 32767 : 0 diff --git a/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift b/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift index 8836d98b6..3c09d1905 100644 --- a/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift +++ b/src/MeloNX/MeloNX/App/Core/Ryujinx/Ryujinx.swift @@ -213,7 +213,6 @@ class Ryujinx : ObservableObject { var language: SystemLanguage = .americanEnglish var regioncode: SystemRegionCode = .usa var handHeldController: Bool = true - var backendMultithreading: Bool = true init(gamepath: String = "", @@ -242,7 +241,6 @@ class Ryujinx : ObservableObject { language: SystemLanguage = .americanEnglish, regioncode: SystemRegionCode = .usa, handHeldController: Bool = false, - backendMultithreading: Bool = true ) { self.gamepath = gamepath self.inputids = inputids @@ -270,7 +268,6 @@ class Ryujinx : ObservableObject { self.language = language self.regioncode = regioncode self.handHeldController = handHeldController - self.backendMultithreading = backendMultithreading } @@ -538,9 +535,9 @@ class Ryujinx : ObservableObject { args.append(contentsOf: ["--memory-manager-mode", config.memoryManagerMode]) - // args.append(contentsOf: ["--exclusive-fullscreen", String(true)]) - // args.append(contentsOf: ["--exclusive-fullscreen-width", "\(Int(UIScreen.main.bounds.width))"]) - // args.append(contentsOf: ["--exclusive-fullscreen-height", "\(Int(UIScreen.main.bounds.height))"]) + args.append(contentsOf: ["--exclusive-fullscreen", String(true)]) + args.append(contentsOf: ["--exclusive-fullscreen-width", "\(Int(UIScreen.main.bounds.width))"]) + args.append(contentsOf: ["--exclusive-fullscreen-height", "\(Int(UIScreen.main.bounds.height))"]) // We don't need this. Ryujinx should handle it fine :3 // this also causes crashes in some games :3 @@ -568,13 +565,9 @@ class Ryujinx : ObservableObject { args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue]) - // args.append(contentsOf: ["--system-timezone", TimeZone.current.identifier]) + args.append(contentsOf: ["--system-timezone", TimeZone.current.identifier]) - args.append(contentsOf: ["--system-time-offset", String(TimeZone.current.secondsFromGMT())]) - - if !config.backendMultithreading { - args.append(contentsOf: ["--backend-multithreading", "Off"]) - } + // args.append(contentsOf: ["--system-time-offset", String(TimeZone.current.secondsFromGMT())]) if config.nintendoinput { diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/ControllerView.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/ControllerView.swift index e538ecac5..aaf5f758a 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/ControllerView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/ControllerView/ControllerView.swift @@ -264,7 +264,6 @@ struct ABXYView: View { struct ButtonView: View { var button: VirtualControllerButton - var callback: (() -> Void)? = nil @AppStorage("onscreenhandheld") var onscreenjoy: Bool = false @AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0 @@ -345,29 +344,23 @@ struct ButtonView: View { } private func handleButtonPress() { - if let callback { - callback() + guard !isPressed || istoggle else { return } + + if istoggle { + toggleState.toggle() + isPressed = toggleState + let value = toggleState ? 1 : 0 + Ryujinx.shared.virtualController.setButtonState(Uint8(value), for: button) + Haptics.shared.play(.medium) } else { - guard !isPressed || istoggle else { return } - - if istoggle { - toggleState.toggle() - isPressed = toggleState - let value = toggleState ? 1 : 0 - Ryujinx.shared.virtualController.setButtonState(Uint8(value), for: button) - Haptics.shared.play(.medium) - } else { - isPressed = true - Ryujinx.shared.virtualController.setButtonState(1, for: button) - Haptics.shared.play(.medium) - } + isPressed = true + Ryujinx.shared.virtualController.setButtonState(1, for: button) + Haptics.shared.play(.medium) } } private func handleButtonRelease() { if istoggle { return } - - if let callback { return } guard isPressed else { return } @@ -404,23 +397,40 @@ struct ButtonView: View { // Centralized button configuration private var buttonConfig: ButtonConfiguration { switch button { - case .A: return .init(iconName: "a.circle.fill") - case .B: return .init(iconName: "b.circle.fill") - case .X: return .init(iconName: "x.circle.fill") - case .Y: return .init(iconName: "y.circle.fill") - case .leftStick: return .init(iconName: "l.joystick.press.down.fill") - case .rightStick: return .init(iconName: "r.joystick.press.down.fill") - case .dPadUp: return .init(iconName: "arrowtriangle.up.circle.fill") - case .dPadDown: return .init(iconName: "arrowtriangle.down.circle.fill") - case .dPadLeft: return .init(iconName: "arrowtriangle.left.circle.fill") - case .dPadRight: return .init(iconName: "arrowtriangle.right.circle.fill") - case .leftTrigger: return .init(iconName: "zl.rectangle.roundedtop.fill") - case .rightTrigger: return .init(iconName: "zr.rectangle.roundedtop.fill") - case .leftShoulder: return .init(iconName: "l.rectangle.roundedbottom.fill") - case .rightShoulder: return .init(iconName: "r.rectangle.roundedbottom.fill") - case .start: return .init(iconName: "plus.circle.fill") - case .back: return .init(iconName: "minus.circle.fill") - case .guide: return .init(iconName: "gearshape.fill") + case .A: + return ButtonConfiguration(iconName: "a.circle.fill") + case .B: + return ButtonConfiguration(iconName: "b.circle.fill") + case .X: + return ButtonConfiguration(iconName: "x.circle.fill") + case .Y: + return ButtonConfiguration(iconName: "y.circle.fill") + case .leftStick: + return ButtonConfiguration(iconName: "l.joystick.press.down.fill") + case .rightStick: + return ButtonConfiguration(iconName: "r.joystick.press.down.fill") + case .dPadUp: + return ButtonConfiguration(iconName: "arrowtriangle.up.circle.fill") + case .dPadDown: + return ButtonConfiguration(iconName: "arrowtriangle.down.circle.fill") + case .dPadLeft: + return ButtonConfiguration(iconName: "arrowtriangle.left.circle.fill") + case .dPadRight: + return ButtonConfiguration(iconName: "arrowtriangle.right.circle.fill") + case .leftTrigger: + return ButtonConfiguration(iconName: "zl.rectangle.roundedtop.fill") + case .rightTrigger: + return ButtonConfiguration(iconName: "zr.rectangle.roundedtop.fill") + case .leftShoulder: + return ButtonConfiguration(iconName: "l.rectangle.roundedbottom.fill") + case .rightShoulder: + return ButtonConfiguration(iconName: "r.rectangle.roundedbottom.fill") + case .start: + return ButtonConfiguration(iconName: "plus.circle.fill") + case .back: + return ButtonConfiguration(iconName: "minus.circle.fill") + case .guide: + return ButtonConfiguration(iconName: "house.circle.fill") } } diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/EmulationView.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/EmulationView.swift index 43043a43c..58a2b6d49 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/EmulationView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/EmulationView.swift @@ -142,6 +142,15 @@ struct EmulationView: View { // print(cool) } } + + RegisterCallback("exit-emulation") { cool in + DispatchQueue.main.async { + print(cool) + startgame = nil + stop_emulation() + try? Ryujinx.shared.stop() + } + } } .onChange(of: scenePhase) { newPhase in // Detect when the app enters the background diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/InGameSettingsManager/InGameSettingsManager.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/InGameSettingsManager/InGameSettingsManager.swift index 10958d1e8..acbb15195 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/InGameSettingsManager/InGameSettingsManager.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/EmulationView/InGameSettingsManager/InGameSettingsManager.swift @@ -35,7 +35,6 @@ class InGameSettingsManager: PerGameSettingsManaging { Ryujinx.shared.config = config[currentgame.titleId] let args = Ryujinx.shared.buildCommandLineArgs(from: config[currentgame.titleId] ?? Ryujinx.Arguments()) - // Convert Arguments to ones that Ryujinx can Read let cArgs = args.map { strdup($0) } defer { cArgs.forEach { free($0) } } var argvPtrs = cArgs diff --git a/src/MeloNX/MeloNX/App/Views/Main/Emulation/MetalView/MeloMTKView.swift b/src/MeloNX/MeloNX/App/Views/Main/Emulation/MetalView/MeloMTKView.swift index 442065c85..25d296012 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/Emulation/MetalView/MeloMTKView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/Emulation/MetalView/MeloMTKView.swift @@ -9,10 +9,10 @@ import MetalKit import UIKit class MeloMTKView: MTKView { - private var activeTouches: [UITouch] = [] private var ignoredTouches: Set = [] - + private var touchIndexMap: [UITouch: Int] = [:] + private let baseWidth: CGFloat = 1280 private let baseHeight: CGFloat = 720 private var aspectRatio: AspectRatio = .fixed16x9 @@ -84,83 +84,112 @@ class MeloMTKView: MTKView { return CGPoint(x: scaledX, y: scaledY) } + private func getNextAvailableIndex() -> Int { + for i in 0.., with event: UIEvent?) { super.touchesBegan(touches, with: event) - + let disabled = UserDefaults.standard.bool(forKey: "disableTouch") + guard !disabled else { return } setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9) - - if !disabled { - for touch in touches { - let location = touch.location(in: self) - if scaleToTargetResolution(location) == nil { - ignoredTouches.insert(touch) - continue - } - - activeTouches.append(touch) - let index = activeTouches.firstIndex(of: touch)! - - let scaledLocation = scaleToTargetResolution(location)! - // // print("Touch began at: \(scaledLocation) and \(self.aspectRatio)") - touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index)) + + for touch in touches { + let location = touch.location(in: self) + guard let scaledLocation = scaleToTargetResolution(location) else { + ignoredTouches.insert(touch) + continue } + + let index = getNextAvailableIndex() + touchIndexMap[touch] = index + activeTouches.append(touch) + + touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index)) } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { super.touchesEnded(touches, with: event) - + let disabled = UserDefaults.standard.bool(forKey: "disableTouch") - - setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9) - - if !disabled { + guard !disabled else { for touch in touches { - if ignoredTouches.contains(touch) { - ignoredTouches.remove(touch) - continue - } - + ignoredTouches.remove(touch) if let index = activeTouches.firstIndex(of: touch) { activeTouches.remove(at: index) - - // // print("Touch ended for index \(index)") - touch_ended(Int32(index)) } + touchIndexMap.removeValue(forKey: touch) + } + return + } + + for touch in touches { + if ignoredTouches.remove(touch) != nil { + continue + } + + if let touchIndex = touchIndexMap[touch] { + touch_ended(Int32(touchIndex)) + + if let arrayIndex = activeTouches.firstIndex(of: touch) { + activeTouches.remove(at: arrayIndex) + } + touchIndexMap.removeValue(forKey: touch) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { super.touchesMoved(touches, with: event) - + let disabled = UserDefaults.standard.bool(forKey: "disableTouch") + guard !disabled else { return } setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9) - - if !disabled { - for touch in touches { - if ignoredTouches.contains(touch) { - continue - } - - let location = touch.location(in: self) - guard let scaledLocation = scaleToTargetResolution(location) else { - if let index = activeTouches.firstIndex(of: touch) { - activeTouches.remove(at: index) - // // print("Touch left active area, removed index \(index)") - touch_ended(Int32(index)) - } - continue - } - - if let index = activeTouches.firstIndex(of: touch) { - // // print("Touch moved to: \(scaledLocation)") - touch_moved(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index)) - } + + for touch in touches { + if ignoredTouches.contains(touch) { + continue } + + guard let touchIndex = touchIndexMap[touch] else { + continue + } + + let location = touch.location(in: self) + guard let scaledLocation = scaleToTargetResolution(location) else { + touch_ended(Int32(touchIndex)) + + if let arrayIndex = activeTouches.firstIndex(of: touch) { + activeTouches.remove(at: arrayIndex) + } + touchIndexMap.removeValue(forKey: touch) + ignoredTouches.insert(touch) + continue + } + + touch_moved(Float(scaledLocation.x), Float(scaledLocation.y), Int32(touchIndex)) } } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + touchesEnded(touches, with: event) + } + + + func resetTouchTracking() { + activeTouches.removeAll() + ignoredTouches.removeAll() + touchIndexMap.removeAll() + } } + diff --git a/src/MeloNX/MeloNX/App/Views/Main/UI/ContentView.swift b/src/MeloNX/MeloNX/App/Views/Main/UI/ContentView.swift index 0c5e6f02e..1eb05187d 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/UI/ContentView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/UI/ContentView.swift @@ -81,8 +81,6 @@ struct ContentView: View { _settings = State(initialValue: defaultSettings) - // print(SDL_CONTROLLER_BUTTON_LEFTSTICK.rawValue) - initializeSDL() } @@ -132,7 +130,6 @@ struct ContentView: View { JITPopover() { ryujinx.jitenabled = false } - // .interactiveDismissDisabled() } } @@ -153,11 +150,8 @@ struct ContentView: View { refreshControllersList() } - UserDefaults.standard.set(false, forKey: "lockInApp") - // print(MTLHud.shared.isEnabled) - initControllerObservers() Air.play(AnyView( @@ -166,7 +160,6 @@ struct ContentView: View { refreshControllersList() - ryujinx.addGames() checkJitStatus() @@ -287,7 +280,6 @@ struct ContentView: View { queue: .main ) { notification in if let controller = notification.object as? GCController { - // print("Controller connected: \(controller.productCategory)") nativeControllers[controller] = .init(controller) refreshControllersList() } @@ -299,7 +291,8 @@ struct ContentView: View { queue: .main ) { notification in if let controller = notification.object as? GCController { - // print("Controller disconnected: \(controller.productCategory)") + currentControllers = [] + controllersList = [] nativeControllers[controller]?.cleanup() nativeControllers[controller] = nil refreshControllersList() @@ -316,6 +309,9 @@ struct ContentView: View { } private func refreshControllersList() { + currentControllers = [] + controllersList = [] + controllersList = ryujinx.getConnectedControllers() if let onscreen = controllersList.first(where: { $0.name == ryujinx.virtualController.controllername }) { @@ -324,8 +320,6 @@ struct ContentView: View { controllersList.removeAll(where: { $0.id == "0" || (!$0.name.starts(with: "GC - ") && $0 != onscreencontroller) }) controllersList.mutableForEach { $0.name = $0.name.replacingOccurrences(of: "GC - ", with: "") } - - currentControllers = [] if controllersList.count == 1 { currentControllers.append(controllersList[0]) diff --git a/src/MeloNX/MeloNX/App/Views/Main/UI/GamesList/GameListView.swift b/src/MeloNX/MeloNX/App/Views/Main/UI/GamesList/GameListView.swift index 6125bdae9..47124f48e 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/UI/GamesList/GameListView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/UI/GamesList/GameListView.swift @@ -1252,3 +1252,16 @@ extension View { } } } + + +extension View { + @available(iOS, introduced: 14.0, deprecated: 19.0, message: "") + func glassEffect(_ style: Glass, in shape: some Shape) -> some View { + return self + } +} + +@available(iOS, introduced: 14.0, deprecated: 19.0, message: "") +struct Glass: Hashable { + static var regular = Glass() +} diff --git a/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/SettingsView.swift b/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/SettingsView.swift index d8f4a91c4..4205d284f 100644 --- a/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/SettingsView.swift +++ b/src/MeloNX/MeloNX/App/Views/Main/UI/SettingsView/SettingsView.swift @@ -1129,9 +1129,6 @@ struct SettingsViewNew: View { Divider() - SettingsToggle(isOn: config.backendMultithreading, icon: "inset.filled.rectangle.and.person.filled", label: "Backend Multithreading") - - Divider() if MTLHud.shared.canMetalHud { SettingsToggle(isOn: $metalHudEnabler.metalHudEnabled, icon: "speedometer", label: "Metal Performance HUD") diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/Info.plist b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/Info.plist index 6b69c67ad..176855fa8 100644 Binary files a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/Info.plist and b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/Info.plist differ diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/RyujinxHelper b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/RyujinxHelper index b89e9d5b0..b411095c9 100755 Binary files a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/RyujinxHelper and b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/RyujinxHelper differ diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/_CodeSignature/CodeResources b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/_CodeSignature/CodeResources index 0483b32cc..a342a7acb 100644 --- a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/_CodeSignature/CodeResources +++ b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/RyujinxHelper.framework/_CodeSignature/CodeResources @@ -10,7 +10,7 @@ Info.plist - RTwvCLsTMs+YfZ9ZeF25QYe7/LE= + GYWZONTCP5su4yOAk0d5jCd2K88= Modules/module.modulemap diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index 3f6547396..e7fb5f1f7 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -699,13 +699,21 @@ namespace Ryujinx.Graphics.Vulkan if (_dirty.HasFlag(DirtyFlags.Texture)) { - if (false) + if (true) { - UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp); + try + { + UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); + } + catch (Exception e) + { + UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp); + } } else { - try { + try + { UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); } catch (Exception e) @@ -756,7 +764,7 @@ namespace Ryujinx.Graphics.Vulkan if (info.Buffer.Handle == 0) { info.Buffer = dummyBuffer?.Get(cbs).Value ?? default; - info.Offset = 0; + // info.Offset = 0; info.Range = Vk.WholeSize; } @@ -791,6 +799,13 @@ namespace Ryujinx.Graphics.Vulkan } } + var dummyImageInfo = new DescriptorImageInfo + { + ImageView = _dummyTexture.GetImageView().Get(cbs).Value, + Sampler = _dummySampler.GetSampler().Get(cbs).Value, + ImageLayout = ImageLayout.General + }; + DescriptorSetTemplate template = program.Templates[setIndex]; DescriptorSetTemplateWriter tu = _templateUpdater.Begin(template); @@ -864,12 +879,12 @@ namespace Ryujinx.Graphics.Vulkan if (texture.ImageView.Handle == 0) { - texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; + texture.ImageView = dummyImageInfo.ImageView; } if (texture.Sampler.Handle == 0) { - texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; + texture.Sampler = dummyImageInfo.Sampler; } } @@ -909,7 +924,7 @@ namespace Ryujinx.Graphics.Vulkan for (int i = 0; i < count; i++) { - images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default; + images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView; } tu.Push(images[..count]); @@ -946,23 +961,25 @@ namespace Ryujinx.Graphics.Vulkan _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp) { int setIndex = PipelineBase.TextureSetIndex; - ResourceBindingSegment[] bindingSegments = program.BindingSegments[setIndex]; + var bindingSegments = program.BindingSegments[setIndex]; if (bindingSegments.Length == 0) { return; } - if (_updateDescriptorCacheCbIndex) + var dummyImageInfo = new DescriptorImageInfo { - _updateDescriptorCacheCbIndex = false; - program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex); - } + ImageView = _dummyTexture.GetImageView().Get(cbs).Value, + Sampler = _dummySampler.GetSampler().Get(cbs).Value, + ImageLayout = ImageLayout.General + }; - DescriptorSetCollection dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs); + var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs); foreach (ResourceBindingSegment segment in bindingSegments) { @@ -973,56 +990,78 @@ namespace Ryujinx.Graphics.Vulkan { if (segment.Type != ResourceType.BufferTexture) { - Span textures = _textures; - for (int i = 0; i < count; i++) { - ref DescriptorImageInfo texture = ref textures[i]; - ref TextureRef refs = ref _textureRefs[binding + i]; + int index = binding + i; + ref var textureRef = ref _textureRefs[index]; - texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default; - texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + var imageView = textureRef.ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView; + var sampler = textureRef.Sampler?.Get(cbs).Value ?? dummyImageInfo.Sampler; - if (texture.ImageView.Handle == 0) + var imageInfo = new DescriptorImageInfo { - texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value; - } + ImageView = imageView.Handle != 0 ? imageView : dummyImageInfo.ImageView, + Sampler = sampler.Handle != 0 ? sampler : dummyImageInfo.Sampler, + ImageLayout = ImageLayout.General + }; - if (texture.Sampler.Handle == 0) - { - texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value; - } + dsc.UpdateImages(0, index, new[] { imageInfo }, DescriptorType.CombinedImageSampler); } - - dsc.UpdateImages(0, binding, textures[..count], DescriptorType.CombinedImageSampler); } else { - Span bufferTextures = _bufferTextures; - for (int i = 0; i < count; i++) { - bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default; + int index = binding + i; + var bufferView = _bufferTextureRefs[index]?.GetBufferView(cbs, false) ?? default; + dsc.UpdateBufferImages(0, index, new[] { bufferView }, DescriptorType.UniformTexelBuffer); } - - dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer); } } else { + var arrayRef = _textureArrayRefs[binding]; + if (segment.Type != ResourceType.BufferTexture) { - dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler); + var imageInfos = arrayRef.Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler); + if (imageInfos != null) + { + for (int i = 0; i < imageInfos.Length && i < count; i++) + { + dsc.UpdateImages(0, binding + i, new[] { imageInfos[i] }, DescriptorType.CombinedImageSampler); + } + } + else + { + for (int i = 0; i < count; i++) + { + dsc.UpdateImages(0, binding + i, new[] { dummyImageInfo }, DescriptorType.CombinedImageSampler); + } + } } else { - dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer); + var bufferViews = arrayRef.Array.GetBufferViews(cbs); + if (bufferViews != null) + { + for (int i = 0; i < bufferViews.Length && i < count; i++) + { + dsc.UpdateBufferImages(0, binding + i, new[] { bufferViews[i] }, DescriptorType.UniformTexelBuffer); + } + } + else + { + for (int i = 0; i < count; i++) + { + dsc.UpdateBufferImages(0, binding + i, new[] { default(BufferView) }, DescriptorType.UniformTexelBuffer); + } + } } } } - DescriptorSet[] sets = dsc.GetSets(); - + var sets = dsc.GetSets(); _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty); } diff --git a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs index 439fcb8ea..205be5aeb 100644 --- a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs +++ b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs @@ -188,15 +188,21 @@ namespace Ryujinx.Graphics.Vulkan var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs); + DescriptorSetTemplate template = program.Templates[setIndex]; + + DescriptorSetTemplateWriter tu = templateUpdater.Begin(template); + if (!_isBuffer) { - dsc.UpdateImages(0, 0, GetImageInfos(_gd, cbs, dummyTexture), DescriptorType.StorageImage); + tu.Push(GetImageInfos(_gd, cbs, dummyTexture)); } else { - dsc.UpdateBufferImages(0, 0, GetBufferViews(cbs), DescriptorType.StorageTexelBuffer); + tu.Push(GetBufferViews(cbs)); } + templateUpdater.Commit(_gd, device, sets[0]); + sets = dsc.GetSets(); return sets; diff --git a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs index 99238b1f5..d2e16fc32 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs @@ -159,8 +159,8 @@ namespace Ryujinx.Graphics.Vulkan } texture.ImageLayout = ImageLayout.General; - texture.ImageView = refs.View?.Get(cbs).Value ?? default; - texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default; + texture.ImageView = refs.View?.Get(cbs).Value ?? dummyTexture.GetImageView().Get(cbs).Value; + texture.Sampler = refs.Sampler?.Get(cbs).Value ?? dummySampler.GetSampler().Get(cbs).Value; if (texture.ImageView.Handle == 0) { diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 5f3e91cdc..231d61718 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -647,6 +647,14 @@ namespace Ryujinx.Graphics.Vulkan Format.Bc7Srgb, Format.Bc7Unorm); + if (!OperatingSystem.IsIOSVersionAtLeast(16, 4)) + { + // On iOS 16.3.1 and earlier, these formats are not supported. + supportsBc123CompressionFormat = false; + supportsBc45CompressionFormat = false; + supportsBc67CompressionFormat = false; + } + bool supportsEtc2CompressionFormat = FormatCapabilities.OptimalFormatsSupport(compressedFormatFeatureFlags, Format.Etc2RgbaSrgb, Format.Etc2RgbaUnorm, @@ -714,7 +722,7 @@ namespace Ryujinx.Graphics.Vulkan SystemMemoryType memoryType; - if (IsSharedMemory) + if (IsSharedMemory && !IsMoltenVk) { memoryType = SystemMemoryType.UnifiedMemory; } diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs index fc02ea172..d0f1f8da3 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs @@ -1,10 +1,14 @@ using Ryujinx.Common; +using System.Runtime.InteropServices; using System; namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy { class ILibraryAppletSelfAccessor : IpcService { + [DllImport("RyujinxHelper.framework/RyujinxHelper", CallingConvention = CallingConvention.Cdecl)] + public static extern void TriggerCallback(string cIdentifier); + private readonly AppletStandalone _appletStandalone = new(); public ILibraryAppletSelfAccessor(ServiceCtx context) @@ -45,6 +49,14 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib return ResultCode.Success; } + [CommandCmif(1)] + public ResultCode PushOutData(ServiceCtx context) + { + TriggerCallback("exit-emulation"); + return ResultCode.Success; + } + + [CommandCmif(11)] // GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo public ResultCode GetLibraryAppletInfo(ServiceCtx context) diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs index 311084aa1..664ff07bd 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs @@ -11,6 +11,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE Data = data; } + public byte[] GetData() + { + return Data; + } + [CommandCmif(0)] // Open() -> object public ResultCode Open(ServiceCtx context)