diff --git a/AltPlugin/ALTPluginService.h b/AltPlugin/ALTPluginService.h deleted file mode 100644 index b87f972a..00000000 --- a/AltPlugin/ALTPluginService.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// ALTPluginService.h -// AltPlugin -// -// Created by Riley Testut on 11/14/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -#import - -@class ALTAnisetteData; - -NS_ASSUME_NONNULL_BEGIN - -@interface ALTPluginService : NSObject - -@property (class, nonatomic, readonly) ALTPluginService *sharedService; - -- (ALTAnisetteData *)requestAnisetteData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltPlugin/ALTPluginService.m b/AltPlugin/ALTPluginService.m deleted file mode 100644 index 9bbd6c29..00000000 --- a/AltPlugin/ALTPluginService.m +++ /dev/null @@ -1,105 +0,0 @@ -// -// ALTPluginService.m -// AltPlugin -// -// Created by Riley Testut on 11/14/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -#import "ALTPluginService.h" - -#import - -#import "ALTAnisetteData.h" - -@import AppKit; - -@interface AKAppleIDSession : NSObject -- (id)appleIDHeadersForRequest:(id)arg1; -@end - -@interface AKDevice -+ (AKDevice *)currentDevice; -- (NSString *)uniqueDeviceIdentifier; -- (nullable NSString *)serialNumber; -- (NSString *)serverFriendlyDescription; -@end - -@interface ALTPluginService () - -@property (nonatomic, readonly) NSISO8601DateFormatter *dateFormatter; - -@end - -@implementation ALTPluginService - -+ (instancetype)sharedService -{ - static ALTPluginService *_service = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _service = [[self alloc] init]; - }); - - return _service; -} - -- (instancetype)init -{ - self = [super init]; - if (self) - { - _dateFormatter = [[NSISO8601DateFormatter alloc] init]; - } - - return self; -} - -+ (void)initialize -{ - [[ALTPluginService sharedService] start]; -} - -- (void)start -{ - dlopen("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit", RTLD_NOW); - - [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"com.rileytestut.AltServer.FetchAnisetteData" object:nil]; -} - -- (ALTAnisetteData *)requestAnisetteData -{ - NSMutableURLRequest* req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA"]]; - [req setHTTPMethod:@"POST"]; - - AKAppleIDSession *session = [[NSClassFromString(@"AKAppleIDSession") alloc] initWithIdentifier:@"com.apple.gs.xcode.auth"]; - NSDictionary *headers = [session appleIDHeadersForRequest:req]; - - AKDevice *device = [NSClassFromString(@"AKDevice") currentDevice]; - NSDate *date = [self.dateFormatter dateFromString:headers[@"X-Apple-I-Client-Time"]]; - - ALTAnisetteData *anisetteData = [[NSClassFromString(@"ALTAnisetteData") alloc] initWithMachineID:headers[@"X-Apple-I-MD-M"] - oneTimePassword:headers[@"X-Apple-I-MD"] - localUserID:headers[@"X-Apple-I-MD-LU"] - routingInfo:[headers[@"X-Apple-I-MD-RINFO"] longLongValue] - deviceUniqueIdentifier:device.uniqueDeviceIdentifier - deviceSerialNumber:device.serialNumber ?: @"C02LKHBBFD57" // serialNumber can be nil, so provide valid fallback serial number. - deviceDescription:device.serverFriendlyDescription - date:date - locale:[NSLocale currentLocale] - timeZone:[NSTimeZone localTimeZone]]; - - return anisetteData; -} - -- (void)receiveNotification:(NSNotification *)notification -{ - NSString *requestUUID = notification.userInfo[@"requestUUID"]; - - ALTAnisetteData *anisetteData = [self requestAnisetteData]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:anisetteData requiringSecureCoding:YES error:nil]; - - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.rileytestut.AltServer.AnisetteDataResponse" object:nil userInfo:@{@"requestUUID": requestUUID, @"anisetteData": data} deliverImmediately:YES]; -} - -@end diff --git a/AltPlugin/Info.plist b/AltPlugin/Info.plist deleted file mode 100644 index 03abe87f..00000000 --- a/AltPlugin/Info.plist +++ /dev/null @@ -1,171 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2019 Riley Testut. All rights reserved. - NSPrincipalClass - ALTPluginService - Supported10.14PluginCompatibilityUUIDs - - # UUIDs for versions from 10.12 to 99.99.99 - # For mail version 10.0 (3226) on OS X Version 10.12 (build 16A319) - 36CCB8BB-2207-455E-89BC-B9D6E47ABB5B - # For mail version 10.1 (3251) on OS X Version 10.12.1 (build 16B2553a) - 9054AFD9-2607-489E-8E63-8B09A749BC61 - # For mail version 10.2 (3259) on OS X Version 10.12.2 (build 16D12b) - 1CD3B36A-0E3B-4A26-8F7E-5BDF96AAC97E - # For mail version 10.3 (3273) on OS X Version 10.12.4 (build 16G1036) - 21560BD9-A3CC-482E-9B99-95B7BF61EDC1 - # For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i) - C86CD990-4660-4E36-8CDA-7454DEB2E199 - # For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d) - A4343FAF-AE18-40D0-8A16-DFAE481AF9C1 - # For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d) - 6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053 - - Supported10.15PluginCompatibilityUUIDs - - # UUIDs for versions from 10.12 to 99.99.99 - # For mail version 10.0 (3226) on OS X Version 10.12 (build 16A319) - 36CCB8BB-2207-455E-89BC-B9D6E47ABB5B - # For mail version 10.1 (3251) on OS X Version 10.12.1 (build 16B2553a) - 9054AFD9-2607-489E-8E63-8B09A749BC61 - # For mail version 10.2 (3259) on OS X Version 10.12.2 (build 16D12b) - 1CD3B36A-0E3B-4A26-8F7E-5BDF96AAC97E - # For mail version 10.3 (3273) on OS X Version 10.12.4 (build 16G1036) - 21560BD9-A3CC-482E-9B99-95B7BF61EDC1 - # For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i) - C86CD990-4660-4E36-8CDA-7454DEB2E199 - # For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d) - A4343FAF-AE18-40D0-8A16-DFAE481AF9C1 - # For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d) - 6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053 - - Supported11.0PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.10PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.1PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.2PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.3PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.4PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.5PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.6PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.7PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.8PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported11.9PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - - Supported12.0PluginCompatibilityUUIDs - - D985F0E4-3BBC-4B95-BBA1-12056AC4A531 - 224E7F96-2099-499C-A501-63FB68C79CD2 - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - - Supported12.1PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - - Supported12.2PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - - Supported12.3PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - A4B49485-0377-4FAB-8D8E-E3B8018CFC21 - - Supported12.4PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - A4B49485-0377-4FAB-8D8E-E3B8018CFC21 - - Supported12.5PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - A4B49485-0377-4FAB-8D8E-E3B8018CFC21 - - Supported12.6PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - A4B49485-0377-4FAB-8D8E-E3B8018CFC21 - - Supported12.7PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - A4B49485-0377-4FAB-8D8E-E3B8018CFC21 - - Supported12.8PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - A4B49485-0377-4FAB-8D8E-E3B8018CFC21 - - Supported12.9PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - A4B49485-0377-4FAB-8D8E-E3B8018CFC21 - - Supported13.0PluginCompatibilityUUIDs - - 25288CEF-7D9B-49A8-BE6B-E41DA6277CF3 - 6FF8B077-81FA-45A4-BD57-17CDE79F13A5 - A4B49485-0377-4FAB-8D8E-E3B8018CFC21 - 890E3F5B-9490-4828-8F3F-B6561E513FCC - - - diff --git a/AltServer.xcconfig b/AltServer.xcconfig deleted file mode 100644 index 7816cc9a..00000000 --- a/AltServer.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Build.xcconfig" diff --git a/AltServer/AltPlugin.zip b/AltServer/AltPlugin.zip deleted file mode 100644 index 389f9e7c..00000000 Binary files a/AltServer/AltPlugin.zip and /dev/null differ diff --git a/AltServer/AltServer-Bridging-Header.h b/AltServer/AltServer-Bridging-Header.h deleted file mode 100644 index c42bab66..00000000 --- a/AltServer/AltServer-Bridging-Header.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - -#import "ALTDeviceManager.h" -#import "ALTWiredConnection.h" -#import "ALTNotificationConnection.h" -#import "ALTDebugConnection.h" - -// Shared -#import "ALTConstants.h" -#import "ALTConnection.h" -#import "AltXPCProtocol.h" -#import "NSError+ALTServerError.h" -#import "CFNotificationName+AltStore.h" diff --git a/AltServer/AltServer.entitlements b/AltServer/AltServer.entitlements deleted file mode 100644 index 53b24dcc..00000000 --- a/AltServer/AltServer.entitlements +++ /dev/null @@ -1,11 +0,0 @@ - - - - - com.apple.security.temporary-exception.mach-lookup.global-name - - $(PRODUCT_BUNDLE_IDENTIFIER)-spks - $(PRODUCT_BUNDLE_IDENTIFIER)-spki - - - diff --git a/AltServer/AnisetteDataManager.swift b/AltServer/AnisetteDataManager.swift deleted file mode 100644 index c5799d3a..00000000 --- a/AltServer/AnisetteDataManager.swift +++ /dev/null @@ -1,163 +0,0 @@ -// -// AnisetteDataManager.swift -// AltServer -// -// Created by Riley Testut on 11/16/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -import Foundation -import AppKit - -private extension Bundle -{ - struct ID - { - static let mail = "com.apple.mail" - static let altXPC = "com.rileytestut.AltXPC" - } -} - -private extension ALTAnisetteData -{ - func sanitize(byReplacingBundleID bundleID: String) - { - guard let range = self.deviceDescription.lowercased().range(of: "(" + bundleID.lowercased()) else { return } - - var adjustedDescription = self.deviceDescription[..) -> Void] = [:] - private var anisetteDataTimers: [String: Timer] = [:] - - private lazy var xpcConnection: NSXPCConnection = { - let connection = NSXPCConnection(serviceName: Bundle.ID.altXPC) - connection.remoteObjectInterface = NSXPCInterface(with: AltXPCProtocol.self) - connection.resume() - return connection - }() - - private override init() - { - super.init() - - DistributedNotificationCenter.default().addObserver(self, selector: #selector(AnisetteDataManager.handleAnisetteDataResponse(_:)), name: Notification.Name("com.rileytestut.AltServer.AnisetteDataResponse"), object: nil) - } - - func requestAnisetteData(_ completion: @escaping (Result) -> Void) - { - if #available(macOS 10.15, *) - { - self.requestAnisetteDataFromXPCService { (result) in - do - { - let anisetteData = try result.get() - completion(.success(anisetteData)) - } - catch CocoaError.xpcConnectionInterrupted - { - // SIP and/or AMFI are not disabled, so fall back to Mail plug-in. - self.requestAnisetteDataFromPlugin { (result) in - completion(result) - } - } - catch - { - completion(.failure(error)) - } - } - } - else - { - self.requestAnisetteDataFromPlugin { (result) in - completion(result) - } - } - } - - func isXPCAvailable(completion: @escaping (Bool) -> Void) - { - guard let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler({ (error) in - completion(false) - }) as? AltXPCProtocol else { return } - - proxy.ping { - completion(true) - } - } -} - -private extension AnisetteDataManager -{ - @available(macOS 10.15, *) - func requestAnisetteDataFromXPCService(completion: @escaping (Result) -> Void) - { - guard let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler({ (error) in - print("Anisette XPC Error:", error) - completion(.failure(error)) - }) as? AltXPCProtocol else { return } - - proxy.requestAnisetteData { (anisetteData, error) in - anisetteData?.sanitize(byReplacingBundleID: Bundle.ID.altXPC) - completion(Result(anisetteData, error)) - } - } - - func requestAnisetteDataFromPlugin(completion: @escaping (Result) -> Void) - { - let requestUUID = UUID().uuidString - self.anisetteDataCompletionHandlers[requestUUID] = completion - - let isMailRunning = NSWorkspace.shared.runningApplications.map { $0.bundleIdentifier }.contains { $0 == "com.apple.mail" } - - if !isMailRunning, let mailApp = FileManager.default.urls(for: .applicationDirectory,in: .systemDomainMask).first?.appendingPathComponent("Mail.app") { - NSWorkspace.shared.open(mailApp) - } - - let timer = Timer(timeInterval: 5.0, repeats: false) { (timer) in - self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.pluginNotFound))) - } - self.anisetteDataTimers[requestUUID] = timer - - RunLoop.main.add(timer, forMode: .default) - - DistributedNotificationCenter.default().postNotificationName(Notification.Name("com.rileytestut.AltServer.FetchAnisetteData"), object: nil, userInfo: ["requestUUID": requestUUID], options: .deliverImmediately) - } - - @objc func handleAnisetteDataResponse(_ notification: Notification) - { - guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return } - - if - let archivedAnisetteData = userInfo["anisetteData"] as? Data, - let anisetteData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ALTAnisetteData.self, from: archivedAnisetteData) - { - anisetteData.sanitize(byReplacingBundleID: Bundle.ID.mail) - self.finishRequest(forUUID: requestUUID, result: .success(anisetteData)) - } - else - { - self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.invalidAnisetteData))) - } - } - - func finishRequest(forUUID requestUUID: String, result: Result) - { - let completionHandler = self.anisetteDataCompletionHandlers[requestUUID] - self.anisetteDataCompletionHandlers[requestUUID] = nil - - let timer = self.anisetteDataTimers[requestUUID] - self.anisetteDataTimers[requestUUID] = nil - - timer?.invalidate() - completionHandler?(result) - } -} diff --git a/AltServer/AppDelegate.swift b/AltServer/AppDelegate.swift deleted file mode 100644 index 0fdbfa92..00000000 --- a/AltServer/AppDelegate.swift +++ /dev/null @@ -1,617 +0,0 @@ -// -// AppDelegate.swift -// AltServer -// -// Created by Riley Testut on 5/24/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -import Cocoa -import UserNotifications - -import AltSign - -import LaunchAtLogin -import Sparkle - -#if STAGING -private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altstore.ipa")! -#elseif BETA -private let altstoreAppURL = URL(string: "https://cdn.altstore.io/file/altstore/altstore-beta.ipa")! -#else -private let altstoreAppURL = URL(string: "https://cdn.altstore.io/file/altstore/altstore.ipa")! -#endif - -extension ALTDevice: MenuDisplayable {} - -@NSApplicationMain -class AppDelegate: NSObject, NSApplicationDelegate { - - private let pluginManager = PluginManager() - - private var statusItem: NSStatusItem? - - private var connectedDevices = [ALTDevice]() - - private weak var authenticationAlert: NSAlert? - - @IBOutlet private var appMenu: NSMenu! - @IBOutlet private var connectedDevicesMenu: NSMenu! - @IBOutlet private var sideloadIPAConnectedDevicesMenu: NSMenu! - @IBOutlet private var enableJITMenu: NSMenu! - - @IBOutlet private var launchAtLoginMenuItem: NSMenuItem! - @IBOutlet private var installMailPluginMenuItem: NSMenuItem! - @IBOutlet private var installAltStoreMenuItem: NSMenuItem! - @IBOutlet private var sideloadAppMenuItem: NSMenuItem! - @IBOutlet private var checkForUpdatesMenuItem: NSMenuItem! - - private weak var authenticationAppleIDTextField: NSTextField? - private weak var authenticationPasswordTextField: NSSecureTextField? - - private var connectedDevicesMenuController: MenuController! - private var sideloadIPAConnectedDevicesMenuController: MenuController! - private var enableJITMenuController: MenuController! - - private var _jitAppListMenuControllers = [AnyObject]() - - private var isAltPluginUpdateAvailable = false - - @IBOutlet private var updaterController: SPUStandardUpdaterController! = { - return SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) - }() - - func applicationDidFinishLaunching(_ aNotification: Notification) { - UserDefaults.standard.registerDefaults() - - UNUserNotificationCenter.current().delegate = self - - ServerConnectionManager.shared.start() - ALTDeviceManager.shared.start() - - let item = NSStatusBar.system.statusItem(withLength: -1) - item.menu = self.appMenu - item.button?.image = NSImage(named: "MenuBarIcon") - self.statusItem = item - - self.appMenu.delegate = self - - self.sideloadAppMenuItem.keyEquivalentModifierMask = .option - self.sideloadAppMenuItem.isAlternate = true - - let placeholder = NSLocalizedString("No Connected Devices", comment: "") - - self.connectedDevicesMenuController = MenuController(menu: self.connectedDevicesMenu, items: []) - self.connectedDevicesMenuController.placeholder = placeholder - self.connectedDevicesMenuController.action = { [weak self] device in - self?.installAltStore(to: device) - } - - self.sideloadIPAConnectedDevicesMenuController = MenuController(menu: self.sideloadIPAConnectedDevicesMenu, items: []) - self.sideloadIPAConnectedDevicesMenuController.placeholder = placeholder - self.sideloadIPAConnectedDevicesMenuController.action = { [weak self] device in - self?.sideloadIPA(to: device) - } - - self.enableJITMenuController = MenuController(menu: self.enableJITMenu, items: []) - self.enableJITMenuController.placeholder = placeholder - - UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (success, error) in - guard success else { return } - - if !UserDefaults.standard.didPresentInitialNotification - { - let content = UNMutableNotificationContent() - content.title = NSLocalizedString("AltServer Running", comment: "") - content.body = NSLocalizedString("AltServer runs in the background as a menu bar app listening for AltStore.", comment: "") - - let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) - UNUserNotificationCenter.current().add(request) - - UserDefaults.standard.didPresentInitialNotification = true - } - } - - self.pluginManager.isUpdateAvailable { result in - guard let isUpdateAvailable = try? result.get() else { return } - self.isAltPluginUpdateAvailable = isUpdateAvailable - - if isUpdateAvailable - { - self.installMailPlugin() - } - } - } - - func applicationWillTerminate(_ aNotification: Notification) - { - // Insert code here to tear down your application - } -} - -private extension AppDelegate -{ - @objc func installAltStore(to device: ALTDevice) - { - self.installApplication(at: altstoreAppURL, to: device) - } - - @objc func sideloadIPA(to device: ALTDevice) - { - NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) - - let openPanel = NSOpenPanel() - openPanel.canChooseDirectories = false - openPanel.allowsMultipleSelection = false - openPanel.allowedFileTypes = ["ipa"] - openPanel.begin { (response) in - guard let fileURL = openPanel.url, response == .OK else { return } - self.installApplication(at: fileURL, to: device) - } - } - - func enableJIT(for app: InstalledApp, on device: ALTDevice) - { - func finish(_ result: Result) - { - DispatchQueue.main.async { - switch result - { - case .failure(let error): - self.showErrorAlert(error: error, localizedFailure: String(format: NSLocalizedString("JIT compilation could not be enabled for %@.", comment: ""), app.name)) - - case .success: - let alert = NSAlert() - alert.messageText = String(format: NSLocalizedString("Successfully enabled JIT for %@.", comment: ""), app.name) - alert.informativeText = String(format: NSLocalizedString("JIT will remain enabled until you quit the app. You can now disconnect %@ from your computer.", comment: ""), device.name) - alert.runModal() - } - } - } - - ALTDeviceManager.shared.prepare(device) { (result) in - switch result - { - case .failure(let error as NSError): return finish(.failure(error)) - case .success: - ALTDeviceManager.shared.startDebugConnection(to: device) { (connection, error) in - guard let connection = connection else { - return finish(.failure(error! as NSError)) - } - - connection.enableUnsignedCodeExecutionForProcess(withName: app.executableName) { (success, error) in - guard success else { - return finish(.failure(error!)) - } - - finish(.success(())) - } - } - } - } - } - - func installApplication(at url: URL, to device: ALTDevice) - { - let alert = NSAlert() - alert.messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "") - alert.informativeText = NSLocalizedString("Your Apple ID and password are not saved and are only sent to Apple for authentication.", comment: "") - - let textFieldSize = NSSize(width: 300, height: 22) - - let appleIDTextField = NSTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height)) - appleIDTextField.delegate = self - appleIDTextField.translatesAutoresizingMaskIntoConstraints = false - appleIDTextField.placeholderString = NSLocalizedString("Apple ID", comment: "") - alert.window.initialFirstResponder = appleIDTextField - self.authenticationAppleIDTextField = appleIDTextField - - let passwordTextField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height)) - passwordTextField.delegate = self - passwordTextField.translatesAutoresizingMaskIntoConstraints = false - passwordTextField.placeholderString = NSLocalizedString("Password", comment: "") - self.authenticationPasswordTextField = passwordTextField - - appleIDTextField.nextKeyView = passwordTextField - - let stackView = NSStackView(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height * 2)) - stackView.orientation = .vertical - stackView.distribution = .equalSpacing - stackView.spacing = 0 - stackView.addArrangedSubview(appleIDTextField) - stackView.addArrangedSubview(passwordTextField) - alert.accessoryView = stackView - - alert.addButton(withTitle: NSLocalizedString("Install", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - - self.authenticationAlert = alert - self.validate() - - NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) - - let response = alert.runModal() - guard response == .alertFirstButtonReturn else { return } - - let username = appleIDTextField.stringValue - let password = passwordTextField.stringValue - - func finish(_ result: Result) - { - switch result - { - case .success(let application): - let content = UNMutableNotificationContent() - content.title = NSLocalizedString("Installation Succeeded", comment: "") - content.body = String(format: NSLocalizedString("%@ was successfully installed on %@.", comment: ""), application.name, device.name) - - let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) - UNUserNotificationCenter.current().add(request) - - case .failure(InstallError.cancelled), .failure(ALTAppleAPIError.requiresTwoFactorAuthentication): - // Ignore - break - - case .failure(let error): - DispatchQueue.main.async { - self.showErrorAlert(error: error, localizedFailure: String(format: NSLocalizedString("Could not install app to %@.", comment: ""), device.name)) - } - } - } - - func install() - { - ALTDeviceManager.shared.installApplication(at: url, to: device, appleID: username, password: password, completion: finish(_:)) - } - - AnisetteDataManager.shared.isXPCAvailable { isAvailable in - if isAvailable - { - // XPC service is available, so we don't need to install/update Mail plug-in. - // Users can still manually do so from the AltServer menu. - install() - } - else - { - self.pluginManager.isUpdateAvailable { result in - switch result - { - case .failure(let error): finish(.failure(error)) - case .success(let isUpdateAvailable): - self.isAltPluginUpdateAvailable = isUpdateAvailable - - if !self.pluginManager.isMailPluginInstalled || isUpdateAvailable - { - self.installMailPlugin { result in - switch result - { - case .failure: break - case .success: install() - } - } - } - else - { - install() - } - } - } - } - } - } - - func showErrorAlert(error: Error, localizedFailure: String) - { - let nsError = error as NSError - - let alert = NSAlert() - alert.alertStyle = .critical - alert.messageText = localizedFailure - - var messageComponents = [String]() - - let separator: String - switch error - { - case ALTServerError.maximumFreeAppLimitReached: separator = "\n\n" - default: separator = " " - } - - if let errorFailure = nsError.localizedFailure - { - if let debugDescription = nsError.localizedDebugDescription - { - alert.messageText = errorFailure - messageComponents.append(debugDescription) - } - else if let failureReason = nsError.localizedFailureReason - { - if nsError.localizedDescription.starts(with: errorFailure) - { - alert.messageText = errorFailure - messageComponents.append(failureReason) - } - else - { - alert.messageText = errorFailure - messageComponents.append(nsError.localizedDescription) - } - } - else - { - // No failure reason given. - - if nsError.localizedDescription.starts(with: errorFailure) - { - // No need to duplicate errorFailure in both title and message. - alert.messageText = localizedFailure - messageComponents.append(nsError.localizedDescription) - } - else - { - alert.messageText = errorFailure - messageComponents.append(nsError.localizedDescription) - } - } - } - else - { - alert.messageText = localizedFailure - - if let debugDescription = nsError.localizedDebugDescription - { - messageComponents.append(debugDescription) - } - else - { - messageComponents.append(nsError.localizedDescription) - } - } - - if let recoverySuggestion = nsError.localizedRecoverySuggestion - { - messageComponents.append(recoverySuggestion) - } - - let informativeText = messageComponents.joined(separator: separator) - alert.informativeText = informativeText - - NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) - - alert.runModal() - } - - @objc func toggleLaunchAtLogin(_ item: NSMenuItem) - { - LaunchAtLogin.isEnabled.toggle() - } - - @objc func handleInstallMailPluginMenuItem(_ item: NSMenuItem) - { - if !self.pluginManager.isMailPluginInstalled || self.isAltPluginUpdateAvailable - { - self.installMailPlugin() - } - else - { - self.uninstallMailPlugin() - } - } - - private func installMailPlugin(completion: ((Result) -> Void)? = nil) - { - self.pluginManager.installMailPlugin { (result) in - DispatchQueue.main.async { - switch result - { - case .failure(PluginError.cancelled): break - case .failure(let error): - let alert = NSAlert() - alert.messageText = NSLocalizedString("Failed to Install Mail Plug-in", comment: "") - alert.informativeText = error.localizedDescription - alert.runModal() - - case .success: - let alert = NSAlert() - alert.messageText = NSLocalizedString("Mail Plug-in Installed", comment: "") - alert.informativeText = NSLocalizedString("Please restart Mail and enable AltPlugin in Mail's Preferences. Mail must be running when installing or refreshing apps with AltServer.", comment: "") - alert.runModal() - - self.isAltPluginUpdateAvailable = false - } - - completion?(result) - } - } - } - - private func uninstallMailPlugin() - { - self.pluginManager.uninstallMailPlugin { (result) in - DispatchQueue.main.async { - switch result - { - case .failure(PluginError.cancelled): break - case .failure(let error): - let alert = NSAlert() - alert.messageText = NSLocalizedString("Failed to Uninstall Mail Plug-in", comment: "") - alert.informativeText = error.localizedDescription - alert.runModal() - - case .success: - let alert = NSAlert() - alert.messageText = NSLocalizedString("Mail Plug-in Uninstalled", comment: "") - alert.informativeText = NSLocalizedString("Please restart Mail for changes to take effect. You will not be able to use AltServer until the plug-in is reinstalled.", comment: "") - alert.runModal() - } - } - } - } -} - -extension AppDelegate: NSMenuDelegate -{ - func menuWillOpen(_ menu: NSMenu) - { - guard menu == self.appMenu else { return } - - // Clear any cached _jitAppListMenuControllers. - self._jitAppListMenuControllers.removeAll() - - self.connectedDevices = ALTDeviceManager.shared.availableDevices - - self.connectedDevicesMenuController.items = self.connectedDevices - self.sideloadIPAConnectedDevicesMenuController.items = self.connectedDevices - self.enableJITMenuController.items = self.connectedDevices - - self.launchAtLoginMenuItem.target = self - self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:)) - self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off - - if self.isAltPluginUpdateAvailable - { - self.installMailPluginMenuItem.title = NSLocalizedString("Update Mail Plug-in…", comment: "") - } - else if self.pluginManager.isMailPluginInstalled - { - self.installMailPluginMenuItem.title = NSLocalizedString("Uninstall Mail Plug-in…", comment: "") - } - else - { - self.installMailPluginMenuItem.title = NSLocalizedString("Install Mail Plug-in…", comment: "") - } - self.installMailPluginMenuItem.target = self - self.installMailPluginMenuItem.action = #selector(AppDelegate.handleInstallMailPluginMenuItem(_:)) - - // Need to re-set this every time menu appears so we can refresh device app list. - self.enableJITMenuController.submenuHandler = { [weak self] device in - let submenu = NSMenu(title: NSLocalizedString("Sideloaded Apps", comment: "")) - - guard let `self` = self else { return submenu } - - let submenuController = MenuController(menu: submenu, items: []) - submenuController.placeholder = NSLocalizedString("Loading...", comment: "") - submenuController.action = { [weak self] (appInfo) in - self?.enableJIT(for: appInfo, on: device) - } - - // Keep strong reference - self._jitAppListMenuControllers.append(submenuController) - - ALTDeviceManager.shared.fetchInstalledApps(on: device) { (installedApps, error) in - DispatchQueue.main.async { - guard let installedApps = installedApps else { - print("Failed to fetch installed apps from \(device).", error!) - submenuController.placeholder = error?.localizedDescription - return - } - - print("Fetched \(installedApps.count) apps for \(device).") - - let sortedApps = installedApps.sorted { (app1, app2) in - if app1.name == app2.name - { - return app1.bundleIdentifier < app2.bundleIdentifier - } - else - { - return app1.name < app2.name - } - } - - submenuController.items = sortedApps - - if submenuController.items.isEmpty - { - submenuController.placeholder = NSLocalizedString("No Sideloaded Apps", comment: "") - } - } - } - - return submenu - } - } - - func menuDidClose(_ menu: NSMenu) - { - guard menu == self.appMenu else { return } - - // Clearing _jitAppListMenuControllers now prevents action handler from being called. - // self._jitAppListMenuControllers = [] - - // Set `submenuHandler` to nil to prevent prematurely fetching installed apps in menuWillOpen(_:) - // when assigning self.connectedDevices to `items` (which implicitly calls `submenuHandler`) - self.enableJITMenuController.submenuHandler = nil - } - - func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) - { - guard menu == self.appMenu else { return } - - // The submenu won't update correctly if the user holds/releases - // the Option key while the submenu is visible. - // Workaround: temporarily set submenu to nil to dismiss it, - // which will then cause the correct submenu to appear. - - let previousItem: NSMenuItem - switch item - { - case self.sideloadAppMenuItem: previousItem = self.installAltStoreMenuItem - case self.installAltStoreMenuItem: previousItem = self.sideloadAppMenuItem - default: return - } - - let submenu = previousItem.submenu - previousItem.submenu = nil - previousItem.submenu = submenu - } -} - -extension AppDelegate: NSTextFieldDelegate -{ - func controlTextDidChange(_ obj: Notification) - { - self.validate() - } - - func controlTextDidEndEditing(_ obj: Notification) - { - self.validate() - } - - private func validate() - { - guard - let appleID = self.authenticationAppleIDTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines), - let password = self.authenticationPasswordTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) - else { return } - - if appleID.isEmpty || password.isEmpty - { - self.authenticationAlert?.buttons.first?.isEnabled = false - } - else - { - self.authenticationAlert?.buttons.first?.isEnabled = true - } - - self.authenticationAlert?.layout() - } -} - -extension AppDelegate: UNUserNotificationCenterDelegate -{ - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) - { - completionHandler([.alert, .sound, .badge]) - } -} - -// MARK: - Sparkle -//extension AppDelegate: SPUUpdaterDelegate { -// -//} -// -//extension AppDelegate: SPUStandardUserDriverDelegate { -// -//} diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Contents.json b/AltServer/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index e20d34e5..00000000 --- a/AltServer/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "Icon@16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "Icon@32-1.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "Icon@32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "Icon@64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "Icon@128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "Icon@256-1.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "Icon@256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "Icon@512-1.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "Icon@512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "Icon@1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@1024.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@1024.png deleted file mode 100644 index f93aed92..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@1024.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@128.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@128.png deleted file mode 100644 index c1bd9fff..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@128.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@16.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@16.png deleted file mode 100644 index 41bc9989..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@16.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256-1.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256-1.png deleted file mode 100644 index cca771f2..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256-1.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256.png deleted file mode 100644 index cca771f2..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@256.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32-1.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32-1.png deleted file mode 100644 index 82334a85..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32-1.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32.png deleted file mode 100644 index 82334a85..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@32.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512-1.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512-1.png deleted file mode 100644 index ccf5340b..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512-1.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512.png deleted file mode 100644 index ccf5340b..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@512.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@64.png b/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@64.png deleted file mode 100644 index 4db3dea3..00000000 Binary files a/AltServer/Assets.xcassets/AppIcon.appiconset/Icon@64.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/Contents.json b/AltServer/Assets.xcassets/Contents.json deleted file mode 100644 index da4a164c..00000000 --- a/AltServer/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/AltServer/Assets.xcassets/MenuBarIcon.imageset/Contents.json b/AltServer/Assets.xcassets/MenuBarIcon.imageset/Contents.json deleted file mode 100644 index 4b85493e..00000000 --- a/AltServer/Assets.xcassets/MenuBarIcon.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "MenuBar@19.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "MenuBar@38.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" - } -} \ No newline at end of file diff --git a/AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@19.png b/AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@19.png deleted file mode 100644 index 344e5bd6..00000000 Binary files a/AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@19.png and /dev/null differ diff --git a/AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@38.png b/AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@38.png deleted file mode 100644 index 1222aa43..00000000 Binary files a/AltServer/Assets.xcassets/MenuBarIcon.imageset/MenuBar@38.png and /dev/null differ diff --git a/AltServer/Base.lproj/Main.storyboard b/AltServer/Base.lproj/Main.storyboard deleted file mode 100644 index 061518b9..00000000 --- a/AltServer/Base.lproj/Main.storyboard +++ /dev/null @@ -1,390 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AltServer/Categories/NSError+libimobiledevice.h b/AltServer/Categories/NSError+libimobiledevice.h deleted file mode 100644 index 5a711529..00000000 --- a/AltServer/Categories/NSError+libimobiledevice.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// NSError+libimobiledevice.h -// AltServer -// -// Created by Riley Testut on 3/23/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -#import -#import -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface NSError (libimobiledevice) - -+ (nullable instancetype)errorWithMobileImageMounterError:(mobile_image_mounter_error_t)error device:(nullable ALTDevice *)device; -+ (nullable instancetype)errorWithDebugServerError:(debugserver_error_t)error device:(nullable ALTDevice *)device; -+ (nullable instancetype)errorWithInstallationProxyError:(instproxy_error_t)error device:(nullable ALTDevice *)device; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltServer/Categories/NSError+libimobiledevice.mm b/AltServer/Categories/NSError+libimobiledevice.mm deleted file mode 100644 index cbeb3c22..00000000 --- a/AltServer/Categories/NSError+libimobiledevice.mm +++ /dev/null @@ -1,86 +0,0 @@ -// -// NSError+libimobiledevice.m -// AltServer -// -// Created by Riley Testut on 3/23/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -#import "NSError+libimobiledevice.h" -#import "NSError+ALTServerError.h" - -@implementation NSError (libimobiledevice) - -+ (nullable instancetype)errorWithMobileImageMounterError:(mobile_image_mounter_error_t)error device:(nullable ALTDevice *)device -{ - NSMutableDictionary *userInfo = [@{ - ALTUnderlyingErrorDomainErrorKey: @"Mobile Image Mounter", - ALTUnderlyingErrorCodeErrorKey: [@(error) description], - } mutableCopy]; - - if (device) - { - userInfo[ALTDeviceNameErrorKey] = device.name; - } - - switch (error) - { - case MOBILE_IMAGE_MOUNTER_E_SUCCESS: return nil; - case MOBILE_IMAGE_MOUNTER_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo]; - case MOBILE_IMAGE_MOUNTER_E_PLIST_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo]; - case MOBILE_IMAGE_MOUNTER_E_CONN_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo]; - case MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo]; - case MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorDeviceLocked userInfo:userInfo]; - case MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo]; - } -} - -+ (nullable instancetype)errorWithDebugServerError:(debugserver_error_t)error device:(nullable ALTDevice *)device -{ - NSMutableDictionary *userInfo = [@{ - ALTUnderlyingErrorDomainErrorKey: @"Debug Server", - ALTUnderlyingErrorCodeErrorKey: [@(error) description], - } mutableCopy]; - - if (device) - { - userInfo[ALTDeviceNameErrorKey] = device.name; - } - - switch (error) - { - case DEBUGSERVER_E_SUCCESS: return nil; - case DEBUGSERVER_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo]; - case DEBUGSERVER_E_MUX_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo]; - case DEBUGSERVER_E_SSL_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorSSL userInfo:userInfo]; - case DEBUGSERVER_E_RESPONSE_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo]; - case DEBUGSERVER_E_TIMEOUT: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorTimedOut userInfo:userInfo]; - case DEBUGSERVER_E_UNKNOWN_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo]; - } -} - -+ (nullable instancetype)errorWithInstallationProxyError:(instproxy_error_t)error device:(nullable ALTDevice *)device -{ - NSMutableDictionary *userInfo = [@{ - ALTUnderlyingErrorDomainErrorKey: @"Installation Proxy", - ALTUnderlyingErrorCodeErrorKey: [@(error) description], - } mutableCopy]; - - if (device) - { - userInfo[ALTDeviceNameErrorKey] = device.name; - } - - switch (error) - { - case INSTPROXY_E_SUCCESS: return nil; - case INSTPROXY_E_INVALID_ARG: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidRequest userInfo:userInfo]; - case INSTPROXY_E_PLIST_ERROR: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorInvalidResponse userInfo:userInfo]; - case INSTPROXY_E_CONN_FAILED: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUsbmuxd userInfo:userInfo]; - case INSTPROXY_E_RECEIVE_TIMEOUT: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorTimedOut userInfo:userInfo]; -// case INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW: return [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnsupportediOSVersion userInfo:nil]; // Error message assumes we're installing AltStore - default: return [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:userInfo]; - } -} - -@end diff --git a/AltServer/Connections/ALTDebugConnection+Private.h b/AltServer/Connections/ALTDebugConnection+Private.h deleted file mode 100644 index 74d9b442..00000000 --- a/AltServer/Connections/ALTDebugConnection+Private.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// ALTDebugConnection+Private.h -// AltServer -// -// Created by Riley Testut on 2/19/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -#import "ALTDebugConnection.h" - -#include -#include - -NS_ASSUME_NONNULL_BEGIN - -@interface ALTDebugConnection () - -@property (nonatomic, readonly) dispatch_queue_t connectionQueue; - -@property (nonatomic, nullable) debugserver_client_t client; - -- (instancetype)initWithDevice:(ALTDevice *)device; - -- (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltServer/Connections/ALTDebugConnection.h b/AltServer/Connections/ALTDebugConnection.h deleted file mode 100644 index fba2abd2..00000000 --- a/AltServer/Connections/ALTDebugConnection.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// ALTDebugConnection.h -// AltServer -// -// Created by Riley Testut on 2/19/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -#import "AltSign.h" - -NS_ASSUME_NONNULL_BEGIN - -NS_SWIFT_NAME(DebugConnection) -@interface ALTDebugConnection : NSObject - -@property (nonatomic, copy, readonly) ALTDevice *device; - -- (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; -- (void)enableUnsignedCodeExecutionForProcessWithID:(NSInteger)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - -- (void)disconnect; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltServer/Connections/ALTDebugConnection.mm b/AltServer/Connections/ALTDebugConnection.mm deleted file mode 100644 index e7e2598a..00000000 --- a/AltServer/Connections/ALTDebugConnection.mm +++ /dev/null @@ -1,315 +0,0 @@ -// -// ALTDebugConnection.m -// AltServer -// -// Created by Riley Testut on 2/19/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -#import "ALTDebugConnection+Private.h" - -#import "NSError+ALTServerError.h" -#import "NSError+libimobiledevice.h" - -char *bin2hex(const unsigned char *bin, size_t length) -{ - if (bin == NULL || length == 0) - { - return NULL; - } - - char *hex = (char *)malloc(length * 2 + 1); - for (size_t i = 0; i < length; i++) - { - hex[i * 2] = "0123456789ABCDEF"[bin[i] >> 4]; - hex[i * 2 + 1] = "0123456789ABCDEF"[bin[i] & 0x0F]; - } - hex[length * 2] = '\0'; - - return hex; -} - -@implementation ALTDebugConnection - -- (instancetype)initWithDevice:(ALTDevice *)device -{ - self = [super init]; - if (self) - { - _device = device; - _connectionQueue = dispatch_queue_create_with_target("io.altstore.AltServer.DebugConnection", - DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL, - dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)); - } - - return self; -} - -- (void)dealloc -{ - [self disconnect]; -} - -- (void)disconnect -{ - if (_client == nil) - { - return; - } - - debugserver_client_free(_client); - _client = nil; -} - -- (void)connectWithCompletionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler -{ - __block idevice_t device = NULL; - - void (^finish)(BOOL, NSError *) = ^(BOOL success, NSError *error) { - if (device) - { - idevice_free(device); - } - - completionHandler(success, error); - }; - - dispatch_async(self.connectionQueue, ^{ - /* Find Device */ - if (idevice_new_with_options(&device, self.device.identifier.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) - { - return finish(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to debugserver */ - debugserver_client_t client = NULL; - debugserver_error_t error = debugserver_client_start_service(device, &client, "AltServer"); - if (error != DEBUGSERVER_E_SUCCESS) - { - return finish(NO, [NSError errorWithDebugServerError:error device:self.device]); - } - - self.client = client; - - finish(YES, nil); - }); -} - -- (void)enableUnsignedCodeExecutionForProcessWithName:(NSString *)processName completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler -{ - [self _enableUnsignedCodeExecutionForProcessWithName:processName pid:0 completionHandler:completionHandler]; -} - -- (void)enableUnsignedCodeExecutionForProcessWithID:(NSInteger)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler -{ - [self _enableUnsignedCodeExecutionForProcessWithName:nil pid:(int32_t)pid completionHandler:completionHandler]; -} - -- (void)_enableUnsignedCodeExecutionForProcessWithName:(nullable NSString *)processName pid:(int32_t)pid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler -{ - dispatch_async(self.connectionQueue, ^{ - NSString *name = processName ?: NSLocalizedString(@"this app", @""); - NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"JIT could not be enabled for %@.", comment: @""), name]; - - NSString *attachCommand = nil; - - if (processName) - { - NSString *encodedName = @(bin2hex((const unsigned char *)processName.UTF8String, (size_t)strlen(processName.UTF8String))); - attachCommand = [NSString stringWithFormat:@"vAttachOrWait;%@", encodedName]; - } - else - { - // Convert to Big-endian. - int32_t rawPID = CFSwapInt32HostToBig(pid); - - NSString *encodedName = @(bin2hex((const unsigned char *)&rawPID, 4)); - attachCommand = [NSString stringWithFormat:@"vAttach;%@", encodedName]; - } - - NSError *error = nil; - if (![self sendCommand:attachCommand arguments:nil error:&error]) - { - NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; - userInfo[ALTAppNameErrorKey] = processName; - userInfo[ALTDeviceNameErrorKey] = self.device.name; - userInfo[NSLocalizedFailureErrorKey] = localizedFailure; - - NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; - return completionHandler(NO, returnError); - } - - NSString *detachCommand = @"D"; - if (![self sendCommand:detachCommand arguments:nil error:&error]) - { - NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; - userInfo[ALTAppNameErrorKey] = processName; - userInfo[ALTDeviceNameErrorKey] = self.device.name; - userInfo[NSLocalizedFailureErrorKey] = localizedFailure; - - NSError *returnError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; - return completionHandler(NO, returnError); - } - - completionHandler(YES, nil); - }); -} - -#pragma mark - Private - - -- (BOOL)sendCommand:(NSString *)command arguments:(nullable NSArray *)arguments error:(NSError **)error -{ - int argc = (int)arguments.count; - char **argv = new char*[argc + 1]; - - for (int i = 0; i < argc; i++) - { - NSString *argument = arguments[i]; - argv[i] = (char *)argument.UTF8String; - } - - argv[argc] = NULL; - - debugserver_command_t debugCommand = NULL; - debugserver_command_new(command.UTF8String, argc, argv, &debugCommand); - - delete[] argv; - - char *response = NULL; - size_t responseSize = 0; - debugserver_error_t debugServerError = debugserver_client_send_command(self.client, debugCommand, &response, &responseSize); - debugserver_command_free(debugCommand); - - if (debugServerError != DEBUGSERVER_E_SUCCESS) - { - if (error) - { - *error = [NSError errorWithDebugServerError:debugServerError device:self.device]; - } - - return NO; - } - - NSString *rawResponse = (response != nil) ? @(response) : nil; - if (![self processResponse:rawResponse error:error]) - { - return NO; - } - - return YES; -} - -- (BOOL)processResponse:(NSString *)rawResponse error:(NSError **)error -{ - if (rawResponse == nil) - { - if (error) - { - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil]; - return NO; - } - } - - if (rawResponse.length == 0 || [rawResponse isEqualToString:@"OK"]) - { - return YES; - } - - char type = [rawResponse characterAtIndex:0]; - NSString *response = [rawResponse substringFromIndex:1]; - - switch (type) - { - case 'O': - { - // stdout/stderr - - char *decodedResponse = NULL; - debugserver_decode_string(response.UTF8String, response.length, &decodedResponse); - - NSLog(@"Response: %@", @(decodedResponse)); - - if (decodedResponse) - { - free(decodedResponse); - } - - return YES; - } - - case 'T': - { - // Thread Information - - NSLog(@"Thread stopped. Details:\n%s", response.UTF8String + 1); - - // Parse thread state to determine if app is running. - NSArray *components = [response componentsSeparatedByString:@";"]; - if (components.count <= 1) - { - if (error) - { - *error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}]; - } - - return NO; - } - - NSString *mainThread = components[1]; - NSString *threadState = [[mainThread componentsSeparatedByString:@":"] lastObject]; - - NSScanner *scanner = [NSScanner scannerWithString:threadState]; - - unsigned long long mainThreadState = 0; - [scanner scanHexLongLong:&mainThreadState]; - - // If main thread state == 0, app is not running. - if (mainThreadState == 0) - { - if (error) - { - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil]; - } - - return NO; - } - - return YES; - } - - case 'E': - { - // Error - - if (error) - { - NSInteger errorCode = [[[response componentsSeparatedByString:@";"] firstObject] integerValue]; - - switch (errorCode) - { - case 96: - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorRequestedAppNotRunning userInfo:nil]; - break; - - default: - *error = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: response}]; - break; - } - } - - return NO; - } - - case 'W': - { - // Warning - - NSLog(@"WARNING: %@", response); - return YES; - } - } - - return YES; -} - -@end diff --git a/AltServer/Connections/ALTNotificationConnection+Private.h b/AltServer/Connections/ALTNotificationConnection+Private.h deleted file mode 100644 index ee484915..00000000 --- a/AltServer/Connections/ALTNotificationConnection+Private.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ALTNotificationConnection+Private.h -// AltServer -// -// Created by Riley Testut on 1/10/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -#import "ALTNotificationConnection.h" - -#include -#include - -NS_ASSUME_NONNULL_BEGIN - -@interface ALTNotificationConnection () - -@property (nonatomic, readonly) np_client_t client; - -- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltServer/Connections/ALTNotificationConnection.h b/AltServer/Connections/ALTNotificationConnection.h deleted file mode 100644 index 4654ab2e..00000000 --- a/AltServer/Connections/ALTNotificationConnection.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// ALTNotificationConnection.h -// AltServer -// -// Created by Riley Testut on 1/10/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -#import "AltSign.h" - -NS_ASSUME_NONNULL_BEGIN - -NS_SWIFT_NAME(NotificationConnection) -@interface ALTNotificationConnection : NSObject - -@property (nonatomic, copy, readonly) ALTDevice *device; - -@property (nonatomic, copy, nullable) void (^receivedNotificationHandler)(CFNotificationName notification); - -- (void)startListeningForNotifications:(NSArray *)notifications - completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - -- (void)sendNotification:(CFNotificationName)notification - completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - -- (void)disconnect; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltServer/Connections/ALTNotificationConnection.mm b/AltServer/Connections/ALTNotificationConnection.mm deleted file mode 100644 index 4c486d05..00000000 --- a/AltServer/Connections/ALTNotificationConnection.mm +++ /dev/null @@ -1,94 +0,0 @@ -// -// ALTNotificationConnection.m -// AltServer -// -// Created by Riley Testut on 1/10/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -#import "ALTNotificationConnection+Private.h" - -#import "NSError+ALTServerError.h" - -void ALTDeviceReceivedNotification(const char *notification, void *user_data); - -@implementation ALTNotificationConnection - -- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client -{ - self = [super init]; - if (self) - { - _device = [device copy]; - _client = client; - } - - return self; -} - -- (void)dealloc -{ - [self disconnect]; -} - -- (void)disconnect -{ - np_client_free(self.client); - _client = nil; -} - -- (void)startListeningForNotifications:(NSArray *)notifications completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler -{ - dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ - const char **notificationNames = (const char **)malloc((notifications.count + 1) * sizeof(char *)); - for (int i = 0; i < notifications.count; i++) - { - NSString *name = notifications[i]; - notificationNames[i] = name.UTF8String; - } - notificationNames[notifications.count] = NULL; // Must have terminating NULL entry. - - np_error_t result = np_observe_notifications(self.client, notificationNames); - if (result != NP_E_SUCCESS) - { - return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); - } - - result = np_set_notify_callback(self.client, ALTDeviceReceivedNotification, (__bridge void *)self); - if (result != NP_E_SUCCESS) - { - return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); - } - - completionHandler(YES, nil); - - free(notificationNames); - }); -} - -- (void)sendNotification:(CFNotificationName)notification completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler -{ - dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ - np_error_t result = np_post_notification(self.client, [(__bridge NSString *)notification UTF8String]); - if (result == NP_E_SUCCESS) - { - completionHandler(YES, nil); - } - else - { - completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); - } - }); -} - -@end - -void ALTDeviceReceivedNotification(const char *notification, void *user_data) -{ - ALTNotificationConnection *connection = (__bridge ALTNotificationConnection *)user_data; - - if (connection.receivedNotificationHandler) - { - connection.receivedNotificationHandler((__bridge CFNotificationName)@(notification)); - } -} diff --git a/AltServer/Connections/ALTWiredConnection+Private.h b/AltServer/Connections/ALTWiredConnection+Private.h deleted file mode 100644 index a6c64d14..00000000 --- a/AltServer/Connections/ALTWiredConnection+Private.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// ALTWiredConnection+Private.h -// AltServer -// -// Created by Riley Testut on 1/10/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -#import "ALTWiredConnection.h" - -#include - -NS_ASSUME_NONNULL_BEGIN - -@interface ALTWiredConnection () - -@property (nonatomic, readwrite, getter=isConnected) BOOL connected; - -@property (nonatomic, readonly) idevice_connection_t connection; - -- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltServer/Connections/ALTWiredConnection.h b/AltServer/Connections/ALTWiredConnection.h deleted file mode 100644 index d6fde691..00000000 --- a/AltServer/Connections/ALTWiredConnection.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ALTWiredConnection.h -// AltServer -// -// Created by Riley Testut on 1/10/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -#import "AltSign.h" - -#import "ALTConnection.h" - -NS_ASSUME_NONNULL_BEGIN - -NS_SWIFT_NAME(WiredConnection) -@interface ALTWiredConnection : NSObject - -@property (nonatomic, readonly, getter=isConnected) BOOL connected; - -@property (nonatomic, copy, readonly) ALTDevice *device; - -- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler; -- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler; - -- (void)disconnect; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltServer/Connections/ALTWiredConnection.mm b/AltServer/Connections/ALTWiredConnection.mm deleted file mode 100644 index 6ad1f3f8..00000000 --- a/AltServer/Connections/ALTWiredConnection.mm +++ /dev/null @@ -1,119 +0,0 @@ -// -// ALTWiredConnection.m -// AltServer -// -// Created by Riley Testut on 1/10/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -#import "ALTWiredConnection+Private.h" - -#import "ALTConnection.h" -#import "NSError+ALTServerError.h" - -@implementation ALTWiredConnection - -- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection -{ - self = [super init]; - if (self) - { - _device = [device copy]; - _connection = connection; - - self.connected = YES; - } - - return self; -} - -- (void)dealloc -{ - [self disconnect]; -} - -- (void)disconnect -{ - if (![self isConnected]) - { - return; - } - - idevice_disconnect(self.connection); - _connection = nil; - - self.connected = NO; -} - -- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler -{ - void (^finish)(NSError *error) = ^(NSError *error) { - if (error != nil) - { - NSLog(@"Send Error: %@", error); - completionHandler(NO, error); - } - else - { - completionHandler(YES, nil); - } - }; - - dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ - NSMutableData *mutableData = [data mutableCopy]; - while (mutableData.length > 0) - { - uint32_t sentBytes = 0; - if (idevice_connection_send(self.connection, (const char *)mutableData.bytes, (int32_t)mutableData.length, &sentBytes) != IDEVICE_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); - } - - [mutableData replaceBytesInRange:NSMakeRange(0, sentBytes) withBytes:NULL length:0]; - } - - finish(nil); - }); -} - -- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler -{ - void (^finish)(NSData *data, NSError *error) = ^(NSData *data, NSError *error) { - if (error != nil) - { - NSLog(@"Receive Data Error: %@", error); - } - - completionHandler(data, error); - }; - - dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ - char bytes[4096]; - NSMutableData *receivedData = [NSMutableData dataWithCapacity:expectedSize]; - - while (receivedData.length < expectedSize) - { - uint32_t size = MIN(4096, (uint32_t)expectedSize - (uint32_t)receivedData.length); - - uint32_t receivedBytes = 0; - if (idevice_connection_receive_timeout(self.connection, bytes, size, &receivedBytes, 10000) != IDEVICE_E_SUCCESS) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]); - } - - NSData *data = [NSData dataWithBytesNoCopy:bytes length:receivedBytes freeWhenDone:NO]; - [receivedData appendData:data]; - } - - finish(receivedData, nil); - }); -} - -#pragma mark - NSObject - - -- (NSString *)description -{ - return [NSString stringWithFormat:@"%@ (Wired)", self.device.name]; -} - -@end diff --git a/AltServer/Connections/RequestHandler.swift b/AltServer/Connections/RequestHandler.swift deleted file mode 100644 index 6d879951..00000000 --- a/AltServer/Connections/RequestHandler.swift +++ /dev/null @@ -1,263 +0,0 @@ -// -// RequestHandler.swift -// AltServer -// -// Created by Riley Testut on 5/23/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -import Foundation - -typealias ServerConnectionManager = ConnectionManager - -private let connectionManager = ConnectionManager(requestHandler: ServerRequestHandler(), - connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()]) - -extension ServerConnectionManager -{ - static var shared: ConnectionManager { - return connectionManager - } -} - -struct ServerRequestHandler: RequestHandler -{ - func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) - { - AnisetteDataManager.shared.requestAnisetteData { (result) in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success(let anisetteData): - let response = AnisetteDataResponse(anisetteData: anisetteData) - completionHandler(.success(response)) - } - } - } - - func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) - { - var temporaryURL: URL? - - func finish(_ result: Result) - { - if let temporaryURL = temporaryURL - { - do { try FileManager.default.removeItem(at: temporaryURL) } - catch { print("Failed to remove .ipa.", error) } - } - - completionHandler(result) - } - - self.receiveApp(for: request, from: connection) { (result) in - print("Received app with result:", result) - - switch result - { - case .failure(let error): finish(.failure(error)) - case .success(let fileURL): - temporaryURL = fileURL - - print("Awaiting begin installation request...") - - connection.receiveRequest() { (result) in - print("Received begin installation request with result:", result) - - switch result - { - case .failure(let error): finish(.failure(error)) - case .success(.beginInstallation(let installRequest)): - print("Installing app to device \(request.udid)...") - - self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in - print("Installed app to device with result:", result) - switch result - { - case .failure(let error): finish(.failure(error)) - case .success: - let response = InstallationProgressResponse(progress: 1.0) - finish(.success(response)) - } - } - - case .success: finish(.failure(ALTServerError(.unknownRequest))) - } - } - } - } - } - - func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection, - completionHandler: @escaping (Result) -> Void) - { - ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in - if let error = error, !success - { - print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error) - completionHandler(.failure(ALTServerError(error))) - } - else - { - print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier }) - - let response = InstallProvisioningProfilesResponse() - completionHandler(.success(response)) - } - } - } - - func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection, - completionHandler: @escaping (Result) -> Void) - { - ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in - if let error = error, !success - { - print("Failed to remove profiles \(request.bundleIdentifiers):", error) - completionHandler(.failure(ALTServerError(error))) - } - else - { - print("Removed profiles:", request.bundleIdentifiers) - - let response = RemoveProvisioningProfilesResponse() - completionHandler(.success(response)) - } - } - } - - func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) - { - ALTDeviceManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier, fromDeviceWithUDID: request.udid) { (success, error) in - if let error = error, !success - { - print("Failed to remove app \(request.bundleIdentifier):", error) - completionHandler(.failure(ALTServerError(error))) - } - else - { - print("Removed app:", request.bundleIdentifier) - - let response = RemoveAppResponse() - completionHandler(.success(response)) - } - } - } - - func handleEnableUnsignedCodeExecutionRequest(_ request: EnableUnsignedCodeExecutionRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) - { - guard let device = ALTDeviceManager.shared.availableDevices.first(where: { $0.identifier == request.udid }) else { return completionHandler(.failure(ALTServerError(.deviceNotFound))) } - - ALTDeviceManager.shared.prepare(device) { result in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success: - ALTDeviceManager.shared.startDebugConnection(to: device) { (connection, error) in - guard let connection = connection else { return completionHandler(.failure(error!)) } - - func finish(success: Bool, error: Error?) - { - if let error = error, !success - { - print("Failed to enable unsigned code execution for process \(request.processID?.description ?? request.processName ?? "nil"):", error) - completionHandler(.failure(ALTServerError(error))) - } - else - { - print("Enabled unsigned code execution for process:", request.processID ?? request.processName ?? "nil") - - let response = EnableUnsignedCodeExecutionResponse() - completionHandler(.success(response)) - } - } - - if let processID = request.processID - { - connection.enableUnsignedCodeExecutionForProcess(withID: processID, completionHandler: finish) - } - else if let processName = request.processName - { - connection.enableUnsignedCodeExecutionForProcess(withName: processName, completionHandler: finish) - } - else - { - finish(success: false, error: ALTServerError(.invalidRequest)) - } - } - } - } - } -} - -private extension RequestHandler -{ - func receiveApp(for request: PrepareAppRequest, from connection: Connection, completionHandler: @escaping (Result) -> Void) - { - connection.receiveData(expectedSize: request.contentSize) { (result) in - do - { - print("Received app data!") - - let data = try result.get() - - guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) } - - print("Writing app data...") - - let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa") - try data.write(to: temporaryURL, options: .atomic) - - print("Wrote app to URL:", temporaryURL) - - completionHandler(.success(temporaryURL)) - } - catch - { - print("Error processing app data:", error) - - completionHandler(.failure(ALTServerError(error))) - } - } - } - - func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set?, connection: Connection, completionHandler: @escaping (Result) -> Void) - { - let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default) - var isSending = false - - var observation: NSKeyValueObservation? - - let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in - print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription) - - if let error = error.map({ ALTServerError($0) }) - { - completionHandler(.failure(error)) - } - else - { - completionHandler(.success(())) - } - - observation?.invalidate() - observation = nil - } - - observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in - serialQueue.async { - guard !isSending else { return } - isSending = true - - print("Progress:", progress.fractionCompleted) - let response = InstallationProgressResponse(progress: progress.fractionCompleted) - - connection.send(response) { (result) in - serialQueue.async { - isSending = false - } - } - } - }) - } -} diff --git a/AltServer/Connections/WiredConnectionHandler.swift b/AltServer/Connections/WiredConnectionHandler.swift deleted file mode 100644 index c5f4c002..00000000 --- a/AltServer/Connections/WiredConnectionHandler.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// WiredConnectionHandler.swift -// AltServer -// -// Created by Riley Testut on 6/1/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -import Foundation - -class WiredConnectionHandler: ConnectionHandler -{ - var connectionHandler: ((Connection) -> Void)? - var disconnectionHandler: ((Connection) -> Void)? - - private var notificationConnections = [ALTDevice: NotificationConnection]() - private let queue = DispatchQueue(label: "WiredConnectionHandler", autoreleaseFrequency: .workItem, target: .global(qos: .utility)) - - func startListening() - { - NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil) - } - - func stopListening() - { - NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidConnect, object: nil) - NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidDisconnect, object: nil) - } -} - -private extension WiredConnectionHandler -{ - func startNotificationConnection(to device: ALTDevice) - { - self.queue.async { - ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in - guard let connection = connection else { return } - - let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest] - connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in - guard success else { return } - - self.queue.async { - connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in - guard let self = self, let connection = connection else { return } - self.handle(notification, for: connection) - } - - self.notificationConnections[device] = connection - } - } - } - } - } - - func stopNotificationConnection(to device: ALTDevice) - { - self.queue.async { - guard let connection = self.notificationConnections[device] else { return } - connection.disconnect() - - self.notificationConnections[device] = nil - } - } - - func handle(_ notification: CFNotificationName, for connection: NotificationConnection) - { - switch notification - { - case .wiredServerConnectionAvailableRequest: - connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in - if let error = error, !success - { - print("Error sending wired server connection response.", error) - } - else - { - print("Sent wired server connection available response!") - } - } - - case .wiredServerConnectionStartRequest: - ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in - if let wiredConnection = wiredConnection - { - print("Started wired server connection!") - self.connectionHandler?(wiredConnection) - - var observation: NSKeyValueObservation? - observation = wiredConnection.observe(\.isConnected) { [weak self] (connection, change) in - guard !connection.isConnected else { return } - self?.disconnectionHandler?(connection) - - observation?.invalidate() - } - } - else if let error = error - { - print("Error starting wired server connection.", error) - } - } - - default: break - } - } -} - -private extension WiredConnectionHandler -{ - @objc func deviceDidConnect(_ notification: Notification) - { - guard let device = notification.object as? ALTDevice else { return } - self.startNotificationConnection(to: device) - } - - @objc func deviceDidDisconnect(_ notification: Notification) - { - guard let device = notification.object as? ALTDevice else { return } - self.stopNotificationConnection(to: device) - } -} diff --git a/AltServer/Connections/WirelessConnectionHandler.swift b/AltServer/Connections/WirelessConnectionHandler.swift deleted file mode 100644 index 1db96c54..00000000 --- a/AltServer/Connections/WirelessConnectionHandler.swift +++ /dev/null @@ -1,153 +0,0 @@ -// -// WirelessConnectionHandler.swift -// AltKit -// -// Created by Riley Testut on 6/1/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -import Foundation -import Network - -extension WirelessConnectionHandler -{ - public enum State - { - case notRunning - case connecting - case running(NWListener.Service) - case failed(Swift.Error) - } -} - -public class WirelessConnectionHandler: ConnectionHandler -{ - public var connectionHandler: ((Connection) -> Void)? - public var disconnectionHandler: ((Connection) -> Void)? - - public var stateUpdateHandler: ((State) -> Void)? - - public private(set) var state: State = .notRunning { - didSet { - self.stateUpdateHandler?(self.state) - } - } - - private lazy var listener = self.makeListener() - private let dispatchQueue = DispatchQueue(label: "io.altstore.WirelessConnectionListener", qos: .utility) - - public func startListening() - { - switch self.state - { - case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue) - default: break - } - } - - public func stopListening() - { - switch self.state - { - case .running: self.listener.cancel() - default: break - } - } -} - -private extension WirelessConnectionHandler -{ - func makeListener() -> NWListener - { - let listener = try! NWListener(using: .tcp) - - let service: NWListener.Service - - if let serverID = UserDefaults.standard.serverID?.data(using: .utf8) - { - let txtDictionary = ["serverID": serverID] - let txtData = NetService.data(fromTXTRecord: txtDictionary) - - service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData) - } - else - { - service = NWListener.Service(type: ALTServerServiceType) - } - - listener.service = service - - listener.serviceRegistrationUpdateHandler = { (serviceChange) in - switch serviceChange - { - case .add(.service(let name, let type, let domain, _)): - let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil) - self.state = .running(service) - - default: break - } - } - - listener.stateUpdateHandler = { (state) in - switch state - { - case .ready: break - case .waiting, .setup: self.state = .connecting - case .cancelled: self.state = .notRunning - case .failed(let error): self.state = .failed(error) - @unknown default: break - } - } - - listener.newConnectionHandler = { [weak self] (connection) in - self?.prepare(connection) - } - - return listener - } - - func prepare(_ nwConnection: NWConnection) - { - print("Preparing:", nwConnection) - - // Use same instance for all callbacks. - let connection = NetworkConnection(nwConnection) - - nwConnection.stateUpdateHandler = { [weak self] (state) in - switch state - { - case .setup, .preparing: break - - case .ready: - print("Connected to client:", connection) - self?.connectionHandler?(connection) - - case .waiting: - print("Waiting for connection...") - - case .failed(let error): - print("Failed to connect to service \(nwConnection.endpoint).", error) - self?.disconnect(connection) - - case .cancelled: - self?.disconnect(connection) - - @unknown default: break - } - } - - nwConnection.start(queue: self.dispatchQueue) - } - - func disconnect(_ connection: Connection) - { - connection.disconnect() - - self.disconnectionHandler?(connection) - - if let networkConnection = connection as? NetworkConnection - { - networkConnection.nwConnection.stateUpdateHandler = nil - } - } -} diff --git a/AltServer/DeveloperDiskManager.swift b/AltServer/DeveloperDiskManager.swift deleted file mode 100644 index 3830c912..00000000 --- a/AltServer/DeveloperDiskManager.swift +++ /dev/null @@ -1,327 +0,0 @@ -// -// DeveloperDiskManager.swift -// AltServer -// -// Created by Riley Testut on 2/19/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -import Foundation - -import AltSign - -enum DeveloperDiskError: LocalizedError -{ - case unknownDownloadURL - case unsupportedOperatingSystem - case downloadedDiskNotFound - - var errorDescription: String? { - switch self - { - case .unknownDownloadURL: return NSLocalizedString("The URL to download the Developer disk image could not be determined.", comment: "") - case .unsupportedOperatingSystem: return NSLocalizedString("The device's operating system does not support installing Developer disk images.", comment: "") - case .downloadedDiskNotFound: return NSLocalizedString("DeveloperDiskImage.dmg and its signature could not be found in the downloaded archive.", comment: "") - } - } -} - -private extension URL -{ - #if STAGING - static let developerDiskDownloadURLs = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altserver/developerdisks.json")! - #else - static let developerDiskDownloadURLs = URL(string: "https://cdn.altstore.io/file/altstore/altserver/developerdisks.json")! - #endif -} - -private extension DeveloperDiskManager -{ - struct FetchURLsResponse: Decodable - { - struct Disks: Decodable - { - var iOS: [String: DeveloperDiskURL]? - var tvOS: [String: DeveloperDiskURL]? - } - - var version: Int - var disks: Disks - } - - enum DeveloperDiskURL: Decodable - { - case archive(URL) - case separate(diskURL: URL, signatureURL: URL) - - private enum CodingKeys: CodingKey - { - case archive - case disk - case signature - } - - init(from decoder: Decoder) throws - { - let container = try decoder.container(keyedBy: CodingKeys.self) - - if container.contains(.archive) - { - let archiveURL = try container.decode(URL.self, forKey: .archive) - self = .archive(archiveURL) - } - else - { - let diskURL = try container.decode(URL.self, forKey: .disk) - let signatureURL = try container.decode(URL.self, forKey: .signature) - self = .separate(diskURL: diskURL, signatureURL: signatureURL) - } - } - } -} - -class DeveloperDiskManager -{ - private let session = URLSession(configuration: .ephemeral) - - func downloadDeveloperDisk(for device: ALTDevice, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void) - { - do - { - guard let osName = device.type.osName else { throw DeveloperDiskError.unsupportedOperatingSystem } - - let osKeyPath: KeyPath - switch device.type - { - case .iphone, .ipad: osKeyPath = \FetchURLsResponse.Disks.iOS - case .appletv: osKeyPath = \FetchURLsResponse.Disks.tvOS - default: throw DeveloperDiskError.unsupportedOperatingSystem - } - - var osVersion = device.osVersion - osVersion.patchVersion = 0 // Patch is irrelevant for developer disks - - let osDirectoryURL = FileManager.default.developerDisksDirectory.appendingPathComponent(osName) - let developerDiskDirectoryURL = osDirectoryURL.appendingPathComponent(osVersion.stringValue, isDirectory: true) - try FileManager.default.createDirectory(at: developerDiskDirectoryURL, withIntermediateDirectories: true, attributes: nil) - - let developerDiskURL = developerDiskDirectoryURL.appendingPathComponent("DeveloperDiskImage.dmg") - let developerDiskSignatureURL = developerDiskDirectoryURL.appendingPathComponent("DeveloperDiskImage.dmg.signature") - - let isCachedDiskCompatible = self.isDeveloperDiskCompatible(with: device) - if isCachedDiskCompatible && FileManager.default.fileExists(atPath: developerDiskURL.path) && FileManager.default.fileExists(atPath: developerDiskSignatureURL.path) - { - // The developer disk is cached and we've confirmed it works, so re-use it. - return completionHandler(.success((developerDiskURL, developerDiskSignatureURL))) - } - - func finish(_ result: Result<(URL, URL), Error>) - { - do - { - let (diskFileURL, signatureFileURL) = try result.get() - - if FileManager.default.fileExists(atPath: developerDiskURL.path) - { - try FileManager.default.removeItem(at: developerDiskURL) - } - - if FileManager.default.fileExists(atPath: developerDiskSignatureURL.path) - { - try FileManager.default.removeItem(at: developerDiskSignatureURL) - } - - try FileManager.default.copyItem(at: diskFileURL, to: developerDiskURL) - try FileManager.default.copyItem(at: signatureFileURL, to: developerDiskSignatureURL) - - completionHandler(.success((developerDiskURL, developerDiskSignatureURL))) - } - catch - { - completionHandler(.failure(error)) - } - } - - self.fetchDeveloperDiskURLs { (result) in - do - { - let developerDiskURLs = try result.get() - guard let diskURL = developerDiskURLs[keyPath: osKeyPath]?[osVersion.stringValue] else { throw DeveloperDiskError.unknownDownloadURL } - - switch diskURL - { - case .archive(let archiveURL): self.downloadDiskArchive(from: archiveURL, completionHandler: finish(_:)) - case .separate(let diskURL, let signatureURL): self.downloadDisk(from: diskURL, signatureURL: signatureURL, completionHandler: finish(_:)) - } - } - catch - { - finish(.failure(error)) - } - } - } - catch - { - completionHandler(.failure(error)) - } - } - - func setDeveloperDiskCompatible(_ isCompatible: Bool, with device: ALTDevice) - { - guard let id = self.developerDiskCompatibilityID(for: device) else { return } - UserDefaults.standard.set(isCompatible, forKey: id) - } - - func isDeveloperDiskCompatible(with device: ALTDevice) -> Bool - { - guard let id = self.developerDiskCompatibilityID(for: device) else { return false } - - let isCompatible = UserDefaults.standard.bool(forKey: id) - return isCompatible - } -} - -private extension DeveloperDiskManager -{ - func developerDiskCompatibilityID(for device: ALTDevice) -> String? - { - guard let osName = device.type.osName else { return nil } - - var osVersion = device.osVersion - osVersion.patchVersion = 0 // Patch is irrelevant for developer disks - - let id = ["ALTDeveloperDiskCompatible", osName, device.osVersion.stringValue].joined(separator: "_") - return id - } - - func fetchDeveloperDiskURLs(completionHandler: @escaping (Result) -> Void) - { - let dataTask = self.session.dataTask(with: .developerDiskDownloadURLs) { (data, response, error) in - do - { - guard let data = data else { throw error! } - - let response = try JSONDecoder().decode(FetchURLsResponse.self, from: data) - completionHandler(.success(response.disks)) - } - catch - { - completionHandler(.failure(error)) - } - } - - dataTask.resume() - } - - func downloadDiskArchive(from url: URL, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void) - { - let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in - do - { - guard let fileURL = fileURL else { throw error! } - defer { try? FileManager.default.removeItem(at: fileURL) } - - let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil) - defer { try? FileManager.default.removeItem(at: temporaryDirectory) } - - try FileManager.default.unzipArchive(at: fileURL, toDirectory: temporaryDirectory) - - guard let enumerator = FileManager.default.enumerator(at: temporaryDirectory, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsPackageDescendants]) else { - throw CocoaError(.fileNoSuchFile, userInfo: [NSURLErrorKey: temporaryDirectory]) - } - - var tempDiskFileURL: URL? - var tempSignatureFileURL: URL? - - for case let fileURL as URL in enumerator - { - switch fileURL.pathExtension.lowercased() - { - case "dmg": tempDiskFileURL = fileURL - case "signature": tempSignatureFileURL = fileURL - default: break - } - } - - guard let diskFileURL = tempDiskFileURL, let signatureFileURL = tempSignatureFileURL else { throw DeveloperDiskError.downloadedDiskNotFound } - - completionHandler(.success((diskFileURL, signatureFileURL))) - } - catch - { - completionHandler(.failure(error)) - } - } - - downloadTask.resume() - } - - func downloadDisk(from diskURL: URL, signatureURL: URL, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void) - { - let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - - do { try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil) } - catch { return completionHandler(.failure(error)) } - - var diskFileURL: URL? - var signatureFileURL: URL? - - var downloadError: Error? - - let dispatchGroup = DispatchGroup() - dispatchGroup.enter() - dispatchGroup.enter() - - let diskDownloadTask = URLSession.shared.downloadTask(with: diskURL) { (fileURL, response, error) in - do - { - guard let fileURL = fileURL else { throw error! } - - let destinationURL = temporaryDirectory.appendingPathComponent("DeveloperDiskImage.dmg") - try FileManager.default.copyItem(at: fileURL, to: destinationURL) - - diskFileURL = destinationURL - } - catch - { - downloadError = error - } - - dispatchGroup.leave() - } - - let signatureDownloadTask = URLSession.shared.downloadTask(with: signatureURL) { (fileURL, response, error) in - do - { - guard let fileURL = fileURL else { throw error! } - - let destinationURL = temporaryDirectory.appendingPathComponent("DeveloperDiskImage.dmg.signature") - try FileManager.default.copyItem(at: fileURL, to: destinationURL) - - signatureFileURL = destinationURL - } - catch - { - downloadError = error - } - - dispatchGroup.leave() - } - - diskDownloadTask.resume() - signatureDownloadTask.resume() - - dispatchGroup.notify(queue: .global(qos: .userInitiated)) { - defer { - try? FileManager.default.removeItem(at: temporaryDirectory) - } - - guard let diskFileURL = diskFileURL, let signatureFileURL = signatureFileURL else { - return completionHandler(.failure(downloadError ?? DeveloperDiskError.downloadedDiskNotFound)) - } - - completionHandler(.success((diskFileURL, signatureFileURL))) - } - } -} diff --git a/AltServer/Devices/ALTDeviceManager+Installation.swift b/AltServer/Devices/ALTDeviceManager+Installation.swift deleted file mode 100644 index 74fbe3ab..00000000 --- a/AltServer/Devices/ALTDeviceManager+Installation.swift +++ /dev/null @@ -1,941 +0,0 @@ -// -// ALTDeviceManager+Installation.swift -// AltServer -// -// Created by Riley Testut on 7/1/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -import Cocoa -import UserNotifications -import ObjectiveC - -private let appGroupsSemaphore = DispatchSemaphore(value: 1) - -private let developerDiskManager = DeveloperDiskManager() - -enum InstallError: Int, LocalizedError, _ObjectiveCBridgeableError -{ - case cancelled - case noTeam - case missingPrivateKey - case missingCertificate - - var errorDescription: String? { - switch self - { - case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "") - case .noTeam: return "You are not a member of any developer teams." - case .missingPrivateKey: return "The developer certificate's private key could not be found." - case .missingCertificate: return "The developer certificate could not be found." - } - } - - init?(_bridgedNSError error: NSError) - { - guard error.domain == InstallError.cancelled._domain else { return nil } - - if let installError = InstallError(rawValue: error.code) - { - self = installError - } - else - { - return nil - } - } -} - -extension ALTDeviceManager -{ - func installApplication(at url: URL, to altDevice: ALTDevice, appleID: String, password: String, completion: @escaping (Result) -> Void) - { - let destinationDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - - var appName = (url.isFileURL) ? url.deletingPathExtension().lastPathComponent : NSLocalizedString("AltStore", comment: "") - - func finish(_ result: Result, title: String = "") - { - DispatchQueue.main.async { - switch result - { - case .success(let app): completion(.success(app)) - case .failure(var error as NSError): - if error.localizedFailure == nil - { - error = error.withLocalizedFailure(String(format: NSLocalizedString("Could not install %@ to %@.", comment: ""), appName, altDevice.name)) - } - - completion(.failure(error)) - } - } - - try? FileManager.default.removeItem(at: destinationDirectoryURL) - } - - AnisetteDataManager.shared.requestAnisetteData { (result) in - do - { - let anisetteData = try result.get() - - self.authenticate(appleID: appleID, password: password, anisetteData: anisetteData) { (result) in - do - { - let (account, session) = try result.get() - - self.fetchTeam(for: account, session: session) { (result) in - do - { - let team = try result.get() - - self.register(altDevice, team: team, session: session) { (result) in - do - { - let device = try result.get() - device.osVersion = altDevice.osVersion - - self.fetchCertificate(for: team, session: session) { (result) in - do - { - let certificate = try result.get() - - if !url.isFileURL - { - // Show alert before downloading remote .ipa. - self.showInstallationAlert(appName: NSLocalizedString("AltStore", comment: ""), deviceName: device.name) - } - - self.prepare(device) { (result) in - switch result - { - case .failure(let error): - print("Failed to install DeveloperDiskImage.dmg to \(device).", error) - fallthrough // Continue installing app even if we couldn't install Developer disk image. - - case .success: - self.downloadApp(from: url) { (result) in - do - { - let fileURL = try result.get() - - try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil) - - let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL) - guard let application = ALTApplication(fileURL: appBundleURL) else { throw ALTError(.invalidApp) } - - if url.isFileURL - { - // Show alert after "downloading" local .ipa. - self.showInstallationAlert(appName: application.name, deviceName: device.name) - } - - appName = application.name - - // Refresh anisette data to prevent session timeouts. - AnisetteDataManager.shared.requestAnisetteData { (result) in - do - { - let anisetteData = try result.get() - session.anisetteData = anisetteData - - self.prepareAllProvisioningProfiles(for: application, device: device, team: team, session: session) { (result) in - do - { - let profiles = try result.get() - - self.install(application, to: device, team: team, certificate: certificate, profiles: profiles) { (result) in - finish(result.map { application }, title: "Failed to Install AltStore") - } - } - catch - { - finish(.failure(error), title: "Failed to Fetch Provisioning Profiles") - } - } - } - catch - { - finish(.failure(error), title: "Failed to Refresh Anisette Data") - } - } - } - catch - { - finish(.failure(error), title: "Failed to Download AltStore") - } - } - } - } - } - catch - { - finish(.failure(error), title: "Failed to Fetch Certificate") - } - } - } - catch - { - finish(.failure(error), title: "Failed to Register Device") - } - } - } - catch - { - finish(.failure(error), title: "Failed to Fetch Team") - } - } - } - catch - { - finish(.failure(error), title: "Failed to Authenticate") - } - } - } - catch - { - finish(.failure(error), title: "Failed to Fetch Anisette Data") - } - } - } -} - -extension ALTDeviceManager -{ - func prepare(_ device: ALTDevice, completionHandler: @escaping (Result) -> Void) - { - ALTDeviceManager.shared.isDeveloperDiskImageMounted(for: device) { (isMounted, error) in - switch (isMounted, error) - { - case (_, let error?): return completionHandler(.failure(error)) - case (true, _): return completionHandler(.success(())) - case (false, _): - developerDiskManager.downloadDeveloperDisk(for: device) { (result) in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success((let diskFileURL, let signatureFileURL)): - ALTDeviceManager.shared.installDeveloperDiskImage(at: diskFileURL, signatureURL: signatureFileURL, to: device) { (success, error) in - switch Result(success, error) - { - case .failure(let error as ALTServerError) where error.code == .incompatibleDeveloperDisk: - developerDiskManager.setDeveloperDiskCompatible(false, with: device) - completionHandler(.failure(error)) - - case .failure(let error): - // Don't mark developer disk as incompatible because it probably failed for a different reason. - completionHandler(.failure(error)) - - case .success: - developerDiskManager.setDeveloperDiskCompatible(true, with: device) - completionHandler(.success(())) - } - } - } - } - } - } - } -} - -private extension ALTDeviceManager -{ - func downloadApp(from url: URL, completionHandler: @escaping (Result) -> Void) - { - guard !url.isFileURL else { return completionHandler(.success(url)) } - - let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in - do - { - let (fileURL, _) = try Result((fileURL, response), error).get() - completionHandler(.success(fileURL)) - - do { try FileManager.default.removeItem(at: fileURL) } - catch { print("Failed to remove downloaded .ipa.", error) } - } - catch - { - completionHandler(.failure(error)) - } - } - - downloadTask.resume() - } - - func authenticate(appleID: String, password: String, anisetteData: ALTAnisetteData, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void) - { - func handleVerificationCode(_ completionHandler: @escaping (String?) -> Void) - { - DispatchQueue.main.async { - let alert = NSAlert() - alert.messageText = NSLocalizedString("Two-Factor Authentication Enabled", comment: "") - alert.informativeText = NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: "") - - let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 22)) - textField.delegate = self - textField.translatesAutoresizingMaskIntoConstraints = false - textField.placeholderString = NSLocalizedString("123456", comment: "") - alert.accessoryView = textField - alert.window.initialFirstResponder = textField - - alert.addButton(withTitle: NSLocalizedString("Continue", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - - self.securityCodeAlert = alert - self.securityCodeTextField = textField - self.validate() - - NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) - - let response = alert.runModal() - if response == .alertFirstButtonReturn - { - let code = textField.stringValue - completionHandler(code) - } - else - { - completionHandler(nil) - } - } - } - - ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData, verificationHandler: handleVerificationCode) { (account, session, error) in - if let account = account, let session = session - { - completionHandler(.success((account, session))) - } - else - { - completionHandler(.failure(error ?? ALTAppleAPIError(.unknown))) - } - } - } - - func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in - do - { - let teams = try Result(teams, error).get() - - if let team = teams.first(where: { $0.type == .individual }) - { - return completionHandler(.success(team)) - } - else if let team = teams.first(where: { $0.type == .free }) - { - return completionHandler(.success(team)) - } - else if let team = teams.first - { - return completionHandler(.success(team)) - } - else - { - throw InstallError.noTeam - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - - func fetchCertificate(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in - do - { - let certificates = try Result(certificates, error).get() - - let certificateFileURL = FileManager.default.certificatesDirectory.appendingPathComponent(team.identifier + ".p12") - try FileManager.default.createDirectory(at: FileManager.default.certificatesDirectory, withIntermediateDirectories: true, attributes: nil) - - var isCancelled = false - - // Check if there is another AltStore certificate, which means AltStore has been installed with this Apple ID before. - let altstoreCertificate = certificates.first { $0.machineName?.starts(with: "AltStore") == true } - if let previousCertificate = altstoreCertificate - { - if FileManager.default.fileExists(atPath: certificateFileURL.path), - let data = try? Data(contentsOf: certificateFileURL), - let certificate = ALTCertificate(p12Data: data, password: previousCertificate.machineIdentifier) - { - // Manually set machineIdentifier so we can encrypt + embed certificate if needed. - certificate.machineIdentifier = previousCertificate.machineIdentifier - return completionHandler(.success(certificate)) - } - - DispatchQueue.main.sync { - let alert = NSAlert() - alert.messageText = NSLocalizedString("Multiple AltServers Not Supported", comment: "") - alert.informativeText = NSLocalizedString("Please use the same AltServer you previously used with this Apple ID, or else apps installed with other AltServers will stop working.\n\nAre you sure you want to continue?", comment: "") - - alert.addButton(withTitle: NSLocalizedString("Continue", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - - NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) - - let buttonIndex = alert.runModal() - if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn - { - isCancelled = true - } - } - - guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) } - } - - func addCertificate() - { - ALTAppleAPI.shared.addCertificate(machineName: "AltStore", to: team, session: session) { (certificate, error) in - do - { - let certificate = try Result(certificate, error).get() - guard let privateKey = certificate.privateKey else { throw InstallError.missingPrivateKey } - - 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 { - throw InstallError.missingCertificate - } - - certificate.privateKey = privateKey - - completionHandler(.success(certificate)) - - if let machineIdentifier = certificate.machineIdentifier, - let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier) - { - // Cache certificate. - do { try encryptedData.write(to: certificateFileURL, options: .atomic) } - catch { print("Failed to cache certificate:", error) } - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - - if let certificate = altstoreCertificate ?? certificates.first - { - if team.type != .free - { - DispatchQueue.main.sync { - let alert = NSAlert() - alert.messageText = NSLocalizedString("Installing this app will revoke your iOS development certificate.", comment: "") - alert.informativeText = NSLocalizedString(""" - This will not affect apps you've submitted to the App Store, but may cause apps you've installed to your devices with Xcode to stop working until you reinstall them. - - To prevent this from happening, feel free to try again with another Apple ID. - """, comment: "") - - alert.addButton(withTitle: NSLocalizedString("Continue", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - - NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) - - let buttonIndex = alert.runModal() - if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn - { - isCancelled = true - } - } - - guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) } - } - - ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in - do - { - try Result(success, error).get() - addCertificate() - } - catch - { - completionHandler(.failure(error)) - } - } - } - else - { - addCertificate() - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - - func prepareAllProvisioningProfiles(for application: ALTApplication, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, - completion: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void) - { - self.prepareProvisioningProfile(for: application, parentApp: nil, device: device, team: team, session: session) { (result) in - do - { - let profile = try result.get() - - var profiles = [application.bundleIdentifier: profile] - var error: Error? - - let dispatchGroup = DispatchGroup() - - for appExtension in application.appExtensions - { - dispatchGroup.enter() - - self.prepareProvisioningProfile(for: appExtension, parentApp: application, device: device, team: team, session: session) { (result) in - switch result - { - case .failure(let e): error = e - case .success(let profile): profiles[appExtension.bundleIdentifier] = profile - } - - dispatchGroup.leave() - } - } - - dispatchGroup.notify(queue: .global()) { - if let error = error - { - completion(.failure(error)) - } - else - { - completion(.success(profiles)) - } - } - } - catch - { - completion(.failure(error)) - } - } - } - - func prepareProvisioningProfile(for application: ALTApplication, parentApp: ALTApplication?, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - let parentBundleID = parentApp?.bundleIdentifier ?? application.bundleIdentifier - let updatedParentBundleID: String - - if application.isAltStoreApp - { - // Use legacy bundle ID format for AltStore (and its extensions). - updatedParentBundleID = "com.\(team.identifier).\(parentBundleID)" - } - else - { - updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track. - } - - let bundleID = application.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID) - - let preferredName: String - - if let parentApp = parentApp - { - preferredName = parentApp.name + " " + application.name - } - else - { - preferredName = application.name - } - - self.registerAppID(name: preferredName, bundleID: bundleID, team: team, session: session) { (result) in - do - { - let appID = try result.get() - - self.updateFeatures(for: appID, app: application, team: team, session: session) { (result) in - do - { - let appID = try result.get() - - self.updateAppGroups(for: appID, app: application, team: team, session: session) { (result) in - do - { - let appID = try result.get() - - self.fetchProvisioningProfile(for: appID, device: device, team: team, session: session) { (result) in - completionHandler(result) - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - - func registerAppID(name appName: String, bundleID: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in - do - { - let appIDs = try Result(appIDs, error).get() - - if let appID = appIDs.first(where: { $0.bundleIdentifier == bundleID }) - { - completionHandler(.success(appID)) - } - else - { - ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team, session: session) { (appID, error) in - completionHandler(Result(appID, error)) - } - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - - func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - let requiredFeatures = app.entitlements.compactMap { (entitlement, value) -> (ALTFeature, Any)? in - guard let feature = ALTFeature(entitlement: entitlement) else { return nil } - return (feature, value) - } - - var features = requiredFeatures.reduce(into: [ALTFeature: Any]()) { $0[$1.0] = $1.1 } - - if let applicationGroups = app.entitlements[.appGroups] as? [String], !applicationGroups.isEmpty - { - // App uses app groups, so assign `true` to enable the feature. - features[.appGroups] = true - } - else - { - // App has no app groups, so assign `false` to disable the feature. - features[.appGroups] = false - } - - var updateFeatures = false - - // Determine whether the required features are already enabled for the AppID. - for (feature, value) in features - { - if let appIDValue = appID.features[feature] as AnyObject?, (value as AnyObject).isEqual(appIDValue) - { - // AppID already has this feature enabled and the values are the same. - continue - } - else if appID.features[feature] == nil, let shouldEnableFeature = value as? Bool, !shouldEnableFeature - { - // AppID doesn't already have this feature enabled, but we want it disabled anyway. - continue - } - else - { - // AppID either doesn't have this feature enabled or the value has changed, - // so we need to update it to reflect new values. - updateFeatures = true - break - } - } - - if updateFeatures - { - let appID = appID.copy() as! ALTAppID - appID.features = features - - ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in - completionHandler(Result(appID, error)) - } - } - else - { - completionHandler(.success(appID)) - } - } - - func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - guard let applicationGroups = app.entitlements[.appGroups] as? [String], !applicationGroups.isEmpty else { - // Assigning an App ID to an empty app group array fails, - // so just do nothing if there are no app groups. - return completionHandler(.success(appID)) - } - - // Dispatch onto global queue to prevent appGroupsSemaphore deadlock. - DispatchQueue.global().async { - - // Ensure we're not concurrently fetching and updating app groups, - // which can lead to race conditions such as adding an app group twice. - appGroupsSemaphore.wait() - - func finish(_ result: Result) - { - appGroupsSemaphore.signal() - completionHandler(result) - } - - ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in - switch Result(groups, error) - { - case .failure(let error): finish(.failure(error)) - case .success(let fetchedGroups): - let dispatchGroup = DispatchGroup() - - var groups = [ALTAppGroup]() - var errors = [Error]() - - for groupIdentifier in applicationGroups - { - let adjustedGroupIdentifier = groupIdentifier + "." + team.identifier - - if let group = fetchedGroups.first(where: { $0.groupIdentifier == adjustedGroupIdentifier }) - { - groups.append(group) - } - else - { - dispatchGroup.enter() - - // Not all characters are allowed in group names, so we replace periods with spaces (like Apple does). - let name = "AltStore " + groupIdentifier.replacingOccurrences(of: ".", with: " ") - - ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in - switch Result(group, error) - { - case .success(let group): groups.append(group) - case .failure(let error): errors.append(error) - } - - dispatchGroup.leave() - } - } - } - - dispatchGroup.notify(queue: .global()) { - if let error = errors.first - { - finish(.failure(error)) - } - else - { - ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in - let result = Result(success, error) - finish(result.map { _ in appID }) - } - } - } - } - } - } - } - - func register(_ device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - ALTAppleAPI.shared.fetchDevices(for: team, types: device.type, session: session) { (devices, error) in - do - { - let devices = try Result(devices, error).get() - - if let device = devices.first(where: { $0.identifier == device.identifier }) - { - completionHandler(.success(device)) - } - else - { - ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, type: device.type, team: team, session: session) { (device, error) in - completionHandler(Result(device, error)) - } - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - - func fetchProvisioningProfile(for appID: ALTAppID, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: device.type, team: team, session: session) { (profile, error) in - completionHandler(Result(profile, error)) - } - } - - func install(_ application: ALTApplication, to device: ALTDevice, team: ALTTeam, certificate: ALTCertificate, profiles: [String: ALTProvisioningProfile], completionHandler: @escaping (Result) -> Void) - { - func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws - { - guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) } - guard let profile = profiles[identifier] else { throw ALTError(.missingProvisioningProfile) } - guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) } - - infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier - infoDictionary[Bundle.Info.altBundleID] = identifier - - if (infoDictionary.keys.contains(Bundle.Info.deviceID)) { - infoDictionary[Bundle.Info.deviceID] = device.identifier - } - - for (key, value) in additionalInfoDictionaryValues - { - infoDictionary[key] = value - } - - if let appGroups = profile.entitlements[.appGroups] as? [String] - { - infoDictionary[Bundle.Info.appGroups] = appGroups - } - - try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL) - } - - DispatchQueue.global().async { - do - { - guard let appBundle = Bundle(url: application.fileURL) else { throw ALTError(.missingAppBundle) } - guard let infoDictionary = appBundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) } - - let openAppURL = URL(string: "altstore-" + application.bundleIdentifier + "://")! - - var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? [] - - // Embed open URL so AltBackup can return to AltStore. - let altstoreURLScheme = ["CFBundleTypeRole": "Editor", - "CFBundleURLName": application.bundleIdentifier, - "CFBundleURLSchemes": [openAppURL.scheme!]] as [String : Any] - allURLSchemes.append(altstoreURLScheme) - - var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes] - - if application.isAltStoreApp - { - additionalValues[Bundle.Info.deviceID] = device.identifier - additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID - - if - let machineIdentifier = certificate.machineIdentifier, - let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier) - { - additionalValues[Bundle.Info.certificateID] = certificate.serialNumber - - let certificateURL = application.fileURL.appendingPathComponent("ALTCertificate.p12") - try encryptedData.write(to: certificateURL, options: .atomic) - } - } - else if infoDictionary.keys.contains(Bundle.Info.deviceID) - { - // There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID. - additionalValues[Bundle.Info.deviceID] = device.identifier - additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID - } - - try prepare(appBundle, additionalInfoDictionaryValues: additionalValues) - - for appExtension in application.appExtensions - { - guard let bundle = Bundle(url: appExtension.fileURL) else { throw ALTError(.missingAppBundle) } - try prepare(bundle) - } - - let resigner = ALTSigner(team: team, certificate: certificate) - resigner.signApp(at: application.fileURL, provisioningProfiles: Array(profiles.values)) { (success, error) in - do - { - try Result(success, error).get() - - let activeProfiles: Set? = (team.type == .free && application.isAltStoreApp) ? Set(profiles.values.map(\.bundleIdentifier)) : nil - ALTDeviceManager.shared.installApp(at: application.fileURL, toDeviceWithUDID: device.identifier, activeProvisioningProfiles: activeProfiles) { (success, error) in - completionHandler(Result(success, error)) - } - } - catch - { - print("Failed to install app", error) - completionHandler(.failure(error)) - } - } - } - catch - { - print("Failed to install AltStore", error) - completionHandler(.failure(error)) - } - } - } - - func showInstallationAlert(appName: String, deviceName: String) - { - let content = UNMutableNotificationContent() - content.title = String(format: NSLocalizedString("Installing %@ to %@...", comment: ""), appName, deviceName) - content.body = NSLocalizedString("This may take a few seconds.", comment: "") - - let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) - UNUserNotificationCenter.current().add(request) - } -} - -private var securityCodeAlertKey = 0 -private var securityCodeTextFieldKey = 0 - -extension ALTDeviceManager: NSTextFieldDelegate -{ - var securityCodeAlert: NSAlert? { - get { return objc_getAssociatedObject(self, &securityCodeAlertKey) as? NSAlert } - set { objc_setAssociatedObject(self, &securityCodeAlertKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } - } - - var securityCodeTextField: NSTextField? { - get { return objc_getAssociatedObject(self, &securityCodeTextFieldKey) as? NSTextField } - set { objc_setAssociatedObject(self, &securityCodeTextFieldKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } - } - - public func controlTextDidChange(_ obj: Notification) - { - self.validate() - } - - public func controlTextDidEndEditing(_ obj: Notification) - { - self.validate() - } - - private func validate() - { - guard let code = self.securityCodeTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) else { return } - - if code.count == 6 - { - self.securityCodeAlert?.buttons.first?.isEnabled = true - } - else - { - self.securityCodeAlert?.buttons.first?.isEnabled = false - } - - self.securityCodeAlert?.layout() - } -} diff --git a/AltServer/Devices/ALTDeviceManager.h b/AltServer/Devices/ALTDeviceManager.h deleted file mode 100644 index 12295a2b..00000000 --- a/AltServer/Devices/ALTDeviceManager.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// ALTDeviceManager.h -// AltServer -// -// Created by Riley Testut on 5/24/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -#import -#import "AltSign.h" - -@class ALTWiredConnection; -@class ALTNotificationConnection; -@class ALTDebugConnection; - -@class ALTInstalledApp; - -NS_ASSUME_NONNULL_BEGIN - -extern NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidConnect); -extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidDisconnect); - -@interface ALTDeviceManager : NSObject - -@property (class, nonatomic, readonly) ALTDeviceManager *sharedManager; - -@property (nonatomic, readonly) NSArray *connectedDevices; -@property (nonatomic, readonly) NSArray *availableDevices; - -- (void)start; - -/* App Installation */ -- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; -- (void)removeAppForBundleIdentifier:(NSString *)bundleIdentifier fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - -/* Provisioning Profiles */ -- (void)installProvisioningProfiles:(NSSet *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; -- (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - -/* Developer Disk Image */ -- (void)isDeveloperDiskImageMountedForDevice:(ALTDevice *)device - completionHandler:(void (^)(BOOL isMounted, NSError *_Nullable error))completionHandler; -- (void)installDeveloperDiskImageAtURL:(NSURL *)diskURL signatureURL:(NSURL *)signatureURL toDevice:(ALTDevice *)device - completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - -/* Apps */ -- (void)fetchInstalledAppsOnDevice:(ALTDevice *)altDevice completionHandler:(void (^)(NSSet *_Nullable installedApps, NSError *_Nullable error))completionHandler; - -/* Connections */ -- (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler; -- (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler; -- (void)startDebugConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTDebugConnection *_Nullable connection, NSError * _Nullable error))completionHandler; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AltServer/Devices/ALTDeviceManager.mm b/AltServer/Devices/ALTDeviceManager.mm deleted file mode 100644 index a7d6aff7..00000000 --- a/AltServer/Devices/ALTDeviceManager.mm +++ /dev/null @@ -1,1786 +0,0 @@ -// -// ALTDeviceManager.m -// AltServer -// -// Created by Riley Testut on 5/24/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -#import "ALTDeviceManager.h" - -#import "ALTWiredConnection+Private.h" -#import "ALTNotificationConnection+Private.h" -#import "ALTDebugConnection+Private.h" - -#import "ALTConstants.h" -#import "NSError+ALTServerError.h" -#import "NSError+libimobiledevice.h" - -#import -#import -#import "AltServer-Swift.h" - -#include -#include -#include -#include -#include -#include -#include - -void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *udid); -void ALTDeviceManagerUpdateAppDeletionStatus(plist_t command, plist_t status, void *uuid); -void ALTDeviceDidChangeConnectionStatus(const idevice_event_t *event, void *user_data); -ssize_t ALTDeviceManagerUploadFile(void *buffer, size_t size, void *user_data); - -NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification = @"ALTDeviceManagerDeviceDidConnectNotification"; -NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALTDeviceManagerDeviceDidDisconnectNotification"; - -@interface ALTDeviceManager () - -@property (nonatomic, readonly) NSMutableDictionary *installationCompletionHandlers; -@property (nonatomic, readonly) NSMutableDictionary *deletionCompletionHandlers; - -@property (nonatomic, readonly) NSMutableDictionary *installationProgress; - -@property (nonatomic, readonly) dispatch_queue_t installationQueue; -@property (nonatomic, readonly) dispatch_queue_t devicesQueue; - -@property (nonatomic, readonly) NSMutableSet *cachedDevices; - -@end - -@implementation ALTDeviceManager - -+ (ALTDeviceManager *)sharedManager -{ - static ALTDeviceManager *_manager = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _manager = [[self alloc] init]; - }); - - return _manager; -} - -- (instancetype)init -{ - self = [super init]; - if (self) - { - _installationCompletionHandlers = [NSMutableDictionary dictionary]; - _deletionCompletionHandlers = [NSMutableDictionary dictionary]; - - _installationProgress = [NSMutableDictionary dictionary]; - - _installationQueue = dispatch_queue_create("com.rileytestut.AltServer.Installation", DISPATCH_QUEUE_SERIAL); - _devicesQueue = dispatch_queue_create("com.rileytestut.AltServer.Devices", DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL); - - _cachedDevices = [NSMutableSet set]; - } - - return self; -} - -- (void)start -{ - idevice_event_subscribe(ALTDeviceDidChangeConnectionStatus, nil); -} - -#pragma mark - App Installation - - -- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler -{ - NSProgress *progress = [NSProgress discreteProgressWithTotalUnitCount:4]; - - dispatch_async(self.installationQueue, ^{ - NSUUID *UUID = [NSUUID UUID]; - __block char *uuidString = (char *)malloc(UUID.UUIDString.length + 1); - strncpy(uuidString, (const char *)UUID.UUIDString.UTF8String, UUID.UUIDString.length); - uuidString[UUID.UUIDString.length] = '\0'; - - __block idevice_t device = NULL; - __block lockdownd_client_t client = NULL; - __block instproxy_client_t ipc = NULL; - __block afc_client_t afc = NULL; - __block misagent_client_t mis = NULL; - __block lockdownd_service_descriptor_t service = NULL; - - NSMutableDictionary *cachedProfiles = [NSMutableDictionary dictionary]; - NSMutableSet *installedProfiles = [NSMutableSet set]; - - void (^finish)(NSError *error) = ^(NSError *e) { - __block NSError *error = e; - - if (activeProvisioningProfiles != nil) - { - // Remove installed provisioning profiles if they're not active. - - for (ALTProvisioningProfile *installedProfile in installedProfiles) - { - if (![activeProvisioningProfiles containsObject:installedProfile.bundleIdentifier]) - { - NSError *removeError = nil; - if (![self removeProvisioningProfile:installedProfile misagent:mis error:&removeError]) - { - if (error == nil) - { - error = removeError; - } - } - } - } - } - - [cachedProfiles enumerateKeysAndObjectsUsingBlock:^(NSString *bundleID, ALTProvisioningProfile *profile, BOOL * _Nonnull stop) { - for (ALTProvisioningProfile *installedProfile in installedProfiles) - { - if ([installedProfile.bundleIdentifier isEqualToString:profile.bundleIdentifier]) - { - // Don't reinstall cached profile because it was installed with the app. - return; - } - } - - NSError *installError = nil; - if (![self installProvisioningProfile:profile misagent:mis error:&installError]) - { - if (error == nil) - { - error = installError; - } - } - }]; - - instproxy_client_free(ipc); - afc_client_free(afc); - lockdownd_client_free(client); - misagent_client_free(mis); - idevice_free(device); - lockdownd_service_descriptor_free(service); - - free(uuidString); - uuidString = NULL; - - if (error != nil) - { - completionHandler(NO, error); - } - else - { - completionHandler(YES, nil); - } - }; - - NSURL *appBundleURL = nil; - NSURL *temporaryDirectoryURL = nil; - - if ([fileURL.pathExtension.lowercaseString isEqualToString:@"app"]) - { - appBundleURL = fileURL; - temporaryDirectoryURL = nil; - } - else if ([fileURL.pathExtension.lowercaseString isEqualToString:@"ipa"]) - { - NSLog(@"Unzipping .ipa..."); - - temporaryDirectoryURL = [NSFileManager.defaultManager.temporaryDirectory URLByAppendingPathComponent:[[NSUUID UUID] UUIDString] isDirectory:YES]; - - NSError *error = nil; - if (![[NSFileManager defaultManager] createDirectoryAtURL:temporaryDirectoryURL withIntermediateDirectories:YES attributes:nil error:&error]) - { - return finish(error); - } - - appBundleURL = [[NSFileManager defaultManager] unzipAppBundleAtURL:fileURL toDirectory:temporaryDirectoryURL error:&error]; - if (appBundleURL == nil) - { - return finish(error); - } - } - else - { - return finish([NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadCorruptFileError userInfo:@{NSURLErrorKey: fileURL}]); - } - - ALTApplication *application = [[ALTApplication alloc] initWithFileURL:appBundleURL]; - if (application.provisioningProfile) - { - [installedProfiles addObject:application.provisioningProfile]; - } - - for (ALTApplication *appExtension in application.appExtensions) - { - if (appExtension.provisioningProfile) - { - [installedProfiles addObject:appExtension.provisioningProfile]; - } - } - - /* Find Device */ - if (idevice_new_with_options(&device, udid.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to Device */ - if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - /* Connect to Installation Proxy */ - if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if (instproxy_client_new(device, service, &ipc) != INSTPROXY_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if (service) - { - lockdownd_service_descriptor_free(service); - service = NULL; - } - - - /* Connect to Misagent */ - // Must connect now, since if we take too long writing files to device, connecting may fail later when managing profiles. - if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - - /* Connect to AFC service */ - if ((lockdownd_start_service(client, "com.apple.afc", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - NSURL *stagingURL = [NSURL fileURLWithPath:@"PublicStaging" isDirectory:YES]; - - /* Prepare for installation */ - char **files = NULL; - if (afc_get_file_info(afc, stagingURL.relativePath.fileSystemRepresentation, &files) != AFC_E_SUCCESS) - { - if (afc_make_directory(afc, stagingURL.relativePath.fileSystemRepresentation) != AFC_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceWriteFailed userInfo:nil]); - } - } - - if (files) - { - int i = 0; - - while (files[i]) - { - free(files[i]); - i++; - } - - free(files); - } - - NSLog(@"Writing to device..."); - - plist_t options = instproxy_client_options_new(); - instproxy_client_options_add(options, "PackageType", "Developer", NULL); - - NSURL *destinationURL = [stagingURL URLByAppendingPathComponent:appBundleURL.lastPathComponent]; - - // Writing files to device should be worth 3/4 of total work. - [progress becomeCurrentWithPendingUnitCount:3]; - - NSError *writeError = nil; - if (![self writeDirectory:appBundleURL toDestinationURL:destinationURL client:afc progress:nil error:&writeError]) - { - int removeResult = afc_remove_path_and_contents(afc, stagingURL.relativePath.fileSystemRepresentation); - NSLog(@"Remove staging app result: %@", @(removeResult)); - - return finish(writeError); - } - - NSLog(@"Finished writing to device."); - - if (service) - { - lockdownd_service_descriptor_free(service); - service = NULL; - } - - BOOL shouldManageProfiles = (activeProvisioningProfiles != nil || [application.provisioningProfile isFreeProvisioningProfile]); - if (shouldManageProfiles) - { - // Free developer account was used to sign this app, so we need to remove all - // provisioning profiles in order to remain under sideloaded app limit. - - NSError *error = nil; - NSDictionary *removedProfiles = [self removeAllFreeProfilesExcludingBundleIdentifiers:nil misagent:mis error:&error]; - if (removedProfiles == nil) - { - return finish(error); - } - - [removedProfiles enumerateKeysAndObjectsUsingBlock:^(NSString *bundleID, ALTProvisioningProfile *profile, BOOL * _Nonnull stop) { - if (activeProvisioningProfiles != nil) - { - if ([activeProvisioningProfiles containsObject:bundleID]) - { - // Only cache active profiles to reinstall afterwards. - cachedProfiles[bundleID] = profile; - } - } - else - { - // Cache all profiles to reinstall afterwards if we didn't provide activeProvisioningProfiles. - cachedProfiles[bundleID] = profile; - } - }]; - } - - lockdownd_client_free(client); - client = NULL; - - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - NSProgress *installationProgress = [NSProgress progressWithTotalUnitCount:100 parent:progress pendingUnitCount:1]; - - self.installationProgress[UUID] = installationProgress; - self.installationCompletionHandlers[UUID] = ^(NSError *error) { - finish(error); - - if (temporaryDirectoryURL != nil) - { - NSError *error = nil; - if (![[NSFileManager defaultManager] removeItemAtURL:temporaryDirectoryURL error:&error]) - { - NSLog(@"Error removing temporary directory. %@", error); - } - } - - dispatch_semaphore_signal(semaphore); - }; - - NSLog(@"Installing to device %@...", udid); - - instproxy_install(ipc, destinationURL.relativePath.fileSystemRepresentation, options, ALTDeviceManagerUpdateStatus, uuidString); - instproxy_client_options_free(options); - - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - }); - - return progress; -} - -- (BOOL)writeDirectory:(NSURL *)directoryURL toDestinationURL:(NSURL *)destinationURL client:(afc_client_t)afc progress:(NSProgress *)progress error:(NSError **)error -{ - afc_make_directory(afc, destinationURL.relativePath.fileSystemRepresentation); - - if (progress == nil) - { - NSDirectoryEnumerator *countEnumerator = [[NSFileManager defaultManager] enumeratorAtURL:directoryURL - includingPropertiesForKeys:@[] - options:0 - errorHandler:^BOOL(NSURL * _Nonnull url, NSError * _Nonnull error) { - if (error) { - NSLog(@"[Error] %@ (%@)", error, url); - return NO; - } - - return YES; - }]; - - NSInteger totalCount = 0; - for (NSURL *__unused fileURL in countEnumerator) - { - totalCount++; - } - - progress = [NSProgress progressWithTotalUnitCount:totalCount]; - } - - NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:directoryURL - includingPropertiesForKeys:@[NSURLIsDirectoryKey] - options:NSDirectoryEnumerationSkipsSubdirectoryDescendants - errorHandler:^BOOL(NSURL * _Nonnull url, NSError * _Nonnull error) { - if (error) { - NSLog(@"[Error] %@ (%@)", error, url); - return NO; - } - - return YES; - }]; - - for (NSURL *fileURL in enumerator) - { - NSNumber *isDirectory = nil; - if (![fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:error]) - { - return NO; - } - - if ([isDirectory boolValue]) - { - NSURL *destinationDirectoryURL = [destinationURL URLByAppendingPathComponent:fileURL.lastPathComponent isDirectory:YES]; - if (![self writeDirectory:fileURL toDestinationURL:destinationDirectoryURL client:afc progress:progress error:error]) - { - return NO; - } - } - else - { - NSURL *destinationFileURL = [destinationURL URLByAppendingPathComponent:fileURL.lastPathComponent isDirectory:NO]; - if (![self writeFile:fileURL toDestinationURL:destinationFileURL progress:progress client:afc error:error]) - { - return NO; - } - } - - progress.completedUnitCount += 1; - } - - return YES; -} - -- (BOOL)writeFile:(NSURL *)fileURL toDestinationURL:(NSURL *)destinationURL progress:(NSProgress *)progress client:(afc_client_t)afc error:(NSError **)error -{ - NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:fileURL.path]; - if (fileHandle == nil) - { - if (error) - { - *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileNoSuchFileError userInfo:@{NSURLErrorKey: fileURL}]; - } - - return NO; - } - - NSData *data = [fileHandle readDataToEndOfFile]; - - uint64_t af = 0; - - int openResult = afc_file_open(afc, destinationURL.relativePath.fileSystemRepresentation, AFC_FOPEN_WRONLY, &af); - if (openResult != AFC_E_SUCCESS || af == 0) - { - if (openResult == AFC_E_OBJECT_IS_DIR) - { - NSLog(@"Treating file as directory: %@ %@", fileURL, destinationURL); - return [self writeDirectory:fileURL toDestinationURL:destinationURL client:afc progress:progress error:error]; - } - - if (error) - { - *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSURLErrorKey: destinationURL}]; - } - - return NO; - } - - BOOL success = YES; - uint32_t bytesWritten = 0; - - while (bytesWritten < data.length) - { - uint32_t count = 0; - - int writeResult = afc_file_write(afc, af, (const char *)data.bytes + bytesWritten, (uint32_t)data.length - bytesWritten, &count); - if (writeResult != AFC_E_SUCCESS) - { - if (error) - { - NSLog(@"Failed writing file with error: %@ (%@ %@)", @(writeResult), fileURL, destinationURL); - *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSURLErrorKey: destinationURL}]; - } - - success = NO; - break; - } - - bytesWritten += count; - } - - if (bytesWritten != data.length) - { - if (error) - { - NSLog(@"Failed writing file due to mismatched sizes: %@ vs %@ (%@ %@)", @(bytesWritten), @(data.length), fileURL, destinationURL); - *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSURLErrorKey: destinationURL}]; - } - - success = NO; - } - - afc_file_close(afc, af); - - return success; -} - -- (void)removeAppForBundleIdentifier:(NSString *)bundleIdentifier fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler -{ - __block idevice_t device = NULL; - __block lockdownd_client_t client = NULL; - __block instproxy_client_t ipc = NULL; - __block lockdownd_service_descriptor_t service = NULL; - - void (^finish)(NSError *error) = ^(NSError *e) { - __block NSError *error = e; - - lockdownd_service_descriptor_free(service); - instproxy_client_free(ipc); - lockdownd_client_free(client); - idevice_free(device); - - if (error != nil) - { - completionHandler(NO, error); - } - else - { - completionHandler(YES, nil); - } - }; - - /* Find Device */ - if (idevice_new_with_options(&device, udid.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to Device */ - if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - /* Connect to Installation Proxy */ - if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if (instproxy_client_new(device, service, &ipc) != INSTPROXY_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if (service) - { - lockdownd_service_descriptor_free(service); - service = NULL; - } - - NSUUID *UUID = [NSUUID UUID]; - __block char *uuidString = (char *)malloc(UUID.UUIDString.length + 1); - strncpy(uuidString, (const char *)UUID.UUIDString.UTF8String, UUID.UUIDString.length); - uuidString[UUID.UUIDString.length] = '\0'; - - self.deletionCompletionHandlers[UUID] = ^(NSError *error) { - if (error != nil) - { - NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"Could not remove “%@”.", @""), bundleIdentifier]; - - NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; - userInfo[NSLocalizedFailureErrorKey] = localizedFailure; - - NSError *localizedError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; - finish(localizedError); - } - else - { - finish(nil); - } - - free(uuidString); - }; - - instproxy_uninstall(ipc, bundleIdentifier.UTF8String, NULL, ALTDeviceManagerUpdateAppDeletionStatus, uuidString); -} - -#pragma mark - Provisioning Profiles - - -- (void)installProvisioningProfiles:(NSSet *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *error))completionHandler -{ - dispatch_async(self.installationQueue, ^{ - __block idevice_t device = NULL; - __block lockdownd_client_t client = NULL; - __block afc_client_t afc = NULL; - __block misagent_client_t mis = NULL; - __block lockdownd_service_descriptor_t service = NULL; - - void (^finish)(NSError *_Nullable) = ^(NSError *error) { - lockdownd_service_descriptor_free(service); - misagent_client_free(mis); - afc_client_free(afc); - lockdownd_client_free(client); - idevice_free(device); - - completionHandler(error == nil, error); - }; - - /* Find Device */ - if (idevice_new_with_options(&device, udid.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to Device */ - if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - /* Connect to Misagent */ - if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - NSError *error = nil; - - if (activeProvisioningProfiles != nil) - { - // Remove all non-active free provisioning profiles. - - NSMutableSet *excludedBundleIdentifiers = [activeProvisioningProfiles mutableCopy]; - for (ALTProvisioningProfile *provisioningProfile in provisioningProfiles) - { - // Ensure we DO remove old versions of profiles we're about to install, even if they are active. - [excludedBundleIdentifiers removeObject:provisioningProfile.bundleIdentifier]; - } - - if (![self removeAllFreeProfilesExcludingBundleIdentifiers:excludedBundleIdentifiers misagent:mis error:&error]) - { - return finish(error); - } - } - else - { - // Remove only older versions of provisioning profiles we're about to install. - - NSMutableSet *bundleIdentifiers = [NSMutableSet set]; - for (ALTProvisioningProfile *provisioningProfile in provisioningProfiles) - { - [bundleIdentifiers addObject:provisioningProfile.bundleIdentifier]; - } - - if (![self removeProvisioningProfilesForBundleIdentifiers:bundleIdentifiers misagent:mis error:&error]) - { - return finish(error); - } - } - - for (ALTProvisioningProfile *provisioningProfile in provisioningProfiles) - { - if (![self installProvisioningProfile:provisioningProfile misagent:mis error:&error]) - { - return finish(error); - } - } - - finish(nil); - }); -} - -- (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *error))completionHandler -{ - dispatch_async(self.installationQueue, ^{ - __block idevice_t device = NULL; - __block lockdownd_client_t client = NULL; - __block afc_client_t afc = NULL; - __block misagent_client_t mis = NULL; - __block lockdownd_service_descriptor_t service = NULL; - - void (^finish)(NSError *_Nullable) = ^(NSError *error) { - lockdownd_service_descriptor_free(service); - misagent_client_free(mis); - afc_client_free(afc); - lockdownd_client_free(client); - idevice_free(device); - - completionHandler(error == nil, error); - }; - - /* Find Device */ - if (idevice_new_with_options(&device, udid.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to Device */ - if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - /* Connect to Misagent */ - if (lockdownd_start_service(client, "com.apple.misagent", &service) != LOCKDOWN_E_SUCCESS || service == NULL) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - NSError *error = nil; - if (![self removeProvisioningProfilesForBundleIdentifiers:bundleIdentifiers misagent:mis error:&error]) - { - return finish(error); - } - - finish(nil); - }); -} - -- (NSDictionary *)removeProvisioningProfilesForBundleIdentifiers:(NSSet *)bundleIdentifiers misagent:(misagent_client_t)mis error:(NSError **)error -{ - return [self removeAllProfilesForBundleIdentifiers:bundleIdentifiers excludingBundleIdentifiers:nil limitedToFreeProfiles:NO misagent:mis error:error]; -} - -- (NSDictionary *)removeAllFreeProfilesExcludingBundleIdentifiers:(nullable NSSet *)bundleIdentifiers misagent:(misagent_client_t)mis error:(NSError **)error -{ - return [self removeAllProfilesForBundleIdentifiers:nil excludingBundleIdentifiers:bundleIdentifiers limitedToFreeProfiles:YES misagent:mis error:error]; -} - -- (NSDictionary *)removeAllProfilesForBundleIdentifiers:(nullable NSSet *)includedBundleIdentifiers - excludingBundleIdentifiers:(nullable NSSet *)excludedBundleIdentifiers - limitedToFreeProfiles:(BOOL)limitedToFreeProfiles - misagent:(misagent_client_t)mis - error:(NSError **)error -{ - NSMutableDictionary *ignoredProfiles = [NSMutableDictionary dictionary]; - NSMutableDictionary *removedProfiles = [NSMutableDictionary dictionary]; - - NSArray *provisioningProfiles = [self copyProvisioningProfilesWithClient:mis error:error]; - if (provisioningProfiles == nil) - { - return nil; - } - - for (ALTProvisioningProfile *provisioningProfile in provisioningProfiles) - { - if (limitedToFreeProfiles && ![provisioningProfile isFreeProvisioningProfile]) - { - continue; - } - - if (includedBundleIdentifiers != nil && ![includedBundleIdentifiers containsObject:provisioningProfile.bundleIdentifier]) - { - continue; - } - - if (excludedBundleIdentifiers != nil && [excludedBundleIdentifiers containsObject:provisioningProfile.bundleIdentifier]) - { - // This provisioning profile has an excluded bundle identifier. - // Ignore it, unless we've already ignored one with the same bundle identifier, - // in which case remove whichever profile is the oldest. - - ALTProvisioningProfile *previousProfile = ignoredProfiles[provisioningProfile.bundleIdentifier]; - if (previousProfile != nil) - { - // We've already ignored a profile with this bundle identifier, - // so make sure we only ignore the newest one and remove the oldest one. - BOOL isNewerThanPreviousProfile = ([provisioningProfile.expirationDate compare:previousProfile.expirationDate] == NSOrderedDescending); - ALTProvisioningProfile *oldestProfile = isNewerThanPreviousProfile ? previousProfile : provisioningProfile; - ALTProvisioningProfile *newestProfile = isNewerThanPreviousProfile ? provisioningProfile : previousProfile; - - ignoredProfiles[provisioningProfile.bundleIdentifier] = newestProfile; - - // Don't cache this profile or else it will be reinstalled, so just remove it without caching. - if (![self removeProvisioningProfile:oldestProfile misagent:mis error:error]) - { - return nil; - } - } - else - { - ignoredProfiles[provisioningProfile.bundleIdentifier] = provisioningProfile; - } - - continue; - } - - ALTProvisioningProfile *preferredProfile = removedProfiles[provisioningProfile.bundleIdentifier]; - if (preferredProfile != nil) - { - if ([provisioningProfile.expirationDate compare:preferredProfile.expirationDate] == NSOrderedDescending) - { - removedProfiles[provisioningProfile.bundleIdentifier] = provisioningProfile; - } - } - else - { - removedProfiles[provisioningProfile.bundleIdentifier] = provisioningProfile; - } - - if (![self removeProvisioningProfile:provisioningProfile misagent:mis error:error]) - { - return nil; - } - } - - return removedProfiles; -} - -- (BOOL)installProvisioningProfile:(ALTProvisioningProfile *)provisioningProfile misagent:(misagent_client_t)mis error:(NSError **)error -{ - plist_t pdata = plist_new_data((const char *)provisioningProfile.data.bytes, provisioningProfile.data.length); - - misagent_error_t result = misagent_install(mis, pdata); - plist_free(pdata); - - if (result == MISAGENT_E_SUCCESS) - { - NSLog(@"Installed profile: %@ (%@)", provisioningProfile.bundleIdentifier, provisioningProfile.UUID); - return YES; - } - else - { - int statusCode = misagent_get_status_code(mis); - NSLog(@"Failed to install provisioning profile %@ (%@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.UUID, @(statusCode)); - - if (error) - { - switch (statusCode) - { - case -402620383: - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorMaximumFreeAppLimitReached userInfo:nil]; - break; - - default: - NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"Could not install profile “%@”", @""), provisioningProfile.bundleIdentifier]; - - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnderlyingError userInfo:@{ - NSLocalizedFailureErrorKey: localizedFailure, - ALTUnderlyingErrorCodeErrorKey: [@(statusCode) description], - ALTProvisioningProfileBundleIDErrorKey: provisioningProfile.bundleIdentifier - }]; - } - } - - return NO; - } -} - -- (BOOL)removeProvisioningProfile:(ALTProvisioningProfile *)provisioningProfile misagent:(misagent_client_t)mis error:(NSError **)error -{ - misagent_error_t result = misagent_remove(mis, provisioningProfile.UUID.UUIDString.lowercaseString.UTF8String); - if (result == MISAGENT_E_SUCCESS) - { - NSLog(@"Removed provisioning profile: %@ (%@)", provisioningProfile.bundleIdentifier, provisioningProfile.UUID); - return YES; - } - else - { - int statusCode = misagent_get_status_code(mis); - NSLog(@"Failed to remove provisioning profile %@ (%@). Error Code: %@", provisioningProfile.bundleIdentifier, provisioningProfile.UUID, @(statusCode)); - - if (error) - { - switch (statusCode) - { - case -402620405: - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorProfileNotFound userInfo:nil]; - break; - - default: - { - NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"Could not remove profile “%@”", @""), provisioningProfile.bundleIdentifier]; - - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnderlyingError userInfo:@{ - NSLocalizedFailureErrorKey: localizedFailure, - ALTUnderlyingErrorCodeErrorKey: [@(statusCode) description], - ALTProvisioningProfileBundleIDErrorKey: provisioningProfile.bundleIdentifier - }]; - } - } - } - - return NO; - } -} - -- (nullable NSArray *)copyProvisioningProfilesWithClient:(misagent_client_t)mis error:(NSError **)error -{ - plist_t rawProfiles = NULL; - misagent_error_t result = misagent_copy_all(mis, &rawProfiles); - if (result != MISAGENT_E_SUCCESS) - { - int statusCode = misagent_get_status_code(mis); - - if (error) - { - *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnderlyingError userInfo:@{ - NSLocalizedFailureErrorKey: NSLocalizedString(@"Could not copy provisioning profiles.", @""), - ALTUnderlyingErrorCodeErrorKey: [@(statusCode) description] - }]; - } - - return nil; - } - - /* Copy all provisioning profiles */ - - // For some reason, libplist now fails to parse `rawProfiles` correctly. - // Specifically, it no longer recognizes the nodes in the plist array as "data" nodes. - // However, if we encode it as XML then decode it again, it'll work ¯\_(ツ)_/¯ - char *plistXML = nullptr; - uint32_t plistLength = 0; - plist_to_xml(rawProfiles, &plistXML, &plistLength); - - plist_t profiles = NULL; - plist_from_xml(plistXML, plistLength, &profiles); - - free(plistXML); - - NSMutableArray *provisioningProfiles = [NSMutableArray array]; - - uint32_t profileCount = plist_array_get_size(profiles); - for (int i = 0; i < profileCount; i++) - { - plist_t profile = plist_array_get_item(profiles, i); - if (plist_get_node_type(profile) != PLIST_DATA) - { - continue; - } - - char *bytes = NULL; - uint64_t length = 0; - - plist_get_data_val(profile, &bytes, &length); - if (bytes == NULL) - { - continue; - } - - NSData *data = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES]; - ALTProvisioningProfile *provisioningProfile = [[ALTProvisioningProfile alloc] initWithData:data]; - if (provisioningProfile == nil) - { - continue; - } - - [provisioningProfiles addObject:provisioningProfile]; - } - - plist_free(rawProfiles); - plist_free(profiles); - - return provisioningProfiles; -} - -#pragma mark - Developer Disk Image - - -- (void)isDeveloperDiskImageMountedForDevice:(ALTDevice *)altDevice completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler -{ - __block idevice_t device = NULL; - __block instproxy_client_t ipc = NULL; - __block lockdownd_client_t client = NULL; - __block lockdownd_service_descriptor_t service = NULL; - __block mobile_image_mounter_client_t mim = NULL; - - __block BOOL isMounted = NO; - - void (^finish)(NSError *) = ^(NSError *error) { - if (mim) { - mobile_image_mounter_hangup(mim); - mobile_image_mounter_free(mim); - } - - if (service) { - lockdownd_service_descriptor_free(service); - } - - if (client) { - lockdownd_client_free(client); - } - - if (ipc) { - instproxy_client_free(ipc); - } - - if (device) { - idevice_free(device); - } - - completionHandler(isMounted, error); - }; - - dispatch_async(self.installationQueue, ^{ - - /* Find Device */ - if (idevice_new_with_options(&device, altDevice.identifier.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to Device */ - if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - /* Connect to Mobile Image Mounter Proxy */ - if ((lockdownd_start_service(client, "com.apple.mobile.mobile_image_mounter", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - mobile_image_mounter_error_t err = mobile_image_mounter_new(device, service, &mim); - if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) - { - return finish([NSError errorWithMobileImageMounterError:err device:altDevice]); - } - - plist_t result = NULL; - err = mobile_image_mounter_lookup_image(mim, "Developer", &result); - if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) - { - return finish([NSError errorWithMobileImageMounterError:err device:altDevice]); - } - - plist_dict_iter it = NULL; - plist_dict_new_iter(result, &it); - - char* key = NULL; - plist_t subnode = NULL; - plist_dict_next_item(result, it, &key, &subnode); - - while (subnode) - { - // If the ImageSignature key in the returned plist contains a subentry the disk image is already uploaded. - // Hopefully this works for older iOS versions as well. - // (via https://github.com/Schlaubischlump/LocationSimulator/blob/fdbd93ad16be5f69111b571d71ed6151e850144b/LocationSimulator/MobileDevice/devicemount/deviceimagemounter.c) - plist_type type = plist_get_node_type(subnode); - if (strcmp(key, "ImageSignature") == 0 && PLIST_ARRAY == type) - { - isMounted = (plist_array_get_size(subnode) != 0); - } - - free(key); - key = NULL; - - if (isMounted) - { - break; - } - - plist_dict_next_item(result, it, &key, &subnode); - } - - free(it); - - finish(nil); - }); -} - -- (void)installDeveloperDiskImageAtURL:(NSURL *)diskURL signatureURL:(NSURL *)signatureURL toDevice:(ALTDevice *)altDevice - completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler -{ - __block idevice_t device = NULL; - __block instproxy_client_t ipc = NULL; - __block lockdownd_client_t client = NULL; - __block lockdownd_service_descriptor_t service = NULL; - __block mobile_image_mounter_client_t mim = NULL; - - void (^finish)(NSError *) = ^(NSError *error) { - if (mim) { - mobile_image_mounter_hangup(mim); - mobile_image_mounter_free(mim); - } - - if (service) { - lockdownd_service_descriptor_free(service); - } - - if (client) { - lockdownd_client_free(client); - } - - if (ipc) { - instproxy_client_free(ipc); - } - - if (device) { - idevice_free(device); - } - - if (error) - { - error = [error alt_errorWithLocalizedFailure:[NSString stringWithFormat:NSLocalizedString(@"The Developer disk image could not be installed onto %@.", @""), altDevice.name]]; - } - - completionHandler(error == nil, error); - }; - - dispatch_async(self.installationQueue, ^{ - - /* Find Device */ - if (idevice_new_with_options(&device, altDevice.identifier.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to Device */ - if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - /* Connect to Mobile Image Mounter Proxy */ - if ((lockdownd_start_service(client, "com.apple.mobile.mobile_image_mounter", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) - { - return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - mobile_image_mounter_error_t err = mobile_image_mounter_new(device, service, &mim); - if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) - { - return finish([NSError errorWithMobileImageMounterError:err device:altDevice]); - } - - NSError *error = nil; - NSDictionary *diskAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:diskURL.path error:&error]; - if (diskAttributes == nil) - { - return finish(error); - } - - NSDictionary *signatureAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:signatureURL.path error:&error]; - if (signatureAttributes == nil) - { - return finish(error); - } - - size_t diskSize = [diskAttributes fileSize]; - - NSData *signature = [[NSData alloc] initWithContentsOfURL:signatureURL options:0 error:&error]; - if (signature == nil) - { - return finish(error); - } - - FILE *file = fopen(diskURL.fileSystemRepresentation, "rb"); - err = mobile_image_mounter_upload_image(mim, "Developer", diskSize, (const char *)signature.bytes, (size_t)signature.length, ALTDeviceManagerUploadFile, file); - fclose(file); - - if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) - { - return finish([NSError errorWithMobileImageMounterError:err device:altDevice]); - } - - NSString *diskPath = @"/private/var/mobile/Media/PublicStaging/staging.dimage"; - - plist_t result = NULL; - err = mobile_image_mounter_mount_image(mim, diskPath.UTF8String, (const char *)signature.bytes, (size_t)signature.length, "Developer", &result); - if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) - { - return finish([NSError errorWithMobileImageMounterError:err device:altDevice]); - } - - plist_t errorDescriptionNode = plist_dict_get_item(result, "DetailedError"); - if (errorDescriptionNode != NULL) - { - char *errorDescription = NULL; - plist_get_string_val(errorDescriptionNode, &errorDescription); - - NSString *nsErrorDescription = @(errorDescription); - plist_free(result); - - NSError *returnError = nil; - - if ([nsErrorDescription containsString:@"Failed to verify"]) - { - // iOS device needs to be rebooted in order to mount disk to /Developer. - NSString *recoverySuggestion = [NSString stringWithFormat:NSLocalizedString(@"Please reboot %@ and try again.", @""), altDevice.name]; - - // ALTServerErrorUnderlyingError uses its underlying error's failure reason as its error description (if one exists), - // so assign our recovery suggestion to NSLocalizedFailureReasonErrorKey to make sure it's always displayed on client. - NSError *underlyingError = [NSError errorWithDomain:AltServerConnectionErrorDomain code:ALTServerConnectionErrorUnknown userInfo:@{NSLocalizedFailureReasonErrorKey: recoverySuggestion}]; - - returnError = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnderlyingError userInfo:@{NSUnderlyingErrorKey: underlyingError}]; - } - else - { - // Installation failed, so we assume the developer disk is NOT compatible with this iOS version. - - NSMutableDictionary *userInfo = [@{ - ALTOperatingSystemVersionErrorKey: NSStringFromOperatingSystemVersion(altDevice.osVersion), - NSFilePathErrorKey: diskURL.path, - } mutableCopy]; - - NSString *osName = ALTOperatingSystemNameForDeviceType(altDevice.type); - if (osName != nil) - { - userInfo[ALTOperatingSystemNameErrorKey] = osName; - } - - returnError = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorIncompatibleDeveloperDisk userInfo:userInfo]; - } - - return finish(returnError); - } - - plist_free(result); - - // Verify that the developer disk has been successfully installed. - ALTDebugConnection *testConnection = [[ALTDebugConnection alloc] initWithDevice:altDevice]; - [testConnection connectWithCompletionHandler:^(BOOL success, NSError * _Nullable error) { - [testConnection disconnect]; - - if (success) - { - finish(nil); - } - else - { - finish(error); - } - }]; - }); -} - -#pragma mark - Apps - - -- (void)fetchInstalledAppsOnDevice:(ALTDevice *)altDevice completionHandler:(void (^)(NSSet *_Nullable installedApps, NSError *_Nullable error))completionHandler -{ - __block idevice_t device = NULL; - __block instproxy_client_t ipc = NULL; - __block lockdownd_client_t client = NULL; - __block lockdownd_service_descriptor_t service = NULL; - __block plist_t options = NULL; - - void (^finish)(NSSet *, NSError *) = ^(NSSet *installedApps, NSError *error) { - if (error != nil) { - NSLog(@"Notification Connection Error: %@", error); - } - - if (options) { - instproxy_client_options_free(options); - } - - if (service) { - lockdownd_service_descriptor_free(service); - } - - if (client) { - lockdownd_client_free(client); - } - - if (ipc) { - instproxy_client_free(ipc); - } - - if (device) { - idevice_free(device); - } - - completionHandler(installedApps, error); - }; - - // Don't use installationQueue since this operation can potentially take a very long time and will block other operations. - // dispatch_async(self.installationQueue, ^{ - dispatch_async(self.devicesQueue, ^{ - /* Find Device */ - if (idevice_new_with_options(&device, altDevice.identifier.UTF8String, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)) != IDEVICE_E_SUCCESS) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &client, "AltServer")) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service) != LOCKDOWN_E_SUCCESS) || !service) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - instproxy_error_t err = instproxy_client_new(device, service, &ipc); - if (err != INSTPROXY_E_SUCCESS) - { - return finish(nil, [NSError errorWithInstallationProxyError:err device:altDevice]); - } - - options = instproxy_client_options_new(); - instproxy_client_options_add(options, "ApplicationType", "User", NULL); - - plist_t plist = NULL; - err = instproxy_browse(ipc, options, &plist); - if (err != INSTPROXY_E_SUCCESS) - { - return finish(nil, [NSError errorWithInstallationProxyError:err device:altDevice]); - } - - char *plistXML = NULL; - uint32_t length = 0; - plist_to_xml(plist, &plistXML, &length); - - NSData *plistData = [@(plistXML) dataUsingEncoding:NSUTF8StringEncoding]; - free(plistXML); - plist_free(plist); - - NSError *error = nil; - NSArray *appDictionaries = [NSPropertyListSerialization propertyListWithData:plistData options:0 format:nil error:&error]; - if (appDictionaries == nil) - { - return finish(nil, error); - } - - NSMutableSet *installedApps = [NSMutableSet set]; - for (NSDictionary *appInfo in appDictionaries) - { - if (appInfo[@"ALTBundleIdentifier"] != nil) - { - // Only return apps installed with AltStore. - - ALTInstalledApp *installedApp = [[ALTInstalledApp alloc] initWithDictionary:appInfo]; - if (installedApp) - { - [installedApps addObject:installedApp]; - } - } - } - - finish(installedApps, nil); - }); -} - -#pragma mark - Connections - - -- (void)startWiredConnectionToDevice:(ALTDevice *)altDevice completionHandler:(void (^)(ALTWiredConnection * _Nullable, NSError * _Nullable))completionHandler -{ - void (^finish)(ALTWiredConnection *connection, NSError *error) = ^(ALTWiredConnection *connection, NSError *error) { - if (error != nil) - { - NSLog(@"Wired Connection Error: %@", error); - } - - completionHandler(connection, error); - }; - - idevice_t device = NULL; - idevice_connection_t connection = NULL; - - /* Find Device */ - if (idevice_new_with_options(&device, altDevice.identifier.UTF8String, IDEVICE_LOOKUP_USBMUX) != IDEVICE_E_SUCCESS) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to Listening Socket */ - if (idevice_connect(device, ALTDeviceListeningSocket, &connection) != IDEVICE_E_SUCCESS) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - idevice_free(device); - - ALTWiredConnection *wiredConnection = [[ALTWiredConnection alloc] initWithDevice:altDevice connection:connection]; - finish(wiredConnection, nil); -} - -- (void)startNotificationConnectionToDevice:(ALTDevice *)altDevice completionHandler:(void (^)(ALTNotificationConnection * _Nullable, NSError * _Nullable))completionHandler -{ - void (^finish)(ALTNotificationConnection *, NSError *) = ^(ALTNotificationConnection *connection, NSError *error) { - if (error != nil) - { - NSLog(@"Notification Connection Error: %@", error); - } - - completionHandler(connection, error); - }; - - idevice_t device = NULL; - lockdownd_client_t lockdownClient = NULL; - lockdownd_service_descriptor_t service = NULL; - - np_client_t client = NULL; - - /* Find Device */ - if (idevice_new_with_options(&device, altDevice.identifier.UTF8String, IDEVICE_LOOKUP_USBMUX) != IDEVICE_E_SUCCESS) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); - } - - /* Connect to Device */ - if (lockdownd_client_new_with_handshake(device, &lockdownClient, "altserver") != LOCKDOWN_E_SUCCESS) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - /* Connect to Notification Proxy */ - if ((lockdownd_start_service(lockdownClient, "com.apple.mobile.notification_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - /* Connect to Client */ - if (np_client_new(device, service, &client) != NP_E_SUCCESS) - { - return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); - } - - lockdownd_service_descriptor_free(service); - lockdownd_client_free(lockdownClient); - idevice_free(device); - - ALTNotificationConnection *notificationConnection = [[ALTNotificationConnection alloc] initWithDevice:altDevice client:client]; - completionHandler(notificationConnection, nil); -} - -- (void)startDebugConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTDebugConnection * _Nullable, NSError * _Nullable))completionHandler -{ - ALTDebugConnection *connection = [[ALTDebugConnection alloc] initWithDevice:device]; - [connection connectWithCompletionHandler:^(BOOL success, NSError * _Nullable error) { - if (success) - { - completionHandler(connection, nil); - } - else - { - completionHandler(nil, error); - } - }]; -} -#pragma mark - Getters - - -- (NSArray *)connectedDevices -{ - return [self availableDevicesIncludingNetworkDevices:NO]; -} - -- (NSArray *)availableDevices -{ - return [self availableDevicesIncludingNetworkDevices:YES]; -} - -- (NSArray *)availableDevicesIncludingNetworkDevices:(BOOL)includingNetworkDevices -{ - NSMutableSet *connectedDevices = [NSMutableSet set]; - - int count = 0; - idevice_info_t *devices = NULL; - - if (idevice_get_device_list_extended(&devices, &count) < 0) - { - fprintf(stderr, "ERROR: Unable to retrieve device list!\n"); - return @[]; - } - - for (int i = 0; i < count; i++) - { - idevice_info_t device_info = devices[i]; - char *udid = device_info->udid; - - idevice_t device = NULL; - lockdownd_client_t client = NULL; - - char *device_name = NULL; - char *device_type_string = NULL; - char *device_version_string = NULL; - - plist_t device_type_plist = NULL; - plist_t device_version_plist = NULL; - - void (^cleanUp)(void) = ^{ - if (device_version_plist) { - plist_free(device_version_plist); - } - - if (device_type_plist) { - plist_free(device_type_plist); - } - - if (device_version_string) { - free(device_version_string); - } - - if (device_type_string) { - free(device_type_string); - } - - if (device_name) { - free(device_name); - } - - if (client) { - lockdownd_client_free(client); - } - - if (device) { - idevice_free(device); - } - }; - - if (includingNetworkDevices) - { - idevice_new_with_options(&device, udid, (enum idevice_options)((int)IDEVICE_LOOKUP_NETWORK | (int)IDEVICE_LOOKUP_USBMUX)); - } - else - { - idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX); - } - - if (!device) - { - continue; - } - - int result = lockdownd_client_new(device, &client, "altserver"); - if (result != LOCKDOWN_E_SUCCESS) - { - fprintf(stderr, "ERROR: Connecting to device %s failed! (%d)\n", udid, result); - - cleanUp(); - continue; - } - - if (lockdownd_get_device_name(client, &device_name) != LOCKDOWN_E_SUCCESS || device_name == NULL) - { - fprintf(stderr, "ERROR: Could not get device name!\n"); - - cleanUp(); - continue; - } - - if (lockdownd_get_value(client, NULL, "ProductType", &device_type_plist) != LOCKDOWN_E_SUCCESS) - { - fprintf(stderr, "ERROR: Could not get device type for %s!\n", device_name); - - cleanUp(); - continue; - } - - plist_get_string_val(device_type_plist, &device_type_string); - - ALTDeviceType deviceType = ALTDeviceTypeiPhone; - if ([@(device_type_string) hasPrefix:@"iPhone"]) - { - deviceType = ALTDeviceTypeiPhone; - } - else if ([@(device_type_string) hasPrefix:@"iPad"]) - { - deviceType = ALTDeviceTypeiPad; - } - else if ([@(device_type_string) hasPrefix:@"AppleTV"]) - { - deviceType = ALTDeviceTypeAppleTV; - } - else - { - fprintf(stderr, "ERROR: Unknown device type %s for %s!\n", device_type_string, device_name); - - cleanUp(); - continue; - } - - if (lockdownd_get_value(client, NULL, "ProductVersion", &device_version_plist) != LOCKDOWN_E_SUCCESS) - { - fprintf(stderr, "ERROR: Could not get device type for %s!\n", device_name); - - cleanUp(); - continue; - } - - plist_get_string_val(device_version_plist, &device_version_string); - NSOperatingSystemVersion osVersion = NSOperatingSystemVersionFromString(@(device_version_string)); - - NSString *name = [NSString stringWithCString:device_name encoding:NSUTF8StringEncoding]; - NSString *identifier = [NSString stringWithCString:udid encoding:NSUTF8StringEncoding]; - - ALTDevice *altDevice = [[ALTDevice alloc] initWithName:name identifier:identifier type:deviceType]; - altDevice.osVersion = osVersion; - [connectedDevices addObject:altDevice]; - - cleanUp(); - } - - idevice_device_list_extended_free(devices); - - return connectedDevices.allObjects; -} - -@end - -#pragma mark - Callbacks - - -void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid) -{ - NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:[NSString stringWithUTF8String:(const char *)uuid]]; - - NSProgress *progress = ALTDeviceManager.sharedManager.installationProgress[UUID]; - if (progress == nil) - { - return; - } - - int percent = -1; - instproxy_status_get_percent_complete(status, &percent); - - char *name = NULL; - char *description = NULL; - uint64_t code = 0; - instproxy_status_get_error(status, &name, &description, &code); - - if ((percent == -1 && progress.completedUnitCount > 0) || code != 0 || name != NULL) - { - void (^completionHandler)(NSError *) = ALTDeviceManager.sharedManager.installationCompletionHandlers[UUID]; - if (completionHandler != nil) - { - NSString *localizedDescription = @(description ?: ""); - - if (code != 0 || name != NULL) - { - NSLog(@"Error installing app. %@ (%@). %@", @(code), @(name ?: ""), localizedDescription); - - NSError *error = nil; - - if (code == 3892346913) - { - NSDictionary *userInfo = (localizedDescription.length != 0) ? @{NSLocalizedDescriptionKey: localizedDescription} : nil; - error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorMaximumFreeAppLimitReached userInfo:userInfo]; - } - else - { - NSString *errorName = [NSString stringWithCString:name ?: "" encoding:NSUTF8StringEncoding]; - if ([errorName isEqualToString:@"DeviceOSVersionTooLow"]) - { - error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnsupportediOSVersion userInfo:nil]; - } - else - { - NSError *underlyingError = [NSError errorWithDomain:AltServerInstallationErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: localizedDescription}]; - error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorInstallationFailed userInfo:@{NSUnderlyingErrorKey: underlyingError}]; - } - } - - completionHandler(error); - } - else - { - NSLog(@"Finished installing app!"); - completionHandler(nil); - } - - ALTDeviceManager.sharedManager.installationCompletionHandlers[UUID] = nil; - ALTDeviceManager.sharedManager.installationProgress[UUID] = nil; - } - } - else if (progress.completedUnitCount < percent) - { - progress.completedUnitCount = percent; - - NSLog(@"Installation Progress: %@", @(percent)); - } -} - -void ALTDeviceManagerUpdateAppDeletionStatus(plist_t command, plist_t status, void *uuid) -{ - NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:[NSString stringWithUTF8String:(const char *)uuid]]; - - char *statusName = NULL; - instproxy_status_get_name(status, &statusName); - - char *errorName = NULL; - char *errorDescription = NULL; - uint64_t code = 0; - instproxy_status_get_error(status, &errorName, &errorDescription, &code); - - if ([@(statusName) isEqualToString:@"Complete"] || code != 0 || errorName != NULL) - { - void (^completionHandler)(NSError *) = ALTDeviceManager.sharedManager.deletionCompletionHandlers[UUID]; - if (completionHandler != nil) - { - if (code != 0 || errorName != NULL) - { - NSLog(@"Error removing app. %@ (%@). %@", @(code), @(errorName ?: ""), @(errorDescription ?: "")); - - NSError *underlyingError = [NSError errorWithDomain:AltServerInstallationErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(errorDescription ?: "")}]; - NSError *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorAppDeletionFailed userInfo:@{NSUnderlyingErrorKey: underlyingError}]; - - completionHandler(error); - } - else - { - NSLog(@"Finished removing app!"); - completionHandler(nil); - } - - ALTDeviceManager.sharedManager.deletionCompletionHandlers[UUID] = nil; - } - } -} - -void ALTDeviceDidChangeConnectionStatus(const idevice_event_t *event, void *user_data) -{ - ALTDevice * (^deviceForUDID)(NSString *, NSArray *) = ^ALTDevice *(NSString *udid, NSArray *devices) { - for (ALTDevice *device in devices) - { - if ([device.identifier isEqualToString:udid]) - { - return device; - } - } - - return nil; - }; - - switch (event->event) - { - case IDEVICE_DEVICE_ADD: - { - ALTDevice *device = deviceForUDID(@(event->udid), ALTDeviceManager.sharedManager.connectedDevices); - [[NSNotificationCenter defaultCenter] postNotificationName:ALTDeviceManagerDeviceDidConnectNotification object:device]; - - if (device) - { - [ALTDeviceManager.sharedManager.cachedDevices addObject:device]; - } - - break; - } - - case IDEVICE_DEVICE_REMOVE: - { - ALTDevice *device = deviceForUDID(@(event->udid), ALTDeviceManager.sharedManager.cachedDevices.allObjects); - [[NSNotificationCenter defaultCenter] postNotificationName:ALTDeviceManagerDeviceDidDisconnectNotification object:device]; - - if (device) - { - [ALTDeviceManager.sharedManager.cachedDevices removeObject:device]; - } - - break; - } - - default: break; - } -} - -ssize_t ALTDeviceManagerUploadFile(void *buffer, size_t size, void *user_data) -{ - return fread(buffer, 1, size, (FILE*)user_data); -} diff --git a/AltServer/Extensions/FileManager+URLs.swift b/AltServer/Extensions/FileManager+URLs.swift deleted file mode 100644 index 81b5a77e..00000000 --- a/AltServer/Extensions/FileManager+URLs.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// FileManager+URLs.swift -// AltServer -// -// Created by Riley Testut on 2/23/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -import Foundation - -extension FileManager -{ - var altserverDirectory: URL { - let applicationSupportDirectoryURL = self.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] - - let altserverDirectoryURL = applicationSupportDirectoryURL.appendingPathComponent("com.rileytestut.AltServer") - return altserverDirectoryURL - } - - var certificatesDirectory: URL { - let certificatesDirectoryURL = self.altserverDirectory.appendingPathComponent("Certificates") - return certificatesDirectoryURL - } - - var developerDisksDirectory: URL { - let developerDisksDirectoryURL = self.altserverDirectory.appendingPathComponent("DeveloperDiskImages") - return developerDisksDirectoryURL - } -} diff --git a/AltServer/Extensions/UserDefaults+AltServer.swift b/AltServer/Extensions/UserDefaults+AltServer.swift deleted file mode 100644 index 499cf69e..00000000 --- a/AltServer/Extensions/UserDefaults+AltServer.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// UserDefaults+AltServer.swift -// AltServer -// -// Created by Riley Testut on 7/31/19. -// Copyright © 2019 Riley Testut. All rights reserved. -// - -import Foundation - -extension UserDefaults -{ - var serverID: String? { - get { - return self.string(forKey: "serverID") - } - set { - self.set(newValue, forKey: "serverID") - } - } - - var didPresentInitialNotification: Bool { - get { - return self.bool(forKey: "didPresentInitialNotification") - } - set { - self.set(newValue, forKey: "didPresentInitialNotification") - } - } - - func registerDefaults() - { - if self.serverID == nil - { - self.serverID = UUID().uuidString - } - } -} diff --git a/AltServer/Info.plist b/AltServer/Info.plist deleted file mode 100644 index df713a6b..00000000 --- a/AltServer/Info.plist +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - LSUIElement - - NSHumanReadableCopyright - Copyright © 2019 Riley Testut. All rights reserved. - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - SUFeedURL - https://altstore.io/altserver/sparkle-macos.xml - SUFeedURL-Staging - https://altstore.io/altserver/sparkle-macos-staging.xml - - diff --git a/AltServer/InstalledApp.swift b/AltServer/InstalledApp.swift deleted file mode 100644 index 003dcbf6..00000000 --- a/AltServer/InstalledApp.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// InstalledApp.swift -// AltServer -// -// Created by Riley Testut on 5/25/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -import Foundation - -@objc(ALTInstalledApp) @objcMembers -class InstalledApp: NSObject, MenuDisplayable -{ - let name: String - let bundleIdentifier: String - let executableName: String - - init?(dictionary: [String: Any]) - { - guard let name = dictionary[kCFBundleNameKey as String] as? String, - let bundleIdentifier = dictionary[kCFBundleIdentifierKey as String] as? String, - let executableName = dictionary[kCFBundleExecutableKey as String] as? String - else { return nil } - - self.name = name - self.bundleIdentifier = bundleIdentifier - self.executableName = executableName - } -} diff --git a/AltServer/MenuController.swift b/AltServer/MenuController.swift deleted file mode 100644 index 67715344..00000000 --- a/AltServer/MenuController.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// MenuController.swift -// AltServer -// -// Created by Riley Testut on 3/3/21. -// Copyright © 2021 Riley Testut. All rights reserved. -// - -import Foundation -import AppKit - -protocol MenuDisplayable -{ - var name: String { get } -} - -class MenuController: NSObject, NSMenuDelegate -{ - let menu: NSMenu - - var items: [T] { - didSet { - self.submenus.removeAll() - self.updateMenu() - } - } - - var placeholder: String? { - didSet { - self.updateMenu() - } - } - - var action: ((T) -> Void)? - - var submenuHandler: ((T) -> NSMenu)? - private var submenus = [T: NSMenu]() - - init(menu: NSMenu, items: [T]) - { - self.menu = menu - self.items = items - - super.init() - - self.menu.delegate = self - } - - @objc - private func performAction(_ menuItem: NSMenuItem) - { - guard case let index = self.menu.index(of: menuItem), index != -1 else { return } - - let item = self.items[index] - self.action?(item) - } - - @objc - func numberOfItems(in menu: NSMenu) -> Int - { - let numberOfItems = (self.items.isEmpty && self.placeholder != nil) ? 1 : self.items.count - return numberOfItems - } - - @objc - func menu(_ menu: NSMenu, update menuItem: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool - { - if let text = self.placeholder, self.items.isEmpty - { - menuItem.title = text - menuItem.isEnabled = false - menuItem.target = nil - menuItem.action = nil - } - else - { - let item = self.items[index] - - menuItem.title = item.name - menuItem.isEnabled = true - menuItem.target = self - menuItem.action = #selector(MenuController.performAction(_:)) - menuItem.tag = index - - if let submenu = self.submenus[item] ?? self.submenuHandler?(item) - { - menuItem.submenu = submenu - - // Cache submenu to prevent duplicate calls to submenuHandler. - self.submenus[item] = submenu - } - } - - return true - } -} - -private extension MenuController -{ - func updateMenu() - { - self.menu.removeAllItems() - - let numberOfItems = self.numberOfItems(in: self.menu) - for index in 0 ..< numberOfItems - { - let menuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "") - guard self.menu(self.menu, update: menuItem, at: index, shouldCancel: false) else { break } - - self.menu.addItem(menuItem) - } - - self.menu.update() - } -} diff --git a/AltServer/Plugin/PluginManager.swift b/AltServer/Plugin/PluginManager.swift deleted file mode 100644 index fb2e9c09..00000000 --- a/AltServer/Plugin/PluginManager.swift +++ /dev/null @@ -1,372 +0,0 @@ -// -// PluginManager.swift -// AltServer -// -// Created by Riley Testut on 9/16/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -import Foundation -import AppKit -import CryptoKit - -import STPrivilegedTask - -private let pluginDirectoryURL = URL(fileURLWithPath: "/Library/Mail/Bundles", isDirectory: true) -private let pluginURL = pluginDirectoryURL.appendingPathComponent("AltPlugin.mailbundle") - -enum PluginError: LocalizedError -{ - case cancelled - case unknown - case notFound - case mismatchedHash(hash: String, expectedHash: String) - case taskError(String) - case taskErrorCode(Int) - - var errorDescription: String? { - switch self - { - case .cancelled: return NSLocalizedString("Mail plug-in installation was cancelled.", comment: "") - case .unknown: return NSLocalizedString("Failed to install Mail plug-in.", comment: "") - case .notFound: return NSLocalizedString("The Mail plug-in does not exist at the requested URL.", comment: "") - case .mismatchedHash(let hash, let expectedHash): return String(format: NSLocalizedString("The hash of the downloaded Mail plug-in does not match the expected hash.\n\nHash:\n%@\n\nExpected Hash:\n%@", comment: ""), hash, expectedHash) - case .taskError(let output): return output - case .taskErrorCode(let errorCode): return String(format: NSLocalizedString("There was an error installing the Mail plug-in. (Error Code: %@)", comment: ""), NSNumber(value: errorCode)) - } - } -} - -private extension URL -{ - #if STAGING - static let altPluginUpdateURL = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altserver/altplugin/altplugin.json")! - #else - static let altPluginUpdateURL = URL(string: "https://cdn.altstore.io/file/altstore/altserver/altplugin/altplugin.json")! - #endif -} - -class PluginManager -{ - private let session = URLSession(configuration: .ephemeral) - private var latestPluginVersion: PluginVersion? - - var isMailPluginInstalled: Bool { - let isMailPluginInstalled = FileManager.default.fileExists(atPath: pluginURL.path) - return isMailPluginInstalled - } - - func isUpdateAvailable(completionHandler: @escaping (Result) -> Void) - { - self.isUpdateAvailable(useCache: false, completionHandler: completionHandler) - } - - private func isUpdateAvailable(useCache: Bool, completionHandler: @escaping (Result) -> Void) - { - do - { - // If Mail plug-in is not yet installed, then there is no update available. - var isDirectory: ObjCBool = false - guard FileManager.default.fileExists(atPath: pluginURL.path, isDirectory: &isDirectory), isDirectory.boolValue else { return completionHandler(.success(false)) } - - // Load Info.plist from disk because Bundle.infoDictionary is cached by system. - let infoDictionaryURL = pluginURL.appendingPathComponent("Contents/Info.plist") - guard let infoDictionary = NSDictionary(contentsOf: infoDictionaryURL) as? [String: Any], - let localVersion = infoDictionary["CFBundleShortVersionString"] as? String - else { throw CocoaError(.fileReadCorruptFile, userInfo: [NSURLErrorKey: infoDictionaryURL]) } - - if let pluginVersion = self.latestPluginVersion, useCache - { - let isUpdateAvailable = (localVersion != pluginVersion.version) - completionHandler(.success(isUpdateAvailable)) - } - else - { - self.fetchLatestPluginVersion(useCache: useCache) { result in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success(let pluginVersion): - let isUpdateAvailable = (localVersion != pluginVersion.version) - completionHandler(.success(isUpdateAvailable)) - } - } - } - } - catch - { - completionHandler(.failure(error)) - } - } -} - -extension PluginManager -{ - func installMailPlugin(completionHandler: @escaping (Result) -> Void) - { - self.isUpdateAvailable(useCache: true) { result in - DispatchQueue.main.async { - do - { - let isUpdateAvailable = try result.get() - - let alert = NSAlert() - if isUpdateAvailable - { - alert.messageText = NSLocalizedString("Update Mail Plug-in", comment: "") - alert.informativeText = NSLocalizedString("An update is available for AltServer's Mail plug-in. Please update the plug-in now in order to keep using AltStore.", comment: "") - - alert.addButton(withTitle: NSLocalizedString("Update Plug-in", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - } - else - { - alert.messageText = NSLocalizedString("Install Mail Plug-in", comment: "") - alert.informativeText = NSLocalizedString("AltServer requires a Mail plug-in in order to retrieve necessary information about your Apple ID. Would you like to install it now?", comment: "") - - alert.addButton(withTitle: NSLocalizedString("Install Plug-in", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - } - - NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) - - let response = alert.runModal() - guard response == .alertFirstButtonReturn else { throw PluginError.cancelled } - - self.downloadPlugin { (result) in - do - { - let fileURL = try result.get() - - // Ensure plug-in directory exists. - let authorization = try self.runAndKeepAuthorization("mkdir", arguments: ["-p", pluginDirectoryURL.path]) - - // Create temporary directory. - let temporaryDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil) - defer { try? FileManager.default.removeItem(at: temporaryDirectoryURL) } - - // Unzip AltPlugin to temporary directory. - try self.runAndKeepAuthorization("unzip", arguments: ["-o", fileURL.path, "-d", temporaryDirectoryURL.path], authorization: authorization) - - if FileManager.default.fileExists(atPath: pluginURL.path) - { - // Delete existing Mail plug-in. - try self.runAndKeepAuthorization("rm", arguments: ["-rf", pluginURL.path], authorization: authorization) - } - - // Copy AltPlugin to Mail plug-ins directory. - // Must be separate step than unzip to prevent macOS from considering plug-in corrupted. - let unzippedPluginURL = temporaryDirectoryURL.appendingPathComponent(pluginURL.lastPathComponent) - try self.runAndKeepAuthorization("cp", arguments: ["-R", unzippedPluginURL.path, pluginDirectoryURL.path], authorization: authorization) - - guard self.isMailPluginInstalled else { throw PluginError.unknown } - - // Enable Mail plug-in preferences. - try self.run("defaults", arguments: ["write", "/Library/Preferences/com.apple.mail", "EnableBundles", "-bool", "YES"], authorization: authorization) - - print("Finished installing Mail plug-in!") - - completionHandler(.success(())) - } - catch - { - completionHandler(.failure(error)) - } - } - } - catch - { - completionHandler(.failure(error)) - } - } - } - } - - func uninstallMailPlugin(completionHandler: @escaping (Result) -> Void) - { - let alert = NSAlert() - alert.messageText = NSLocalizedString("Uninstall Mail Plug-in", comment: "") - alert.informativeText = NSLocalizedString("Are you sure you want to uninstall the AltServer Mail plug-in? You will no longer be able to install or refresh apps with AltStore.", comment: "") - - alert.addButton(withTitle: NSLocalizedString("Uninstall Plug-in", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - - NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) - - let response = alert.runModal() - guard response == .alertFirstButtonReturn else { return completionHandler(.failure(PluginError.cancelled)) } - - DispatchQueue.global().async { - do - { - if FileManager.default.fileExists(atPath: pluginURL.path) - { - // Delete Mail plug-in from privileged directory. - try self.run("rm", arguments: ["-rf", pluginURL.path]) - } - - completionHandler(.success(())) - } - catch - { - completionHandler(.failure(error)) - } - } - } -} - -private extension PluginManager -{ - func fetchLatestPluginVersion(useCache: Bool, completionHandler: @escaping (Result) -> Void) - { - if let pluginVersion = self.latestPluginVersion, useCache - { - return completionHandler(.success(pluginVersion)) - } - - guard #available(macOS 11, *) else { - // macOS versions prior to 11.0 require Mail plug-ins be *unsigned*, - // so we hardcode these versions to use the unsigned AltPlugin v1.0. - return completionHandler(.success(.v1_0)) - } - - let dataTask = self.session.dataTask(with: .altPluginUpdateURL) { (data, response, error) in - do - { - if let response = response as? HTTPURLResponse - { - guard response.statusCode != 404 else { return completionHandler(.failure(PluginError.notFound)) } - } - - guard let data = data else { throw error! } - - let response = try JSONDecoder().decode(PluginVersionResponse.self, from: data) - completionHandler(.success(response.pluginVersion)) - } - catch - { - completionHandler(.failure(error)) - } - } - - dataTask.resume() - } - - func downloadPlugin(completion: @escaping (Result) -> Void) - { - self.fetchLatestPluginVersion(useCache: true) { result in - switch result - { - case .failure(let error): completion(.failure(error)) - case .success(let pluginVersion): - - func finish(_ result: Result) - { - do - { - let fileURL = try result.get() - - if #available(OSX 10.15, *) - { - let data = try Data(contentsOf: fileURL) - let sha256Hash = SHA256.hash(data: data) - let hashString = sha256Hash.compactMap { String(format: "%02x", $0) }.joined() - - print("Comparing Mail plug-in hash (\(hashString)) against expected hash (\(pluginVersion.sha256Hash))...") - guard hashString == pluginVersion.sha256Hash else { throw PluginError.mismatchedHash(hash: hashString, expectedHash: pluginVersion.sha256Hash) } - } - - completion(.success(fileURL)) - } - catch - { - completion(.failure(error)) - } - } - - if pluginVersion.url.isFileURL - { - finish(.success(pluginVersion.url)) - } - else - { - let downloadTask = URLSession.shared.downloadTask(with: pluginVersion.url) { (fileURL, response, error) in - if let response = response as? HTTPURLResponse - { - guard response.statusCode != 404 else { return finish(.failure(PluginError.notFound)) } - } - - let result = Result(fileURL, error) - finish(result) - - if let fileURL = fileURL - { - try? FileManager.default.removeItem(at: fileURL) - } - } - - downloadTask.resume() - } - } - } - } - - func run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws - { - _ = try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: true) - } - - @discardableResult - func runAndKeepAuthorization(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws -> AuthorizationRef - { - return try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: false) - } - - func _run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil, freeAuthorization: Bool) throws -> AuthorizationRef - { - var launchPath = "/usr/bin/" + program - if !FileManager.default.fileExists(atPath: launchPath) - { - launchPath = "/bin/" + program - } - - print("Running program:", launchPath) - - let task = STPrivilegedTask() - task.launchPath = launchPath - task.arguments = arguments - task.freeAuthorizationWhenDone = freeAuthorization - - let errorCode: OSStatus - - if let authorization = authorization - { - errorCode = task.launch(withAuthorization: authorization) - } - else - { - errorCode = task.launch() - } - - guard errorCode == 0 else { throw PluginError.taskErrorCode(Int(errorCode)) } - - task.waitUntilExit() - - print("Exit code:", task.terminationStatus) - - guard task.terminationStatus == 0 else { - let outputData = task.outputFileHandle.readDataToEndOfFile() - - if let outputString = String(data: outputData, encoding: .utf8), !outputString.isEmpty - { - throw PluginError.taskError(outputString) - } - - throw PluginError.taskErrorCode(Int(task.terminationStatus)) - } - - guard let authorization = task.authorization else { throw PluginError.unknown } - return authorization - } -} diff --git a/AltServer/Plugin/PluginVersion.swift b/AltServer/Plugin/PluginVersion.swift deleted file mode 100644 index 72f7bcb5..00000000 --- a/AltServer/Plugin/PluginVersion.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// PluginVersion.swift -// AltServer -// -// Created by Riley Testut and Weedles on 2/15/22 <3 -// Copyright © 2022 Riley Testut. All rights reserved. -// - -import Foundation - -struct PluginVersion: Decodable -{ - var url: URL - var sha256Hash: String - var version: String - - static let v1_0 = PluginVersion(url: URL(string: "https://f000.backblazeb2.com/file/altstore/altserver/altplugin/1_0.zip")!, - sha256Hash: "070e9b7e1f74e7a6474d36253ab5a3623ff93892acc9e1043c3581f2ded12200", - version: "1.0") - - static let v1_9 = PluginVersion(url: Bundle.main.url(forResource: "AltPlugin", withExtension: "zip")!, - sha256Hash: "83ead26d8776ef6850e06fe3d1c5c5559aca284718b1cf3cc49785ba6b1e2849", - version: "1.9") - - private enum CodingKeys: String, CodingKey - { - case url - case sha256Hash = "sha256" - case version - } -} - -struct PluginVersionResponse: Decodable -{ - var version: Int - var pluginVersion: PluginVersion -} diff --git a/AltXPC.xcconfig b/AltXPC.xcconfig deleted file mode 100644 index 7816cc9a..00000000 --- a/AltXPC.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Build.xcconfig" diff --git a/AltXPC/AltXPC-Bridging-Header.h b/AltXPC/AltXPC-Bridging-Header.h deleted file mode 100644 index 8d7fdbf3..00000000 --- a/AltXPC/AltXPC-Bridging-Header.h +++ /dev/null @@ -1,7 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - -#import "ALTAnisetteData.h" -#import "AltXPCProtocol.h" -#import "ALTPluginService.h" diff --git a/AltXPC/AltXPC.entitlements b/AltXPC/AltXPC.entitlements deleted file mode 100644 index 760f348f..00000000 --- a/AltXPC/AltXPC.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - com.apple.authkit.client.internal - - - diff --git a/AltXPC/AltXPC.swift b/AltXPC/AltXPC.swift deleted file mode 100644 index c91b65d7..00000000 --- a/AltXPC/AltXPC.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// AltXPC.swift -// AltXPC -// -// Created by Riley Testut on 12/3/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -import Foundation - -@objc(AltXPC) -class AltXPC: NSObject, AltXPCProtocol -{ - func ping(_ completionHandler: @escaping () -> Void) - { - completionHandler() - } - - func requestAnisetteData(completionHandler: @escaping (ALTAnisetteData?, Error?) -> Void) - { - let anisetteData = ALTPluginService.shared.requestAnisetteData() - completionHandler(anisetteData, nil) - } -} diff --git a/AltXPC/Info.plist b/AltXPC/Info.plist deleted file mode 100644 index 5e394254..00000000 --- a/AltXPC/Info.plist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - AltXPC - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - NSHumanReadableCopyright - Copyright © 2020 Riley Testut. All rights reserved. - XPCService - - ServiceType - Application - - - diff --git a/AltXPC/main.swift b/AltXPC/main.swift deleted file mode 100644 index 42c0ad36..00000000 --- a/AltXPC/main.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// main.swift -// AltXPC -// -// Created by Riley Testut on 12/3/20. -// Copyright © 2020 Riley Testut. All rights reserved. -// - -import Foundation - -class ServiceDelegate : NSObject, NSXPCListenerDelegate -{ - func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool - { - newConnection.exportedInterface = NSXPCInterface(with: AltXPCProtocol.self) - - let exportedObject = AltXPC() - newConnection.exportedObject = exportedObject - newConnection.resume() - return true - } -} - -let serviceDelegate = ServiceDelegate() - -let listener = NSXPCListener.service() -listener.delegate = serviceDelegate -listener.resume() - -RunLoop.main.run() -