diff --git a/AltServer/Devices/ALTDeviceManager+Installation.swift b/AltServer/Devices/ALTDeviceManager+Installation.swift index 810880cc..63339ac0 100644 --- a/AltServer/Devices/ALTDeviceManager+Installation.swift +++ b/AltServer/Devices/ALTDeviceManager+Installation.swift @@ -117,51 +117,18 @@ extension ALTDeviceManager let anisetteData = try result.get() session.anisetteData = anisetteData - self.registerAppID(name: "AltStore", identifier: application.bundleIdentifier, team: team, session: session) { (result) in + self.prepareAllProvisioningProfiles(for: application, team: team, session: session) { (result) in do { - let appID = try result.get() + let profiles = 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, team: team, session: session) { (result) in - do - { - let provisioningProfile = try result.get() - - self.install(application, to: device, team: team, appID: appID, certificate: certificate, profile: provisioningProfile) { (result) in - finish(result.error, title: "Failed to Install AltStore") - } - } - catch - { - finish(error, title: "Failed to Fetch Provisioning Profile") - } - } - } - catch - { - finish(error, title: "Failed to Update App Groups") - } - } - } - catch - { - finish(error, title: "Failed to Update App ID") - } + self.install(application, to: device, team: team, certificate: certificate, profiles: profiles) { (result) in + finish(result.error, title: "Failed to Install AltStore") } } catch { - finish(error, title: "Failed to Register App") + finish(error, title: "Failed to Fetch Provisioning Profiles") } } } @@ -440,6 +407,92 @@ To prevent this from happening, feel free to try again with another Apple ID to } } + func prepareAllProvisioningProfiles(for application: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, + completion: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void) + { + self.prepareProvisioningProfile(for: application, 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, 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, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) + { + self.registerAppID(name: application.name, identifier: application.bundleIdentifier, 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, 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, identifier: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) { let bundleID = "com.\(team.identifier).\(identifier)" @@ -629,18 +682,35 @@ To prevent this from happening, feel free to try again with another Apple ID to } } - func install(_ application: ALTApplication, to device: ALTDevice, team: ALTTeam, appID: ALTAppID, certificate: ALTCertificate, profile: ALTProvisioningProfile, completionHandler: @escaping (Result) -> Void) + 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 + + 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 { - let infoPlistURL = application.fileURL.appendingPathComponent("Info.plist") - - guard var infoDictionary = NSDictionary(contentsOf: infoPlistURL) as? [String: Any] else { throw ALTError(.missingInfoPlist) } - infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier - infoDictionary[Bundle.Info.deviceID] = device.identifier - infoDictionary[Bundle.Info.serverID] = UserDefaults.standard.serverID - infoDictionary[Bundle.Info.certificateID] = certificate.serialNumber + 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 + "://")! @@ -652,30 +722,35 @@ To prevent this from happening, feel free to try again with another Apple ID to "CFBundleURLSchemes": [openAppURL.scheme!]] as [String : Any] allURLSchemes.append(altstoreURLScheme) - infoDictionary[Bundle.Info.urlTypes] = allURLSchemes + var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes] + additionalValues[Bundle.Info.deviceID] = device.identifier + additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID - if let appGroups = profile.entitlements[.appGroups] as? [String] - { - infoDictionary[Bundle.Info.appGroups] = appGroups - } - - try (infoDictionary as NSDictionary).write(to: infoPlistURL) - 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) + + 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: [profile]) { (success, error) in + resigner.signApp(at: application.fileURL, provisioningProfiles: Array(profiles.values)) { (success, error) in do { try Result(success, error).get() - let activeProfiles: Set? = (team.type == .free) ? [profile.bundleIdentifier] : nil + let activeProfiles: Set? = (team.type == .free) ? Set(profiles.values.map(\.bundleIdentifier)) : nil ALTDeviceManager.shared.installApp(at: application.fileURL, toDeviceWithUDID: device.identifier, activeProvisioningProfiles: activeProfiles) { (success, error) in completionHandler(Result(success, error)) }