diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index 5b29c80fa..68b5b44e3 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -59,7 +59,7 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */ - 4E80AA0A2CD6FAA800029585 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = { + 4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */ = { isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet; attributesByRelativePath = { "Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (CodeSignOnCopy, ); @@ -68,13 +68,13 @@ "Dependencies/Dynamic Libraries/libavutil.dylib" = (CodeSignOnCopy, ); Dependencies/XCFrameworks/MoltenVK.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/SDL2.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); - Dependencies/XCFrameworks/libSPIRV.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libavcodec.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libavfilter.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libavformat.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libavutil.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libswresample.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); Dependencies/XCFrameworks/libswscale.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); + Dependencies/XCFrameworks/libteakra.xcframework = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */; membershipExceptions = ( @@ -86,9 +86,9 @@ Dependencies/XCFrameworks/libavfilter.xcframework, Dependencies/XCFrameworks/libavformat.xcframework, Dependencies/XCFrameworks/libavutil.xcframework, - Dependencies/XCFrameworks/libSPIRV.xcframework, Dependencies/XCFrameworks/libswresample.xcframework, Dependencies/XCFrameworks/libswscale.xcframework, + Dependencies/XCFrameworks/libteakra.xcframework, Dependencies/XCFrameworks/MoltenVK.xcframework, Dependencies/XCFrameworks/SDL2.xcframework, ); @@ -100,7 +100,7 @@ isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( 4E80AA1D2CD7015100029585 /* Exceptions for "MeloNX" folder in "MeloNX" target */, - 4E80AA0A2CD6FAA800029585 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */, + 4E9A82F32CF87822006D7086 /* Exceptions for "MeloNX" folder in "Embed Libraries" phase from "MeloNX" target */, ); path = MeloNX; sourceTree = ""; @@ -542,6 +542,18 @@ "$(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", + "$(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", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; @@ -622,6 +634,18 @@ "$(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", + "$(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", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = 1.0; 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 8b26026b6..fdf28bf02 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 b48bf4f6f..fd8969bbb 100644 --- a/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX.xcscheme +++ b/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX.xcscheme @@ -55,7 +55,7 @@ Void)? + +var VirtualController: GCVirtualController! +func showVirtualController() { + let config = GCVirtualController.Configuration() + if UserDefaults.standard.bool(forKey: "RyuDemoControls") { + config.elements = [ + GCInputLeftThumbstick, + GCInputButtonA, + GCInputButtonB, + GCInputButtonX, + GCInputButtonY, + // GCInputRightThumbstick, + GCInputRightTrigger, + GCInputLeftTrigger, + GCInputLeftShoulder, + GCInputRightShoulder + ] + } else { + config.elements = [ + GCInputLeftThumbstick, + GCInputButtonA, + GCInputButtonB, + GCInputButtonX, + GCInputButtonY, + GCInputRightThumbstick, + GCInputRightTrigger, + GCInputLeftTrigger, + GCInputLeftShoulder, + GCInputRightShoulder + ] + } + VirtualController = GCVirtualController(configuration: config) + VirtualController.connect { err in + print("controller connect: \(String(describing: err))") + patchMakeKeyAndVisible() + if let controllerCallback { + controllerCallback() + } + } +} + +func waitforcontroller() { + Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in + + if let window = UIApplication.shared.windows.first { + // Function to recursively search for GCControllerView + func findGCControllerView(in view: UIView) -> UIView? { + // Check if current view is GCControllerView + if String(describing: type(of: view)) == "GCControllerView" { + return view + } + + // Search through subviews + for subview in view.subviews { + if let found = findGCControllerView(in: subview) { + return found + } + } + + return nil + } + + if let gcControllerView = findGCControllerView(in: window) { + // Found the GCControllerView + print("Found GCControllerView:", gcControllerView) + + if let theWindow = theWindow, (findGCControllerView(in: theWindow) == nil) { + theWindow.addSubview(gcControllerView) + + theWindow.bringSubviewToFront(gcControllerView) + } + } + } + } +} + +@available(iOS 15.0, *) +func reconnectVirtualController() { + VirtualController.disconnect() + DispatchQueue.main.async { + VirtualController.connect { err in + print("reconnected: err \(String(describing: err))") + } + } +} + + diff --git a/src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift b/src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift new file mode 100644 index 000000000..ef423ef42 --- /dev/null +++ b/src/MeloNX/MeloNX/Core/Swift/Display/DisplayVisible.swift @@ -0,0 +1,40 @@ +// +// Untitled.swift +// MeloNX +// +// Created by Stossy11 on 28/11/2024. +// + +import Foundation +import GameController +import UIKit + + + +var theWindow: UIWindow? = nil +extension UIWindow { + @objc func wdb_makeKeyAndVisible() { + if #available(iOS 13.0, *) { + self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene) + } + self.wdb_makeKeyAndVisible() + theWindow = self + if #available(iOS 15.0, *) { + reconnectVirtualController() + } + + + if let window = theWindow { + waitforcontroller() + } + } +} + + +func patchMakeKeyAndVisible() { + let uiwindowClass = UIWindow.self + if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)), + let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) { + method_exchangeImplementations(m1, m2) + } +} diff --git a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift index a93288beb..74ad74716 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift @@ -44,6 +44,8 @@ class Ryujinx { var resscale: Float var debuglogs: Bool var tracelogs: Bool + var nintendoinput: Bool + var enableInternet: Bool var listinputids: Bool var fullscreen: Bool var memoryManagerMode: String @@ -64,6 +66,8 @@ class Ryujinx { disableVSync: Bool = false, disableShaderCache: Bool = false, disableDockedMode: Bool = false, + nintendoinput: Bool = true, + enableInternet: Bool = false, enableTextureRecompression: Bool = true, additionalArgs: [String] = [], resscale: Float = 1.00 @@ -81,6 +85,8 @@ class Ryujinx { self.additionalArgs = additionalArgs self.memoryManagerMode = memoryManagerMode self.resscale = resscale + self.nintendoinput = nintendoinput + self.enableInternet = enableInternet } } @@ -151,6 +157,13 @@ class Ryujinx { args.append(contentsOf: ["--resolution-scale", String(config.resscale)]) } + if config.nintendoinput { + args.append("--correct-ons-controller") + } + if config.enableInternet { + args.append("--enable-internet-connection") + } + // Adding default args directly into additionalArgs if config.disableVSync { args.append("--disable-vsync") diff --git a/src/MeloNX/MeloNX/Views/ContentView.swift b/src/MeloNX/MeloNX/Views/ContentView.swift index 85e050f2c..04de504c0 100644 --- a/src/MeloNX/MeloNX/Views/ContentView.swift +++ b/src/MeloNX/MeloNX/Views/ContentView.swift @@ -16,196 +16,230 @@ struct MoltenVKSettings: Codable, Hashable { } struct ContentView: View { - @State public var theWindow: UIWindow? = nil + // MARK: - Properties + @State private var theWindow: UIWindow? @State private var virtualController: GCVirtualController? - @State var game: URL? = nil - @State var controllersList: [Controller] = [] - @State var currentControllers: [Controller] = [] - @State var config: Ryujinx.Configuration = Ryujinx.Configuration(gamepath: "") - - @State var settings: [MoltenVKSettings] = [ - // MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: ""), - // MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "1"), - MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"), - MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", value: "1"), - MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1") - ] + @State private var game: URL? + @State private var controllersList: [Controller] = [] + @State private var currentControllers: [Controller] = [] + @State private var config: Ryujinx.Configuration + @State private var settings: [MoltenVKSettings] + @State private var isVirtualControllerActive: Bool = false + // MARK: - Initialization init() { - // Initialize SDL - DispatchQueue.main.async { [self] in - setMoltenVKSettings() - SDL_SetMainReady() - SDL_iPhoneSetEventPump(SDL_TRUE) - SDL_Init(SDL_INIT_VIDEO) - patchMakeKeyAndVisible() + let defaultConfig = Ryujinx.Configuration(gamepath: "") + _config = State(initialValue: defaultConfig) + + let defaultSettings: [MoltenVKSettings] = [ + MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "1024"), + MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", value: "1"), + MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1") + ] + _settings = State(initialValue: defaultSettings) + + initializeSDL() + } + + // MARK: - Body + var body: some View { + iOSNav { + if let game { + emulationView + } else { + mainMenuView + } + } + .onChange(of: isVirtualControllerActive) { newValue in + if newValue { + createVirtualController() + } else { + destroyVirtualController() + } } } - func setupVirtualController() { + // MARK: - View Components + private var emulationView: some View { + ZStack {} + .onAppear { + setupEmulation() + } + } + + private var mainMenuView: some View { + HStack { + GameListView(startemu: $game) + .onAppear { + createVirtualController() + refreshControllersList() + } + + settingsListView + } + } + + private var settingsListView: some View { + List { + Section("Settings") { + NavigationLink("Config") { + SettingsView(config: $config, MoltenVKSettings: $settings) + } + } + + Section("Controller") { + Button("Refresh", action: refreshControllersList) + + ForEach(controllersList, id: \.self) { controller in + if controller.name != "Apple Touch Controller" { + controllerRow(for: controller) + } + } + } + } + } + + private func controllerRow(for controller: Controller) -> some View { + HStack { + Button(controller.name) { + toggleController(controller) + } + Spacer() + if currentControllers.contains(where: { $0.id == controller.id }) { + Image(systemName: "checkmark.circle.fill") + } + } + } + + // MARK: - Controller Management + private func createVirtualController() { let configuration = GCVirtualController.Configuration() configuration.elements = [ + /* GCInputLeftThumbstick, GCInputRightThumbstick, GCInputButtonA, GCInputButtonB, GCInputButtonX, - GCInputButtonY + GCInputButtonY, + */ ] - let controller = GCVirtualController(configuration: configuration) - self.virtualController = controller - self.virtualController?.connect() + virtualController = GCVirtualController(configuration: configuration) + virtualController?.connect() + + controllersList.removeAll(where: { $0.name == "Apple Touch Controller" }) } - var body: some View { - iOSNav { + private func destroyVirtualController() { + virtualController?.disconnect() + virtualController = nil + + // Remove virtual controller from current controllers + controllersList.removeAll(where: { $0.name == "Apple Touch Controller" }) + } + + // MARK: - Helper Methods + private func initializeSDL() { + DispatchQueue.main.async { + setMoltenVKSettings() + SDL_SetMainReady() + SDL_iPhoneSetEventPump(SDL_TRUE) + SDL_Init(SDL_INIT_VIDEO) + } + } + + private func setupEmulation() { + virtualController?.disconnect() + + + controllerCallback = { + DispatchQueue.main.async { + controllersList = Ryujinx.shared.getConnectedControllers() + currentControllers.removeAll(where: { $0.name == "Apple Touch Controller" }) + if controllersList.count == 2, + controllersList.contains(where: { $0.name == "Apple Touch Controller" }) { + currentControllers.append(controllersList[1]) + } + + print(currentControllers) + start(displayid: 1) + } + } + + + showVirtualController() + } + + private func refreshControllersList() { + Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in + controllersList = Ryujinx.shared.getConnectedControllers() + controllersList.removeAll(where: { $0.id == "0" }) - if let game { - ZStack { - - } - .onAppear { - start(displayid: 0) - } - } else { - HStack { - GameListView(startemu: $game) - .onAppear() { - Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in - controllersList = Ryujinx.shared.getConnectedControllers() - controllersList.removeAll(where: { $0.id == "0" }) - } - } - - List { - Section("Settings") { - NavigationLink { - SettingsView(config: $config, MoltenVKSettings: $settings) - } label: { - Text("Config") - } - } - Section("Controller") { - Button { - controllersList = Ryujinx.shared.getConnectedControllers() - controllersList.removeAll(where: { $0.id == "0" }) - } label: { - Text("Refresh") - } - ForEach(controllersList, id: \.self) { controller in - HStack { - Button { - if currentControllers.contains(where: { $0.id == controller.id }) { - currentControllers.removeAll(where: { $0.id == controller.id }) - } else { - currentControllers.append(controller) - } - } label: { - Text(controller.name) - } - Spacer() - if currentControllers.contains(where: { $0.id == controller.id }) { - Image(systemName: "checkmark.circle.fill") - } - } - } - } - } - - } + controllersList.removeAll(where: { $0.name == "Apple Touch Controller" }) + + if let controller = controllersList.first, !controllersList.isEmpty { + currentControllers.append(controller) } } } - func start(displayid: UInt32) { - - if let game { - self.config.gamepath = game.path - - self.config.inputids = currentControllers.map(\.id) - - allocateSixGB() - - // Start the emulation - - print("Is MetalHud Enabled? " + (MTLHud.shared.isEnabled ? "yeah" : "nope")) - do { - setupVirtualController() - - try Ryujinx.shared.start(with: config) - - - } catch { - print("Error \(error.localizedDescription)") - } + private func toggleController(_ controller: Controller) { + if currentControllers.contains(where: { $0.id == controller.id }) { + currentControllers.removeAll(where: { $0.id == controller.id }) } else { - + currentControllers.append(controller) } - } - func allocateSixGB() -> UnsafeMutableRawPointer? { - + private func start(displayid: UInt32) { + guard let game else { return } + + config.gamepath = game.path + config.inputids = currentControllers.map(\.id) + + allocateMemory() + + do { + try Ryujinx.shared.start(with: config) + } catch { + print("Error: \(error.localizedDescription)") + } + } + + private func allocateMemory() { let physicalMemory = ProcessInfo.processInfo.physicalMemory let totalMemoryInGB = Double(physicalMemory) / (1024 * 1024 * 1024) - let mem = totalMemoryInGB - print(mem) - // Allocate memory - let pointer = UnsafeMutableRawPointer.allocate(byteCount: Int(mem), alignment: MemoryLayout.alignment) - - // Optionally initialize the memory - pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(mem)) - - print("Successfully allocated 6GB of memory.") - return pointer + + let pointer = UnsafeMutableRawPointer.allocate( + byteCount: Int(totalMemoryInGB), + alignment: MemoryLayout.alignment + ) + pointer.initializeMemory(as: UInt8.self, repeating: 0, count: Int(totalMemoryInGB)) } - func patchMakeKeyAndVisible() { - let uiwindowClass = UIWindow.self - if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)), - let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) { - method_exchangeImplementations(m1, m2) - } - } - - private func setMoltenVKSettings() { - if let configs = loadSettings() { self.config = configs - print(configs) } settings.forEach { setting in setenv(setting.string, setting.value, 1) } } - } + +// MARK: - Helper Functions func loadSettings() -> Ryujinx.Configuration? { - guard let jsonString = UserDefaults.standard.string(forKey: "config") else { + guard let jsonString = UserDefaults.standard.string(forKey: "config"), + let data = jsonString.data(using: .utf8) else { return nil } do { - let decoder = JSONDecoder() - if let data = jsonString.data(using: .utf8) { - return try decoder.decode(Ryujinx.Configuration.self, from: data) - } + return try JSONDecoder().decode(Ryujinx.Configuration.self, from: data) } catch { print("Failed to load settings: \(error)") - } - return nil -} - -extension UIWindow { - @objc func wdb_makeKeyAndVisible() { - print("Making window key and visible...") - - self.windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene - - self.wdb_makeKeyAndVisible() + return nil } } - diff --git a/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift b/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift index 3902a69e2..ddd89965f 100644 --- a/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift +++ b/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift @@ -14,19 +14,22 @@ struct SettingsView: View { var memoryManagerModes = [ ("HostMapped", "Host (fast)"), ("HostMappedUnsafe", "Host Unchecked (fast, unstable / unsafe)"), - ("SoftwarePageTable", "Software") + ("SoftwarePageTable", "Software (slow)"), ] + @AppStorage("RyuDemoControls") var ryuDemo: Bool = false + @AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false var body: some View { ScrollView { VStack { - Section(header: Text("Graphics and Performance")) { + Section(header: Title("Graphics and Performance")) { Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen) Toggle("Disable V-Sync", isOn: $config.disableVSync) Toggle("Disable Shader Cache", isOn: $config.disableShaderCache) Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression) + Toggle("Disable Docked Mode", isOn: $config.disableDockedMode) Resolution(value: $config.resscale) Toggle("Enable Metal HUD", isOn: $metalHUDEnabled) .onChange(of: metalHUDEnabled) { newValue in @@ -38,17 +41,18 @@ struct SettingsView: View { } } - Section(header: Text("Input Settings")) { + Section(header: Title("Input Settings")) { Toggle("List Input IDs", isOn: $config.listinputids) + Toggle("Nintendo Controller Layout", isOn: $config.nintendoinput) + Toggle("Ryujinx Demo On-Screen Controller", isOn: $ryuDemo) // Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory) - Toggle("Disable Docked Mode", isOn: $config.disableDockedMode) } - Section(header: Text("Logging Settings")) { + Section(header: Title("Logging Settings")) { Toggle("Enable Debug Logs", isOn: $config.debuglogs) Toggle("Enable Trace Logs", isOn: $config.tracelogs) } - Section(header: Text("CPU Mode")) { + Section(header: Title("CPU Mode")) { HStack { Spacer() Picker("Memory Manager Mode", selection: $config.memoryManagerMode) { @@ -62,9 +66,11 @@ struct SettingsView: View { - Section(header: Text("Additional Settings")) { + Section(header: Title("Additional Settings")) { //TextField("Game Path", text: $config.gamepath) + Text("PageSize \(String(Int(getpagesize())))") + TextField("Additional Arguments", text: Binding( get: { config.additionalArgs.joined(separator: ", ") @@ -75,8 +81,8 @@ struct SettingsView: View { )) } } + .padding() } - .padding() .onAppear { if let configs = loadSettings() { self.config = configs @@ -158,3 +164,20 @@ extension NumberFormatter { return formatter } } + + +struct Title: View { + let string: String + + init(_ string: String) { + self.string = string + } + + var body: some View { + VStack { + Text(string) + .font(.title2) + Divider() + } + } +} diff --git a/src/Ryujinx.Common/Utilities/NetworkHelpers.cs b/src/Ryujinx.Common/Utilities/NetworkHelpers.cs index 71e02184e..e42e30e4f 100644 --- a/src/Ryujinx.Common/Utilities/NetworkHelpers.cs +++ b/src/Ryujinx.Common/Utilities/NetworkHelpers.cs @@ -1,3 +1,4 @@ +using System; using System.Buffers.Binary; using System.Net; using System.Net.NetworkInformation; @@ -10,12 +11,13 @@ namespace Ryujinx.Common.Utilities { IPInterfaceProperties properties = adapter.GetIPProperties(); - if (isPreferred || (properties.GatewayAddresses.Count > 0 && properties.DnsAddresses.Count > 0)) + // Skip problematic checks on non-Windows and iOS platforms + if (isPreferred || OperatingSystem.IsWindows() || properties.UnicastAddresses.Count > 0) { foreach (UnicastIPAddressInformation info in properties.UnicastAddresses) { // Only accept an IPv4 address - if (info.Address.GetAddressBytes().Length == 4) + if (info.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { return (properties, info); } @@ -44,8 +46,9 @@ namespace Ryujinx.Common.Utilities { bool isPreferred = adapter.Id == guid; - // Ignore loopback and non IPv4 capable interface. - if (isPreferred || (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4))) + // Ignore loopback and ensure the adapter supports IPv4 + if (isPreferred || + (targetProperties == null && adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && adapter.Supports(NetworkInterfaceComponent.IPv4))) { (IPInterfaceProperties properties, UnicastIPAddressInformation info) = GetLocalInterface(adapter, isPreferred); @@ -77,7 +80,13 @@ namespace Ryujinx.Common.Utilities public static IPAddress ConvertUint(uint ipAddress) { - return new IPAddress(new byte[] { (byte)((ipAddress >> 24) & 0xFF), (byte)((ipAddress >> 16) & 0xFF), (byte)((ipAddress >> 8) & 0xFF), (byte)(ipAddress & 0xFF) }); + return new IPAddress(new byte[] + { + (byte)((ipAddress >> 24) & 0xFF), + (byte)((ipAddress >> 16) & 0xFF), + (byte)((ipAddress >> 8) & 0xFF), + (byte)(ipAddress & 0xFF) + }); } } } diff --git a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs index 3971a33be..d3831d8f3 100644 --- a/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs +++ b/src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs @@ -107,35 +107,53 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard CreateFonts(uiTheme.FontFamily); } - private void CreateFonts(string uiThemeFontFamily) +private void CreateFonts(string uiThemeFontFamily) +{ + // Try a list of fonts in case any of them is not available in the system. + string[] availableFonts = { uiThemeFontFamily }; + + // If it's iOS, we'll want to use a more appropriate set of fonts. + if (OperatingSystem.IsIOS()) + { + availableFonts = new string[] { + "Chalkboard", + "Chalkboard", // San Francisco is the default font on iOS + "Chalkboard", // Legacy iOS font + "Chalkboard" // Common system font + }; + } + else + { + // Fallback for other platforms (e.g., Android, Windows, etc.) + availableFonts = new string[] { + uiThemeFontFamily, + "Liberation Sans", + "FreeSans", + "DejaVu Sans", + "Lucida Grande" + }; + } + + // Try to create the fonts with the selected font families + foreach (string fontFamily in availableFonts) + { + try { - // Try a list of fonts in case any of them is not available in the system. + _messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular); + _inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular); + _labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular); - string[] availableFonts = { - uiThemeFontFamily, - "Liberation Sans", - "FreeSans", - "DejaVu Sans", - "Lucida Grande", - }; - - foreach (string fontFamily in availableFonts) - { - try - { - _messageFont = SystemFonts.CreateFont(fontFamily, 26, FontStyle.Regular); - _inputTextFont = SystemFonts.CreateFont(fontFamily, _inputTextFontSize, FontStyle.Regular); - _labelsTextFont = SystemFonts.CreateFont(fontFamily, 24, FontStyle.Regular); - - return; - } - catch - { - } - } - - throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!"); + return; } + catch + { + // If the font creation fails, try the next font family + } + } + + throw new Exception($"None of these fonts were found in the system: {String.Join(", ", availableFonts)}!"); +} + private static Color ToColor(ThemeColor color, byte? overrideAlpha = null, bool flipRgb = false) { diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs index af80db480..e0348ddc3 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs @@ -1,6 +1,7 @@ using System; using System.Net.NetworkInformation; using System.Runtime.InteropServices; +using System.Net; namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types { @@ -15,16 +16,23 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types public DnsSetting(IPInterfaceProperties interfaceProperties) { IsDynamicDnsEnabled = OperatingSystem.IsWindows() && interfaceProperties.IsDynamicDnsEnabled; + + IPAddress ip = IPAddress.Parse("1.1.1.1"); - if (interfaceProperties.DnsAddresses.Count == 0) - { - PrimaryDns = new IpV4Address(); - SecondaryDns = new IpV4Address(); - } - else - { - PrimaryDns = new IpV4Address(interfaceProperties.DnsAddresses[0]); - SecondaryDns = new IpV4Address(interfaceProperties.DnsAddresses[interfaceProperties.DnsAddresses.Count > 1 ? 1 : 0]); + if (OperatingSystem.IsIOS()) { + PrimaryDns = new IpV4Address(ip); + SecondaryDns = new IpV4Address(ip); + } else { + if (interfaceProperties.DnsAddresses.Count == 0) + { + PrimaryDns = new IpV4Address(); + SecondaryDns = new IpV4Address(); + } + else + { + PrimaryDns = new IpV4Address(interfaceProperties.DnsAddresses[0]); + SecondaryDns = new IpV4Address(interfaceProperties.DnsAddresses[interfaceProperties.DnsAddresses.Count > 1 ? 1 : 0]); + } } } } diff --git a/src/Ryujinx.Headless.SDL2/Options.cs b/src/Ryujinx.Headless.SDL2/Options.cs index ea2063758..37521a57e 100644 --- a/src/Ryujinx.Headless.SDL2/Options.cs +++ b/src/Ryujinx.Headless.SDL2/Options.cs @@ -31,6 +31,9 @@ namespace Ryujinx.Headless.SDL2 // Input + [Option("correct-ons-controller", Required = false, Default = false, HelpText = "Makes the on-screen controller (iOS) buttons correspond to what they show.")] + public bool OnScreenCorrespond { get; set; } + [Option("input-profile-1", Required = false, HelpText = "Set the input profile in use for Player 1.")] public string InputProfile1Name { get; set; } diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index 0997e15dc..42732c6c3 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -127,11 +127,17 @@ namespace Ryujinx.Headless.SDL2 }; } - Parser.Default.ParseArguments(args) - .WithParsed(Load) - .WithNotParsed(errors => errors.Output()); + var result = Parser.Default.ParseArguments(args) + .WithParsed(options => + { + Load(options); // Load is called with the parsed options + }) + .WithNotParsed(errors => errors.Output()); + + } + [UnmanagedCallersOnly(EntryPoint = "get_game_controllers")] public static unsafe IntPtr GetGamepadList() { @@ -164,7 +170,7 @@ namespace Ryujinx.Headless.SDL2 return ptr; } - private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) + private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option) { if (inputId == null) { @@ -264,8 +270,9 @@ namespace Ryujinx.Headless.SDL2 }; } else - { - bool isNintendoStyle = gamepadName.Contains("Nintendo"); + { + bool isAppleController = gamepadName.Contains("Apple") ? option.OnScreenCorrespond : false; + bool isNintendoStyle = gamepadName.Contains("Nintendo") || isAppleController; config = new StandardControllerInputConfig { @@ -461,9 +468,9 @@ namespace Ryujinx.Headless.SDL2 _enableKeyboard = option.EnableKeyboard; _enableMouse = option.EnableMouse; - static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) + static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index, Options option) { - InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index); + InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index, option); if (inputConfig != null) { @@ -471,15 +478,15 @@ namespace Ryujinx.Headless.SDL2 } } - LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1); - LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2); - LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3); - LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4); - LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5); - LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6); - LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7); - LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8); - LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld); + LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1, option); + LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2, option); + LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3, option); + LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4, option); + LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5, option); + LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6, option); + LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7, option); + LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8, option); + LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld, option); if (_inputConfiguration.Count == 0) { @@ -627,7 +634,7 @@ namespace Ryujinx.Headless.SDL2 options.AudioVolume, options.UseHypervisor ?? true, options.MultiplayerLanInterfaceId, - Common.Configuration.Multiplayer.MultiplayerMode.Disabled); + Common.Configuration.Multiplayer.MultiplayerMode.LdnMitm); return new Switch(configuration); }