diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index 09a4f0c5f..e44152bbd 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -639,6 +639,10 @@ "$(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 = 0.0.8; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; @@ -790,6 +794,10 @@ "$(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 = 0.0.8; 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 ec38077ce..2cb996d6e 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 a959ba661..44278488f 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 @@ -14,8 +14,8 @@ filePath = "MeloNX/Views/GamesList/GameListView.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "195" - endingLineNumber = "195" + startingLineNumber = "256" + endingLineNumber = "256" landmarkName = "loadGames()" landmarkType = "7"> diff --git a/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h b/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h index 8245a19f2..6fecdba2e 100644 --- a/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h +++ b/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h @@ -26,7 +26,11 @@ struct GameInfo { }; extern struct GameInfo get_game_info(int, char*); -// Declare the main_ryujinx_sdl function, matching the signature + +void install_firmware(const char* inputPtr); + +char* installed_firmware_version(); + int main_ryujinx_sdl(int argc, char **argv); void initialize(); diff --git a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift index 2513287d0..ba96f35b9 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift @@ -34,6 +34,7 @@ class Ryujinx { let virtualController = VirtualController() @Published var controllerMap: [Controller] = [] + @State var firmwareversion = "0" static let shared = Ryujinx() @@ -197,6 +198,38 @@ class Ryujinx { return args } + func fetchFirmwareVersion() -> String { + do { + let firmwareVersionPointer = installed_firmware_version() + if let pointer = firmwareVersionPointer { + let firmwareVersion = String(cString: pointer) + DispatchQueue.main.async { + self.firmwareversion = firmwareVersion + } + return firmwareVersion + } + + } catch { + print(error) + } + + return "0" + } + + func installFirmware(firmwarePath: String) { + guard let cString = firmwarePath.cString(using: .utf8) else { + print("Invalid firmware path") + return + } + + install_firmware(cString) + + let version = fetchFirmwareVersion() + if !version.isEmpty { + self.firmwareversion = version + } + } + func getConnectedControllers() -> [Controller] { @@ -227,6 +260,38 @@ class Ryujinx { return controllers } + + func removeFirmware() { + let fileManager = FileManager.default + + let documentsfolder = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + + + let bisFolder = documentsfolder.appendingPathComponent("bis") + let systemFolder = bisFolder.appendingPathComponent("system") + let contentsFolder = systemFolder.appendingPathComponent("Contents") + let registeredFolder = contentsFolder.appendingPathComponent("registered").path + + + do { + if fileManager.fileExists(atPath: registeredFolder) { + try fileManager.removeItem(atPath: registeredFolder) + print("Folder removed successfully.") + let version = fetchFirmwareVersion() + + if version.isEmpty { + self.firmwareversion = "0" + } else { + print("Firmware eeeeee \(version)") + } + + } else { + print("Folder does not exist.") + } + } catch { + print("Error removing folder: \(error)") + } + } diff --git a/src/MeloNX/MeloNX/Views/GamesList/GameListView.swift b/src/MeloNX/MeloNX/Views/GamesList/GameListView.swift index 1fdee655e..c1873eafa 100644 --- a/src/MeloNX/MeloNX/Views/GamesList/GameListView.swift +++ b/src/MeloNX/MeloNX/Views/GamesList/GameListView.swift @@ -17,6 +17,8 @@ struct GameLibraryView: View { @AppStorage("recentGames") private var recentGamesData: Data = Data() @State private var recentGames: [Game] = [] @Environment(\.colorScheme) var colorScheme + @State var firmwareInstaller = false + @State var firmwareversion = "0" var filteredGames: [Game] { if searchText.isEmpty { @@ -104,6 +106,42 @@ struct GameLibraryView: View { .onAppear { loadGames() loadRecentGames() + + + let firmware = Ryujinx.shared.fetchFirmwareVersion() + firmwareversion = (firmware == "" ? "0" : firmware) + } + } + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Menu { + + Button { + } label: { + Text("Firmware Version: \(firmwareversion)") + .tint(.white) + } + + if firmwareversion == "0" { + Button { + firmwareInstaller.toggle() + } label: { + Text("Install Firmware") + } + + } else { + Button { + Ryujinx.shared.removeFirmware() + let firmware = Ryujinx.shared.fetchFirmwareVersion() + firmwareversion = (firmware == "" ? "0" : firmware) + } label: { + Text("Remove Firmware") + } + } + } label: { + Image(systemName: "plus") + .foregroundColor(.blue) + } } } } @@ -112,8 +150,31 @@ struct GameLibraryView: View { .onChange(of: searchText) { _ in isSearching = !searchText.isEmpty } + .fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in + switch result { + + case .success(let url): + + do { + + let fun = url.startAccessingSecurityScopedResource() + let path = url.path + + Ryujinx.shared.installFirmware(firmwarePath: path) + + firmwareversion = (Ryujinx.shared.fetchFirmwareVersion() == "" ? "0" : Ryujinx.shared.fetchFirmwareVersion()) + if fun { + url.stopAccessingSecurityScopedResource() + } + } + + case .failure(let error): + print(error) + } + } } + private func addToRecentGames(_ game: Game) { recentGames.removeAll { $0.id == game.id } diff --git a/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift b/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift index 4f42e8136..a059f6699 100644 --- a/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift +++ b/src/MeloNX/MeloNX/Views/SettingsView/SettingsView.swift @@ -282,6 +282,13 @@ struct SettingsView: View { } label: { Text("Advanced Options") } + + Button { + Ryujinx.shared.removeFirmware() + + } label: { + Text("Remove Firmware") + } } header: { Text("Advanced") .font(.title3.weight(.semibold)) diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index 5b583da7a..d686cfe88 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -226,6 +226,68 @@ namespace Ryujinx.Headless.SDL2 } + [UnmanagedCallersOnly(EntryPoint = "install_firmware")] + public static void InstallFirmwareNative(IntPtr inputPtr) + { + try + { + if (inputPtr == IntPtr.Zero) + { + Console.Error.WriteLine("Error: inputPtr is null."); + return; + } + + string inputString = Marshal.PtrToStringAnsi(inputPtr); + + if (string.IsNullOrEmpty(inputString)) + { + Console.Error.WriteLine("Error: inputString is null or empty."); + return; + } + + InstallFirmware(inputString); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error in InstallFirmwareNative: {ex.Message}"); + } + } + + public static void InstallFirmware(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + throw new ArgumentException("File path cannot be null or empty.", nameof(filePath)); + } + + if (_contentManager == null) + { + throw new InvalidOperationException("_contentManager is not initialized."); + } + + _contentManager.InstallFirmware(filePath); + } + + + [UnmanagedCallersOnly(EntryPoint = "installed_firmware_version")] + public static IntPtr GetInstalledFirmwareVersionNative() + { + var result = GetInstalledFirmwareVersion(); + return Marshal.StringToHGlobalAnsi(result); + } + + public static string GetInstalledFirmwareVersion() + { + var version = _contentManager.GetCurrentFirmwareVersion(); + + if (version != null) + { + return version.VersionString; + } + + return String.Empty; + } + [UnmanagedCallersOnly(EntryPoint = "get_game_controllers")] public static unsafe IntPtr GetGamepadList()