diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index de6257d3..49a4e672 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -1146,27 +1146,28 @@ private extension AppManager case .activate(let app) where UserDefaults.standard.isLegacyDeactivationSupported: fallthrough case .refresh(let app): // Check if backup app is installed in place of real app. - let uti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary? +// let altBackupUti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary? - if app.certificateSerialNumber != group.context.certificate?.serialNumber || - uti != nil || - app.needsResign || +// if app.certificateSerialNumber != group.context.certificate?.serialNumber || +// altBackupUti != nil || // why would altbackup requires reinstall? it shouldn't cause we are just renewing profiles +// app.needsResign || // why would an app require resign during refresh? it shouldn't! // We need to reinstall ourselves on refresh to ensure the new provisioning profile is used - app.bundleIdentifier == StoreApp.altstoreAppID - { + // => mahee96: jkcoxson confirmed misagent manages profiles independently without requiring lockdownd or installd intervention, so sidestore profile renewal shouldn't require reinstall +// app.bundleIdentifier == StoreApp.altstoreAppID +// { // Resign app instead of just refreshing profiles because either: - // * Refreshing using different certificate - // * Backup app is still installed - // * App explicitly needs resigning + // * Refreshing using different certificate // when can this happen?, lets assume, refreshing with different certificate, why not just ask user to re-install manually? (probably we need re-install button) + // * Backup app is still installed // but why? I mean the AltBackup was put in place for a reason? ie during refresh just renew appIDs don't care about the app itself. + // * App explicitly needs resigning // when can this happen? // * Device is jailbroken and using AltDaemon on iOS 14.0 or later (b/c refreshing with provisioning profiles is broken) - let installProgress = self._install(app, operation: operation, group: group) { (result) in - self.finish(operation, result: result, group: group, progress: progress) - } - progress?.addChild(installProgress, withPendingUnitCount: 80) - } - else - { +// let installProgress = self._install(app, operation: operation, group: group) { (result) in +// self.finish(operation, result: result, group: group, progress: progress) +// } +// progress?.addChild(installProgress, withPendingUnitCount: 80) +// } +// else +// { // Refreshing with same certificate as last time, and backup app isn't still installed, // so we can just refresh provisioning profiles. @@ -1174,7 +1175,7 @@ private extension AppManager self.finish(operation, result: result, group: group, progress: progress) } progress?.addChild(refreshProgress, withPendingUnitCount: 80) - } +// } case .activate(let app): let activateProgress = self._activate(app, operation: operation, group: group) { (result) in @@ -1371,7 +1372,7 @@ private extension AppManager /* Fetch Provisioning Profiles */ - let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context) + let fetchProvisioningProfilesOperation = FetchProvisioningProfilesInstallOperation(context: context) fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements fetchProvisioningProfilesOperation.resultHandler = { (result) in switch result @@ -1657,26 +1658,27 @@ private extension AppManager let context = AppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context) context.app = ALTApplication(fileURL: app.fileURL) - //App-Extensions: Ensure DB data and disk state must match - let dbAppEx: Set = Set(app.appExtensions) - let diskAppEx: Set = Set(context.app!.appExtensions) - let diskAppExNames = diskAppEx.map { $0.bundleIdentifier } - let dbAppExNames = dbAppEx.map{ $0.bundleIdentifier } - let isMatching = Set(dbAppExNames) == Set(diskAppExNames) + // Since this doesn't involve modifying app bundle which will cause re-install, this is safe in refresh path + //App-Extensions: Ensure DB data and disk state must match + let dbAppEx: Set = Set(app.appExtensions) + let diskAppEx: Set = Set(context.app!.appExtensions) + let diskAppExNames = diskAppEx.map { $0.bundleIdentifier } + let dbAppExNames = dbAppEx.map{ $0.bundleIdentifier } + let isMatching = Set(dbAppExNames) == Set(diskAppExNames) - let validateAppExtensionsOperation = RSTAsyncBlockOperation { op in - - let errMessage = "AppManager.refresh: App Extensions in DB and Disk are matching: \(isMatching)\n" - + "AppManager.refresh: dbAppEx: \(dbAppExNames); diskAppEx: \(String(describing: diskAppExNames))\n" - print(errMessage) - if(!isMatching){ - completionHandler(.failure(OperationError.refreshAppFailed(message: errMessage))) - } - op.finish() - } + let validateAppExtensionsOperation = RSTAsyncBlockOperation { op in + + let errMessage = "AppManager.refresh: App Extensions in DB and Disk are matching: \(isMatching)\n" + + "AppManager.refresh: dbAppEx: \(dbAppExNames); diskAppEx: \(String(describing: diskAppExNames))\n" + print(errMessage) + if(!isMatching){ + completionHandler(.failure(OperationError.refreshAppFailed(message: errMessage))) + } + op.finish() + } /* Fetch Provisioning Profiles */ - let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context) + let fetchProvisioningProfilesOperation = FetchProvisioningProfilesRefreshOperation(context: context) fetchProvisioningProfilesOperation.resultHandler = { (result) in switch result { @@ -1686,7 +1688,7 @@ private extension AppManager } } progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 60) - fetchProvisioningProfilesOperation.addDependency(validateAppExtensionsOperation) + // fetchProvisioningProfilesOperation.addDependency(validateAppExtensionsOperation) /* Refresh */ let refreshAppOperation = RefreshAppOperation(context: context) @@ -1695,7 +1697,10 @@ private extension AppManager { case .success(let installedApp): completionHandler(.success(installedApp)) - + + + // refreshing local app's provisioning profile means talking to misagent daemon + // which requires loopback vpn case .failure(MinimuxerError.ProfileInstall): completionHandler(.failure(OperationError.noWiFi)) @@ -1720,7 +1725,8 @@ private extension AppManager progress.addChild(refreshAppOperation.progress, withPendingUnitCount: 40) refreshAppOperation.addDependency(fetchProvisioningProfilesOperation) - let operations = [validateAppExtensionsOperation, fetchProvisioningProfilesOperation, refreshAppOperation] +// let operations = [validateAppExtensionsOperation, fetchProvisioningProfilesOperation, refreshAppOperation] + let operations = [fetchProvisioningProfilesOperation, refreshAppOperation] group.add(operations) self.run(operations, context: group.context) diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index f75cb632..24dd159c 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -1151,7 +1151,9 @@ private extension MyAppsViewController func refresh(_ installedApp: InstalledApp) { - guard minimuxerStatus else { return } + // we do need minimuxer, coz it needs to talk to misagent daemon which manages profiles + // so basically loopback vpn is still required + guard minimuxerStatus else { return } // we don't need minimuxer when renewing appIDs only do we, heck we can even do it on mobile internet let previousProgress = AppManager.shared.refreshProgress(for: installedApp) guard previousProgress == nil else { @@ -1467,7 +1469,10 @@ private extension MyAppsViewController do { let tempApp = context.object(with: installedApp.objectID) as! InstalledApp - tempApp.needsResign = true + tempApp.needsResign = true // why do we want to resign it during refresh ?!!!! + // I see now, so here we just mark that icon needs to be changed but leave it for refresh/install to do it + // this is bad, coz now the weight of installing goes to refresh step !!! which is not what we want + tempApp.hasAlternateIcon = (image != nil) if let image = image diff --git a/AltStore/Operations/FetchProvisioningProfilesOperation.swift b/AltStore/Operations/FetchProvisioningProfilesOperation.swift index d6f7ce01..471aabbb 100644 --- a/AltStore/Operations/FetchProvisioningProfilesOperation.swift +++ b/AltStore/Operations/FetchProvisioningProfilesOperation.swift @@ -13,15 +13,16 @@ import AltSign import Roxas @objc(FetchProvisioningProfilesOperation) -final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]> +class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioningProfile]> { let context: AppOperationContext var additionalEntitlements: [ALTEntitlement: Any]? - private let appGroupsLock = NSLock() + internal let appGroupsLock = NSLock() - init(context: AppOperationContext) + // this class is abstract or shouldn't be instantiated outside, use the subclasses + fileprivate init(context: AppOperationContext) { self.context = context @@ -40,11 +41,13 @@ final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProv return } - guard - let team = self.context.team, - let session = self.context.session - else { - return self.finish(.failure(OperationError.invalidParameters("FetchProvisioningProfilesOperation.main: self.context.team or self.context.session is nil"))) } + guard let team = self.context.team, + let session = self.context.session else { + + return self.finish(.failure( + OperationError.invalidParameters("FetchProvisioningProfilesOperation.main: self.context.team or self.context.session is nil")) + ) + } guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound(name: nil))) } @@ -120,7 +123,11 @@ final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProv extension FetchProvisioningProfilesOperation { - func prepareProvisioningProfile(for app: ALTApplication, parentApp: ALTApplication?, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) + private func prepareProvisioningProfile(for app: ALTApplication, + parentApp: ALTApplication?, + team: ALTTeam, + session: ALTAppleAPISession, c + completionHandler: @escaping (Result) -> Void) { DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in @@ -213,35 +220,22 @@ extension FetchProvisioningProfilesOperation { case .failure(let error): completionHandler(.failure(error)) case .success(let appID): - - // Update features - self.updateFeatures(for: appID, app: app, team: team, session: session) { (result) in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success(let appID): - - // Update app groups - self.updateAppGroups(for: appID, app: app, team: team, session: session) { (result) in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success(let appID): - - // Fetch Provisioning Profile - self.fetchProvisioningProfile(for: appID, team: team, session: session) { (result) in - completionHandler(result) - } - } - } - } - } + + //process + self.fetchProvisioningProfile( + for: appID, team: team, session: session, completionHandler: completionHandler + ) } } } } - func registerAppID(for application: ALTApplication, name: String, bundleIdentifier: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) + private func registerAppID(for application: ALTApplication, + name: String, + bundleIdentifier: String, + team: ALTTeam, + session: ALTAppleAPISession, + completionHandler: @escaping (Result) -> Void) { ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in do @@ -335,7 +329,81 @@ extension FetchProvisioningProfilesOperation } } - func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) + internal func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) + { + 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)) + case .success(let profile): + + // Delete existing profile + ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in + switch Result(success, error) + { + case .failure: + // As of March 20, 2023, the free provisioning profile is re-generated each fetch, and you can no longer delete it. + // So instead, we just return the fetched profile from above. + completionHandler(.success(profile)) + + case .success: + Logger.sideload.notice("Generating new free provisioning profile for App ID \(appID.bundleIdentifier, privacy: .public).") + + // Fetch new provisioning profile + ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in + completionHandler(Result(profile, error)) + } + } + } + } + } + } +} + +class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesOperation, @unchecked Sendable { + override init(context: AppOperationContext) + { + super.init(context: context) + } +} + +class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperation, @unchecked Sendable{ + override init(context: AppOperationContext) + { + super.init(context: context) + } + + // modify Operations are allowed for the app groups and other stuffs + func fetchProvisioningProfile(appID: ALTAppID, + for app: ALTApplication, + team: ALTTeam, + session: ALTAppleAPISession, + completionHandler: @escaping (Result) -> Void) + { + + // Update features + self.updateFeatures(for: appID, app: app, team: team, session: session) { (result) in + switch result + { + case .failure(let error): completionHandler(.failure(error)) + case .success(let appID): + + // Update app groups + self.updateAppGroups(for: appID, app: app, team: team, session: session) { (result) in + switch result + { + case .failure(let error): completionHandler(.failure(error)) + case .success(let appID): + + // Fetch Provisioning Profile + super.fetchProvisioningProfile(for: appID, team: team, session: session, completionHandler: completionHandler) + } + } + } + } + } + + private func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) { var entitlements = app.entitlements for (key, value) in additionalEntitlements ?? [:] @@ -409,7 +477,7 @@ extension FetchProvisioningProfilesOperation } } - func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) + private func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) { var entitlements = app.entitlements for (key, value) in additionalEntitlements ?? [:] @@ -508,7 +576,7 @@ extension FetchProvisioningProfilesOperation Logger.sideload.notice("Created new App Group \(group.groupIdentifier, privacy: .public).") groups.append(group) - case .failure(let error): + case .failure(let error): Logger.sideload.notice("Failed to create new App Group \(adjustedGroupIdentifier, privacy: .public). \(error.localizedDescription, privacy: .public)") errors.append(error) } @@ -544,34 +612,4 @@ extension FetchProvisioningProfilesOperation } } } - - func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - 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)) - case .success(let profile): - - // Delete existing profile - ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in - switch Result(success, error) - { - case .failure: - // As of March 20, 2023, the free provisioning profile is re-generated each fetch, and you can no longer delete it. - // So instead, we just return the fetched profile from above. - completionHandler(.success(profile)) - - case .success: - Logger.sideload.notice("Generating new free provisioning profile for App ID \(appID.bundleIdentifier, privacy: .public).") - - // Fetch new provisioning profile - ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in - completionHandler(Result(profile, error)) - } - } - } - } - } - } } diff --git a/AltStore/Settings/OperationsLoggingContolView.swift b/AltStore/Settings/OperationsLoggingContolView.swift index 1c784f4f..84680809 100644 --- a/AltStore/Settings/OperationsLoggingContolView.swift +++ b/AltStore/Settings/OperationsLoggingContolView.swift @@ -77,10 +77,10 @@ struct OperationsLoggingControlView: View { } )) - CustomToggle("7. FetchProvisioningProfiles", isOn: Binding( - get: { self.viewModel.getFromDatabase(for: FetchProvisioningProfilesOperation.self) }, + CustomToggle("7. FetchProvisioningProfiles(I)", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: FetchProvisioningProfilesInstallOperation.self) }, set: { value in - self.viewModel.updateDatabase(for: FetchProvisioningProfilesOperation.self, value: value) + self.viewModel.updateDatabase(for: FetchProvisioningProfilesInstallOperation.self, value: value) } )) @@ -108,7 +108,14 @@ struct OperationsLoggingControlView: View { CustomSection(header: Text("Refresh Operations")) { - CustomToggle("1. RefreshApp", isOn: Binding( + CustomToggle("1. FetchProvisioningProfiles(R)", isOn: Binding( + get: { self.viewModel.getFromDatabase(for: FetchProvisioningProfilesRefreshOperation.self) }, + set: { value in + self.viewModel.updateDatabase(for: FetchProvisioningProfilesRefreshOperation.self, value: value) + } + )) + + CustomToggle("2. RefreshApp", isOn: Binding( get: { self.viewModel.getFromDatabase(for: RefreshAppOperation.self) }, set: { value in self.viewModel.updateDatabase(for: RefreshAppOperation.self, value: value)