From 6d511de31dbe1116cdbc64d752b89954499a2ccd Mon Sep 17 00:00:00 2001 From: mahee96 <47920326+mahee96@users.noreply.github.com> Date: Mon, 2 Feb 2026 08:39:32 +0530 Subject: [PATCH 1/6] Fix: base bundleID for AltBackup debug config was incorrect --- xcconfigs/AltBackup.xcconfig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xcconfigs/AltBackup.xcconfig b/xcconfigs/AltBackup.xcconfig index 4e75bc4f..05b86917 100644 --- a/xcconfigs/AltBackup.xcconfig +++ b/xcconfigs/AltBackup.xcconfig @@ -1,6 +1,4 @@ #include "../Build.xcconfig" -PRODUCT_BUNDLE_IDENTIFIER[config=Debug] = $(ORG_IDENTIFIER).SideStore.AltBackup. //$(DEVELOPMENT_TEAM) -//PRODUCT_BUNDLE_IDENTIFIER[config=Release] = $(ORG_IDENTIFIER).SideStore.AltBackup -//PRODUCT_BUNDLE_IDENTIFIER[config=Debug] = $(ORG_IDENTIFIER).SideStore.AltBackup +PRODUCT_BUNDLE_IDENTIFIER[config=Debug] = $(ORG_IDENTIFIER).SideStore.$(DEVELOPMENT_TEAM).AltBackup PRODUCT_BUNDLE_IDENTIFIER[config=Release] = $(ORG_IDENTIFIER).SideStore.AltBackup From 7c038568f83bbf2f8954a0334b83a6f0aff6ab41 Mon Sep 17 00:00:00 2001 From: mahee96 <47920326+mahee96@users.noreply.github.com> Date: Mon, 2 Feb 2026 08:39:50 +0530 Subject: [PATCH 2/6] added gitignore for minimuxer submodule --- SideStore/minimuxer-swift/.gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 SideStore/minimuxer-swift/.gitignore diff --git a/SideStore/minimuxer-swift/.gitignore b/SideStore/minimuxer-swift/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/SideStore/minimuxer-swift/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc From d70c91622219c3b60c2de280ab7534f890b95352 Mon Sep 17 00:00:00 2001 From: mahee96 <47920326+mahee96@users.noreply.github.com> Date: Mon, 2 Feb 2026 08:41:42 +0530 Subject: [PATCH 3/6] cleanup - removed unused commented code (already validated in 0.6.2 that refresh path doesnt need reinstalls since it only resigns the app) --- AltStore/Managing Apps/AppManager.swift | 36 +++---------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 1609599d..80161c79 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -1153,38 +1153,10 @@ 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 altBackupUti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary? - -// 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 - // => 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 // 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 -// { - // Refreshing with same certificate as last time, and backup app isn't still installed, - // so we can just refresh provisioning profiles. - - let refreshProgress = self._refresh(app, operation: operation, group: group) { (result) in - self.finish(operation, result: result, group: group, progress: progress) - } - progress?.addChild(refreshProgress, withPendingUnitCount: 80) -// } - + let refreshProgress = self._refresh(app, operation: operation, group: group) { (result) in + 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 self.finish(operation, result: result, group: group, progress: progress) From 30c03aad423eba22c7e7aa56003a0e275ac634e4 Mon Sep 17 00:00:00 2001 From: mahee96 <47920326+mahee96@users.noreply.github.com> Date: Mon, 2 Feb 2026 08:44:49 +0530 Subject: [PATCH 4/6] feature: added a prompt before installing to customize appId --- AltStore/Managing Apps/AppManager.swift | 196 ++++++++++++++++-- .../FetchProvisioningProfilesOperation.swift | 26 ++- AltStore/Operations/ResignAppOperation.swift | 21 +- AltStore/Operations/VerifyAppOperation.swift | 7 +- 4 files changed, 212 insertions(+), 38 deletions(-) diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 80161c79..a4c9cd80 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -8,7 +8,6 @@ import Foundation import UIKit -import SwiftUI import UserNotifications import MobileCoreServices import Intents @@ -705,9 +704,36 @@ extension AppManager } } - let operation = AppOperation.install(app) - self.perform([operation], presentingViewController: presentingViewController, group: group) + Task{ + var app: AppProtocol = app + // ---- Preflight bundle ID resolution ---- + if let presentingViewController { + let originalBundleID = app.bundleIdentifier + + let resolution = await self.resolveBundleID( + initial: originalBundleID, + presentingViewController: presentingViewController + ) + + switch resolution { + case .cancelled: + completionHandler(.failure(OperationError.cancelled)) + group.progress.cancel() + + case .resolved(let newBundleID): + app = AnyApp( + name: app.name, + bundleIdentifier: newBundleID, + url: app.url, + storeApp: app.storeApp + ) + } + } + + await self.perform([.install(app)], presentingViewController: presentingViewController, group: group) + + } return group } @@ -732,10 +758,11 @@ extension AppManager } } - let operation = AppOperation.update(appVersion) - assert(operation.app as AnyObject !== installedApp) // Make sure we never accidentally "update" to already installed app. + assert(appVersion as AnyObject !== installedApp) // Make sure we never accidentally "update" to already installed app. - self.perform([operation], presentingViewController: presentingViewController, group: group) + Task{ + await self.perform([.update(appVersion)], presentingViewController: presentingViewController, group: group) + } return group.progress } @@ -745,16 +772,20 @@ extension AppManager { let group = group ?? RefreshGroup() - let operations = installedApps.map { AppOperation.refresh($0) } - return self.perform(operations, presentingViewController: presentingViewController, group: group) + Task{ + await self.perform(installedApps.map { .refresh($0) }, presentingViewController: presentingViewController, group: group) + } + + return group } func activate(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result) -> Void) { let group = RefreshGroup() - let operation = AppOperation.activate(installedApp) - self.perform([operation], presentingViewController: presentingViewController, group: group) + Task{ + await self.perform([.activate(installedApp)], presentingViewController: presentingViewController, group: group) + } group.completionHandler = { (results) in do @@ -812,8 +843,9 @@ extension AppManager } } - let operation = AppOperation.deactivate(installedApp) - self.perform([operation], presentingViewController: presentingViewController, group: group) + Task{ + await self.perform([.deactivate(installedApp)], presentingViewController: presentingViewController, group: group) + } } } @@ -837,8 +869,9 @@ extension AppManager } } - let operation = AppOperation.backup(installedApp) - self.perform([operation], presentingViewController: presentingViewController, group: group) + Task{ + await self.perform([.backup(installedApp)], presentingViewController: presentingViewController, group: group) + } } func restore(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result) -> Void) @@ -863,8 +896,9 @@ extension AppManager } } - let operation = AppOperation.restore(installedApp) - self.perform([operation], presentingViewController: presentingViewController, group: group) + Task{ + await self.perform([.restore(installedApp)], presentingViewController: presentingViewController, group: group) + } } func remove(_ installedApp: InstalledApp, completionHandler: @escaping (Result) -> Void) @@ -1091,7 +1125,7 @@ private extension AppManager } @discardableResult - private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup + private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) async -> RefreshGroup { let operations = operations.filter { self.progress(for: $0) == nil || self.progress(for: $0)?.isCancelled == true } @@ -1226,7 +1260,7 @@ private extension AppManager { let progress = Progress.discreteProgress(totalUnitCount: 100) - let context = context ?? InstallAppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context) + let context = InstallAppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context) assert(context.authenticatedContext === group.context) context.beginInstallationHandler = { (installedApp) in @@ -1304,7 +1338,7 @@ private extension AppManager /* Verify App */ let permissionsMode = UserDefaults.shared.permissionCheckingDisabled ? .none : permissionReviewMode - let verifyOperation = VerifyAppOperation(permissionsMode: permissionsMode, context: context) + let verifyOperation = VerifyAppOperation(permissionsMode: permissionsMode, context: context, customBundleId: app.bundleIdentifier) verifyOperation.resultHandler = { (result) in do { @@ -1457,7 +1491,7 @@ private extension AppManager let patchAppURL = URL(string: patchAppLink) else { throw OperationError.invalidApp } - let patchApp = AnyApp(name: app.name, bundleIdentifier: app.bundleIdentifier, url: patchAppURL, storeApp: nil) + let patchApp = AnyApp(name: app.name, bundleIdentifier: context.bundleIdentifier, url: patchAppURL, storeApp: nil) DispatchQueue.main.async { let storyboard = UIStoryboard(name: "PatchApp", bundle: nil) @@ -1479,7 +1513,7 @@ private extension AppManager presentingViewController?.dismiss(animated: true, completion: nil) } } - presentingViewController.present(navigationController, animated: true, completion: nil) + presentingViewController.present(navigationController, animated: true, completion: nil) } } catch @@ -2274,3 +2308,123 @@ private extension AppManager } } } + +private enum BundleIDAlertKeys { + static var okAction: UInt8 = 0 +} + +private func _isValidBundleID(_ value: String) -> Bool { + let pattern = #"^[A-Za-z][A-Za-z0-9\-]*(\.[A-Za-z0-9\-]+)+$"# + return value.range(of: pattern, options: .regularExpression) != nil +} + +private extension UIResponder { + @objc func _validateBundleIDText(_ sender: UITextField) { + let isValid = sender.text.map(_isValidBundleID) ?? false + + sender.backgroundColor = + isValid || sender.text?.isEmpty == true + ? .clear + : UIColor.systemRed.withAlphaComponent(0.2) + + if + let alert = sender.superview?.superview as? UIAlertController, + let okAction = objc_getAssociatedObject(alert, &BundleIDAlertKeys.okAction) as? UIAlertAction + { + okAction.isEnabled = isValid + } + } +} + + + +private extension AppManager { + + func _presentBundleIDOverrideDialog( + bundleIdentifier: String, + presentingViewController: UIViewController, + completion: @escaping (BundleIDResolution) -> Void + ) { + let alert = self._makeBundleIDOverrideAlert( + initialBundleID: bundleIdentifier, + completion: completion + ) + + presentingViewController.present(alert, animated: true) + } + + func _makeBundleIDOverrideAlert( + initialBundleID: String, + completion: @escaping (BundleIDResolution) -> Void + ) -> UIAlertController { + + let alert = UIAlertController( + title: NSLocalizedString("Override Bundle Identifier", comment: ""), + message: nil, + preferredStyle: .alert + ) + + var okAction: UIAlertAction! + + alert.addTextField { textField in + textField.text = initialBundleID + textField.autocapitalizationType = .none + textField.autocorrectionType = .no + textField.addTarget( + nil, + action: #selector(UIResponder._validateBundleIDText(_:)), + for: .editingChanged + ) + } + + okAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default) { _ in + completion(.resolved(alert.textFields?.first?.text ?? initialBundleID)) + } + + okAction.isEnabled = _isValidBundleID(initialBundleID) + + let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in + completion(.cancelled) + } + + alert.addAction(cancelAction) + alert.addAction(okAction) + + objc_setAssociatedObject( + alert, + &BundleIDAlertKeys.okAction, + okAction, + .OBJC_ASSOCIATION_ASSIGN + ) + + return alert + } +} + + +// ---- Part 1: Add async resolver ---- + +private extension AppManager { + + enum BundleIDResolution { + case resolved(String) + case cancelled + } + + @MainActor + func resolveBundleID( + initial: String, + presentingViewController: UIViewController + ) async -> BundleIDResolution { + + await withCheckedContinuation { continuation in + let alert = self._makeBundleIDOverrideAlert( + initialBundleID: initial + ) { result in + continuation.resume(returning: result) + } + + presentingViewController.present(alert, animated: true) + } + } +} diff --git a/AltStore/Operations/FetchProvisioningProfilesOperation.swift b/AltStore/Operations/FetchProvisioningProfilesOperation.swift index 90106cb6..862e4018 100644 --- a/AltStore/Operations/FetchProvisioningProfilesOperation.swift +++ b/AltStore/Operations/FetchProvisioningProfilesOperation.swift @@ -54,7 +54,8 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni Logger.sideload.notice("Fetching provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)...") self.progress.totalUnitCount = Int64(1 + app.appExtensions.count) - + let effectiveBundleId = self.context.bundleIdentifier + self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in do { @@ -62,7 +63,7 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni let profile = try result.get() - var profiles = [app.bundleIdentifier: profile] + var profiles = [effectiveBundleId: profile] var error: Error? let dispatchGroup = DispatchGroup() @@ -220,19 +221,30 @@ extension FetchProvisioningProfilesOperation // Or, if the app _is_ installed but with a different team, we need to create a new // bundle identifier anyway to prevent collisions with the previous team. let parentBundleID = parentApp?.bundleIdentifier ?? app.bundleIdentifier + let effectiveParentBundleID = self.context.bundleIdentifier + let updatedParentBundleID: String - + if app.isAltStoreApp { // Use legacy bundle ID format for AltStore (and its extensions). - updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track. + updatedParentBundleID = effectiveParentBundleID + "." + team.identifier // Append just team identifier to make it harder to track. } else { - updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track. + updatedParentBundleID = effectiveParentBundleID + "." + team.identifier // Append just team identifier to make it harder to track. + } + + if let parentApp = parentApp, + app.bundleIdentifier.hasPrefix(parentBundleID + ".") + { + let suffix = String(app.bundleIdentifier.dropFirst(parentBundleID.count)) + bundleID = updatedParentBundleID + suffix + } + else + { + bundleID = updatedParentBundleID } - - bundleID = app.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID) } let preferredName: String diff --git a/AltStore/Operations/ResignAppOperation.swift b/AltStore/Operations/ResignAppOperation.swift index 0bfd2a96..6d5ca496 100644 --- a/AltStore/Operations/ResignAppOperation.swift +++ b/AltStore/Operations/ResignAppOperation.swift @@ -55,6 +55,7 @@ final class ResignAppOperation: ResultOperation let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2) self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3) + let effectiveBundleId = self.context.bundleIdentifier let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles, appexBundleIds: context.appexBundleIds ?? [:]) { (result) in guard let appBundleURL = self.process(result) else { return } @@ -65,7 +66,13 @@ final class ResignAppOperation: ResultOperation // Finish do { - let destinationURL = InstalledApp.refreshedIPAURL(for: app) + let updatedApp = AnyApp( + name: app.name, + bundleIdentifier: effectiveBundleId, + url: app.fileURL, + storeApp: app.storeApp + ) + let destinationURL = InstalledApp.refreshedIPAURL(for: updatedApp) try FileManager.default.copyItem(at: resignedURL, to: destinationURL, shouldReplace: true) print("Successfully resigned app to \(destinationURL.absoluteString)") @@ -110,15 +117,13 @@ private extension ResignAppOperation func prepareAppBundle(for app: ALTApplication, profiles: [String: ALTProvisioningProfile], appexBundleIds: [String: String], completionHandler: @escaping (Result) -> Void) -> Progress { let progress = Progress.discreteProgress(totalUnitCount: 1) - - let bundleIdentifier = app.bundleIdentifier + let openURL = InstalledApp.openAppURL(for: app) - let fileURL = app.fileURL - + let identifier = context.bundleIdentifier + func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws { - guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) } guard let profile = context.useMainProfile ? profiles.values.first : profiles[identifier] else { throw ALTError(.missingProvisioningProfile) } guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) } @@ -189,7 +194,7 @@ private extension ResignAppOperation var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? [] let altstoreURLScheme = ["CFBundleTypeRole": "Editor", - "CFBundleURLName": bundleIdentifier, + "CFBundleURLName": identifier, "CFBundleURLSchemes": [openURL.scheme!]] as [String : Any] allURLSchemes.append(altstoreURLScheme) @@ -198,7 +203,7 @@ private extension ResignAppOperation if app.isAltStoreApp { guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID } - guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID } + guard Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) is String else { throw OperationError.unknownUDID } additionalValues[Bundle.Info.devicePairingString] = "" additionalValues[Bundle.Info.deviceID] = udid additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID diff --git a/AltStore/Operations/VerifyAppOperation.swift b/AltStore/Operations/VerifyAppOperation.swift index cf741287..7e8772d7 100644 --- a/AltStore/Operations/VerifyAppOperation.swift +++ b/AltStore/Operations/VerifyAppOperation.swift @@ -38,11 +38,13 @@ final class VerifyAppOperation: ResultOperation { let permissionsMode: PermissionReviewMode let context: InstallAppOperationContext + var customBundleId: String? - init(permissionsMode: PermissionReviewMode, context: InstallAppOperationContext) + init(permissionsMode: PermissionReviewMode, context: InstallAppOperationContext, customBundleId: String? = nil) { self.permissionsMode = permissionsMode self.context = context + self.customBundleId = customBundleId super.init() } @@ -65,7 +67,8 @@ final class VerifyAppOperation: ResultOperation } if !["ny.litritt.ignited", "com.litritt.ignited"].contains(where: { $0 == app.bundleIdentifier }) { - guard app.bundleIdentifier == self.context.bundleIdentifier else { + let bundleId = customBundleId ?? app.bundleIdentifier + guard bundleId == self.context.bundleIdentifier else { throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app) } } From 255db2bac08193b95cdb840919c15380dc27ce4b Mon Sep 17 00:00:00 2001 From: mahee96 <47920326+mahee96@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:28:48 +0530 Subject: [PATCH 5/6] fix: cached app after download was not respecting overriden appId/bundleId --- AltStore/Managing Apps/AppManager.swift | 3 ++- AltStore/Operations/DownloadAppOperation.swift | 4 ++-- AltStoreCore/Protocols/AppProtocol.swift | 7 +++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index a4c9cd80..d22b6e57 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -1320,7 +1320,8 @@ private extension AppManager if cacheApp { - try FileManager.default.copyItem(at: app.fileURL, to: InstalledApp.fileURL(for: app), shouldReplace: true) + let updatedApp = AnyApp(from: app, bundleId: context.bundleIdentifier) + try FileManager.default.copyItem(at: app.fileURL, to: InstalledApp.fileURL(for: updatedApp), shouldReplace: true) } } catch diff --git a/AltStore/Operations/DownloadAppOperation.swift b/AltStore/Operations/DownloadAppOperation.swift index 7a1890a3..0131c43a 100644 --- a/AltStore/Operations/DownloadAppOperation.swift +++ b/AltStore/Operations/DownloadAppOperation.swift @@ -38,7 +38,7 @@ final class DownloadAppOperation: ResultOperation self.context = context self.appName = app.name - self.bundleIdentifier = app.bundleIdentifier + self.bundleIdentifier = context.bundleIdentifier self.sourceURL = app.url self.destinationURL = destinationURL @@ -77,7 +77,7 @@ final class DownloadAppOperation: ResultOperation guard let latestVersion = storeApp.latestAvailableVersion else { let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName) throw OperationError.unknown(failureReason: failureReason) - } + } // Attempt to download latest _available_ version, and fall back to older versions if necessary. appVersion = latestVersion diff --git a/AltStoreCore/Protocols/AppProtocol.swift b/AltStoreCore/Protocols/AppProtocol.swift index 98cda889..aaae28a7 100644 --- a/AltStoreCore/Protocols/AppProtocol.swift +++ b/AltStoreCore/Protocols/AppProtocol.swift @@ -32,6 +32,13 @@ public struct AnyApp: AppProtocol self.url = url self.storeApp = storeApp } + + public init(from app: AppProtocol, name: String? = nil, bundleId: String? = nil, url: URL? = nil, storeApp: StoreApp? = nil) { + self.name = name ?? app.name + self.bundleIdentifier = bundleId ?? app.bundleIdentifier + self.url = url ?? app.url + self.storeApp = storeApp ?? app.storeApp + } } extension ALTApplication: AppProtocol From 277b5b0bd472d365886d44587a71ae7da5d341dd Mon Sep 17 00:00:00 2001 From: mahee96 <47920326+mahee96@users.noreply.github.com> Date: Sat, 7 Feb 2026 07:59:39 +0530 Subject: [PATCH 6/6] settings: added new switch to allow enabling appId customization (which will be shown during install) --- AltStore/Managing Apps/AppManager.swift | 3 +- AltStore/Settings/Settings.storyboard | 164 +++++++++++------- .../Settings/SettingsViewController.swift | 12 +- .../Extensions/UserDefaults+AltStore.swift | 2 + 4 files changed, 114 insertions(+), 67 deletions(-) diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index d22b6e57..0ebf5542 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -708,7 +708,8 @@ extension AppManager Task{ var app: AppProtocol = app // ---- Preflight bundle ID resolution ---- - if let presentingViewController { + if UserDefaults.shared.customizeAppId, // only show prompt when enabled by user + let presentingViewController { let originalBundleID = app.bundleIdentifier let resolution = await self.resolveBundleID( diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard index 0bb4abcc..fafa2eee 100644 --- a/AltStore/Settings/Settings.storyboard +++ b/AltStore/Settings/Settings.storyboard @@ -1,9 +1,9 @@ - + - + @@ -22,7 +22,7 @@ - +