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 1fbaf0d30..f83b83b56 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/App/Core/Headers/Ryujinx-Header.h b/src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h index f106a9b5e..fd1ed568d 100644 --- a/src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h +++ b/src/MeloNX/MeloNX/App/Core/Headers/Ryujinx-Header.h @@ -6,6 +6,7 @@ // #define DRM 1 +#define CS_DEBUGGED 0x10000000 #ifndef RyujinxHeader #define RyujinxHeader diff --git a/src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift b/src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift new file mode 100644 index 000000000..ebb447708 --- /dev/null +++ b/src/MeloNX/MeloNX/App/Core/JIT/IsJITEnabled.swift @@ -0,0 +1,19 @@ +// +// IsJITEnabled.swift +// MeloNX +// +// Created by Stossy11 on 10/02/2025. +// + + + +func isJITEnabled() -> Bool { + var flags: Int = 0 + + csops(getpid(), 0, &flags, sizeof(flags)) + return (Int32(flags) & CS_DEBUGGED) != 0; +} + +func sizeof(_ value: T) -> Int { + return MemoryLayout.size +} diff --git a/src/MeloNX/MeloNX/App/Core/JIT/JitStreamerEB/EnableJIT.swift b/src/MeloNX/MeloNX/App/Core/JIT/JitStreamerEB/EnableJIT.swift new file mode 100644 index 000000000..6aefdd297 --- /dev/null +++ b/src/MeloNX/MeloNX/App/Core/JIT/JitStreamerEB/EnableJIT.swift @@ -0,0 +1,78 @@ +// +// EnableJIT.swift +// MeloNX +// +// Created by Stossy11 on 10/02/2025. +// + +import Foundation + +func enableJITEB() { + guard let bundleID = Bundle.main.bundleIdentifier else { + return + } + let address = URL(string: "http://[fd00::]:9172/launch_app/\(bundleID)")! + + let task = URLSession.shared.dataTask(with: address) { data, response, error in + if error != nil { + return + } + + + guard let httpResponse = response as? HTTPURLResponse else { + return + } + DispatchQueue.main.async { + showLaunchAppAlert(jsonData: data!, in: UIApplication.shared.windows.last!.rootViewController!) + } + + return + } + + task.resume() +} + +struct LaunchApp: Codable { + let ok: Bool + let error: String? + let launching: Bool + let position: Int? + let mounting: Bool +} + +func showLaunchAppAlert(jsonData: Data, in viewController: UIViewController) { + do { + let result = try JSONDecoder().decode(LaunchApp.self, from: jsonData) + + var message = "" + + if let error = result.error { + message = "Error: \(error)" + } else if result.mounting { + message = "App is mounting..." + } else if result.launching { + message = "App is launching..." + } else { + message = "App launch status unknown." + } + + if let position = result.position { + message += "\nPosition: \(position)" + } + + let alert = UIAlertController(title: "Launch Status", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + + DispatchQueue.main.async { + viewController.present(alert, animated: true) + } + + } catch { + let alert = UIAlertController(title: "Decoding Error", message: error.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + + DispatchQueue.main.async { + viewController.present(alert, animated: true) + } + } +} diff --git a/src/MeloNX/MeloNX/App/Core/JIT/utils.m b/src/MeloNX/MeloNX/App/Core/JIT/utils.m index de9f31fd6..a7449cd36 100644 --- a/src/MeloNX/MeloNX/App/Core/JIT/utils.m +++ b/src/MeloNX/MeloNX/App/Core/JIT/utils.m @@ -60,15 +60,6 @@ void ShowAlert(NSString* title, NSString* message, _Bool* showok) __attribute__((constructor)) static void entry(int argc, char **argv) { - if (isJITEnabled()) { - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - [defaults setBool:YES forKey:@"JIT"]; - [defaults synchronize]; // Ensure the value is saved immediately - } else { - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - [defaults setBool:NO forKey:@"JIT"]; - [defaults synchronize]; // Ensure the value is saved immediately - } if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) { NSLog(@"Entitlement Does Exist"); diff --git a/src/MeloNX/MeloNX/App/Intents/LaunchGameIntent.swift b/src/MeloNX/MeloNX/App/Intents/LaunchGameIntent.swift index dcd253aa3..d6d314aae 100644 --- a/src/MeloNX/MeloNX/App/Intents/LaunchGameIntent.swift +++ b/src/MeloNX/MeloNX/App/Intents/LaunchGameIntent.swift @@ -32,7 +32,9 @@ struct LaunchGameIntentDef: AppIntent { let ryujinx = Ryujinx.shared.games - let urlString = "melonx://game?\(ryujinx.contains(where: { $0.titleName.localizedCaseInsensitiveContains(gameName) }) ? "name" : "id")=\(gameName)" + let name = findClosestGameName(input: gameName, games: ryujinx.flatMap(\.titleName)) + + let urlString = "melonx://game?name=\(name ?? gameName)" print(urlString) if let url = URL(string: urlString) { UIApplication.shared.open(url, options: [:], completionHandler: nil) @@ -40,14 +42,44 @@ struct LaunchGameIntentDef: AppIntent { return .result() } + + func levenshteinDistance(_ a: String, _ b: String) -> Int { + let aCount = a.count + let bCount = b.count + var matrix = [[Int]](repeating: [Int](repeating: 0, count: bCount + 1), count: aCount + 1) + + for i in 0...aCount { + matrix[i][0] = i + } + + for j in 0...bCount { + matrix[0][j] = j + } + + for i in 1...aCount { + for j in 1...bCount { + let cost = a[a.index(a.startIndex, offsetBy: i - 1)] == b[b.index(b.startIndex, offsetBy: j - 1)] ? 0 : 1 + matrix[i][j] = min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost) + } + } + + return matrix[aCount][bCount] + } + + func findClosestGameName(input: String, games: [String]) -> String? { + let closestGame = games.min { a, b in + let distanceA = levenshteinDistance(input, a) + let distanceB = levenshteinDistance(input, b) + return distanceA < distanceB + } + return closestGame + } } @available(iOS 16.0, *) struct GameOptionsProvider: DynamicOptionsProvider { func results() async throws -> [String] { - Ryujinx.shared.games = Ryujinx.shared.loadGames() - - let dynamicGames = Ryujinx.shared.games + let dynamicGames = Ryujinx.shared.loadGames() return dynamicGames.map { $0.titleName } } diff --git a/src/MeloNX/MeloNX/App/Views/ContentView.swift b/src/MeloNX/MeloNX/App/Views/ContentView.swift index 1112174ab..9d2129ada 100644 --- a/src/MeloNX/MeloNX/App/Views/ContentView.swift +++ b/src/MeloNX/MeloNX/App/Views/ContentView.swift @@ -35,7 +35,7 @@ struct ContentView: View { @AppStorage("useTrollStore") var useTrollStore: Bool = false // JIT - @AppStorage("JIT") var isJITEnabled: Bool = false + @AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false // Other Configuration @State var isMK8: Bool = false @@ -71,7 +71,7 @@ struct ContentView: View { _settings = State(initialValue: defaultSettings) - print("JIT Enabled: \(isJITEnabled)") + print("JIT Enabled: \(isJITEnabled())") initializeSDL() } @@ -122,6 +122,7 @@ struct ContentView: View { if let components = URLComponents(url: url, resolvingAgainstBaseURL: true), components.host == "game" { if let text = components.queryItems?.first(where: { $0.name == "id" })?.value { + game = Ryujinx.shared.games.first(where: { $0.titleId == text }) } else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value { game = Ryujinx.shared.games.first(where: { $0.titleName == text }) @@ -247,12 +248,16 @@ struct ContentView: View { )) - let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED") + let isJIT = isJITEnabled() if !isJIT, useTrollStore { askForJIT() } + if !isJIT, jitStreamerEB { + enableJITEB() + } + } } diff --git a/src/MeloNX/MeloNX/App/Views/Emulation/EmulationView/EmulationView.swift b/src/MeloNX/MeloNX/App/Views/Emulation/EmulationView/EmulationView.swift index f9f2a3d8a..c8a4c1590 100644 --- a/src/MeloNX/MeloNX/App/Views/Emulation/EmulationView/EmulationView.swift +++ b/src/MeloNX/MeloNX/App/Views/Emulation/EmulationView/EmulationView.swift @@ -17,10 +17,10 @@ struct EmulationView: View { if isAirplaying { Text("") .onAppear { - Air.play(AnyView(MetalView().ignoresSafeArea())) + Air.play(AnyView(MetalView(airplay: true).ignoresSafeArea())) } } else { - MetalView() // The Emulation View + MetalView(airplay: false) // The Emulation View .ignoresSafeArea() .edgesIgnoringSafeArea(.all) } diff --git a/src/MeloNX/MeloNX/App/Views/Emulation/MetalView/MetalView.swift b/src/MeloNX/MeloNX/App/Views/Emulation/MetalView/MetalView.swift index cc804aa69..031ea4cd5 100644 --- a/src/MeloNX/MeloNX/App/Views/Emulation/MetalView/MetalView.swift +++ b/src/MeloNX/MeloNX/App/Views/Emulation/MetalView/MetalView.swift @@ -10,8 +10,9 @@ import MetalKit struct MetalView: UIViewRepresentable { + var airplay: Bool // just in case :3 + func makeUIView(context: Context) -> UIView { - let metalLayer = Ryujinx.shared.metalLayer! metalLayer.frame = Ryujinx.shared.emulationUIView.bounds Ryujinx.shared.emulationUIView.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3 diff --git a/src/MeloNX/MeloNX/App/Views/SettingsView/SettingsView.swift b/src/MeloNX/MeloNX/App/Views/SettingsView/SettingsView.swift index 1e1985753..9ebe50eb6 100644 --- a/src/MeloNX/MeloNX/App/Views/SettingsView/SettingsView.swift +++ b/src/MeloNX/MeloNX/App/Views/SettingsView/SettingsView.swift @@ -18,6 +18,8 @@ struct SettingsView: View { @Binding var onscreencontroller: Controller @AppStorage("useTrollStore") var useTrollStore: Bool = false + @AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false + @AppStorage("ignoreJIT") var ignoreJIT: Bool = false var memoryManagerModes = [ @@ -354,21 +356,50 @@ struct SettingsView: View { } .tint(.blue) - Toggle(isOn: $useTrollStore) { - labelWithIcon("TrollStore", iconName: "troll.svg") + if #available(iOS 17.0.1, *) { + Toggle(isOn: $jitStreamerEB) { + labelWithIcon("JitStreamer EB", iconName: "bolt.heart") + } + .tint(.blue) + .contextMenu { + Button { + if let mainWindow = UIApplication.shared.windows.last { + let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert) + + let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in + UIApplication.shared.open(URL(string: "https://jkcoxson.com/jitstreamer")!) + } + alertController.addAction(learnMoreButton) + + let doneButton = UIAlertAction(title: "Done", style: .cancel, handler: nil) + alertController.addAction(doneButton) + + mainWindow.rootViewController?.present(alertController, animated: true) + } + } label: { + Text("About") + } + } + } else { + Toggle(isOn: $useTrollStore) { + labelWithIcon("TrollStore", iconName: "troll.svg") + } + .tint(.blue) } - .tint(.blue) - Toggle(isOn: $config.debuglogs) { - labelWithIcon("Debug Logs", iconName: "exclamationmark.bubble") + DisclosureGroup { + Toggle(isOn: $config.debuglogs) { + labelWithIcon("Debug Logs", iconName: "exclamationmark.bubble") + } + .tint(.blue) + + Toggle(isOn: $config.tracelogs) { + labelWithIcon("Trace Logs", iconName: "waveform.path") + } + .tint(.blue) + } label: { + Text("Logs") } - .tint(.blue) - - Toggle(isOn: $config.tracelogs) { - labelWithIcon("Trace Logs", iconName: "waveform.path") - } - .tint(.blue) - } header: { Text("Miscellaneous Options") @@ -381,6 +412,8 @@ struct SettingsView: View { // Advanced Section { + labelWithIcon("JIT Acquisition: \(isJITEnabled() ? "Aquired" : "Not Aquired" )", iconName: "bolt.fill") + DisclosureGroup { Toggle(isOn: $mVKPreFillBuffer) { diff --git a/src/MeloNX/MeloNX/MeloNXApp.swift b/src/MeloNX/MeloNX/MeloNXApp.swift index f555158b1..b2d45b784 100644 --- a/src/MeloNX/MeloNX/MeloNXApp.swift +++ b/src/MeloNX/MeloNX/MeloNXApp.swift @@ -16,6 +16,7 @@ struct MeloNXApp: App { @State var showed = false @Environment(\.scenePhase) var scenePhase + @State var alert: UIAlertController? = nil var body: some Scene { WindowGroup { @@ -65,7 +66,14 @@ struct MeloNXApp: App { withAnimation { showed = false } - showDMCAAlert() + if !(alert?.isViewLoaded ?? false) { + alert = showDMCAAlert() + } + } else { + DispatchQueue.main.async { + alert?.dismiss(animated: true) + showed = true + } } } } @@ -85,7 +93,7 @@ struct MeloNXApp: App { } - func showAlert() { + func showAlert() -> UIAlertController? { // Create the alert controller if let mainWindow = UIApplication.shared.windows.last { let alertController = UIAlertController(title: "Enter license", message: "Enter license key:", preferredStyle: .alert) @@ -118,23 +126,26 @@ struct MeloNXApp: App { // Present the alert mainWindow.rootViewController!.present(alertController, animated: true, completion: nil) + + return alertController } else { - + return nil } } } -func showDMCAAlert() { - DispatchQueue.main.async { - if let mainWindow = UIApplication.shared.windows.last { - let alertController = UIAlertController(title: "Unauthorized Copy Notice", message: "This app was illegally leaked. Please report the download on the MeloNX Discord. In the meantime, check out Pomelo! \n -Stossy11", preferredStyle: .alert) - - mainWindow.rootViewController!.present(alertController, animated: true, completion: nil) - } else { - // uhoh - } +func showDMCAAlert() -> UIAlertController? { + if let mainWindow = UIApplication.shared.windows.last { + let alertController = UIAlertController(title: "Unauthorized Copy Notice", message: "This app was illegally leaked. Please report the download on the MeloNX Discord. In the meantime, check out Pomelo! \n -Stossy11", preferredStyle: .alert) + + mainWindow.rootViewController!.present(alertController, animated: true, completion: nil) + + return alertController + } else { + // uhoh + return nil } }