Merge branch 'develop' of github.com:rileytestut/AltStore into develop

This commit is contained in:
Riley Testut
2020-12-03 16:25:32 -06:00
13 changed files with 338 additions and 189 deletions

View File

@@ -13,6 +13,12 @@ import AltSign
import LaunchAtLogin
#if STAGING
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altstore.ipa")!
#else
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore.ipa")!
#endif
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@@ -26,6 +32,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet private var appMenu: NSMenu!
@IBOutlet private var connectedDevicesMenu: NSMenu!
@IBOutlet private var sideloadIPAConnectedDevicesMenu: NSMenu!
@IBOutlet private var launchAtLoginMenuItem: NSMenuItem!
@IBOutlet private var installMailPluginMenuItem: NSMenuItem!
@@ -48,6 +55,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
self.appMenu.delegate = self
self.connectedDevicesMenu.delegate = self
self.sideloadIPAConnectedDevicesMenu.delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (success, error) in
guard success else { return }
@@ -81,8 +89,30 @@ private extension AppDelegate
{
@objc func installAltStore(_ item: NSMenuItem)
{
guard case let index = self.connectedDevicesMenu.index(of: item), index != -1 else { return }
guard let index = item.menu?.index(of: item), index != -1 else { return }
let device = self.connectedDevices[index]
self.installApplication(at: altstoreAppURL, to: device)
}
@objc func sideloadIPA(_ item: NSMenuItem)
{
guard let index = item.menu?.index(of: item), index != -1 else { return }
let device = self.connectedDevices[index]
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 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: "")
@@ -125,18 +155,16 @@ private extension AppDelegate
let username = appleIDTextField.stringValue
let password = passwordTextField.stringValue
let device = self.connectedDevices[index]
func install()
{
ALTDeviceManager.shared.installAltStore(to: device, appleID: username, password: password) { (result) in
ALTDeviceManager.shared.installApplication(at: url, to: device, appleID: username, password: password) { (result) in
switch result
{
case .success:
case .success(let application):
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Installation Succeeded", comment: "")
content.body = String(format: NSLocalizedString("AltStore was successfully installed on %@.", comment: ""), device.name)
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)
@@ -272,7 +300,7 @@ extension AppDelegate: NSMenuDelegate
{
guard menu == self.appMenu else { return }
self.connectedDevices = ALTDeviceManager.shared.connectedDevices
self.connectedDevices = ALTDeviceManager.shared.availableDevices
self.launchAtLoginMenuItem.target = self
self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
@@ -296,14 +324,14 @@ extension AppDelegate: NSMenuDelegate
func numberOfItems(in menu: NSMenu) -> Int
{
guard menu == self.connectedDevicesMenu else { return -1 }
guard menu == self.connectedDevicesMenu || menu == self.sideloadIPAConnectedDevicesMenu else { return -1 }
return self.connectedDevices.isEmpty ? 1 : self.connectedDevices.count
}
func menu(_ menu: NSMenu, update item: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool
{
guard menu == self.connectedDevicesMenu else { return false }
guard menu == self.connectedDevicesMenu || menu == self.sideloadIPAConnectedDevicesMenu else { return false }
if self.connectedDevices.isEmpty
{
@@ -318,7 +346,7 @@ extension AppDelegate: NSMenuDelegate
item.title = device.name
item.isEnabled = true
item.target = self
item.action = #selector(AppDelegate.installAltStore)
item.action = (menu == self.connectedDevicesMenu) ? #selector(AppDelegate.installAltStore(_:)) : #selector(AppDelegate.sideloadIPA(_:))
item.tag = index
}

View File

@@ -64,6 +64,7 @@
<outlet property="connectedDevicesMenu" destination="KJ9-WY-pW1" id="Mcv-64-iFU"/>
<outlet property="installMailPluginMenuItem" destination="3CM-gV-X2G" id="lio-ha-z0S"/>
<outlet property="launchAtLoginMenuItem" destination="IyR-FQ-upe" id="Fxn-EP-hwH"/>
<outlet property="sideloadIPAConnectedDevicesMenu" destination="IuI-bV-fTY" id="QQw-St-HfG"/>
</connections>
</customObject>
<customObject id="Arf-IC-5eb" customClass="SUUpdater"/>
@@ -97,6 +98,22 @@
</connections>
</menu>
</menuItem>
<menuItem title="Sideload .ipa" id="x0e-zI-0A2" userLabel="Install .ipa">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Sideload .ipa" systemMenu="recentDocuments" id="IuI-bV-fTY">
<items>
<menuItem title="No Connected Devices" id="in5-an-MD0">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="aUE-On-axK"/>
</connections>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="N3K-su-XV6"/>
</connections>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="1ZZ-BB-xHy"/>
<menuItem title="Launch at Login" id="IyR-FQ-upe" userLabel="Launch At Login">
<modifierMask key="keyEquivalentModifierMask"/>

View File

@@ -10,12 +10,6 @@ import Cocoa
import UserNotifications
import ObjectiveC
#if STAGING
private let appURL = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altstore.ipa")!
#else
private let appURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore.ipa")!
#endif
private let appGroupsLock = NSLock()
enum InstallError: LocalizedError
@@ -38,21 +32,14 @@ enum InstallError: LocalizedError
extension ALTDeviceManager
{
func installAltStore(to device: ALTDevice, appleID: String, password: String, completion: @escaping (Result<Void, Error>) -> Void)
func installApplication(at url: URL, to device: ALTDevice, appleID: String, password: String, completion: @escaping (Result<ALTApplication, Error>) -> Void)
{
let destinationDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
func finish(_ error: Error?, title: String = "")
func finish(_ result: Result<ALTApplication, Error>, title: String = "")
{
DispatchQueue.main.async {
if let error = error
{
completion(.failure(error))
}
else
{
completion(.success(()))
}
completion(result)
}
try? FileManager.default.removeItem(at: destinationDirectoryURL)
@@ -83,14 +70,13 @@ extension ALTDeviceManager
{
let certificate = try result.get()
let content = UNMutableNotificationContent()
content.title = String(format: NSLocalizedString("Installing AltStore to %@...", comment: ""), device.name)
content.body = NSLocalizedString("This may take a few seconds.", comment: "")
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
self.downloadApp { (result) in
if !url.isFileURL
{
// Show alert before downloading remote .ipa.
self.showInstallationAlert(appName: NSLocalizedString("AltStore", comment: ""), deviceName: device.name)
}
self.downloadApp(from: url) { (result) in
do
{
let fileURL = try result.get()
@@ -98,18 +84,14 @@ extension ALTDeviceManager
try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL)
do
{
try FileManager.default.removeItem(at: fileURL)
}
catch
{
print("Failed to remove downloaded .ipa.", error)
}
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)
}
// Refresh anisette data to prevent session timeouts.
AnisetteDataManager.shared.requestAnisetteData { (result) in
do
@@ -117,71 +99,79 @@ extension ALTDeviceManager
let anisetteData = try result.get()
session.anisetteData = anisetteData
self.prepareAllProvisioningProfiles(for: application, team: team, session: session) { (result) in
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.error, title: "Failed to Install AltStore")
finish(result.map { application }, title: "Failed to Install AltStore")
}
}
catch
{
finish(error, title: "Failed to Fetch Provisioning Profiles")
finish(.failure(error), title: "Failed to Fetch Provisioning Profiles")
}
}
}
catch
{
finish(error, title: "Failed to Refresh Anisette Data")
finish(.failure(error), title: "Failed to Refresh Anisette Data")
}
}
}
catch
{
finish(error, title: "Failed to Download AltStore")
finish(.failure(error), title: "Failed to Download AltStore")
}
}
}
catch
{
finish(error, title: "Failed to Fetch Certificate")
finish(.failure(error), title: "Failed to Fetch Certificate")
}
}
}
catch
{
finish(error, title: "Failed to Register Device")
finish(.failure(error), title: "Failed to Register Device")
}
}
}
catch
{
finish(error, title: "Failed to Fetch Team")
finish(.failure(error), title: "Failed to Fetch Team")
}
}
}
catch
{
finish(error, title: "Failed to Authenticate")
finish(.failure(error), title: "Failed to Authenticate")
}
}
}
catch
{
finish(error, title: "Failed to Fetch Anisette Data")
finish(.failure(error), title: "Failed to Fetch Anisette Data")
}
}
}
func downloadApp(completionHandler: @escaping (Result<URL, Error>) -> Void)
}
private extension ALTDeviceManager
{
func downloadApp(from url: URL, completionHandler: @escaping (Result<URL, Error>) -> Void)
{
let downloadTask = URLSession.shared.downloadTask(with: appURL) { (fileURL, response, error) in
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
{
@@ -244,26 +234,91 @@ extension ALTDeviceManager
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
{
func finish(_ result: Result<ALTTeam, Error>)
{
switch result
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in
do
{
case .failure(let error):
completionHandler(.failure(error))
let teams = try Result(teams, error).get()
case .success(let team):
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<ALTCertificate, Error>) -> Void)
{
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
let applicationSupportDirectoryURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
let altserverDirectoryURL = applicationSupportDirectoryURL.appendingPathComponent("com.rileytestut.AltServer")
let certificatesDirectoryURL = altserverDirectoryURL.appendingPathComponent("Certificates")
try FileManager.default.createDirectory(at: certificatesDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let certificateFileURL = certificatesDirectoryURL.appendingPathComponent(team.identifier + ".p12")
var isCancelled = false
// Check if there is another AltStore certificate, which means AltStore has been installed with this Apple ID before.
if let previousCertificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true })
{
if FileManager.default.fileExists(atPath: certificateFileURL.path),
let data = try? Data(contentsOf: certificateFileURL),
let certificate = ALTCertificate(p12Data: data, password: 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)) }
}
if team.type != .free
{
DispatchQueue.main.sync {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Installing AltStore will revoke your iOS development certificate.", comment: "")
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 to install AltStore.
To prevent this from happening, feel free to try again with another Apple ID.
""", comment: "")
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
@@ -278,78 +333,7 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
}
if isCancelled
{
return completionHandler(.failure(InstallError.cancelled))
}
}
completionHandler(.success(team))
}
}
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 == .free })
{
return finish(.success(team))
}
else if let team = teams.first(where: { $0.type == .individual })
{
return finish(.success(team))
}
else if let team = teams.first
{
return finish(.success(team))
}
else
{
throw InstallError.noTeam
}
}
catch
{
finish(.failure(error))
}
}
}
func fetchCertificate(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTCertificate, Error>) -> Void)
{
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
// Check if there is another AltStore certificate, which means AltStore has been installed with this Apple ID before.
if certificates.contains(where: { $0.machineName?.starts(with: "AltStore") == true })
{
var isCancelled = false
DispatchQueue.main.sync {
let alert = NSAlert()
alert.messageText = NSLocalizedString("AltStore already installed on another device.", comment: "")
alert.informativeText = NSLocalizedString("Apps installed with AltStore on your other devices will stop working. Are 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
}
}
if isCancelled
{
return completionHandler(.failure(InstallError.cancelled))
}
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
}
if let certificate = certificates.first
@@ -386,6 +370,14 @@ To prevent this from happening, feel free to try again with another Apple ID to
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
{
@@ -407,10 +399,10 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
}
func prepareAllProvisioningProfiles(for application: ALTApplication, team: ALTTeam, session: ALTAppleAPISession,
func prepareAllProvisioningProfiles(for application: ALTApplication, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession,
completion: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
{
self.prepareProvisioningProfile(for: application, team: team, session: session) { (result) in
self.prepareProvisioningProfile(for: application, parentApp: nil, device: device, team: team, session: session) { (result) in
do
{
let profile = try result.get()
@@ -424,7 +416,7 @@ To prevent this from happening, feel free to try again with another Apple ID to
{
dispatchGroup.enter()
self.prepareProvisioningProfile(for: appExtension, team: team, session: session) { (result) in
self.prepareProvisioningProfile(for: appExtension, parentApp: application, device: device, team: team, session: session) { (result) in
switch result
{
case .failure(let e): error = e
@@ -453,9 +445,35 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
}
func prepareProvisioningProfile(for application: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
func prepareProvisioningProfile(for application: ALTApplication, parentApp: ALTApplication?, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
self.registerAppID(name: application.name, identifier: application.bundleIdentifier, team: team, session: session) { (result) in
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()
@@ -470,7 +488,7 @@ To prevent this from happening, feel free to try again with another Apple ID to
{
let appID = try result.get()
self.fetchProvisioningProfile(for: appID, team: team, session: session) { (result) in
self.fetchProvisioningProfile(for: appID, device: device, team: team, session: session) { (result) in
completionHandler(result)
}
}
@@ -493,10 +511,8 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
}
func registerAppID(name appName: String, identifier: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
func registerAppID(name appName: String, bundleID: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
let bundleID = "com.\(team.identifier).\(identifier)"
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
do
{
@@ -652,7 +668,7 @@ To prevent this from happening, feel free to try again with another Apple ID to
func register(_ device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
{
ALTAppleAPI.shared.fetchDevices(for: team, session: session) { (devices, error) in
ALTAppleAPI.shared.fetchDevices(for: team, types: device.type, session: session) { (devices, error) in
do
{
let devices = try Result(devices, error).get()
@@ -663,7 +679,7 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
else
{
ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, team: team, session: session) { (device, error) in
ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, type: device.type, team: team, session: session) { (device, error) in
completionHandler(Result(device, error))
}
}
@@ -675,9 +691,9 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
}
func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
func fetchProvisioningProfile(for appID: ALTAppID, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team, session: session) { (profile, error) in
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: device.type, team: team, session: session) { (profile, error) in
completionHandler(Result(profile, error))
}
}
@@ -723,17 +739,21 @@ To prevent this from happening, feel free to try again with another Apple ID to
allURLSchemes.append(altstoreURLScheme)
var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes]
additionalValues[Bundle.Info.deviceID] = device.identifier
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID
if
let machineIdentifier = certificate.machineIdentifier,
let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier)
if application.isAltStoreApp
{
additionalValues[Bundle.Info.certificateID] = certificate.serialNumber
additionalValues[Bundle.Info.deviceID] = device.identifier
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID
let certificateURL = application.fileURL.appendingPathComponent("ALTCertificate.p12")
try encryptedData.write(to: certificateURL, options: .atomic)
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)
}
}
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
@@ -750,7 +770,7 @@ To prevent this from happening, feel free to try again with another Apple ID to
{
try Result(success, error).get()
let activeProfiles: Set<String>? = (team.type == .free) ? Set(profiles.values.map(\.bundleIdentifier)) : nil
let activeProfiles: Set<String>? = (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))
}
@@ -769,6 +789,16 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
}
}
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

View File

@@ -297,6 +297,9 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
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);
}
@@ -430,7 +433,7 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
else
{
NSURL *destinationFileURL = [destinationURL URLByAppendingPathComponent:fileURL.lastPathComponent isDirectory:NO];
if (![self writeFile:fileURL toDestinationURL:destinationFileURL client:afc error:error])
if (![self writeFile:fileURL toDestinationURL:destinationFileURL progress:progress client:afc error:error])
{
return NO;
}
@@ -442,7 +445,7 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
return YES;
}
- (BOOL)writeFile:(NSURL *)fileURL toDestinationURL:(NSURL *)destinationURL client:(afc_client_t)afc error:(NSError **)error
- (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)
@@ -458,8 +461,16 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
NSData *data = [fileHandle readDataToEndOfFile];
uint64_t af = 0;
if ((afc_file_open(afc, destinationURL.relativePath.fileSystemRepresentation, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) || 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}];
@@ -475,10 +486,12 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
{
uint32_t count = 0;
if (afc_file_write(afc, af, (const char *)data.bytes + bytesWritten, (uint32_t)data.length - bytesWritten, &count) != AFC_E_SUCCESS)
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}];
}
@@ -493,6 +506,7 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
{
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}];
}
@@ -1113,13 +1127,51 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
continue;
}
plist_t device_type_plist = NULL;
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);
lockdownd_client_free(client);
idevice_free(device);
continue;
}
ALTDeviceType deviceType = ALTDeviceTypeiPhone;
char *device_type_string = NULL;
plist_get_string_val(device_type_plist, &device_type_string);
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);
lockdownd_client_free(client);
idevice_free(device);
continue;
}
lockdownd_client_free(client);
idevice_free(device);
NSString *name = [NSString stringWithCString:device_name encoding:NSUTF8StringEncoding];
NSString *identifier = [NSString stringWithCString:udid encoding:NSUTF8StringEncoding];
ALTDevice *altDevice = [[ALTDevice alloc] initWithName:name identifier:identifier type:ALTDeviceTypeiPhone];
ALTDevice *altDevice = [[ALTDevice alloc] initWithName:name identifier:identifier type:deviceType];
[connectedDevices addObject:altDevice];
if (device_name != NULL)

View File

@@ -335,6 +335,8 @@
BFF0B696232242D3007A79E1 /* LicensesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B695232242D3007A79E1 /* LicensesViewController.swift */; };
BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */; };
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */; };
BFF435D8255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF435D7255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift */; };
BFF435D9255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF435D7255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift */; };
BFF615A82510042B00484D3B /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; };
BFF767C82489A74E0097E58C /* WirelessConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767C72489A74E0097E58C /* WirelessConnectionHandler.swift */; };
BFF7C90F257844C900E55F36 /* AltXPC.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = BFF7C904257844C900E55F36 /* AltXPC.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -769,6 +771,7 @@
BFF0B695232242D3007A79E1 /* LicensesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensesViewController.swift; sourceTree = "<group>"; };
BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionsViewController.swift; sourceTree = "<group>"; };
BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+CompactHeight.swift"; sourceTree = "<group>"; };
BFF435D7255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTApplication+AltStoreApp.swift"; sourceTree = "<group>"; };
BFF767C72489A74E0097E58C /* WirelessConnectionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WirelessConnectionHandler.swift; sourceTree = "<group>"; };
BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTServerError+Conveniences.swift"; sourceTree = "<group>"; };
BFF767CD2489ABE90097E58C /* NetworkConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnection.swift; sourceTree = "<group>"; };
@@ -1629,6 +1632,7 @@
BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */,
BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */,
BF1FE357251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift */,
BFF435D7255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -2260,6 +2264,7 @@
BFECAC8124FD950B0077C41F /* ALTServerError+Conveniences.swift in Sources */,
BFECAC7F24FD950B0077C41F /* CodableServerError.swift in Sources */,
BFECAC8624FD950B0077C41F /* Result+Conveniences.swift in Sources */,
BFF435D9255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift in Sources */,
BF718BD523C928A300A89F2D /* ALTNotificationConnection.mm in Sources */,
BF1E312B229F474900370A3C /* RequestHandler.swift in Sources */,
BF718BD123C91BD300A89F2D /* ALTWiredConnection.mm in Sources */,
@@ -2501,6 +2506,7 @@
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */,
BFF435D8255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift in Sources */,
BF4B78FE24B3D1DB008AB4AC /* SceneDelegate.swift in Sources */,
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */,
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */,

View File

@@ -432,11 +432,11 @@ private extension AuthenticationOperation
{
func selectTeam(from teams: [ALTTeam])
{
if let team = teams.first(where: { $0.type == .free })
if let team = teams.first(where: { $0.type == .individual })
{
return completionHandler(.success(team))
}
else if let team = teams.first(where: { $0.type == .individual })
else if let team = teams.first(where: { $0.type == .free })
{
return completionHandler(.success(team))
}
@@ -582,7 +582,7 @@ private extension AuthenticationOperation
return completionHandler(.failure(OperationError.unknownUDID))
}
ALTAppleAPI.shared.fetchDevices(for: team, session: session) { (devices, error) in
ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session) { (devices, error) in
do
{
let devices = try Result(devices, error).get()
@@ -593,7 +593,7 @@ private extension AuthenticationOperation
}
else
{
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, team: team, session: session) { (device, error) in
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session) { (device, error) in
completionHandler(Result(device, error))
}
}

View File

@@ -133,7 +133,7 @@ extension FetchProvisioningProfilesOperation
#if DEBUG
if app.bundleIdentifier.hasPrefix(StoreApp.altstoreAppID) || StoreApp.alternativeAltStoreAppIDs.contains(where: app.bundleIdentifier.hasPrefix)
if app.isAltStoreApp
{
// Use legacy bundle ID format for AltStore.
preferredBundleID = "com.\(team.identifier).\(app.bundleIdentifier)"
@@ -178,7 +178,7 @@ extension FetchProvisioningProfilesOperation
let parentBundleID = parentApp?.bundleIdentifier ?? app.bundleIdentifier
let updatedParentBundleID: String
if app.bundleIdentifier.hasPrefix(StoreApp.altstoreAppID) || StoreApp.alternativeAltStoreAppIDs.contains(where: app.bundleIdentifier.hasPrefix)
if app.isAltStoreApp
{
// Use legacy bundle ID format for AltStore (and its extensions).
updatedParentBundleID = "com.\(team.identifier).\(parentBundleID)"
@@ -463,7 +463,7 @@ extension FetchProvisioningProfilesOperation
func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team, session: session) { (profile, error) in
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
switch Result(profile, error)
{
case .failure(let error): completionHandler(.failure(error))
@@ -477,7 +477,7 @@ extension FetchProvisioningProfilesOperation
case .success:
// Fetch new provisiong profile
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, team: team, session: session) { (profile, error) in
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in
completionHandler(Result(profile, error))
}
}

View File

@@ -169,7 +169,7 @@ private extension ResignAppOperation
var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes]
if self.context.bundleIdentifier == StoreApp.altstoreAppID || StoreApp.alternativeAltStoreAppIDs.contains(self.context.bundleIdentifier)
if app.isAltStoreApp
{
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
additionalValues[Bundle.Info.deviceID] = udid

View File

@@ -36,10 +36,10 @@
"bundleIdentifier": "com.rileytestut.Delta.Alpha",
"developerName": "Riley Testut",
"subtitle": "Classic games in your pocket.",
"version": "1.3a2",
"versionDate": "2020-10-26T14:00:00-07:00",
"versionDescription": "This alpha fixes incorrect inputs when playing DS games with melonDS core.",
"downloadURL": "https://f000.backblazeb2.com/file/altstore/sources/alpha/delta/1_3_a2.ipa",
"version": "1.3a4",
"versionDate": "2020-11-30T20:00:00-06:00",
"versionDescription": "BUG FIXES\n• Fixes garbled audio during emulation\n• Fixes audio not playing over AirPlay• Fixes crash when activating Siri\n• Fixes MelonDS crashing on devices that don't support JIT",
"downloadURL": "https://f000.backblazeb2.com/file/altstore/sources/alpha/delta/1_3_a4.ipa",
"localizedDescription": "The next console for Delta is coming: this beta version of Delta brings support for playing DS games!\n\nDS support currently includes:\n• Playing DS games\n• Save States\n• Hold Button\n\nFeatures I'm still working on:\n• Fast Forward\n• Cheats\n• Controller skin (using placeholder controller skin for now)\n\nPlease report any issues you find to support@altstore.io. Thanks!",
"iconURL": "https://user-images.githubusercontent.com/705880/63391976-4d311700-c37a-11e9-91a8-4fb0c454413d.png",
"tintColor": "8A28F7",

View File

@@ -92,14 +92,14 @@
"bundleIdentifier": "com.rileytestut.Delta.Beta",
"developerName": "Riley Testut",
"subtitle": "Classic games in your pocket.",
"version": "1.2b3",
"versionDate": "2020-10-12T12:30:00-07:00",
"versionDescription": "**Delta 1.2 GM**\n\nIMPROVEMENTS\n• Improves sync error messages when a game is missing or cant be synced\n\nBUG FIXES\n• Fixes misplaced DS screens when using external controller\n• Fixes NES games crashing after playing an N64 game\n• Fixes various issues with controller skin filters\n• Fixes swapped DS screens\n• Fixes importing games from Files\n\nThanks for your help beta testing Delta these past few months! This should be the last beta before the public release of Delta 1.2, so please report any last-minute issues so we can have a smooth release 🙂",
"downloadURL": "https://f000.backblazeb2.com/file/altstore/apps/delta/1_2_b3.ipa",
"localizedDescription": "The next console for Delta is coming: this beta version of Delta brings support for playing DS games!\n\nDS support currently includes:\n• Playing DS games\n• Save States\n• Hold Button\n\nFeatures I'm still working on:\n• Fast Forward\n• Cheats\n• Controller skin (using placeholder controller skin for now)\n\nPlease report any issues you find to support@altstore.io. Thanks!",
"version": "1.3b2",
"versionDate": "2020-12-02T12:30:00-06:00",
"versionDescription": "BUG FIXES\n• Fixes garbled audio during emulation\n• Fixes MelonDS core crashing on devices that don't support JIT\n• Fixes audio not playing over AirPlay• Fixes crash when activating Siri\n\nPrevious update:\n\nNEW FEATURES\n• [MelonDS] DSi emulation (requires additional DSi BIOS files)\n• [MelonDS] Just-in-time (JIT) compilation on A12 devices running iOS 14.2 or newer.\n• [MelonDS] Microphone support — use your device's microphone to emulate the DS microphone.\n• [MelonDS] Emulate closing/opening lid — sleep device to \"close\" DS lid, wake to \"open\".\n\nIMPROVEMENTS\n• Dark mode support throughout app (thanks @ericlewis!)",
"downloadURL": "https://f000.backblazeb2.com/file/altstore/apps/delta/1_3_b2.ipa",
"localizedDescription": "The next console for Delta is coming: this beta version of Delta brings support for playing DS games!\n\nPlease report any issues you find to support@altstore.io. Thanks!",
"iconURL": "https://user-images.githubusercontent.com/705880/63391976-4d311700-c37a-11e9-91a8-4fb0c454413d.png",
"tintColor": "8A28F7",
"size": 18456630,
"size": 18558635,
"beta": true,
"permissions": [
{
@@ -274,6 +274,6 @@
}
],
"userInfo": {
"patreonAccessToken": "vfuSOfSYTUC8pEq6TM9r6uoL-T-uTJEI85hFescT4AM"
"patreonAccessToken": "dRUfeocZhNq6K3LWfu2poRE9aVkSTcFp2xBviRoOlcY"
}
}

View File

@@ -16,13 +16,10 @@ public extension StoreApp
{
#if ALPHA
static let altstoreAppID = "com.rileytestut.AltStore.Alpha"
static let alternativeAltStoreAppIDs: Set<String> = ["com.rileytestut.AltStore", "com.rileytestut.AltStore.Beta"]
#elseif BETA
static let altstoreAppID = "com.rileytestut.AltStore.Beta"
static let alternativeAltStoreAppIDs: Set<String> = ["com.rileytestut.AltStore", "com.rileytestut.AltStore.Alpha"]
#else
static let altstoreAppID = "com.rileytestut.AltStore"
static let alternativeAltStoreAppIDs: Set<String> = ["com.rileytestut.AltStore.Beta", "com.rileytestut.AltStore.Alpha"]
#endif
static let dolphinAppID = "me.oatmealdome.dolphinios-njb"

View File

@@ -0,0 +1,19 @@
//
// ALTApplication+AltStoreApp.swift
// AltStore
//
// Created by Riley Testut on 11/11/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import AltSign
extension ALTApplication
{
static let altstoreBundleID = "com.rileytestut.AltStore"
var isAltStoreApp: Bool {
let isAltStoreApp = self.bundleIdentifier.contains(ALTApplication.altstoreBundleID)
return isAltStoreApp
}
}