diff --git a/AltServer/Base.lproj/Main.storyboard b/AltServer/Base.lproj/Main.storyboard
index 71d4b3b4..e0524817 100644
--- a/AltServer/Base.lproj/Main.storyboard
+++ b/AltServer/Base.lproj/Main.storyboard
@@ -709,22 +709,131 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NSAllRomanInputSourcesLocaleIdentifier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
diff --git a/AltServer/ALTDeviceManager.h b/AltServer/Devices/ALTDeviceManager.h
similarity index 100%
rename from AltServer/ALTDeviceManager.h
rename to AltServer/Devices/ALTDeviceManager.h
diff --git a/AltServer/ALTDeviceManager.mm b/AltServer/Devices/ALTDeviceManager.mm
similarity index 100%
rename from AltServer/ALTDeviceManager.mm
rename to AltServer/Devices/ALTDeviceManager.mm
diff --git a/AltServer/Extensions/Result+Conveniences.swift b/AltServer/Extensions/Result+Conveniences.swift
new file mode 100644
index 00000000..ad585a18
--- /dev/null
+++ b/AltServer/Extensions/Result+Conveniences.swift
@@ -0,0 +1,41 @@
+//
+// Result+Conveniences.swift
+// AltStore
+//
+// Created by Riley Testut on 5/22/19.
+// Copyright © 2019 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+extension Result
+{
+ init(_ value: Success?, _ error: Failure?)
+ {
+ switch (value, error)
+ {
+ case (let value?, _): self = .success(value)
+ case (_, let error?): self = .failure(error)
+ case (nil, nil): preconditionFailure("Either value or error must be non-nil")
+ }
+ }
+}
+
+extension Result where Success == Void
+{
+ init(_ success: Bool, _ error: Failure?)
+ {
+ if success
+ {
+ self = .success(())
+ }
+ else if let error = error
+ {
+ self = .failure(error)
+ }
+ else
+ {
+ preconditionFailure("Error must be non-nil if success is false")
+ }
+ }
+}
diff --git a/AltServer/ViewController.swift b/AltServer/ViewController.swift
index 965a9697..fa454ed3 100644
--- a/AltServer/ViewController.swift
+++ b/AltServer/ViewController.swift
@@ -8,19 +8,358 @@
import Cocoa
-class ViewController: NSViewController {
+enum InstallError: Error
+{
+ case invalidCredentials
+ case noTeam
+ case missingPrivateKey
+ case missingCertificate
+
+ var localizedDescription: String {
+ switch self
+ {
+ case .invalidCredentials: return "The provided Apple ID and password are incorrect."
+ 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."
+ }
+ }
+}
+class ViewController: NSViewController
+{
+ @IBOutlet private var emailAddressTextField: NSTextField!
+ @IBOutlet private var passwordTextField: NSSecureTextField!
+
+ @IBOutlet private var devicesButton: NSPopUpButton!
+
+ private var currentDevice: ALTDevice?
+
override func viewDidLoad()
{
super.viewDidLoad()
+
+ self.update()
+ }
+
+ func update()
+ {
+ self.devicesButton.removeAllItems()
+
+ let devices = ALTDeviceManager.shared.connectedDevices
+
+ if devices.isEmpty
+ {
+ self.devicesButton.addItem(withTitle: "No Connected Device")
+ }
+ else
+ {
+ for device in devices
+ {
+ self.devicesButton.addItem(withTitle: device.name)
+ }
+ }
+
+ if let currentDevice = self.currentDevice, let index = devices.firstIndex(of: currentDevice)
+ {
+ self.devicesButton.selectItem(at: index)
+ }
+ else
+ {
+ self.currentDevice = devices.first
+ self.devicesButton.selectItem(at: 0)
+ }
}
}
private extension ViewController
{
- @IBAction func listConnectedDevices(_ sender: NSButton)
+ @IBAction func installAltStore(_ sender: NSButton)
+ {
+ guard let device = self.currentDevice else { return }
+ guard !self.emailAddressTextField.stringValue.isEmpty, !self.passwordTextField.stringValue.isEmpty else { return }
+
+ self.installAltStore(to: device)
+ }
+
+ @IBAction func chooseDevice(_ sender: NSPopUpButton)
{
let devices = ALTDeviceManager.shared.connectedDevices
- print(devices)
+ guard !devices.isEmpty else { return }
+
+ let index = sender.indexOfSelectedItem
+
+ let device = devices[index]
+ self.currentDevice = device
+ }
+}
+
+private extension ViewController
+{
+ func installAltStore(to device: ALTDevice)
+ {
+ func present(_ error: Error, title: String)
+ {
+ DispatchQueue.main.async {
+ let alert = NSAlert(error: error)
+ alert.runModal()
+ }
+ }
+
+ self.authenticate() { (result) in
+ do
+ {
+ let account = try result.get()
+ self.fetchTeam(for: account) { (result) in
+ do
+ {
+ let team = try result.get()
+
+ self.register(device, team: team) { (result) in
+ do
+ {
+ let device = try result.get()
+
+ self.fetchCertificate(for: team) { (result) in
+ do
+ {
+ let certificate = try result.get()
+
+ self.registerAppID(name: "AltStore", identifier: "com.rileytestut.AltStore", team: team) { (result) in
+ do
+ {
+ let appID = try result.get()
+ self.fetchProvisioningProfile(for: appID, team: team) { (result) in
+ do
+ {
+ let provisioningProfile = try result.get()
+ try self.installIPA(to: device, team: team, appID: appID, certificate: certificate, profile: provisioningProfile)
+ }
+ catch
+ {
+ present(error, title: "Failed to Fetch Provisioning Profile")
+ }
+ }
+
+ }
+ catch
+ {
+ present(error, title: "Failed to Register App")
+ }
+ }
+ }
+ catch
+ {
+ present(error, title: "Failed to Fetch Certificate")
+ }
+ }
+ }
+ catch
+ {
+ present(error, title: "Failed to Register Device")
+ }
+ }
+ }
+ catch
+ {
+ present(error, title: "Failed to Fetch Team")
+ }
+ }
+ }
+ catch
+ {
+ present(error, title: "Failed to Authenticate")
+ }
+ }
+ }
+
+ func authenticate(completionHandler: @escaping (Result) -> Void)
+ {
+ ALTAppleAPI.shared.authenticate(appleID: self.emailAddressTextField.stringValue, password: self.passwordTextField.stringValue) { (account, error) in
+ let result = Result(account, error)
+ completionHandler(result)
+ }
+ }
+
+ func fetchTeam(for account: ALTAccount, completionHandler: @escaping (Result) -> Void)
+ {
+ ALTAppleAPI.shared.fetchTeams(for: account) { (teams, error) in
+ do
+ {
+ let teams = try Result(teams, error).get()
+ guard let team = teams.first else { throw InstallError.noTeam }
+
+ completionHandler(.success(team))
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+
+ func fetchCertificate(for team: ALTTeam, completionHandler: @escaping (Result) -> Void)
+ {
+ ALTAppleAPI.shared.fetchCertificates(for: team) { (certificates, error) in
+ do
+ {
+ let certificates = try Result(certificates, error).get()
+
+ if let certificate = certificates.first
+ {
+ ALTAppleAPI.shared.revoke(certificate, for: team) { (success, error) in
+ do
+ {
+ try Result(success, error).get()
+ self.fetchCertificate(for: team, completionHandler: completionHandler)
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+ else
+ {
+ ALTAppleAPI.shared.addCertificate(machineName: "AltStore", to: team) { (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) { (certificates, error) in
+ do
+ {
+ let certificates = try Result(certificates, error).get()
+
+ guard let certificate = certificates.first(where: { $0.identifier == certificate.identifier }) else {
+ throw InstallError.missingCertificate
+ }
+
+ certificate.privateKey = privateKey
+
+ completionHandler(.success(certificate))
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+
+ func registerAppID(name appName: String, identifier: String, team: ALTTeam, completionHandler: @escaping (Result) -> Void)
+ {
+ var bundleID = "com." + team.account.firstName.lowercased() + team.account.lastName.lowercased() + "." + identifier
+ bundleID = bundleID.replacingOccurrences(of: " ", with: "")
+
+ ALTAppleAPI.shared.fetchAppIDs(for: team) { (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) { (appID, error) in
+ completionHandler(Result(appID, error))
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+
+ func register(_ device: ALTDevice, team: ALTTeam, completionHandler: @escaping (Result) -> Void)
+ {
+ ALTAppleAPI.shared.fetchDevices(for: team) { (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, team: team) { (device, error) in
+ completionHandler(Result(device, error))
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+
+ func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, completionHandler: @escaping (Result) -> Void)
+ {
+ ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team) { (profile, error) in
+ completionHandler(Result(profile, error))
+ }
+ }
+
+ func installIPA(to device: ALTDevice, team: ALTTeam, appID: ALTAppID, certificate: ALTCertificate, profile: ALTProvisioningProfile) throws
+ {
+ let ipaURL = Bundle.main.url(forResource: "App", withExtension: ".ipa")!
+
+ let destinationDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
+
+ do
+ {
+ try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)
+
+ let appBundleURL = try FileManager.default.unzipAppBundle(at: ipaURL, toDirectory: destinationDirectoryURL)
+ print(appBundleURL)
+
+ let infoPlistURL = appBundleURL.appendingPathComponent("Info.plist")
+
+ guard var infoDictionary = NSDictionary(contentsOf: infoPlistURL) as? [String: Any] else { throw ALTError(.missingInfoPlist) }
+ infoDictionary["altstore"] = ["udid": device.identifier]
+ try (infoDictionary as NSDictionary).write(to: infoPlistURL)
+
+ let zippedURL = try FileManager.default.zipAppBundle(at: appBundleURL)
+
+ let resigner = ALTSigner(team: team, certificate: certificate)
+ resigner.signApp(at: zippedURL, provisioningProfile: profile) { (resignedURL, error) in
+ do
+ {
+ let resignedURL = try Result(resignedURL, error).get()
+ ALTDeviceManager.shared.installApp(at: resignedURL, to: device) { (success, error) in
+ let result = Result(success, error)
+ print(result)
+ }
+ }
+ catch
+ {
+ print("Failed to install app", error)
+ }
+ }
+ }
+ catch
+ {
+ print("Failed to install AltStore", error)
+ }
}
}
diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj
index 6ba93774..2eb9a819 100644
--- a/AltStore.xcodeproj/project.pbxproj
+++ b/AltStore.xcodeproj/project.pbxproj
@@ -110,6 +110,8 @@
BF5AB3A82285FE7500DC914B /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; };
BF5AB3A92285FE7500DC914B /* AltSign.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF9B63C6229DD44E002F0A62 /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9B63C5229DD44D002F0A62 /* AltSign.framework */; };
+ BF9B63F6229DE476002F0A62 /* App.ipa in Resources */ = {isa = PBXBuildFile; fileRef = BF9B63F5229DE476002F0A62 /* App.ipa */; };
+ BF9B6426229E1331002F0A62 /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9B6425229E1331002F0A62 /* Result+Conveniences.swift */; };
BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB11691229322E400BB457C /* DatabaseManager.swift */; };
BFB1169B2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */; };
BFB1169D22932DB100BB457C /* Apps.json in Resources */ = {isa = PBXBuildFile; fileRef = BFB1169C22932DB100BB457C /* Apps.json */; };
@@ -275,6 +277,8 @@
BF4588962298DE6E00BD7491 /* libzip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libzip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF5AB3A72285FE6C00DC914B /* AltSign.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF9B63C5229DD44D002F0A62 /* AltSign.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF9B63F5229DE476002F0A62 /* App.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; path = App.ipa; sourceTree = ""; };
+ BF9B6425229E1331002F0A62 /* Result+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Result+Conveniences.swift"; sourceTree = ""; };
BFB11691229322E400BB457C /* DatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; };
BFB1169A2293274D00BB457C /* JSONDecoder+ManagedObjectContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+ManagedObjectContext.swift"; sourceTree = ""; };
BFB1169C22932DB100BB457C /* Apps.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Apps.json; sourceTree = ""; };
@@ -334,14 +338,12 @@
isa = PBXGroup;
children = (
BF45868F229872EA00BD7491 /* AppDelegate.swift */,
- BF458691229872EA00BD7491 /* ViewController.swift */,
- BF4586C32298CDB800BD7491 /* ALTDeviceManager.h */,
- BF4586C42298CDB800BD7491 /* ALTDeviceManager.mm */,
- BF458693229872EA00BD7491 /* Assets.xcassets */,
BF458695229872EA00BD7491 /* Main.storyboard */,
- BF458698229872EA00BD7491 /* Info.plist */,
- BF458699229872EA00BD7491 /* AltServer.entitlements */,
- BF4586C22298CDB800BD7491 /* AltServer-Bridging-Header.h */,
+ BF458691229872EA00BD7491 /* ViewController.swift */,
+ BF703195229F36FF006E110F /* Devices */,
+ BF703193229F36E5006E110F /* Extensions */,
+ BF703194229F36F6006E110F /* Resources */,
+ BF703196229F370F006E110F /* Supporting Files */,
);
path = AltServer;
sourceTree = "";
@@ -484,6 +486,42 @@
name = libcnary;
sourceTree = "";
};
+ BF703193229F36E5006E110F /* Extensions */ = {
+ isa = PBXGroup;
+ children = (
+ BF9B6425229E1331002F0A62 /* Result+Conveniences.swift */,
+ );
+ path = Extensions;
+ sourceTree = "";
+ };
+ BF703194229F36F6006E110F /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ BF458693229872EA00BD7491 /* Assets.xcassets */,
+ BF9B63F5229DE476002F0A62 /* App.ipa */,
+ );
+ name = Resources;
+ sourceTree = "";
+ };
+ BF703195229F36FF006E110F /* Devices */ = {
+ isa = PBXGroup;
+ children = (
+ BF4586C32298CDB800BD7491 /* ALTDeviceManager.h */,
+ BF4586C42298CDB800BD7491 /* ALTDeviceManager.mm */,
+ );
+ path = Devices;
+ sourceTree = "";
+ };
+ BF703196229F370F006E110F /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ BF458698229872EA00BD7491 /* Info.plist */,
+ BF458699229872EA00BD7491 /* AltServer.entitlements */,
+ BF4586C22298CDB800BD7491 /* AltServer-Bridging-Header.h */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
BFB1169E22933DDC00BB457C /* Updates */ = {
isa = PBXGroup;
children = (
@@ -762,6 +800,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ BF9B63F6229DE476002F0A62 /* App.ipa in Resources */,
BF458694229872EA00BD7491 /* Assets.xcassets in Resources */,
BF458697229872EA00BD7491 /* Main.storyboard in Resources */,
);
@@ -787,6 +826,7 @@
files = (
BF458692229872EA00BD7491 /* ViewController.swift in Sources */,
BF458690229872EA00BD7491 /* AppDelegate.swift in Sources */,
+ BF9B6426229E1331002F0A62 /* Result+Conveniences.swift in Sources */,
BF4586C52298CDB800BD7491 /* ALTDeviceManager.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/Dependencies/AltSign b/Dependencies/AltSign
index 0862204b..b490973e 160000
--- a/Dependencies/AltSign
+++ b/Dependencies/AltSign
@@ -1 +1 @@
-Subproject commit 0862204b83a95c51434bcfe3a980493208fdd064
+Subproject commit b490973e8430a9efc277d9c21b204ea5cc896301