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 2632406fd..193e5ea48 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/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/src/MeloNX/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index d2944cbe7..de2cee217 100644 --- a/src/MeloNX/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/src/MeloNX/MeloNX.xcodeproj/xcuserdata/stossy11.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -23,7 +23,7 @@ + consoleCommand = "process handle -s false -n false"> @@ -45,5 +45,30 @@ landmarkType = "7"> + + + + + + + + + + diff --git a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift index 38af03e4c..a910e8949 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift @@ -20,25 +20,48 @@ class Ryujinx { @Published var controllerMap: [Controller] = [] - public struct Configuration { - let gamepath: String - let inputids: [String] - let debuglogs: Bool - let tracelogs: Bool - let listinputids: Bool - let fullscreen: Bool + public struct Configuration : Codable { + var gamepath: String + var inputids: [String] + var debuglogs: Bool + var tracelogs: Bool + var listinputids: Bool + var fullscreen: Bool + var hostMappedMemory: Bool + var disableVSync: Bool + var disableShaderCache: Bool + var disableDockedMode: Bool + var enableTextureRecompression: Bool var additionalArgs: [String] - init(gamepath: String, additionalArgs: [String] = [], debuglogs: Bool = false, tracelogs: Bool = false, listinputids: Bool = false, inputids: [String] = [], ryufullscreen: Bool = false) { + init(gamepath: String, + inputids: [String] = [], + debuglogs: Bool = false, + tracelogs: Bool = false, + listinputids: Bool = false, + fullscreen: Bool = false, + hostMappedMemory: Bool = false, + disableVSync: Bool = true, + disableShaderCache: Bool = false, + disableDockedMode: Bool = true, + enableTextureRecompression: Bool = true, + additionalArgs: [String] = []) { self.gamepath = gamepath + self.inputids = inputids self.debuglogs = debuglogs self.tracelogs = tracelogs - self.inputids = inputids self.listinputids = listinputids - self.fullscreen = ryufullscreen + self.fullscreen = fullscreen + self.disableVSync = disableVSync + self.disableShaderCache = disableShaderCache + self.disableDockedMode = disableDockedMode + self.enableTextureRecompression = enableTextureRecompression self.additionalArgs = additionalArgs + self.hostMappedMemory = hostMappedMemory } } + + func start(with config: Configuration) throws { guard !isRunning else { @@ -94,25 +117,36 @@ class Ryujinx { args.append("Vulkan") // Fixes the Stubs.DispatchLoop Crash - // args.append(contentsOf: ["--memory-manager-mode", "HostMapped"]) - args.append(contentsOf: ["--memory-manager-mode", "SoftwarePageTable"]) + if config.hostMappedMemory { + args.append(contentsOf: ["--memory-manager-mode", "HostMapped"]) + } else { + args.append(contentsOf: ["--memory-manager-mode", "SoftwarePageTable"]) + } if config.fullscreen { - // args.append(contentsOf: ["--fullscreen", String(config.fullscreen)]) args.append(contentsOf: ["--exclusive-fullscreen", String(config.fullscreen)]) args.append(contentsOf: ["--exclusive-fullscreen-width", "1280"]) args.append(contentsOf: ["--exclusive-fullscreen-height", "720"]) - // exclusive-fullscreen } - args.append(contentsOf: ["--disable-vsync", "true"]) // ios already forces vsync - args.append(contentsOf: ["--disable-shader-cache", "false"]) - args.append(contentsOf: ["--disable-docked-mode", "true"]) - args.append(contentsOf: ["--enable-texture-recompression", "true"]) + + // Adding default args directly into additionalArgs + if config.disableVSync { + args.append("--disable-vsync") + } + if config.disableShaderCache { + args.append("--disable-shader-cache") + } + if config.disableDockedMode { + args.append("--disable-docked-mode") + } + if config.enableTextureRecompression { + args.append("--enable-texture-recompression") + } if config.debuglogs { - args.append(contentsOf: ["--enable-debug-logs", String(config.debuglogs)]) + args.append(contentsOf: ["--enable-debug-logs"]) } if config.tracelogs { - args.append(contentsOf: ["--enable-trace-logs", String(config.tracelogs)]) + args.append(contentsOf: ["--enable-trace-logs"]) } // List the input ids diff --git a/src/MeloNX/MeloNX/Views/ContentView.swift b/src/MeloNX/MeloNX/Views/ContentView.swift index 28adbfbc3..925c1989a 100644 --- a/src/MeloNX/MeloNX/Views/ContentView.swift +++ b/src/MeloNX/MeloNX/Views/ContentView.swift @@ -20,6 +20,7 @@ struct ContentView: View { @State var game: URL? = nil @State var controllersList: [Controller] = [] @State var currentControllers: [Controller] = [] + @State var config: Ryujinx.Configuration = Ryujinx.Configuration(gamepath: "") @State private var settings: [MoltenVKSettings] = [ MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"), @@ -77,26 +78,35 @@ struct ContentView: View { } List { - Button { - controllersList = Ryujinx().getConnectedControllers() - controllersList.removeAll(where: { $0.id == "0" }) - } label: { - Text("Refresh") + Section("Settings") { + NavigationLink { + SettingsView(config: $config) + } label: { + Text("Config") + } } - 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) + Section("Controller") { + Button { + controllersList = Ryujinx().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") } - } label: { - Text(controller.name) - } - Spacer() - if currentControllers.contains(where: { $0.id == controller.id }) { - Image(systemName: "checkmark.circle.fill") } } } @@ -109,30 +119,41 @@ struct ContentView: View { func start(displayid: UInt32) { - let config = Ryujinx.Configuration( - gamepath: game!.path, - additionalArgs: [ - // "--display-id", String(displayid) - ], - debuglogs: false, - tracelogs: false, - listinputids: false, - inputids: currentControllers.map(\.id),// "1-dc180005-045e-0000-130b-0000ff870001"], // "2-1fd70005-057e-0000-0920-0000ff870000"], - ryufullscreen: true + if let game { + self.config.gamepath = game.path - ) - - - // Start the emulation - do { - setupVirtualController() + allocateSixGB() - try Ryujinx().start(with: config) + // Start the emulation + do { + setupVirtualController() + + try Ryujinx().start(with: config) + + + } catch { + print("Error \(error.localizedDescription)") + } + } else { - - } catch { - print("Error \(error.localizedDescription)") } + + } + + func allocateSixGB() -> UnsafeMutableRawPointer? { + + let physicalMemory = ProcessInfo.processInfo.physicalMemory + let totalMemoryInGB = Double(physicalMemory) / (1024 * 1024 * 1024) + let mem = totalMemoryInGB - 1 + 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 } func patchMakeKeyAndVisible() { @@ -145,10 +166,32 @@ struct ContentView: View { private func setMoltenVKSettings() { + + if let configs = loadSettings() { + self.config = configs + print(configs) + } + settings.forEach { setting in setenv(setting.string, setting.value, 1) } } + +} +func loadSettings() -> Ryujinx.Configuration? { + guard let jsonString = UserDefaults.standard.string(forKey: "config") else { + return nil + } + + do { + let decoder = JSONDecoder() + if let data = jsonString.data(using: .utf8) { + return try decoder.decode(Ryujinx.Configuration.self, from: data) + } + } catch { + print("Failed to load settings: \(error)") + } + return nil } extension UIWindow { diff --git a/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift b/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift new file mode 100644 index 000000000..23f8291e7 --- /dev/null +++ b/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift @@ -0,0 +1,74 @@ +// +// SettingsView.swift +// MeloNX +// +// Created by Stossy11 on 25/11/2024. +// + +import SwiftUI + +struct SettingsView: View { + @Binding var config: Ryujinx.Configuration + + var body: some View { + Form { + + Section(header: Text("Graphics and Performance")) { + Toggle("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) + } + + Section(header: Text("Input Settings")) { + Toggle("List Input IDs", isOn: $config.listinputids) + Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory) + Toggle("Disable Docked Mode", isOn: $config.disableDockedMode) + } + + Section(header: Text("Logging Settings")) { + Toggle("Enable Debug Logs", isOn: $config.debuglogs) + Toggle("Enable Trace Logs", isOn: $config.tracelogs) + } + + Section(header: Text("Game Settings")) { + //TextField("Game Path", text: $config.gamepath) + + TextField("Additional Arguments", text: Binding( + get: { + config.additionalArgs.joined(separator: ", ") + }, + set: { newValue in + config.additionalArgs = newValue.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) } + } + )) + } + } + .onAppear { + if let configs = loadSettings() { + self.config = configs + print(configs) + } + } + .navigationTitle("Settings") + .navigationBarItems(trailing: Button("Save") { + saveSettings() + }) + } + + func saveSettings() { + do { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted // Optional: Makes the JSON easier to read + let data = try encoder.encode(config) + let jsonString = String(data: data, encoding: .utf8) + + // Save to UserDefaults + UserDefaults.standard.set(jsonString, forKey: "config") + + print("Settings saved successfully!") + } catch { + print("Failed to save settings: \(error)") + } + } +}