diff --git a/.gitignore b/.gitignore index 7451ca6b..152b1fc5 100644 --- a/.gitignore +++ b/.gitignore @@ -41,5 +41,4 @@ Payload *.ipa.zip xcodebuild.log -Dependencies/em_proxy.xcodeproj/project.pbxproj -Dependencies/minimuxer.xcodeproj/project.pbxproj +build.sh diff --git a/AltStore/Info.plist b/AltStore/Info.plist index 2b4b48de..186097b6 100644 --- a/AltStore/Info.plist +++ b/AltStore/Info.plist @@ -2,6 +2,8 @@ + ALTAnisetteURL + https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx ALTAppGroups group.$(APP_GROUP_IDENTIFIER) @@ -9,12 +11,10 @@ ALTDeviceID 00008101-000129D63698001E - ALTServerID - 1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D ALTPairingFile <insert pairing file here> - ALTAnisetteURL - https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx + ALTServerID + 1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDocumentTypes @@ -44,8 +44,6 @@ $(PRODUCT_NAME) CFBundlePackageType APPL - LSSupportsOpeningDocumentsInPlace - CFBundleShortVersionString $(MARKETING_VERSION) CFBundleURLTypes @@ -93,6 +91,15 @@ LSRequiresIPhoneOS + LSSupportsOpeningDocumentsInPlace + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSAppleMusicUsageDescription + So that we can bypass the 3 app limit and disable revokes using MDC and the tccd exploit. NSBonjourServices _altserver._tcp @@ -131,13 +138,10 @@ fetch remote-notification + UIFileSharingEnabled + UILaunchStoryboardName LaunchScreen - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - UIMainStoryboardFile Main UIRequiredDeviceCapabilities @@ -204,7 +208,5 @@ - UIFileSharingEnabled - diff --git a/AltStore/LaunchViewController.swift b/AltStore/LaunchViewController.swift index 50008be0..5a09fb4a 100644 --- a/AltStore/LaunchViewController.swift +++ b/AltStore/LaunchViewController.swift @@ -46,38 +46,8 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(true) #if !targetEnvironment(simulator) - let dialogMessage = UIAlertController(title: "MDC Patch", message: "please confirm you would like to patch MDC/three-app-limit, press patch to patch, dont patch to launch sidestore without patch", preferredStyle: .alert) - - // Create OK button with action handler - let patch = UIAlertAction(title: "Patch", style: .default, handler: { _ in - patch3AppLimit { result in - switch result { - case .success: - print("patched sucessfully") - case .failure(let err): - switch err { - case .NoFDA(let msg): - self.displayError("Failed to get full disk access: \(msg)") - return - case .FailedPatchd: - self.displayError("Failed to install patchd.") - return - } - } - } - }) - - let noPatch = UIAlertAction(title: "Continue without Patch", style: .default, handler: { _ in - print("starting SS without mdc patch") - }) - - dialogMessage.addAction(patch) - dialogMessage.addAction(noPatch) - - self.present(dialogMessage, animated: true, completion: nil) - start_em_proxy(bind_addr: Consts.Proxy.serverURL) - + guard let pf = fetchPairingFile() else { self.displayError("Device pairing file not found.") return @@ -118,7 +88,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data)) types.append(.xml) let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types) - documentPickerController.shouldShowFileExtensions = true +// documentPickerController.shouldShowFileExtensions = true documentPickerController.delegate = self self.present(documentPickerController, animated: true, completion: nil) }) @@ -242,6 +212,37 @@ extension LaunchViewController { self.destinationViewController.view.alpha = 1.0 } + if UserDefaults.standard.enableMacDirtyCowExploit, UserDefaults.standard.isMacDirtyCowSupported { + if let previous_exploit_time = UserDefaults.standard.object(forKey: "mdcRanBootTime") { + let last_rantime = previous_exploit_time as! Date + if last_rantime == bootTime() { + return print("exploit has ran this boot - \(last_rantime)") + } + } + self.runExploit() + } + self.didFinishLaunching = true } + + func runExploit() { + if UserDefaults.standard.enableMacDirtyCowExploit && UserDefaults.standard.isMacDirtyCowSupported { + patch3AppLimit { result in + switch result { + case .success: + UserDefaults.standard.set(bootTime(), forKey: "mdcRanBootTime") + print("patched sucessfully") + case .failure(let err): + switch err { + case .NoFDA(let msg): + self.displayError("Failed to get full disk access: \(msg)") + return + case .FailedPatchd: + self.displayError("Failed to install patchd.") + return + } + } + } + } + } } diff --git a/AltStore/MDCExploit/MDCExploits.swift b/AltStore/MDCExploit/MDCExploits.swift index c1010d77..021fed19 100644 --- a/AltStore/MDCExploit/MDCExploits.swift +++ b/AltStore/MDCExploit/MDCExploits.swift @@ -16,16 +16,26 @@ func patch3AppLimit(completion: @escaping (PatchResult) -> ()) { if let error = error { completion(.failure(PatchError.NoFDA(msg: "Failed to get full disk access: \(error)"))) } - DispatchQueue.global(qos: .userInitiated).async { - print("This is run on a background queue") - if !patch_installd() { - completion(.failure(PatchError.FailedPatchd)) - } +// DispatchQueue.global(qos: .userInitiated).async { + print("This is run on a background queue") + if !patch_installd() { + completion(.failure(PatchError.FailedPatchd)) } +// } completion(.success) } } +func bootTime() -> Date? { + var tv = timeval() + var tvSize = MemoryLayout.size + let err = sysctlbyname("kern.boottime", &tv, &tvSize, nil, 0); + guard err == 0, tvSize == MemoryLayout.size else { + return nil + } + return Date(timeIntervalSince1970: Double(tv.tv_sec) + Double(tv.tv_usec) / 1_000_000.0) +} + enum WhitelistPatchResult { case success, failure } diff --git a/AltStore/MDCExploit/grant_full_disk_access.m b/AltStore/MDCExploit/grant_full_disk_access.m index 9563412d..828ebbe8 100644 --- a/AltStore/MDCExploit/grant_full_disk_access.m +++ b/AltStore/MDCExploit/grant_full_disk_access.m @@ -580,35 +580,31 @@ static NSData* make_patch_installd(void* executableMap, size_t executableLength) } bool patch_installd() { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - const char* targetPath = "/usr/libexec/installd"; - int fd = open(targetPath, O_RDONLY | O_CLOEXEC); - off_t targetLength = lseek(fd, 0, SEEK_END); - lseek(fd, 0, SEEK_SET); - void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); + const char* targetPath = "/usr/libexec/installd"; + int fd = open(targetPath, O_RDONLY | O_CLOEXEC); + off_t targetLength = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); - NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; - NSData* sourceData = make_patch_installd(targetMap, targetLength); - if (!sourceData) { - NSLog(@"can't patchfind"); -// return ; - } + NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; + NSData* sourceData = make_patch_installd(targetMap, targetLength); + if (!sourceData) { + NSLog(@"can't patchfind"); + return false; + } - if (!overwrite_file(fd, sourceData)) { - overwrite_file(fd, originalData); - munmap(targetMap, targetLength); - NSLog(@"can't overwrite"); -// return ; - } - munmap(targetMap, targetLength); - xpc_crasher("com.apple.mobile.installd"); - sleep(1); + if (!overwrite_file(fd, sourceData)) { + overwrite_file(fd, originalData); + munmap(targetMap, targetLength); + NSLog(@"can't overwrite"); + return false; + } + munmap(targetMap, targetLength); + xpc_crasher("com.apple.mobile.installd"); + sleep(1); - // TODO(zhuowei): for now we revert it once installd starts - // so the change will only last until when this installd exits -// overwrite_file(fd, originalData); - NSLog(@"patched"); -// return; - }); - return true; + // TODO(zhuowei): for now we revert it once installd starts + // so the change will only last until when this installd exits +// overwrite_file(fd, originalData); + return true; } diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index 9f968e16..c97532fa 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -6,13 +6,13 @@ // Copyright © 2019 Riley Testut. All rights reserved. // -import UIKit -import MobileCoreServices -import Intents import Combine +import Intents +import MobileCoreServices +import UIKit -import AltStoreCore import AltSign +import AltStoreCore import Roxas import Nuke @@ -151,8 +151,7 @@ final class MyAppsViewController: UICollectionViewController } @IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue) - { - } + {} } private extension MyAppsViewController @@ -170,7 +169,7 @@ private extension MyAppsViewController dynamicDataSource.numberOfSectionsHandler = { 1 } dynamicDataSource.numberOfItemsHandler = { _ in self.updatesDataSource.itemCount == 0 ? 1 : 0 } dynamicDataSource.cellIdentifierHandler = { _ in "NoUpdatesCell" } - dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in + dynamicDataSource.cellConfigurationHandler = { cell, _, _ in let cell = cell as! NoUpdatesCollectionViewCell cell.layoutMargins.left = self.view.layoutMargins.left cell.layoutMargins.right = self.view.layoutMargins.right @@ -193,7 +192,7 @@ private extension MyAppsViewController let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) dataSource.liveFetchLimit = maximumCollapsedUpdatesCount dataSource.cellIdentifierHandler = { _ in "UpdateCell" } - dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in + dataSource.cellConfigurationHandler = { [weak self] cell, installedApp, _ in guard let self = self else { return } guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return } @@ -245,11 +244,12 @@ private extension MyAppsViewController cell.setNeedsLayout() } - dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in + dataSource.prefetchHandler = { installedApp, _, completionHandler in guard let iconURL = installedApp.storeApp?.iconURL else { return nil } - return RSTAsyncBlockOperation() { (operation) in - ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { (response, error) in + return RSTAsyncBlockOperation + { operation in + ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { response, error in guard !operation.isCancelled else { return operation.finish() } if let image = response?.image @@ -263,7 +263,7 @@ private extension MyAppsViewController }) } } - dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in + dataSource.prefetchCompletionHandler = { cell, image, _, error in let cell = cell as! UpdateCollectionViewCell cell.bannerView.iconImageView.isIndicatingActivity = false cell.bannerView.iconImageView.image = image @@ -288,7 +288,7 @@ private extension MyAppsViewController let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) dataSource.cellIdentifierHandler = { _ in "AppCell" } - dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in + dataSource.cellConfigurationHandler = { cell, installedApp, indexPath in let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary let cell = cell as! InstalledAppCollectionViewCell @@ -363,10 +363,13 @@ private extension MyAppsViewController cell.bannerView.button.progress = nil } } - dataSource.prefetchHandler = { (item, indexPath, completion) in - RSTAsyncBlockOperation { (operation) in - item.managedObjectContext?.perform { - item.loadIcon { (result) in + dataSource.prefetchHandler = { item, _, completion in + RSTAsyncBlockOperation + { _ in + item.managedObjectContext?.perform + { + item.loadIcon + { result in switch result { case .failure(let error): completion(nil, error) @@ -376,7 +379,7 @@ private extension MyAppsViewController } } } - dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in + dataSource.prefetchCompletionHandler = { cell, image, _, _ in let cell = cell as! InstalledAppCollectionViewCell cell.bannerView.iconImageView.image = image cell.bannerView.iconImageView.isIndicatingActivity = false @@ -397,7 +400,7 @@ private extension MyAppsViewController let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) dataSource.cellIdentifierHandler = { _ in "AppCell" } - dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in + dataSource.cellConfigurationHandler = { cell, installedApp, _ in let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary let cell = cell as! InstalledAppCollectionViewCell @@ -437,10 +440,13 @@ private extension MyAppsViewController cell.bannerView.button.progress = nil } } - dataSource.prefetchHandler = { (item, indexPath, completion) in - RSTAsyncBlockOperation { (operation) in - item.managedObjectContext?.perform { - item.loadIcon { (result) in + dataSource.prefetchHandler = { item, _, completion in + RSTAsyncBlockOperation + { _ in + item.managedObjectContext?.perform + { + item.loadIcon + { result in switch result { case .failure(let error): completion(nil, error) @@ -450,7 +456,7 @@ private extension MyAppsViewController } } } - dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in + dataSource.prefetchCompletionHandler = { cell, image, _, _ in let cell = cell as! InstalledAppCollectionViewCell cell.bannerView.iconImageView.image = image cell.bannerView.iconImageView.isIndicatingActivity = false @@ -461,10 +467,7 @@ private extension MyAppsViewController func updateDataSource() { - - self.dataSource.predicate = nil - - + self.dataSource.predicate = nil } } @@ -485,15 +488,17 @@ private extension MyAppsViewController if self.isViewLoaded { - UIView.performWithoutAnimation { + UIView.performWithoutAnimation + { self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue)) } - } + } } func fetchAppIDs() { - AppManager.shared.fetchAppIDs { (result) in + AppManager.shared.fetchAppIDs + { result in do { let (_, context) = try result.get() @@ -506,12 +511,14 @@ private extension MyAppsViewController } } - func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String : Result]) -> Void) + func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String: Result]) -> Void) { let group = AppManager.shared.refresh(installedApps, presentingViewController: self, group: self.refreshGroup) - group.completionHandler = { (results) in - DispatchQueue.main.async { - let failures = results.compactMapValues { (result) -> Error? in + group.completionHandler = { results in + DispatchQueue.main.async + { + let failures = results.compactMapValues + { result -> Error? in switch result { case .failure(OperationError.cancelled): return nil @@ -557,7 +564,8 @@ private extension MyAppsViewController self.refreshGroup = group - UIView.performWithoutAnimation { + UIView.performWithoutAnimation + { self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) } } @@ -570,7 +578,6 @@ private extension MyAppsViewController let visibleCells = self.collectionView.visibleCells self.collectionView.performBatchUpdates({ - self.isUpdateSectionCollapsed.toggle() UIView.animate(withDuration: 0.3, animations: { @@ -644,8 +651,10 @@ private extension MyAppsViewController let installedApps = InstalledApp.fetchAppsForRefreshingAll(in: DatabaseManager.shared.viewContext) - self.refresh(installedApps) { (result) in - DispatchQueue.main.async { + self.refresh(installedApps) + { _ in + DispatchQueue.main.async + { self.isRefreshingAllApps = false self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) } @@ -654,7 +663,8 @@ private extension MyAppsViewController if #available(iOS 14, *) { let interaction = INInteraction.refreshAllApps() - interaction.donate { (error) in + interaction.donate + { error in guard let error = error else { return } print("Failed to donate intent \(interaction.intent).", error) } @@ -669,13 +679,17 @@ private extension MyAppsViewController let installedApp = self.dataSource.item(at: indexPath) let previousProgress = AppManager.shared.installationProgress(for: installedApp) - guard previousProgress == nil else { + guard previousProgress == nil + else + { previousProgress?.cancel() return } - _ = AppManager.shared.update(installedApp, presentingViewController: self) { (result) in - DispatchQueue.main.async { + _ = AppManager.shared.update(installedApp, presentingViewController: self) + { result in + DispatchQueue.main.async + { switch result { case .failure(OperationError.cancelled): @@ -727,11 +741,14 @@ private extension MyAppsViewController { var fileURL: URL? var application: ALTApplication? - var installedApp: InstalledApp? { - didSet { + var installedApp: InstalledApp? + { + didSet + { self.installedAppContext = self.installedApp?.managedObjectContext } } + private var installedAppContext: NSManagedObjectContext? var error: Error? @@ -753,8 +770,10 @@ private extension MyAppsViewController else { let downloadProgress = Progress.discreteProgress(totalUnitCount: 100) - downloadOperation = RSTAsyncBlockOperation { (operation) in - let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in + downloadOperation = RSTAsyncBlockOperation + { operation in + let downloadTask = URLSession.shared.downloadTask(with: url) + { fileURL, response, error in do { let (fileURL, _) = try Result((fileURL, response), error).get() @@ -779,7 +798,8 @@ private extension MyAppsViewController } let unzipProgress = Progress.discreteProgress(totalUnitCount: 1) - let unzipAppOperation = BlockOperation { + let unzipAppOperation = BlockOperation + { do { if let error = context.error @@ -788,7 +808,8 @@ private extension MyAppsViewController } guard let fileURL = context.fileURL else { throw OperationError.invalidParameters } - defer { + defer + { try? FileManager.default.removeItem(at: fileURL) } @@ -813,7 +834,8 @@ private extension MyAppsViewController } let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1) - let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in + let removeAppExtensionsOperation = RSTAsyncBlockOperation + { [weak self] operation in do { if let error = context.error @@ -823,8 +845,10 @@ private extension MyAppsViewController guard let application = context.application else { throw OperationError.invalidParameters } - DispatchQueue.main.async { - self?.removeAppExtensions(from: application) { (result) in + DispatchQueue.main.async + { + self?.removeAppExtensions(from: application) + { result in switch result { case .success: removeAppExtensionsProgress.completedUnitCount = 1 @@ -844,7 +868,8 @@ private extension MyAppsViewController progress.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5) let installProgress = Progress.discreteProgress(totalUnitCount: 100) - let installAppOperation = RSTAsyncBlockOperation { (operation) in + let installAppOperation = RSTAsyncBlockOperation + { operation in do { if let error = context.error @@ -854,7 +879,8 @@ private extension MyAppsViewController guard let application = context.application else { throw OperationError.invalidParameters } - let group = AppManager.shared.install(application, presentingViewController: self) { (result) in + let group = AppManager.shared.install(application, presentingViewController: self) + { result in switch result { case .success(let installedApp): context.installedApp = installedApp @@ -873,7 +899,8 @@ private extension MyAppsViewController installAppOperation.completionBlock = { try? FileManager.default.removeItem(at: temporaryDirectory) - DispatchQueue.main.async { + DispatchQueue.main.async + { self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false self.sideloadingProgressView.observedProgress = nil self.sideloadingProgressView.setHidden(true, animated: true) @@ -883,12 +910,13 @@ private extension MyAppsViewController case .success(let app): completion(.success(())) - app.managedObjectContext?.perform { + app.managedObjectContext?.perform + { print("Successfully installed app:", app.bundleIdentifier) } case .failure(OperationError.cancelled): - completion(.failure((OperationError.cancelled))) + completion(.failure(OperationError.cancelled)) case .failure(let error): let toastView = ToastView(error: error) @@ -930,11 +958,17 @@ private extension MyAppsViewController @objc func presentInactiveAppsAlert() { - let message: String + var message: String if UserDefaults.standard.activeAppLimitIncludesExtensions { message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "") + + if UserDefaults.standard.enableMacDirtyCowExploit + { + message += "\n\n" + message += NSLocalizedString("If you're using the MacDirtyCow exploit to remove the 3-app limit, you can install up to 10 apps and app extensions per Apple ID instead.", comment: "") + } } else { @@ -974,13 +1008,15 @@ private extension MyAppsViewController let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "") let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in + alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { _ in completion(.failure(OperationError.cancelled)) })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) + { _ in completion(.success(())) }) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) + { _ in do { for appExtension in application.appExtensions @@ -1004,7 +1040,8 @@ private extension MyAppsViewController { func open(_ installedApp: InstalledApp) { - UIApplication.shared.open(installedApp.openAppURL) { success in + UIApplication.shared.open(installedApp.openAppURL) + { success in guard !success else { return } let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name)) @@ -1015,16 +1052,20 @@ private extension MyAppsViewController func refresh(_ installedApp: InstalledApp) { let previousProgress = AppManager.shared.refreshProgress(for: installedApp) - guard previousProgress == nil else { + guard previousProgress == nil + else + { previousProgress?.cancel() return } - self.refresh([installedApp]) { (results) in + self.refresh([installedApp]) + { results in // If an error occured, reload the section so the progress bar is no longer visible. if results.values.contains(where: { $0.error != nil }) { - DispatchQueue.main.async { + DispatchQueue.main.async + { self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) } } @@ -1040,7 +1081,8 @@ private extension MyAppsViewController do { let app = try result.get() - app.managedObjectContext?.perform { + app.managedObjectContext?.perform + { try? app.managedObjectContext?.save() } } @@ -1052,7 +1094,8 @@ private extension MyAppsViewController { print("Failed to activate app:", error) - DispatchQueue.main.async { + DispatchQueue.main.async + { installedApp.isActive = false let toastView = ToastView(error: error) @@ -1073,11 +1116,13 @@ private extension MyAppsViewController .filter(\.isActive) .map { $0.publisher(for: \.isActive) } .collect() - .flatMap { publishers in + .flatMap + { publishers in Publishers.MergeMany(publishers) } .first { isActive in !isActive } - .sink { _ in + .sink + { _ in // A previously active app is now inactive, // which means there are now enough slots to activate the app, // so pre-emptively mark it as active to provide visual feedback sooner. @@ -1085,9 +1130,11 @@ private extension MyAppsViewController cancellable?.cancel() } - AppManager.shared.deactivateApps(for: app, presentingViewController: self) { result in + AppManager.shared.deactivateApps(for: app, presentingViewController: self) + { result in cancellable?.cancel() - installedApp.managedObjectContext?.perform { + installedApp.managedObjectContext?.perform + { switch result { case .failure(let error): @@ -1113,7 +1160,8 @@ private extension MyAppsViewController guard installedApp.isActive else { return } installedApp.isActive = false - AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in + AppManager.shared.deactivate(installedApp, presentingViewController: self) + { result in do { let app = try result.get() @@ -1125,7 +1173,8 @@ private extension MyAppsViewController { print("Failed to activate app:", error) - DispatchQueue.main.async { + DispatchQueue.main.async + { installedApp.isActive = true let toastView = ToastView(error: error) @@ -1153,13 +1202,15 @@ private extension MyAppsViewController let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) alertController.addAction(.cancel) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { (action) in - AppManager.shared.remove(installedApp) { (result) in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { _ in + AppManager.shared.remove(installedApp) + { result in switch result { case .success: break case .failure(let error): - DispatchQueue.main.async { + DispatchQueue.main.async + { let toastView = ToastView(error: error) toastView.show(in: self) } @@ -1179,8 +1230,9 @@ private extension MyAppsViewController alertController.addAction(.cancel) let actionTitle = String(format: NSLocalizedString("Back Up %@", comment: ""), installedApp.name) - alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { (action) in - AppManager.shared.backup(installedApp, presentingViewController: self) { (result) in + alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { _ in + AppManager.shared.backup(installedApp, presentingViewController: self) + { result in do { let app = try result.get() @@ -1192,7 +1244,8 @@ private extension MyAppsViewController { print("Failed to back up app:", error) - DispatchQueue.main.async { + DispatchQueue.main.async + { let toastView = ToastView(error: error) toastView.show(in: self) @@ -1201,7 +1254,8 @@ private extension MyAppsViewController } } - DispatchQueue.main.async { + DispatchQueue.main.async + { self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) } })) @@ -1214,8 +1268,9 @@ private extension MyAppsViewController let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name) let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet) alertController.addAction(.cancel) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { (action) in - AppManager.shared.restore(installedApp, presentingViewController: self) { (result) in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { _ in + AppManager.shared.restore(installedApp, presentingViewController: self) + { result in do { let app = try result.get() @@ -1227,14 +1282,16 @@ private extension MyAppsViewController { print("Failed to restore app:", error) - DispatchQueue.main.async { + DispatchQueue.main.async + { let toastView = ToastView(error: error) toastView.show(in: self) } } } - DispatchQueue.main.async { + DispatchQueue.main.async + { self.collectionView.reloadSections([Section.activeApps.rawValue]) } })) @@ -1267,7 +1324,8 @@ private extension MyAppsViewController self.activeAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp) self.inactiveAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp) - DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in + DatabaseManager.shared.persistentContainer.performBackgroundTask + { context in do { let tempApp = context.object(with: installedApp.objectID) as! InstalledApp @@ -1291,7 +1349,8 @@ private extension MyAppsViewController if tempApp.isActive { - DispatchQueue.main.async { + DispatchQueue.main.async + { self.refresh(installedApp) } } @@ -1300,7 +1359,8 @@ private extension MyAppsViewController { print("Failed to change app icon.", error) - DispatchQueue.main.async { + DispatchQueue.main.async + { let toastView = ToastView(error: error) toastView.show(in: self) } @@ -1311,8 +1371,10 @@ private extension MyAppsViewController @available(iOS 14, *) func enableJIT(for installedApp: InstalledApp) { - AppManager.shared.enableJIT(for: installedApp) { result in - DispatchQueue.main.async { + AppManager.shared.enableJIT(for: installedApp) + { result in + DispatchQueue.main.async + { switch result { case .success: break @@ -1329,7 +1391,8 @@ private extension MyAppsViewController { @objc func didFetchSource(_ notification: Notification) { - DispatchQueue.main.async { + DispatchQueue.main.async + { if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil { do { try self.updatesDataSource.fetchedResultsController.performFetch() } @@ -1347,7 +1410,8 @@ private extension MyAppsViewController guard let url = notification.userInfo?[AppDelegate.importAppDeepLinkURLKey] as? URL else { return } - self.sideloadApp(at: url) { (result) in + self.sideloadApp(at: url) + { _ in guard url.isFileURL else { return } do @@ -1374,7 +1438,8 @@ extension MyAppsViewController case .updates: let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView - UIView.performWithoutAnimation { + UIView.performWithoutAnimation + { headerView.button.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15) headerView.button.setTitle("▾", for: .normal) headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28) @@ -1400,7 +1465,8 @@ extension MyAppsViewController case .activeApps where kind == UICollectionView.elementKindSectionHeader: let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ActiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView - UIView.performWithoutAnimation { + UIView.performWithoutAnimation + { headerView.layoutMargins.left = self.view.layoutMargins.left headerView.layoutMargins.right = self.view.layoutMargins.right @@ -1438,7 +1504,8 @@ extension MyAppsViewController case .inactiveApps where kind == UICollectionView.elementKindSectionHeader: let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "InactiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView - UIView.performWithoutAnimation { + UIView.performWithoutAnimation + { headerView.layoutMargins.left = self.view.layoutMargins.left headerView.layoutMargins.right = self.view.layoutMargins.right @@ -1507,50 +1574,61 @@ extension MyAppsViewController { var actions = [UIMenuElement]() - let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app")) { (action) in + let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app")) + { _ in self.open(installedApp) } let openMenu = UIMenu(title: "", options: .displayInline, children: [openAction]) - let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise")) { (action) in + let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise")) + { _ in self.refresh(installedApp) } - let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle")) { (action) in + let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle")) + { _ in self.activate(installedApp) } - let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive) { (action) in + let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive) + { _ in self.deactivate(installedApp) } - let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { (action) in + let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) + { _ in self.remove(installedApp) } - let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt")) { (action) in + let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt")) + { _ in guard #available(iOS 14, *) else { return } self.enableJIT(for: installedApp) } - let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc")) { (action) in + let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc")) + { _ in self.backup(installedApp) } - let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc")) { (action) in + let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc")) + { _ in self.exportBackup(for: installedApp) } - let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc")) { (action) in + let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc")) + { _ in self.restore(installedApp) } - let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo")) { (action) in + let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo")) + { _ in self.chooseIcon(for: installedApp) } - let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) { (action) in + let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) + { _ in self.changeIcon(for: installedApp, to: nil) } @@ -1562,7 +1640,9 @@ extension MyAppsViewController let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions) - guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else { + guard installedApp.bundleIdentifier != StoreApp.altstoreAppID + else + { #if BETA return [refreshAction, changeIconMenu] #else @@ -1605,9 +1685,10 @@ extension MyAppsViewController if let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp) { var backupExists = false - var outError: NSError? = nil + var outError: NSError? - self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError) { (backupDirectoryURL) in + self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError) + { backupDirectoryURL in #if DEBUG backupExists = true #else @@ -1649,7 +1730,7 @@ extension MyAppsViewController // Legacy sideloaded app, so can't detect if it's deleted. actions.append(removeAction) } - else if !UserDefaults.standard.isLegacyDeactivationSupported && !installedApp.isActive + else if !UserDefaults.standard.isLegacyDeactivationSupported, !installedApp.isActive { // Inactive apps are actually deleted, so we need another way // for user to remove them from AltStore. @@ -1670,7 +1751,8 @@ extension MyAppsViewController case .activeApps, .inactiveApps: let installedApp = self.dataSource.item(at: indexPath) - return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { (suggestedActions) -> UIMenu? in + return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) + { _ -> UIMenu? in let actions = self.actions(for: installedApp) let menu = UIMenu(title: "", children: actions) @@ -1868,7 +1950,7 @@ extension MyAppsViewController: UICollectionViewDropDelegate let inactiveAppsHeaderAttributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: Section.inactiveApps.rawValue)) else { return UICollectionViewDropProposal(operation: .cancel) } - var dropDestinationIndexPath: IndexPath? = nil + var dropDestinationIndexPath: IndexPath? defer { @@ -1881,7 +1963,8 @@ extension MyAppsViewController: UICollectionViewDropDelegate let indexPaths = [previousIndexPath, dropDestinationIndexPath].compactMap { $0 } - let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters()) { + let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters()) + { for indexPath in indexPaths { // Access cell directly so we can animate it correctly. @@ -1917,12 +2000,16 @@ extension MyAppsViewController: UICollectionViewDropDelegate { // Activating - guard point.y > activeAppsHeaderAttributes.frame.minY else { + guard point.y > activeAppsHeaderAttributes.frame.minY + else + { // Above active apps section. return UICollectionViewDropProposal(operation: .cancel) } - guard point.y < inactiveAppsHeaderAttributes.frame.minY else { + guard point.y < inactiveAppsHeaderAttributes.frame.minY + else + { // Inactive apps section. return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) } @@ -1940,13 +2027,17 @@ extension MyAppsViewController: UICollectionViewDropDelegate // Not enough active app slots, so we need to deactivate an app. // Provided destinationIndexPath is inaccurate. - guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue else { + guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue + else + { // Invalid destination index path. return UICollectionViewDropProposal(operation: .cancel) } let installedApp = self.dataSource.item(at: indexPath) - guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else { + guard installedApp.bundleIdentifier != StoreApp.altstoreAppID + else + { // Can't deactivate AltStore. return UICollectionViewDropProposal(operation: .forbidden, intent: .insertIntoDestinationIndexPath) } @@ -1978,8 +2069,10 @@ extension MyAppsViewController: UICollectionViewDropDelegate installedApp.isActive = true let previousInstalledApp = self.dataSource.item(at: destinationIndexPath) - self.deactivate(previousInstalledApp) { (result) in - installedApp.managedObjectContext?.perform { + self.deactivate(previousInstalledApp) + { result in + installedApp.managedObjectContext?.perform + { switch result { case .failure: installedApp.isActive = false @@ -2053,7 +2146,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate switch controller.documentPickerMode { case .import, .open: - self.sideloadApp(at: fileURL) { (result) in + self.sideloadApp(at: fileURL) + { result in print("Sideloaded app at \(fileURL) with result:", result) } @@ -2079,7 +2173,7 @@ extension MyAppsViewController: UIViewControllerPreviewingDelegate previewingContext.sourceRect = cell.frame let app = self.dataSource.item(at: indexPath) - guard let storeApp = app.storeApp else { return nil} + guard let storeApp = app.storeApp else { return nil } let appViewController = AppViewController.makeAppViewController(app: storeApp) return appViewController @@ -2099,9 +2193,10 @@ extension MyAppsViewController: UIViewControllerPreviewingDelegate extension MyAppsViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - defer { + defer + { picker.dismiss(animated: true, completion: nil) self._imagePickerInstalledApp = nil } diff --git a/AltStore/Operations/AuthenticationOperation.swift b/AltStore/Operations/AuthenticationOperation.swift index af510de2..b155ac20 100644 --- a/AltStore/Operations/AuthenticationOperation.swift +++ b/AltStore/Operations/AuthenticationOperation.swift @@ -7,11 +7,11 @@ // import Foundation -import Roxas import Network +import Roxas -import AltStoreCore import AltSign +import AltStoreCore enum AuthenticationError: LocalizedError { @@ -22,8 +22,10 @@ enum AuthenticationError: LocalizedError case missingPrivateKey case missingCertificate - var errorDescription: String? { - switch self { + var errorDescription: String? + { + switch self + { case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "") case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "") case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "") @@ -82,7 +84,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A } // Sign In - self.signIn() { (result) in + self.signIn + { result in guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } switch result @@ -93,7 +96,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A self.progress.completedUnitCount += 1 // Fetch Team - self.fetchTeam(for: account, session: session) { (result) in + self.fetchTeam(for: account, session: session) + { result in guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } switch result @@ -104,7 +108,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A self.progress.completedUnitCount += 1 // Fetch Certificate - self.fetchCertificate(for: team, session: session) { (result) in + self.fetchCertificate(for: team, session: session) + { result in guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } switch result @@ -115,7 +120,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A self.progress.completedUnitCount += 1 // Register Device - self.registerCurrentDevice(for: team, session: session) { (result) in + self.registerCurrentDevice(for: team, session: session) + { result in guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } switch result @@ -125,7 +131,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A self.progress.completedUnitCount += 1 // Save account/team to disk. - self.save(team) { (result) in + self.save(team) + { result in guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } switch result @@ -133,7 +140,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A case .failure(let error): self.finish(.failure(error)) case .success: // Must cache App IDs _after_ saving account/team to disk. - self.cacheAppIDs(team: team, session: session) { (result) in + self.cacheAppIDs(team: team, session: session) + { result in let result = result.map { _ in (team, certificate, session) } self.finish(result) } @@ -152,7 +160,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A func save(_ altTeam: ALTTeam, completionHandler: @escaping (Result) -> Void) { let context = DatabaseManager.shared.persistentContainer.newBackgroundContext() - context.performAndWait { + context.performAndWait + { do { let account: Account @@ -204,7 +213,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A print("Finished authenticating with result:", result.error?.localizedDescription ?? "success") let context = DatabaseManager.shared.persistentContainer.newBackgroundContext() - context.perform { + context.perform + { do { let (altTeam, altCertificate, session) = try result.get() @@ -241,7 +251,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1) if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion) { - UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit + UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit } else { @@ -258,14 +268,16 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A Keychain.shared.signingCertificate = altCertificate.p12Data() Keychain.shared.signingCertificatePassword = altCertificate.machineIdentifier - self.showInstructionsIfNecessary() { (didShowInstructions) in - + self.showInstructionsIfNecessary + { _ in let signer = ALTSigner(team: altTeam, certificate: altCertificate) // Refresh screen must go last since a successful refresh will cause the app to quit. - self.showRefreshScreenIfNecessary(signer: signer, session: session) { (didShowRefreshAlert) in + self.showRefreshScreenIfNecessary(signer: signer, session: session) + { _ in super.finish(result) - DispatchQueue.main.async { + DispatchQueue.main.async + { self.navigationController.dismiss(animated: true, completion: nil) } } @@ -275,7 +287,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A { super.finish(result) - DispatchQueue.main.async { + DispatchQueue.main.async + { self.navigationController.dismiss(animated: true, completion: nil) } } @@ -295,7 +308,7 @@ private extension AuthenticationOperation { guard presentingViewController.presentedViewController == nil else { return false } - self.navigationController.setViewControllers([viewController], animated: false) + self.navigationController.setViewControllers([viewController], animated: false) presentingViewController.present(self.navigationController, animated: true, completion: nil) } else @@ -314,14 +327,16 @@ private extension AuthenticationOperation { func authenticate() { - DispatchQueue.main.async { + DispatchQueue.main.async + { let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController - authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in - self.authenticate(appleID: appleID, password: password) { (result) in + authenticationViewController.authenticationHandler = { appleID, password, completionHandler in + self.authenticate(appleID: appleID, password: password) + { result in completionHandler(result) } } - authenticationViewController.completionHandler = { (result) in + authenticationViewController.completionHandler = { result in if let (account, session, password) = result { // We presented the Auth UI and the user signed in. @@ -346,7 +361,8 @@ private extension AuthenticationOperation if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword { - self.authenticate(appleID: appleID, password: password) { (result) in + self.authenticate(appleID: appleID, password: password) + { result in switch result { case .success((let account, let session)): @@ -372,7 +388,7 @@ private extension AuthenticationOperation self.appleIDEmailAddress = appleID let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context) - fetchAnisetteDataOperation.resultHandler = { (result) in + fetchAnisetteDataOperation.resultHandler = { result in switch result { case .failure(let error): completionHandler(.failure(error)) @@ -381,10 +397,12 @@ private extension AuthenticationOperation if let presentingViewController = self.presentingViewController { - verificationHandler = { (completionHandler) in - DispatchQueue.main.async { + verificationHandler = { completionHandler in + DispatchQueue.main.async + { let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert) - alertController.addTextField { (textField) in + alertController.addTextField + { textField in textField.autocorrectionType = .no textField.autocapitalizationType = .none textField.keyboardType = .numberPad @@ -392,7 +410,8 @@ private extension AuthenticationOperation NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField) } - let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in + let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) + { _ in let textField = alertController.textFields?.first let code = textField?.text ?? "" @@ -402,7 +421,8 @@ private extension AuthenticationOperation alertController.addAction(submitAction) self.submitCodeAction = submitAction - alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in + alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) + { _ in completionHandler(nil) }) @@ -424,7 +444,8 @@ private extension AuthenticationOperation } ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData, - verificationHandler: verificationHandler) { (account, session, error) in + verificationHandler: verificationHandler) + { account, session, error in if let account = account, let session = session { completionHandler(.success((account, session))) @@ -443,34 +464,43 @@ private extension AuthenticationOperation func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) { func selectTeam(from teams: [ALTTeam]) - { - if teams.count <= 1 { - if let team = teams.first { - return completionHandler(.success(team)) - } else { - return completionHandler(.failure(AuthenticationError.noTeam)) - } - } else { - DispatchQueue.main.async { - let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController + { + if teams.count <= 1 + { + if let team = teams.first + { + return completionHandler(.success(team)) + } + else + { + return completionHandler(.failure(AuthenticationError.noTeam)) + } + } + else + { + DispatchQueue.main.async + { + let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController - selectTeamViewController.teams = teams - selectTeamViewController.completionHandler = completionHandler + selectTeamViewController.teams = teams + selectTeamViewController.completionHandler = completionHandler - if !self.present(selectTeamViewController) - { - return completionHandler(.failure(AuthenticationError.noTeam)) - } - } - } - } + if !self.present(selectTeamViewController) + { + return completionHandler(.failure(AuthenticationError.noTeam)) + } + } + } + } - ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in + ALTAppleAPI.shared.fetchTeams(for: account, session: session) + { teams, error in switch Result(teams, error) { case .failure(let error): completionHandler(.failure(error)) case .success(let teams): - DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in + DatabaseManager.shared.persistentContainer.performBackgroundTask + { context in if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier }) { completionHandler(.success(altTeam)) @@ -489,18 +519,22 @@ private extension AuthenticationOperation func requestCertificate() { let machineName = "AltStore - " + UIDevice.current.name - ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { (certificate, error) in + ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) + { certificate, error in do { let certificate = try Result(certificate, error).get() guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey } - ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in + ALTAppleAPI.shared.fetchCertificates(for: team, session: session) + { certificates, error in do { let certificates = try Result(certificates, error).get() - guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else { + guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) + else + { throw AuthenticationError.missingCertificate } @@ -524,7 +558,8 @@ private extension AuthenticationOperation { guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) } - ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in + ALTAppleAPI.shared.revoke(certificate, for: team, session: session) + { success, error in if let error = error, !success { completionHandler(.failure(error)) @@ -536,7 +571,8 @@ private extension AuthenticationOperation } } - ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in + ALTAppleAPI.shared.fetchCertificates(for: team, session: session) + { certificates, error in do { let certificates = try Result(certificates, error).get() @@ -593,11 +629,14 @@ private extension AuthenticationOperation func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) { - guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { + guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String + else + { return completionHandler(.failure(OperationError.unknownUDID)) } - ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session) { (devices, error) in + ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session) + { devices, error in do { let devices = try Result(devices, error).get() @@ -608,7 +647,8 @@ private extension AuthenticationOperation } else { - ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session) { (device, error) in + ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session) + { device, error in completionHandler(Result(device, error)) } } @@ -623,7 +663,7 @@ private extension AuthenticationOperation func cacheAppIDs(team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) { let fetchAppIDsOperation = FetchAppIDsOperation(context: self.context) - fetchAppIDsOperation.resultHandler = { (result) in + fetchAppIDsOperation.resultHandler = { result in do { let (_, context) = try result.get() @@ -644,7 +684,8 @@ private extension AuthenticationOperation { guard self.shouldShowInstructions else { return completionHandler(false) } - DispatchQueue.main.async { + DispatchQueue.main.async + { let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController instructionsViewController.showsBottomButton = true instructionsViewController.completionHandler = { @@ -668,7 +709,8 @@ private extension AuthenticationOperation #if DEBUG completionHandler(false) #else - DispatchQueue.main.async { + DispatchQueue.main.async + { let context = AuthenticatedOperationContext(context: self.context) context.operations.removeAllObjects() // Prevent deadlock due to endless waiting on previous operations to finish. diff --git a/AltStore/Operations/InstallAppOperation.swift b/AltStore/Operations/InstallAppOperation.swift index f10c1272..068efe19 100644 --- a/AltStore/Operations/InstallAppOperation.swift +++ b/AltStore/Operations/InstallAppOperation.swift @@ -8,8 +8,8 @@ import Foundation import Network -import AltStoreCore import AltSign +import AltStoreCore import Roxas @objc(InstallAppOperation) @@ -44,8 +44,8 @@ final class InstallAppOperation: ResultOperation else { return self.finish(.failure(OperationError.invalidParameters)) } let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext() - backgroundContext.perform { - + backgroundContext.perform + { /* App */ let installedApp: InstalledApp @@ -141,8 +141,9 @@ final class InstallAppOperation: ResultOperation installedApp.isActive = false } } - - activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in + + activeProfiles = Set(activeApps.flatMap + { installedApp -> [String] in let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier } return [installedApp.resignedBundleIdentifier] + appExtensionProfiles }) @@ -152,11 +153,50 @@ final class InstallAppOperation: ResultOperation let ns_bundle_ptr = UnsafeMutablePointer(mutating: ns_bundle.utf8String) let res = minimuxer_install_ipa(ns_bundle_ptr) - if res == 0 { + if res == 0 + { installedApp.refreshedDate = Date() self.finish(.success(installedApp)) - - } else { + } + else if res == -15 + { + // try again + if UserDefaults.standard.enableMacDirtyCowExploit && UserDefaults.standard.isMacDirtyCowSupported + { + patch3AppLimit + { result in + switch result + { + case .success: + UserDefaults.standard.set(bootTime(), forKey: "mdcRanBootTime") + print("patched sucessfully") + case .failure(let err): + switch err + { + case .NoFDA: + self.finish(.failure(OperationError.mdcNoFDA)) + return + case .FailedPatchd: + self.finish(.failure(OperationError.mdcFailedPatchd)) + return + } + } + } + + let res_try_again = minimuxer_install_ipa(ns_bundle_ptr) + if res_try_again == 0 + { + installedApp.refreshedDate = Date() + self.finish(.success(installedApp)) + } + else + { + self.finish(.failure(minimuxer_to_operation(code: res_try_again))) + } + } + } + else + { self.finish(.failure(minimuxer_to_operation(code: res))) } } diff --git a/AltStore/Operations/OperationError.swift b/AltStore/Operations/OperationError.swift index 9d118a82..464e9cb5 100644 --- a/AltStore/Operations/OperationError.swift +++ b/AltStore/Operations/OperationError.swift @@ -6,11 +6,10 @@ // Copyright © 2019 Riley Testut. All rights reserved. // -import Foundation import AltSign +import Foundation -enum OperationError: LocalizedError -{ +enum OperationError: LocalizedError { static let domain = OperationError.unknown._domain case unknown @@ -45,6 +44,8 @@ enum OperationError: LocalizedError case functionArguments case profileInstall case noConnection + case mdcNoFDA + case mdcFailedPatchd var failureReason: String? { switch self { @@ -73,22 +74,21 @@ enum OperationError: LocalizedError case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "") case .profileInstall: return NSLocalizedString("Unable to manage profiles on the device", comment: "") case .noConnection: return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "") + case .mdcNoFDA: return NSLocalizedString("Unable to get Full Disk Access using MDC.", comment: "") + case .mdcFailedPatchd: return NSLocalizedString("Unable to patch installd using MDC.", comment: "") } } var recoverySuggestion: String? { - switch self - { + switch self { case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date): let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "") let message: String - if requiredAppIDs > 1 - { + if requiredAppIDs > 1 { let availableText: String - switch availableAppIDs - { + switch availableAppIDs { case 0: availableText = NSLocalizedString("none are available", comment: "") case 1: availableText = NSLocalizedString("only 1 is available", comment: "") default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs)) @@ -97,8 +97,7 @@ enum OperationError: LocalizedError let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText) message = prefixMessage + " " + baseMessage } - else - { + else { let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date) let dateComponentsFormatter = DateComponentsFormatter() diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard index ccf9515c..f9042599 100644 --- a/AltStore/Settings/Settings.storyboard +++ b/AltStore/Settings/Settings.storyboard @@ -21,7 +21,7 @@