diff --git a/AltServer/DeveloperDiskManager.swift b/AltServer/DeveloperDiskManager.swift index d4f155cb..3830c912 100644 --- a/AltServer/DeveloperDiskManager.swift +++ b/AltServer/DeveloperDiskManager.swift @@ -86,34 +86,32 @@ class DeveloperDiskManager func downloadDeveloperDisk(for device: ALTDevice, completionHandler: @escaping (Result<(URL, URL), Error>) -> Void) { - let osVersion = "\(device.osVersion.majorVersion).\(device.osVersion.minorVersion)" - - let osName: String - let osKeyPath: KeyPath - - switch device.type - { - case .iphone, .ipad: - osName = "iOS" - osKeyPath = \FetchURLsResponse.Disks.iOS - - case .appletv: - osName = "tvOS" - osKeyPath = \FetchURLsResponse.Disks.tvOS - - default: return completionHandler(.failure(DeveloperDiskError.unsupportedOperatingSystem)) - } - do { + guard let osName = device.type.osName else { throw DeveloperDiskError.unsupportedOperatingSystem } + + let osKeyPath: KeyPath + switch device.type + { + case .iphone, .ipad: osKeyPath = \FetchURLsResponse.Disks.iOS + case .appletv: osKeyPath = \FetchURLsResponse.Disks.tvOS + default: throw DeveloperDiskError.unsupportedOperatingSystem + } + + var osVersion = device.osVersion + osVersion.patchVersion = 0 // Patch is irrelevant for developer disks + let osDirectoryURL = FileManager.default.developerDisksDirectory.appendingPathComponent(osName) - let developerDiskDirectoryURL = osDirectoryURL.appendingPathComponent(osVersion, isDirectory: true) + let developerDiskDirectoryURL = osDirectoryURL.appendingPathComponent(osVersion.stringValue, isDirectory: true) try FileManager.default.createDirectory(at: developerDiskDirectoryURL, withIntermediateDirectories: true, attributes: nil) let developerDiskURL = developerDiskDirectoryURL.appendingPathComponent("DeveloperDiskImage.dmg") let developerDiskSignatureURL = developerDiskDirectoryURL.appendingPathComponent("DeveloperDiskImage.dmg.signature") - guard !FileManager.default.fileExists(atPath: developerDiskURL.path) || !FileManager.default.fileExists(atPath: developerDiskSignatureURL.path) else { + let isCachedDiskCompatible = self.isDeveloperDiskCompatible(with: device) + if isCachedDiskCompatible && FileManager.default.fileExists(atPath: developerDiskURL.path) && FileManager.default.fileExists(atPath: developerDiskSignatureURL.path) + { + // The developer disk is cached and we've confirmed it works, so re-use it. return completionHandler(.success((developerDiskURL, developerDiskSignatureURL))) } @@ -122,6 +120,17 @@ class DeveloperDiskManager do { let (diskFileURL, signatureFileURL) = try result.get() + + if FileManager.default.fileExists(atPath: developerDiskURL.path) + { + try FileManager.default.removeItem(at: developerDiskURL) + } + + if FileManager.default.fileExists(atPath: developerDiskSignatureURL.path) + { + try FileManager.default.removeItem(at: developerDiskSignatureURL) + } + try FileManager.default.copyItem(at: diskFileURL, to: developerDiskURL) try FileManager.default.copyItem(at: signatureFileURL, to: developerDiskSignatureURL) @@ -137,7 +146,7 @@ class DeveloperDiskManager do { let developerDiskURLs = try result.get() - guard let diskURL = developerDiskURLs[keyPath: osKeyPath]?[osVersion] else { throw DeveloperDiskError.unknownDownloadURL } + guard let diskURL = developerDiskURLs[keyPath: osKeyPath]?[osVersion.stringValue] else { throw DeveloperDiskError.unknownDownloadURL } switch diskURL { @@ -156,10 +165,35 @@ class DeveloperDiskManager completionHandler(.failure(error)) } } + + func setDeveloperDiskCompatible(_ isCompatible: Bool, with device: ALTDevice) + { + guard let id = self.developerDiskCompatibilityID(for: device) else { return } + UserDefaults.standard.set(isCompatible, forKey: id) + } + + func isDeveloperDiskCompatible(with device: ALTDevice) -> Bool + { + guard let id = self.developerDiskCompatibilityID(for: device) else { return false } + + let isCompatible = UserDefaults.standard.bool(forKey: id) + return isCompatible + } } private extension DeveloperDiskManager { + func developerDiskCompatibilityID(for device: ALTDevice) -> String? + { + guard let osName = device.type.osName else { return nil } + + var osVersion = device.osVersion + osVersion.patchVersion = 0 // Patch is irrelevant for developer disks + + let id = ["ALTDeveloperDiskCompatible", osName, device.osVersion.stringValue].joined(separator: "_") + return id + } + func fetchDeveloperDiskURLs(completionHandler: @escaping (Result) -> Void) { let dataTask = self.session.dataTask(with: .developerDiskDownloadURLs) { (data, response, error) in diff --git a/AltServer/Devices/ALTDeviceManager+Installation.swift b/AltServer/Devices/ALTDeviceManager+Installation.swift index 4ec2b176..064c35d0 100644 --- a/AltServer/Devices/ALTDeviceManager+Installation.swift +++ b/AltServer/Devices/ALTDeviceManager+Installation.swift @@ -217,8 +217,17 @@ extension ALTDeviceManager ALTDeviceManager.shared.installDeveloperDiskImage(at: diskFileURL, signatureURL: signatureFileURL, to: device) { (success, error) in switch Result(success, error) { - case .failure(let error): completionHandler(.failure(error)) - case .success: completionHandler(.success(())) + case .failure(let error as ALTServerError) where error.code == .incompatibleDeveloperDisk: + developerDiskManager.setDeveloperDiskCompatible(false, with: device) + completionHandler(.failure(error)) + + case .failure(let error): + // Don't mark developer disk as incompatible because it probably failed for a different reason. + completionHandler(.failure(error)) + + case .success: + developerDiskManager.setDeveloperDiskCompatible(true, with: device) + completionHandler(.success(())) } } } diff --git a/AltServer/Devices/ALTDeviceManager.mm b/AltServer/Devices/ALTDeviceManager.mm index 9899f9c6..a1fdc188 100644 --- a/AltServer/Devices/ALTDeviceManager.mm +++ b/AltServer/Devices/ALTDeviceManager.mm @@ -1204,7 +1204,38 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT plist_free(result); } - finish(nil); + // Verify the installed developer disk is compatible with altDevice's operating system version. + ALTDebugConnection *testConnection = [[ALTDebugConnection alloc] initWithDevice:altDevice]; + [testConnection connectWithCompletionHandler:^(BOOL success, NSError * _Nullable error) { + [testConnection disconnect]; + + if (success) + { + // Connection succeeded, so we assume the developer disk is compatible. + finish(nil); + } + else if ([error.domain isEqualToString:AltServerConnectionErrorDomain] && error.code == ALTServerConnectionErrorUnknown) + { + // Connection failed with .unknown error code, so we assume the developer disk is NOT compatible. + NSMutableDictionary *userInfo = [@{ + ALTOperatingSystemVersionErrorKey: NSStringFromOperatingSystemVersion(altDevice.osVersion), + NSUnderlyingErrorKey: error, + } mutableCopy]; + + NSString *osName = ALTOperatingSystemNameForDeviceType(altDevice.type); + if (osName != nil) + { + userInfo[ALTOperatingSystemNameErrorKey] = osName; + } + + NSError *returnError = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorIncompatibleDeveloperDisk userInfo:userInfo]; + finish(returnError); + } + else + { + finish(error); + } + }]; }); } diff --git a/Dependencies/AltSign b/Dependencies/AltSign index cd5362c1..6f3ae24f 160000 --- a/Dependencies/AltSign +++ b/Dependencies/AltSign @@ -1 +1 @@ -Subproject commit cd5362c1394e2249e729dc6ab518b6f121a840ec +Subproject commit 6f3ae24ff596d72b5207aacf952778d7453771ba diff --git a/Shared/Categories/NSError+ALTServerError.h b/Shared/Categories/NSError+ALTServerError.h index 615e9382..4efa8105 100644 --- a/Shared/Categories/NSError+ALTServerError.h +++ b/Shared/Categories/NSError+ALTServerError.h @@ -17,6 +17,8 @@ extern NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey; extern NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey; extern NSErrorUserInfoKey const ALTAppNameErrorKey; extern NSErrorUserInfoKey const ALTDeviceNameErrorKey; +extern NSErrorUserInfoKey const ALTOperatingSystemNameErrorKey; +extern NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey; typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError) { @@ -48,6 +50,7 @@ typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError) ALTServerErrorAppDeletionFailed = 16, ALTServerErrorRequestedAppNotRunning = 100, + ALTServerErrorIncompatibleDeveloperDisk = 101 }; typedef NS_ERROR_ENUM(AltServerConnectionErrorDomain, ALTServerConnectionError) diff --git a/Shared/Categories/NSError+ALTServerError.m b/Shared/Categories/NSError+ALTServerError.m index 0ce58d68..de6ec319 100644 --- a/Shared/Categories/NSError+ALTServerError.m +++ b/Shared/Categories/NSError+ALTServerError.m @@ -17,6 +17,8 @@ NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode" NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey = @"bundleIdentifier"; NSErrorUserInfoKey const ALTAppNameErrorKey = @"appName"; NSErrorUserInfoKey const ALTDeviceNameErrorKey = @"deviceName"; +NSErrorUserInfoKey const ALTOperatingSystemNameErrorKey = @"ALTOperatingSystemName"; +NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSystemVersion"; @implementation NSError (ALTServerError) @@ -131,6 +133,13 @@ NSErrorUserInfoKey const ALTDeviceNameErrorKey = @"deviceName"; NSString *deviceName = self.userInfo[ALTDeviceNameErrorKey] ?: NSLocalizedString(@"the device", @""); return [NSString stringWithFormat:NSLocalizedString(@"%@ is not currently running on %@.", ""), appName, deviceName]; } + + case ALTServerErrorIncompatibleDeveloperDisk: + { + NSString *osVersion = [self altserver_osVersion] ?: NSLocalizedString(@"this device's OS version", @""); + NSString *failureReason = [NSString stringWithFormat:NSLocalizedString(@"The disk is incompatible with %@.", @""), osVersion]; + return failureReason; + } } } @@ -180,6 +189,19 @@ NSErrorUserInfoKey const ALTDeviceNameErrorKey = @"deviceName"; return localizedDescription; } +- (nullable NSString *)altserver_osVersion +{ + NSString *osName = self.userInfo[ALTOperatingSystemNameErrorKey]; + NSString *versionString = self.userInfo[ALTOperatingSystemVersionErrorKey]; + if (osName == nil || versionString == nil) + { + return nil; + } + + NSString *osVersion = [NSString stringWithFormat:@"%@ %@", osName, versionString]; + return osVersion; +} + #pragma mark - AltServerConnectionErrorDomain - - (nullable NSString *)altserver_connection_localizedDescription