Compare commits

..

No commits in common. "XC-ios-ht" and "1.8.0" have entirely different histories.

25 changed files with 200 additions and 327 deletions

View file

@ -28,10 +28,6 @@ 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
@ -145,12 +141,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 Silk.NET.
The GPU emulator emulates the Switch's Maxwell GPU using Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
- **Input**
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.
We currently have support for keyboard, touch input, JoyCon input support, and nearly all controllers.
Motion controls are natively supported in most cases.
- **DLC & Modifications**
@ -161,13 +157,14 @@ 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](LICENSE.txt).
This software is licensed under the terms of the [MeloNX license (Based on MIT 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.

View file

@ -8,4 +8,4 @@
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
VERSION = 2.0.1
VERSION = 2.0

View file

@ -788,9 +788,6 @@
"$(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;
@ -1042,10 +1039,6 @@
"$(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;
@ -1204,9 +1197,6 @@
"$(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;
@ -1458,10 +1448,6 @@
"$(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;

View file

@ -58,7 +58,6 @@
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
disableMainThreadChecker = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@ -69,8 +68,7 @@
allowLocationSimulation = "YES"
queueDebuggingEnabled = "No"
consoleMode = "0"
structuredConsoleMode = "2"
disablePerformanceAntipatternChecker = "YES">
structuredConsoleMode = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference

View file

@ -16,12 +16,10 @@
#include <SDL2/SDL_syswm.h>
#include <StosJIT/StosJIT-Swift.h>
#ifdef __cplusplus
extern "C" {
#endif
struct GameInfo {
long FileSize;
char TitleName[512];
@ -43,10 +41,6 @@ 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);

View file

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

View file

@ -12,7 +12,8 @@ class NativeController: Hashable, BaseController {
private var instanceID: SDL_JoystickID = -1
private var controller: OpaquePointer?
private var nativeController: GCController
private var controllerMotionProvider: DSUMotionProvider?
private var controllerMotionProvider: ControllerMotionProvider?
private var deviceMotionProvider: DeviceMotionProvider?
private let controllerHaptics: CHHapticEngine?
private let rumbleController: RumbleController?
@ -21,21 +22,21 @@ class NativeController: Hashable, BaseController {
init(_ controller: GCController) {
nativeController = controller
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()
controllerHaptics = nativeController.haptics?.createEngine(withLocality: .default)
// 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: 1.2)
rumbleController = RumbleController(engine: hapticsEngine, rumbleMultiplier: 2.5)
// print("CHHapticEngine started and RumbleController initialized.")
} catch {
// print("Error starting CHHapticEngine: \(error.localizedDescription)")
rumbleController = nil
}
} else {
// print("CHHapticEngine is nil. Cannot initialize RumbleController.")
rumbleController = nil
}
setupHandheldController()
@ -48,17 +49,27 @@ 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")
controllerMotionProvider = usesdevicemotion ? DeviceMotionProvider(slot: slot) : ControllerMotionProvider(controller: nativeController, slot: slot)
if let provider = controllerMotionProvider {
dsuServer.register(provider)
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)
}
}
}
internal func tryGetMotionProvider() -> DSUMotionProvider? { return controllerMotionProvider }
internal func tryGetMotionProvider() -> DSUMotionProvider? {
if nativeController.vendorName == "Joy-Con (l/R)" {
return deviceMotionProvider
} else {
return controllerMotionProvider
}
}
private func setupHandheldController() {
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
@ -83,39 +94,38 @@ 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<NativeController>.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<NativeController>.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
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))
// print("Set LED to RGB(\(red), \(green), \(blue))")
return 0
},
SendEffect: { userdata, data, size in
// print("Effect sent with size \(size)")
return 0
}
)
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
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
}
@ -204,6 +214,7 @@ 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

View file

@ -213,6 +213,7 @@ class Ryujinx : ObservableObject {
var language: SystemLanguage = .americanEnglish
var regioncode: SystemRegionCode = .usa
var handHeldController: Bool = true
var backendMultithreading: Bool = true
init(gamepath: String = "",
@ -241,6 +242,7 @@ class Ryujinx : ObservableObject {
language: SystemLanguage = .americanEnglish,
regioncode: SystemRegionCode = .usa,
handHeldController: Bool = false,
backendMultithreading: Bool = true
) {
self.gamepath = gamepath
self.inputids = inputids
@ -268,6 +270,7 @@ class Ryujinx : ObservableObject {
self.language = language
self.regioncode = regioncode
self.handHeldController = handHeldController
self.backendMultithreading = backendMultithreading
}
@ -535,9 +538,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
@ -565,9 +568,13 @@ 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())])
args.append(contentsOf: ["--system-time-offset", String(TimeZone.current.secondsFromGMT())])
if !config.backendMultithreading {
args.append(contentsOf: ["--backend-multithreading", "Off"])
}
if config.nintendoinput {

View file

@ -264,6 +264,7 @@ 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
@ -344,23 +345,29 @@ struct ButtonView: View {
}
private func handleButtonPress() {
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)
if let callback {
callback()
} else {
isPressed = true
Ryujinx.shared.virtualController.setButtonState(1, for: button)
Haptics.shared.play(.medium)
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)
}
}
}
private func handleButtonRelease() {
if istoggle { return }
if let callback { return }
guard isPressed else { return }
@ -397,40 +404,23 @@ struct ButtonView: View {
// Centralized button configuration
private var buttonConfig: ButtonConfiguration {
switch button {
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")
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")
}
}

View file

@ -142,15 +142,6 @@ 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

View file

@ -35,6 +35,7 @@ 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

View file

@ -9,10 +9,10 @@ import MetalKit
import UIKit
class MeloMTKView: MTKView {
private var activeTouches: [UITouch] = []
private var ignoredTouches: Set<UITouch> = []
private var touchIndexMap: [UITouch: Int] = [:]
private let baseWidth: CGFloat = 1280
private let baseHeight: CGFloat = 720
private var aspectRatio: AspectRatio = .fixed16x9
@ -84,112 +84,83 @@ class MeloMTKView: MTKView {
return CGPoint(x: scaledX, y: scaledY)
}
private func getNextAvailableIndex() -> Int {
for i in 0..<Int.max {
if !touchIndexMap.values.contains(i) {
return i
}
}
return 0
}
override func touchesBegan(_ touches: Set<UITouch>, 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)
for touch in touches {
let location = touch.location(in: self)
guard let scaledLocation = scaleToTargetResolution(location) else {
ignoredTouches.insert(touch)
continue
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))
}
let index = getNextAvailableIndex()
touchIndexMap[touch] = index
activeTouches.append(touch)
touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
let disabled = UserDefaults.standard.bool(forKey: "disableTouch")
guard !disabled else {
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
if !disabled {
for touch in touches {
ignoredTouches.remove(touch)
if ignoredTouches.contains(touch) {
ignoredTouches.remove(touch)
continue
}
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<UITouch>, 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)
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)
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))
}
touchIndexMap.removeValue(forKey: touch)
ignoredTouches.insert(touch)
continue
}
touch_moved(Float(scaledLocation.x), Float(scaledLocation.y), Int32(touchIndex))
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
touchesEnded(touches, with: event)
}
func resetTouchTracking() {
activeTouches.removeAll()
ignoredTouches.removeAll()
touchIndexMap.removeAll()
}
}

View file

@ -81,6 +81,8 @@ struct ContentView: View {
_settings = State(initialValue: defaultSettings)
// print(SDL_CONTROLLER_BUTTON_LEFTSTICK.rawValue)
initializeSDL()
}
@ -130,6 +132,7 @@ struct ContentView: View {
JITPopover() {
ryujinx.jitenabled = false
}
// .interactiveDismissDisabled()
}
}
@ -150,8 +153,11 @@ struct ContentView: View {
refreshControllersList()
}
UserDefaults.standard.set(false, forKey: "lockInApp")
// print(MTLHud.shared.isEnabled)
initControllerObservers()
Air.play(AnyView(
@ -160,6 +166,7 @@ struct ContentView: View {
refreshControllersList()
ryujinx.addGames()
checkJitStatus()
@ -280,6 +287,7 @@ struct ContentView: View {
queue: .main
) { notification in
if let controller = notification.object as? GCController {
// print("Controller connected: \(controller.productCategory)")
nativeControllers[controller] = .init(controller)
refreshControllersList()
}
@ -291,8 +299,7 @@ struct ContentView: View {
queue: .main
) { notification in
if let controller = notification.object as? GCController {
currentControllers = []
controllersList = []
// print("Controller disconnected: \(controller.productCategory)")
nativeControllers[controller]?.cleanup()
nativeControllers[controller] = nil
refreshControllersList()
@ -309,9 +316,6 @@ struct ContentView: View {
}
private func refreshControllersList() {
currentControllers = []
controllersList = []
controllersList = ryujinx.getConnectedControllers()
if let onscreen = controllersList.first(where: { $0.name == ryujinx.virtualController.controllername }) {
@ -320,6 +324,8 @@ 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])

View file

@ -1252,16 +1252,3 @@ 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()
}

View file

@ -1129,6 +1129,9 @@ 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")

View file

@ -10,7 +10,7 @@
</data>
<key>Info.plist</key>
<data>
GYWZONTCP5su4yOAk0d5jCd2K88=
RTwvCLsTMs+YfZ9ZeF25QYe7/LE=
</data>
<key>Modules/module.modulemap</key>
<data>

View file

@ -699,21 +699,13 @@ namespace Ryujinx.Graphics.Vulkan
if (_dirty.HasFlag(DirtyFlags.Texture))
{
if (true)
if (false)
{
try
{
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
}
catch (Exception e)
{
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
}
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
}
else
{
try
{
try {
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
}
catch (Exception e)
@ -764,7 +756,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;
}
@ -799,13 +791,6 @@ 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);
@ -879,12 +864,12 @@ namespace Ryujinx.Graphics.Vulkan
if (texture.ImageView.Handle == 0)
{
texture.ImageView = dummyImageInfo.ImageView;
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
}
if (texture.Sampler.Handle == 0)
{
texture.Sampler = dummyImageInfo.Sampler;
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
}
}
@ -924,7 +909,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView;
images[i].ImageView = _imageRefs[binding + i].ImageView?.Get(cbs).Value ?? default;
}
tu.Push<DescriptorImageInfo>(images[..count]);
@ -961,25 +946,23 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
{
int setIndex = PipelineBase.TextureSetIndex;
var bindingSegments = program.BindingSegments[setIndex];
ResourceBindingSegment[] bindingSegments = program.BindingSegments[setIndex];
if (bindingSegments.Length == 0)
{
return;
}
var dummyImageInfo = new DescriptorImageInfo
if (_updateDescriptorCacheCbIndex)
{
ImageView = _dummyTexture.GetImageView().Get(cbs).Value,
Sampler = _dummySampler.GetSampler().Get(cbs).Value,
ImageLayout = ImageLayout.General
};
_updateDescriptorCacheCbIndex = false;
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
}
var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
DescriptorSetCollection dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
foreach (ResourceBindingSegment segment in bindingSegments)
{
@ -990,78 +973,56 @@ namespace Ryujinx.Graphics.Vulkan
{
if (segment.Type != ResourceType.BufferTexture)
{
Span<DescriptorImageInfo> textures = _textures;
for (int i = 0; i < count; i++)
{
int index = binding + i;
ref var textureRef = ref _textureRefs[index];
ref DescriptorImageInfo texture = ref textures[i];
ref TextureRef refs = ref _textureRefs[binding + i];
var imageView = textureRef.ImageView?.Get(cbs).Value ?? dummyImageInfo.ImageView;
var sampler = textureRef.Sampler?.Get(cbs).Value ?? dummyImageInfo.Sampler;
texture.ImageView = refs.ImageView?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
var imageInfo = new DescriptorImageInfo
if (texture.ImageView.Handle == 0)
{
ImageView = imageView.Handle != 0 ? imageView : dummyImageInfo.ImageView,
Sampler = sampler.Handle != 0 ? sampler : dummyImageInfo.Sampler,
ImageLayout = ImageLayout.General
};
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
}
dsc.UpdateImages(0, index, new[] { imageInfo }, DescriptorType.CombinedImageSampler);
if (texture.Sampler.Handle == 0)
{
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
}
}
dsc.UpdateImages(0, binding, textures[..count], DescriptorType.CombinedImageSampler);
}
else
{
Span<BufferView> bufferTextures = _bufferTextures;
for (int i = 0; i < count; i++)
{
int index = binding + i;
var bufferView = _bufferTextureRefs[index]?.GetBufferView(cbs, false) ?? default;
dsc.UpdateBufferImages(0, index, new[] { bufferView }, DescriptorType.UniformTexelBuffer);
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
}
dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer);
}
}
else
{
var arrayRef = _textureArrayRefs[binding];
if (segment.Type != ResourceType.BufferTexture)
{
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);
}
}
dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler);
}
else
{
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);
}
}
dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer);
}
}
}
var sets = dsc.GetSets();
DescriptorSet[] sets = dsc.GetSets();
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
}

View file

@ -188,21 +188,15 @@ 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)
{
tu.Push(GetImageInfos(_gd, cbs, dummyTexture));
dsc.UpdateImages(0, 0, GetImageInfos(_gd, cbs, dummyTexture), DescriptorType.StorageImage);
}
else
{
tu.Push(GetBufferViews(cbs));
dsc.UpdateBufferImages(0, 0, GetBufferViews(cbs), DescriptorType.StorageTexelBuffer);
}
templateUpdater.Commit(_gd, device, sets[0]);
sets = dsc.GetSets();
return sets;

View file

@ -159,8 +159,8 @@ namespace Ryujinx.Graphics.Vulkan
}
texture.ImageLayout = ImageLayout.General;
texture.ImageView = refs.View?.Get(cbs).Value ?? dummyTexture.GetImageView().Get(cbs).Value;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? dummySampler.GetSampler().Get(cbs).Value;
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{

View file

@ -647,14 +647,6 @@ 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,
@ -722,7 +714,7 @@ namespace Ryujinx.Graphics.Vulkan
SystemMemoryType memoryType;
if (IsSharedMemory && !IsMoltenVk)
if (IsSharedMemory)
{
memoryType = SystemMemoryType.UnifiedMemory;
}

View file

@ -1,14 +1,10 @@
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)
@ -49,14 +45,6 @@ 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)

View file

@ -11,11 +11,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
Data = data;
}
public byte[] GetData()
{
return Data;
}
[CommandCmif(0)]
// Open() -> object<nn::am::service::IStorageAccessor>
public ResultCode Open(ServiceCtx context)