diff --git a/MeloNX-XC/MeloNX.xcodeproj/project.pbxproj b/MeloNX-XC/MeloNX.xcodeproj/project.pbxproj index 33a8f9a80..a55c8e096 100644 --- a/MeloNX-XC/MeloNX.xcodeproj/project.pbxproj +++ b/MeloNX-XC/MeloNX.xcodeproj/project.pbxproj @@ -347,6 +347,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportsDocumentBrowser = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -356,6 +357,8 @@ "$(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 = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; @@ -385,6 +388,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportsDocumentBrowser = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -394,6 +398,8 @@ "$(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 = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; diff --git a/MeloNX-XC/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate b/MeloNX-XC/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate index 9664c52eb..db72fd225 100644 Binary files a/MeloNX-XC/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate and b/MeloNX-XC/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/MeloNX-XC/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/MeloNX-XC/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 000000000..5d0175364 --- /dev/null +++ b/MeloNX-XC/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/MeloNX-XC/MeloNX/ContentView.swift b/MeloNX-XC/MeloNX/ContentView.swift index a20c1545d..4cc494676 100644 --- a/MeloNX-XC/MeloNX/ContentView.swift +++ b/MeloNX-XC/MeloNX/ContentView.swift @@ -12,24 +12,31 @@ import GameController var theWindow: UIWindow? = nil struct ContentView: View { + @State var device: MTLDevice? = MTLCreateSystemDefaultDevice() @State var gameUrl: URL? @State var showFileImporter: Bool = false + @State var emulationStarted: Bool = false var body: some View { - VStack { - Button { - showFileImporter.toggle() - } label: { - Text("Select Game") - } - if let gameUrl { + ZStack { + + + VStack { + Text("NX iOS") Button { - DispatchQueue.main.async { - showVirtualController(url: gameUrl) - } + showFileImporter.toggle() } label: { - Text("Go!") + Text("Select Game") + } + if let gameUrl { + Button { + emulationStarted = true + gameUrl.startAccessingSecurityScopedResource() + showVirtualController(url: gameUrl) + } label: { + Text("Go!") + } + .padding(8) } - .padding(8) } } .padding() @@ -55,11 +62,9 @@ func startEmulation(game: URL) { enableKeyboard: false, graphicsBackend: "Vulkan" ) - DispatchQueue.main.async { - SDL_SetMainReady() - SDL_iPhoneSetEventPump(SDL_TRUE) - patchMakeKeyAndVisible() - } + patchMakeKeyAndVisible() + SDL_SetMainReady() + SDL_iPhoneSetEventPump(SDL_TRUE) let emulator = RyujinxEmulator() do { try emulator.startWithRunLoop(config: config) @@ -78,14 +83,10 @@ func patchMakeKeyAndVisible() { extension UIWindow { @objc func wdb_makeKeyAndVisible() { print("Making window key and visible...") - if #available(iOS 13.0, *) { - self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene) - } + self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene) self.wdb_makeKeyAndVisible() theWindow = self - if #available(iOS 15.0, *) { - reconnectVirtualController() - } + reconnectVirtualController() } } diff --git a/MeloNX-XC/MeloNX/Core/Ryujinx.swift b/MeloNX-XC/MeloNX/Core/Ryujinx.swift index 9082f39dc..6bf39257f 100644 --- a/MeloNX-XC/MeloNX/Core/Ryujinx.swift +++ b/MeloNX-XC/MeloNX/Core/Ryujinx.swift @@ -61,13 +61,14 @@ class RyujinxEmulator { ] */ - args.append("ryujinx") - + args.append(config.inputPath) args.append("--graphics-backend") args.append(config.graphicsBackend) - + args.append(contentsOf: ["--memory-manager-mode", "SoftwarePageTable"]) + // args.append(contentsOf: ["--fullscreen", "true"]) + args.append(contentsOf: ["--enable-debug-logs", "true"]) + args.append(contentsOf: ["--enable-trace-logs", "true"]) // args.append("--input-path") - args.append(config.inputPath) args.append(contentsOf: config.additionalArgs) @@ -82,6 +83,7 @@ class RyujinxEmulator { var argvPtrs = cArgs + let result = ryujinxMain(Int32(args.count), &argvPtrs) if result != 0 { @@ -102,16 +104,19 @@ class RyujinxEmulator { let port = Port() runLoop.add(port, forMode: .default) - - do { - try Self.start(with: config) - } catch { - Self.log("Emulation failed to start: \(error)") - self.isRunning = false - return + + DispatchQueue.main.async { + do { + try Self.start(with: config) + } catch { + Self.log("Emulation failed to start: \(error)") + self.isRunning = false + return + } } + while self.isRunning && runLoop.run(mode: .default, before: .distantFuture) { autoreleasepool { } @@ -122,6 +127,8 @@ class RyujinxEmulator { } emulationThread?.name = "RyujinxEmulationThread" + emulationThread?.qualityOfService = .userInteractive + emulationThread?.threadPriority = 0.9 emulationThread?.start() } diff --git a/MeloNX-XC/MeloNX/Info.plist b/MeloNX-XC/MeloNX/Info.plist index 0c67376eb..ff579a6ca 100644 --- a/MeloNX-XC/MeloNX/Info.plist +++ b/MeloNX-XC/MeloNX/Info.plist @@ -1,5 +1,8 @@ - + + UIFileSharingEnabled + + diff --git a/MeloNX-XC/MeloNX/MetalVIew.swift b/MeloNX-XC/MeloNX/MetalVIew.swift new file mode 100644 index 000000000..99603bf91 --- /dev/null +++ b/MeloNX-XC/MeloNX/MetalVIew.swift @@ -0,0 +1,210 @@ +// +// MetalVIew.swift +// MeloNX +// +// Created by Stossy11 on 27/10/2024. +// + +import SwiftUI +import Metal +import MetalKit + +struct MetalView: UIViewRepresentable { + let device: MTLDevice? + let configure: (UIView) -> Void + + func makeUIView(context: Context) -> SudachiScreenView { + let view = SudachiScreenView() + configure(view.primaryScreen) + return view + } + + func updateUIView(_ uiView: SudachiScreenView, context: Context) { + // + } +} + + +class SudachiScreenView: UIView { + var primaryScreen: UIView! + var portraitconstraints = [NSLayoutConstraint]() + var landscapeconstraints = [NSLayoutConstraint]() + var fullscreenconstraints = [NSLayoutConstraint]() + let userDefaults = UserDefaults.standard + + override init(frame: CGRect) { + super.init(frame: frame) + if userDefaults.bool(forKey: "isfullscreen") { + // setupSudachiScreenforcools() + setupSudachiScreen2() + } else if userDefaults.bool(forKey: "isairplay") { + setupSudachiScreen2() + } else if userDefaults.bool(forKey: "169fullscreen") { // this is for the 16/9 aspect ratio full screen + setupSudachiScreenforcools() + } else if UIDevice.current.userInterfaceIdiom == .pad { + setupSudachiScreenforiPad() + } else { + setupSudachiScreen() + } + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + if userDefaults.bool(forKey: "isfullscreen") { + setupSudachiScreen2() + } else if userDefaults.bool(forKey: "isairplay") { + setupSudachiScreen2() + } else if UIDevice.current.userInterfaceIdiom == .pad { + setupSudachiScreenforiPad() + } else { + setupSudachiScreen() + } + + } + + + func setupSudachiScreen2() { + primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice()) + primaryScreen.translatesAutoresizingMaskIntoConstraints = false + primaryScreen.clipsToBounds = true + addSubview(primaryScreen) + + fullscreenconstraints = [ + primaryScreen.topAnchor.constraint(equalTo: topAnchor), + primaryScreen.leadingAnchor.constraint(equalTo: leadingAnchor), + primaryScreen.trailingAnchor.constraint(equalTo: trailingAnchor), + primaryScreen.bottomAnchor.constraint(equalTo: bottomAnchor) + ] + + addConstraints(fullscreenconstraints) + } + + func setupSudachiScreenforcools() { // oh god this took a long time, im going insane + primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice()) + primaryScreen.translatesAutoresizingMaskIntoConstraints = false + primaryScreen.clipsToBounds = true + + addSubview(primaryScreen) + + primaryScreen.layer.cornerRadius = 5 + primaryScreen.layer.masksToBounds = true + + + NSLayoutConstraint.activate([ + primaryScreen.centerXAnchor.constraint(equalTo: centerXAnchor), + primaryScreen.centerYAnchor.constraint(equalTo: centerYAnchor), + primaryScreen.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor), + primaryScreen.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor) + ]) + + let aspectRatio: CGFloat = 16.0/9.0 + let aspectRatioConstraint = NSLayoutConstraint( + item: primaryScreen ?? UIView(), + attribute: .width, + relatedBy: .equal, + toItem: primaryScreen, + attribute: .height, + multiplier: aspectRatio, + constant: 0 + ) + aspectRatioConstraint.priority = .required - 1 + primaryScreen.addConstraint(aspectRatioConstraint) + + let heightConstraint = primaryScreen.heightAnchor.constraint(equalTo: heightAnchor) + heightConstraint.priority = .defaultHigh + let widthConstraint = primaryScreen.widthAnchor.constraint(equalTo: widthAnchor) + widthConstraint.priority = .defaultHigh + + NSLayoutConstraint.activate([heightConstraint, widthConstraint]) + + // Make primaryScreen fill container + fullscreenconstraints = [ + primaryScreen.topAnchor.constraint(equalTo: primaryScreen.topAnchor), + primaryScreen.bottomAnchor.constraint(equalTo: primaryScreen.bottomAnchor), + primaryScreen.leadingAnchor.constraint(equalTo: primaryScreen.leadingAnchor), + primaryScreen.trailingAnchor.constraint(equalTo: primaryScreen.trailingAnchor) + ] + + NSLayoutConstraint.activate(fullscreenconstraints) + } + + func setupSudachiScreenforiPad() { + primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice()) + primaryScreen.translatesAutoresizingMaskIntoConstraints = false + primaryScreen.clipsToBounds = true + primaryScreen.layer.borderColor = UIColor.secondarySystemBackground.cgColor + primaryScreen.layer.borderWidth = 3 + primaryScreen.layer.cornerCurve = .continuous + primaryScreen.layer.cornerRadius = 10 + addSubview(primaryScreen) + + + portraitconstraints = [ + primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10), + primaryScreen.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 10), + primaryScreen.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -10), + primaryScreen.heightAnchor.constraint(equalTo: primaryScreen.widthAnchor, multiplier: 9 / 16), + ] + + landscapeconstraints = [ + primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 50), + primaryScreen.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -100), + primaryScreen.widthAnchor.constraint(equalTo: primaryScreen.heightAnchor, multiplier: 16 / 9), + primaryScreen.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), + ] + + + updateConstraintsForOrientation() + } + + + + func setupSudachiScreen() { + primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice()) + primaryScreen.translatesAutoresizingMaskIntoConstraints = false + primaryScreen.clipsToBounds = true + primaryScreen.layer.borderColor = UIColor.secondarySystemBackground.cgColor + primaryScreen.layer.borderWidth = 3 + primaryScreen.layer.cornerCurve = .continuous + primaryScreen.layer.cornerRadius = 10 + addSubview(primaryScreen) + + + portraitconstraints = [ + primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10), + primaryScreen.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 10), + primaryScreen.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -10), + primaryScreen.heightAnchor.constraint(equalTo: primaryScreen.widthAnchor, multiplier: 9 / 16), + ] + + landscapeconstraints = [ + primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10), + primaryScreen.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -10), + primaryScreen.widthAnchor.constraint(equalTo: primaryScreen.heightAnchor, multiplier: 16 / 9), + primaryScreen.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), + ] + + updateConstraintsForOrientation() + } + + override func layoutSubviews() { + super.layoutSubviews() + updateConstraintsForOrientation() + } + + private func updateConstraintsForOrientation() { + + if userDefaults.bool(forKey: "isfullscreen") { + removeConstraints(portraitconstraints) + removeConstraints(landscapeconstraints) + removeConstraints(fullscreenconstraints) + addConstraints(fullscreenconstraints) + } else { + removeConstraints(portraitconstraints) + removeConstraints(landscapeconstraints) + + let isPortrait = UIApplication.shared.statusBarOrientation.isPortrait + addConstraints(isPortrait ? portraitconstraints : landscapeconstraints) + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs index d6283eb57..d6d5a0160 100644 --- a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs +++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs @@ -5,56 +5,63 @@ using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen; +using System; using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory { /// - /// Represent the shared memory shared between applications for input. + /// Represents the shared memory used for input, shared between applications. /// [StructLayout(LayoutKind.Explicit, Size = 0x40000)] struct SharedMemory { + // Ensure each struct has a defined size and is properly aligned in memory. + /// - /// Debug controller. + /// Debug controller state (size: approximately 0x400). /// [FieldOffset(0)] public RingLifo DebugPad; /// - /// Touchscreen. + /// Touchscreen state (size: approximately 0x3000). /// [FieldOffset(0x400)] public RingLifo TouchScreen; /// - /// Mouse. + /// Mouse state (size: approximately 0x400). /// [FieldOffset(0x3400)] public RingLifo Mouse; /// - /// Keyboard. + /// Keyboard state (size: approximately 0x400). /// [FieldOffset(0x3800)] public RingLifo Keyboard; /// - /// Nintendo Pads. + /// Nintendo Pads (size: approximately 0x800). /// - [FieldOffset(0x9A00)] + [FieldOffset(0x3C00)] public Array10 Npads; + /// + /// Creates a SharedMemory instance with each component initialized. + /// public static SharedMemory Create() { - SharedMemory result = new() - { - DebugPad = RingLifo.Create(), - TouchScreen = RingLifo.Create(), - Mouse = RingLifo.Create(), - Keyboard = RingLifo.Create(), - }; + // Initialize each component separately to avoid potential layout issues. + SharedMemory result = new SharedMemory(); + + result.DebugPad = RingLifo.Create(); + result.TouchScreen = RingLifo.Create(); + result.Mouse = RingLifo.Create(); + result.Keyboard = RingLifo.Create(); + // Initialize each Npad state in a loop for (int i = 0; i < result.Npads.Length; i++) { result.Npads[i] = NpadState.Create(); diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index 0c7112c0a..4083328d0 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -52,7 +52,6 @@ namespace Ryujinx.HLE Gpu = new GpuContext(Configuration.GpuRenderer); System = new HOS.Horizon(this); Statistics = new PerformanceStatistics(); - Hid = new Hid(this, System.HidStorage); Processes = new ProcessLoader(this); TamperMachine = new TamperMachine();