diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index 220ac3c7d..e79c0c1fe 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 256C91642D8126E300F9736D /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 256C91632D8126E300F9736D /* Alamofire */; }; 4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; }; 4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; }; 4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; }; @@ -205,6 +206,7 @@ files = ( 4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */, CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */, + 256C91642D8126E300F9736D /* Alamofire in Frameworks */, 4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */, 4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */, ); @@ -303,6 +305,7 @@ packageProductDependencies = ( 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */, 4EA5AE812D16807500AD0B9F /* SwiftSVG */, + 256C91632D8126E300F9736D /* Alamofire */, ); productName = MeloNX; productReference = 4E80A98D2CD6F54500029585 /* MeloNX.app */; @@ -395,6 +398,7 @@ packageReferences = ( 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */, 4EA5AE802D16807500AD0B9F /* XCRemoteSwiftPackageReference "SwiftSVG" */, + 256C91622D8126E300F9736D /* XCRemoteSwiftPackageReference "Alamofire" */, ); preferredProjectObjectVersion = 56; productRefGroup = 4E80A98E2CD6F54500029585 /* Products */; @@ -651,7 +655,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 95J8WZ4TN8; + DEVELOPMENT_TEAM = 4TD3JXVDW7; ENABLE_PREVIEWS = YES; ENABLE_TESTABILITY = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -710,6 +714,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", ); GCC_OPTIMIZATION_LEVEL = s; GENERATE_INFOPLIST_FILE = YES; @@ -833,9 +839,13 @@ "$(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 = "$(VERSION)"; - PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; + PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h"; @@ -854,7 +864,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 95J8WZ4TN8; + DEVELOPMENT_TEAM = 4TD3JXVDW7; ENABLE_PREVIEWS = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -913,6 +923,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", ); GCC_OPTIMIZATION_LEVEL = s; GENERATE_INFOPLIST_FILE = YES; @@ -1036,9 +1048,13 @@ "$(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 = "$(VERSION)"; - PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; + PRODUCT_BUNDLE_IDENTIFIER = xyz.belladev.MeloNX; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "MeloNX/App/Core/Headers/Ryujinx-Header.h"; @@ -1235,6 +1251,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 256C91622D8126E300F9736D /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.10.2; + }; + }; 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/michael94ellis/SwiftUIJoystick"; @@ -1254,6 +1278,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 256C91632D8126E300F9736D /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = 256C91622D8126E300F9736D /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */ = { isa = XCSwiftPackageProductDependency; package = 4E0DED322D05695D00FEF007 /* XCRemoteSwiftPackageReference "SwiftUIJoystick" */; diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index af8dd513e..1ff84bf65 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { - "originHash" : "d611b071fbe94fdc9900a07a218340eab4ce2c3c7168bf6542f2830c0400a72b", + "originHash" : "587a0e7c5c7d612a2c16a973e66df9a6a582b963cb51df7c89fd96cb28ef4a63", "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire", + "state" : { + "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", + "version" : "5.10.2" + } + }, { "identity" : "swiftsvg", "kind" : "remoteSourceControl", diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/benlawrence.xcuserdatad/UserInterfaceState.xcuserstate b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/benlawrence.xcuserdatad/UserInterfaceState.xcuserstate index d95c76d65..c09dbeb94 100644 Binary files a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/benlawrence.xcuserdatad/UserInterfaceState.xcuserstate and b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/benlawrence.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX - Debug.xcscheme b/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX - Debug.xcscheme new file mode 100644 index 000000000..918fa0943 --- /dev/null +++ b/src/MeloNX/MeloNX.xcodeproj/xcshareddata/xcschemes/MeloNX - Debug.xcscheme @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MeloNX/MeloNX/App/Models/LatestVersionResponse.swift b/src/MeloNX/MeloNX/App/Models/LatestVersionResponse.swift new file mode 100644 index 000000000..cbec8fba8 --- /dev/null +++ b/src/MeloNX/MeloNX/App/Models/LatestVersionResponse.swift @@ -0,0 +1,37 @@ +// +// LatestVersionResponse.swift +// MeloNX +// +// Created by Bella on 12/03/2025. +// + +struct LatestVersionResponse: Codable { + let version_number: String + let version_number_stripped: String + let changelog: String + let download_link: String + + #if DEBUG + static let example1 = LatestVersionResponse( + version_number: "1.0.0", + version_number_stripped: "100", + changelog: """ + - Rewrite Display Code (SDL isn't used for display anymore) + - Add New Onboarding / Setup + - Better Performance + - Remove "SDL Window" option in settings + - Fix JIT Cache Regions + - Fix how JIT is detected in Settings + - Fix ABYX being swapped on controller. + - Settings are now a config.json file + - Fix Performance Overlay not showing when Virtual Controller is hidden + - Add displaying logs when Loading or in-game + - Fix Launching games from outside of the roms folder + - Add Waiting for JIT popup + - Fix spesific Games + - Added Back Herobrine ("You were supposed to be the hero, Bryan") + """, + download_link: "https://example.com" + ) + #endif +} diff --git a/src/MeloNX/MeloNX/App/Views/Main/Updates/MeloNXUpdateSheet.swift b/src/MeloNX/MeloNX/App/Views/Main/Updates/MeloNXUpdateSheet.swift new file mode 100644 index 000000000..63f50da03 --- /dev/null +++ b/src/MeloNX/MeloNX/App/Views/Main/Updates/MeloNXUpdateSheet.swift @@ -0,0 +1,64 @@ +// +// MeloNXUpdateSheet.swift +// MeloNX +// +// Created by Bella on 12/03/2025. +// + +import SwiftUI + +struct MeloNXUpdateSheet: View { + let updateInfo: LatestVersionResponse + @Binding var isPresented: Bool + + var body: some View { + iOSNav { + VStack { + Text("Version \(updateInfo.version_number) is available. You are currently on Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown").") + + VStack { + Text("Changelog:") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.headline) + + ScrollView { + Text(updateInfo.changelog) + .padding() + } + .frame(maxHeight: 400) + .background(Color(.secondarySystemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } + .padding(.top, 15) + + + Spacer() + Button(action: { + if let url = URL(string: updateInfo.download_link) { + UIApplication.shared.open(url) + } + }) { + Text("Download Now") + .font(.title3) + .bold() + .frame(width: 300, height: 40) + } + .buttonStyle(.borderedProminent) + .frame(alignment: .bottom) + } + .padding(.horizontal) + .navigationTitle("Version \(updateInfo.version_number) Available!") + .toolbar { + Button(action: { + isPresented = false + }) { + Text("Close") + } + } + } + } +} + +#Preview { + MeloNXUpdateSheet(updateInfo: LatestVersionResponse.example1, isPresented: .constant(true)) +} diff --git a/src/MeloNX/MeloNX/App/Views/MeloNXApp.swift b/src/MeloNX/MeloNX/App/Views/MeloNXApp.swift index 4f36da0b4..ceada2aa2 100644 --- a/src/MeloNX/MeloNX/App/Views/MeloNXApp.swift +++ b/src/MeloNX/MeloNX/App/Views/MeloNXApp.swift @@ -8,249 +8,82 @@ import SwiftUI import UIKit import CryptoKit - - +import Alamofire @main struct MeloNXApp: App { - - @State var showed = false @Environment(\.scenePhase) var scenePhase - @State var alert: UIAlertController? = nil - @State var finished = false + + // Variables for the update system :) + @State var showOutOfDateSheet = false + @State var updateInfo: LatestVersionResponse? = nil + @AppStorage("hasbeenfinished") var finishedStorage: Bool = false var body: some Scene { WindowGroup { - ZStack { - if showed || DRM != 1 { - - if finishedStorage { - ContentView() - } else { - SetupView(finished: $finished) - .onChange(of: finished) { newValue in - withAnimation { - withAnimation { - finishedStorage = newValue - } - } - } - } + VStack { + if finishedStorage { + ContentView() } else { - Group { - VStack { - Spacer() - - HStack { - Text("Loading...") - ProgressView() - } - Spacer() - - Text(UIDevice.current.identifierForVendor?.uuidString ?? "") - } - } - .onAppear { - initR() - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.black.opacity(1)) - .foregroundColor(.white) - } - } - } - } - - func initR() { - if DRM == 1 { - DispatchQueue.main.async { [self] in - // drmcheck() - InitializeRyujinx() { bool in - if bool { - print("Ryujinx Files Initialized Successfully") - DispatchQueue.main.async { [self] in + SetupView(finished: $finished) + .onChange(of: finished) { newValue in withAnimation { - showed = true - } - - Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in - InitializeRyujinx() { bool in - if !bool, (scenePhase != .background || scenePhase == .inactive) { - withAnimation { - showed = false - } - if !(alert?.isViewLoaded ?? false) { - alert = showDMCAAlert() - } - } else { - DispatchQueue.main.async { - alert?.dismiss(animated: true) - showed = true - } - } + withAnimation { + finishedStorage = newValue } } - } - - } else { - showDMCAAlert() - } - - } - - } - - } - - } - - - 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) - - // Add a text field to the alert - alertController.addTextField { textField in - textField.placeholder = "Enter key here" - } - - // Add the "OK" action - let okAction = UIAlertAction(title: "OK", style: .default) { _ in - // Get the text entered in the text field - if let textField = alertController.textFields?.first, let enteredText = textField.text { - print("Entered text: \(enteredText)") - UserDefaults.standard.set(enteredText, forKey: "MeloDRMID") - // drmcheck() { bool in - // if bool { - // showed = true - // } else { - // exit(0) - // } - // } } } - alertController.addAction(okAction) - - // Add a "Cancel" action - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - // Present the alert - mainWindow.rootViewController!.present(alertController, animated: true, completion: nil) - - return alertController - } else { - return nil - } - } - - -} - -func showDMCAAlert() -> UIAlertController? { - if let mainWindow = UIApplication.shared.windows.first { - 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) - - DispatchQueue.main.async { - mainWindow.rootViewController!.present(alertController, animated: true, completion: nil) - } - - return alertController - } else { - // uhoh - return nil - } -} - -/* -func drmcheck(completion: @escaping (Bool) -> Void) { - if let deviceid = UIDevice.current.identifierForVendor?.uuidString, let base64device = deviceid.data(using: .utf8)?.base64EncodedString() { - if let value = UserDefaults.standard.string(forKey: "MeloDRMID") { - if let url = URL(string: "https://mx.stossy11.com/auth/\(value)/\(base64device)") { - print(url) - // Create a URLSession - let session = URLSession.shared - - // Create a data task - let task = session.dataTask(with: url) { data, response, error in - // Handle errors - if let error = error { - exit(0) - } - - // Check response and data - if let response = response as? HTTPURLResponse, response.statusCode == 200 { - print("Successfully Recieved API Data") - completion(true) - } else if let response = response as? HTTPURLResponse, response.statusCode == 201 { - print("Successfully Created Auth UUID") - completion(true) - } else { - completion(false) + .onAppear { + checkLatestVersion() + } + // this seems like a weird way to show the sheet but, from my history this is the most reliable way for the content to actually show in the sheet, otherwise its blank + .sheet(isPresented: Binding( + get: { showOutOfDateSheet && updateInfo != nil }, + set: { newValue in + if !newValue { + showOutOfDateSheet = false + updateInfo = nil } } - - // Start the task - task.resume() + )) { + if let updateInfo = updateInfo { + MeloNXUpdateSheet(updateInfo: updateInfo, isPresented: $showOutOfDateSheet) + } } - } else { - completion(false) } - } else { - completion(false) } -} -*/ - -func InitializeRyujinx(completion: @escaping (Bool) -> Void) { - let path = "aHR0cHM6Ly9teC5zdG9zc3kxMS5jb20v" - - guard let value = Bundle.main.object(forInfoDictionaryKey: "MeloID") as? String, !value.isEmpty else { - completion(false) - return - } - - - - if (detectRoms(path: path) != value) { - completion(false) - } - - let configuration = URLSessionConfiguration.default - configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData - configuration.urlCache = nil - - let session = URLSession(configuration: configuration) - - guard let url = URL(string: addFolders(path)!) else { - completion(false) - return - } - - let task = session.dataTask(with: url) { data, response, error in - if error != nil { - completion(false) - } + // sends a GET request to the MeloNXSite API and compares the version it returns to the current app version + func checkLatestVersion() { + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0" + let strippedAppVersion = appVersion.replacingOccurrences(of: ".", with: "") + #if DEBUG + // no this isnt a public ip address silly viewers (i know damn well someone thought this was my real ip), this is local :PP + let url = "http://192.168.178.116:8000/api/latest_release" + #else + // dont spam this :pray: + let url = "https://melonx.org/api/latest_release" + #endif - guard let httpResponse = response as? HTTPURLResponse else { - completion(false) - return + // actually sends the request + AF.request(url).responseDecodable(of: LatestVersionResponse.self) { response in + switch response.result { + case .success(let latestVersionResponse): + let latestAPIVersionStripped = latestVersionResponse.version_number_stripped + if Int(strippedAppVersion) ?? 0 < Int(latestAPIVersionStripped) ?? 0 { + updateInfo = latestVersionResponse + showOutOfDateSheet = true + } + case .failure(let error): + print("Error checking for new version: \(error)") + } } - - if httpResponse.statusCode == 200 { - completion(true) - } else { - completion(false) - } - return } - task.resume() } func detectRoms(path string: String) -> String { @@ -259,8 +92,6 @@ func detectRoms(path string: String) -> String { return romHash.compactMap { String(format: "%02x", $0) }.joined() } - - func addFolders(_ folderPath: String) -> String? { let fileManager = FileManager.default if let data = Data(base64Encoded: folderPath), @@ -271,7 +102,6 @@ func addFolders(_ folderPath: String) -> String? { } extension String { - func print() { Swift.print(self) }