[AltServer] Ignores incompatible cached developer disks

Fixes issue where AltServer would always use cached developer disk, even if it isn’t compatible with the device’s operating system version.
This commit is contained in:
Riley Testut
2022-03-01 16:03:03 -08:00
parent 23516d0466
commit 37b00d670b
6 changed files with 124 additions and 25 deletions

View File

@@ -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<FetchURLsResponse.Disks, [String: DeveloperDiskURL]?>
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<FetchURLsResponse.Disks, [String: DeveloperDiskURL]?>
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<FetchURLsResponse.Disks, Error>) -> Void)
{
let dataTask = self.session.dataTask(with: .developerDiskDownloadURLs) { (data, response, error) in

View File

@@ -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(()))
}
}
}

View File

@@ -1204,7 +1204,38 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
plist_free(result);
}
// 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);
}
}];
});
}

View File

@@ -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)

View File

@@ -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