diff --git a/AltKit/NSError+ALTServerError.h b/AltKit/NSError+ALTServerError.h index a63c6905..0fcf48d3 100644 --- a/AltKit/NSError+ALTServerError.h +++ b/AltKit/NSError+ALTServerError.h @@ -39,7 +39,9 @@ typedef NS_ERROR_ENUM(AltServerErrorDomain, ALTServerError) ALTServerErrorInvalidAnisetteData = 13, ALTServerErrorPluginNotFound = 14, - ALTServerErrorProfileNotFound = 15 + ALTServerErrorProfileNotFound = 15, + + ALTServerErrorAppDeletionFailed = 16, }; NS_ASSUME_NONNULL_BEGIN diff --git a/AltKit/NSError+ALTServerError.m b/AltKit/NSError+ALTServerError.m index 03cdd072..c82da672 100644 --- a/AltKit/NSError+ALTServerError.m +++ b/AltKit/NSError+ALTServerError.m @@ -95,6 +95,9 @@ NSErrorUserInfoKey const ALTProvisioningProfileBundleIDErrorKey = @"bundleIdenti case ALTServerErrorProfileNotFound: return [self profileErrorLocalizedDescriptionWithBaseDescription:NSLocalizedString(@"Could not find profile", "")]; + + case ALTServerErrorAppDeletionFailed: + return NSLocalizedString(@"An error occured while removing the app.", @""); } } diff --git a/AltKit/ServerProtocol.swift b/AltKit/ServerProtocol.swift index 8ff666d3..a386eb42 100644 --- a/AltKit/ServerProtocol.swift +++ b/AltKit/ServerProtocol.swift @@ -24,6 +24,7 @@ public enum ServerRequest: Decodable case beginInstallation(BeginInstallationRequest) case installProvisioningProfiles(InstallProvisioningProfilesRequest) case removeProvisioningProfiles(RemoveProvisioningProfilesRequest) + case removeApp(RemoveAppRequest) case unknown(identifier: String, version: Int) var identifier: String { @@ -34,6 +35,7 @@ public enum ServerRequest: Decodable case .beginInstallation(let request): return request.identifier case .installProvisioningProfiles(let request): return request.identifier case .removeProvisioningProfiles(let request): return request.identifier + case .removeApp(let request): return request.identifier case .unknown(let identifier, _): return identifier } } @@ -46,6 +48,7 @@ public enum ServerRequest: Decodable case .beginInstallation(let request): return request.version case .installProvisioningProfiles(let request): return request.version case .removeProvisioningProfiles(let request): return request.version + case .removeApp(let request): return request.version case .unknown(_, let version): return version } } @@ -85,6 +88,10 @@ public enum ServerRequest: Decodable let request = try RemoveProvisioningProfilesRequest(from: decoder) self = .removeProvisioningProfiles(request) + case "RemoveAppRequest": + let request = try RemoveAppRequest(from: decoder) + self = .removeApp(request) + default: self = .unknown(identifier: identifier, version: version) } @@ -97,6 +104,7 @@ public enum ServerResponse: Decodable case installationProgress(InstallationProgressResponse) case installProvisioningProfiles(InstallProvisioningProfilesResponse) case removeProvisioningProfiles(RemoveProvisioningProfilesResponse) + case removeApp(RemoveAppResponse) case error(ErrorResponse) case unknown(identifier: String, version: Int) @@ -107,6 +115,7 @@ public enum ServerResponse: Decodable case .installationProgress(let response): return response.identifier case .installProvisioningProfiles(let response): return response.identifier case .removeProvisioningProfiles(let response): return response.identifier + case .removeApp(let response): return response.identifier case .error(let response): return response.identifier case .unknown(let identifier, _): return identifier } @@ -119,6 +128,7 @@ public enum ServerResponse: Decodable case .installationProgress(let response): return response.version case .installProvisioningProfiles(let response): return response.version case .removeProvisioningProfiles(let response): return response.version + case .removeApp(let response): return response.version case .error(let response): return response.version case .unknown(_, let version): return version } @@ -155,6 +165,10 @@ public enum ServerResponse: Decodable let response = try RemoveProvisioningProfilesResponse(from: decoder) self = .removeProvisioningProfiles(response) + case "RemoveAppResponse": + let response = try RemoveAppResponse(from: decoder) + self = .removeApp(response) + case "ErrorResponse": let response = try ErrorResponse(from: decoder) self = .error(response) @@ -379,3 +393,28 @@ public struct RemoveProvisioningProfilesResponse: ServerMessageProtocol { } } + +public struct RemoveAppRequest: ServerMessageProtocol +{ + public var version = 1 + public var identifier = "RemoveAppRequest" + + public var udid: String + public var bundleIdentifier: String + + public init(udid: String, bundleIdentifier: String) + { + self.udid = udid + self.bundleIdentifier = bundleIdentifier + } +} + +public struct RemoveAppResponse: ServerMessageProtocol +{ + public var version = 1 + public var identifier = "RemoveAppResponse" + + public init() + { + } +} diff --git a/AltServer/Connections/ConnectionManager.swift b/AltServer/Connections/ConnectionManager.swift index 6c5203a9..fa665e8d 100644 --- a/AltServer/Connections/ConnectionManager.swift +++ b/AltServer/Connections/ConnectionManager.swift @@ -273,6 +273,9 @@ private extension ConnectionManager case .success(.removeProvisioningProfiles(let request)): self.handleRemoveProvisioningProfilesRequest(request, for: connection) + case .success(.removeApp(let request)): + self.handleRemoveAppRequest(request, for: connection) + case .success(.unknown): let response = ErrorResponse(error: ALTServerError(.unknownRequest)) connection.send(response, shouldDisconnect: true) { (result) in @@ -485,7 +488,31 @@ private extension ConnectionManager let response = RemoveProvisioningProfilesResponse() connection.send(response, shouldDisconnect: true) { (result) in - print("Sent remove profiles error response to \(connection) with result:", result) + print("Sent remove profiles success response to \(connection) with result:", result) + } + } + } + } + + func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: ClientConnection) + { + ALTDeviceManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier, fromDeviceWithUDID: request.udid) { (success, error) in + if let error = error, !success + { + print("Failed to remove app \(request.bundleIdentifier):", error) + + let errorResponse = ErrorResponse(error: ALTServerError(error)) + connection.send(errorResponse, shouldDisconnect: true) { (result) in + print("Sent remove a[[ error response with result:", result) + } + } + else + { + print("Removed app:", request.bundleIdentifier) + + let response = RemoveAppResponse() + connection.send(response, shouldDisconnect: true) { (result) in + print("Sent remove app success response to \(connection) with result:", result) } } } diff --git a/AltServer/Devices/ALTDeviceManager.h b/AltServer/Devices/ALTDeviceManager.h index 8d27d9f3..296e57df 100644 --- a/AltServer/Devices/ALTDeviceManager.h +++ b/AltServer/Devices/ALTDeviceManager.h @@ -28,6 +28,7 @@ extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification /* App Installation */ - (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; +- (void)removeAppForBundleIdentifier:(NSString *)bundleIdentifier fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - (void)installProvisioningProfiles:(NSSet *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; - (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler; diff --git a/AltServer/Devices/ALTDeviceManager.mm b/AltServer/Devices/ALTDeviceManager.mm index 3711fb11..8055b594 100644 --- a/AltServer/Devices/ALTDeviceManager.mm +++ b/AltServer/Devices/ALTDeviceManager.mm @@ -20,6 +20,7 @@ #include void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *udid); +void ALTDeviceManagerUpdateAppDeletionStatus(plist_t command, plist_t status, void *uuid); void ALTDeviceDidChangeConnectionStatus(const idevice_event_t *event, void *user_data); NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification = @"ALTDeviceManagerDeviceDidConnectNotification"; @@ -28,6 +29,8 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT @interface ALTDeviceManager () @property (nonatomic, readonly) NSMutableDictionary *installationCompletionHandlers; +@property (nonatomic, readonly) NSMutableDictionary *deletionCompletionHandlers; + @property (nonatomic, readonly) NSMutableDictionary *installationProgress; @property (nonatomic, readonly) dispatch_queue_t installationQueue; @@ -54,8 +57,9 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT if (self) { _installationCompletionHandlers = [NSMutableDictionary dictionary]; - _installationProgress = [NSMutableDictionary dictionary]; + _deletionCompletionHandlers = [NSMutableDictionary dictionary]; + _installationProgress = [NSMutableDictionary dictionary]; _installationQueue = dispatch_queue_create("com.rileytestut.AltServer.InstallationQueue", DISPATCH_QUEUE_SERIAL); _cachedDevices = [NSMutableSet set]; @@ -498,6 +502,87 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT return success; } +- (void)removeAppForBundleIdentifier:(NSString *)bundleIdentifier fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler +{ + __block idevice_t device = NULL; + __block lockdownd_client_t client = NULL; + __block instproxy_client_t ipc = NULL; + __block lockdownd_service_descriptor_t service = NULL; + + void (^finish)(NSError *error) = ^(NSError *e) { + __block NSError *error = e; + + lockdownd_service_descriptor_free(service); + instproxy_client_free(ipc); + lockdownd_client_free(client); + idevice_free(device); + + if (error != nil) + { + completionHandler(NO, error); + } + else + { + completionHandler(YES, nil); + } + }; + + /* Find Device */ + if (idevice_new(&device, udid.UTF8String) != IDEVICE_E_SUCCESS) + { + return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorDeviceNotFound userInfo:nil]); + } + + /* Connect to Device */ + if (lockdownd_client_new_with_handshake(device, &client, "altserver") != LOCKDOWN_E_SUCCESS) + { + return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); + } + + /* Connect to Installation Proxy */ + if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service) != LOCKDOWN_E_SUCCESS) || service == NULL) + { + return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); + } + + if (instproxy_client_new(device, service, &ipc) != INSTPROXY_E_SUCCESS) + { + return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorConnectionFailed userInfo:nil]); + } + + if (service) + { + lockdownd_service_descriptor_free(service); + service = NULL; + } + + NSUUID *UUID = [NSUUID UUID]; + __block char *uuidString = (char *)malloc(UUID.UUIDString.length + 1); + strncpy(uuidString, (const char *)UUID.UUIDString.UTF8String, UUID.UUIDString.length); + uuidString[UUID.UUIDString.length] = '\0'; + + self.deletionCompletionHandlers[UUID] = ^(NSError *error) { + if (error != nil) + { + NSString *localizedFailure = [NSString stringWithFormat:NSLocalizedString(@"Could not remove “%@”.", @""), bundleIdentifier]; + + NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; + userInfo[NSLocalizedFailureErrorKey] = localizedFailure; + + NSError *localizedError = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; + finish(localizedError); + } + else + { + finish(nil); + } + + free(uuidString); + }; + + instproxy_uninstall(ipc, bundleIdentifier.UTF8String, NULL, ALTDeviceManagerUpdateAppDeletionStatus, uuidString); +} + #pragma mark - Provisioning Profiles - - (void)installProvisioningProfiles:(NSSet *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *error))completionHandler @@ -1117,6 +1202,43 @@ void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid) } } +void ALTDeviceManagerUpdateAppDeletionStatus(plist_t command, plist_t status, void *uuid) +{ + NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:[NSString stringWithUTF8String:(const char *)uuid]]; + + char *statusName = NULL; + instproxy_status_get_name(status, &statusName); + + char *errorName = NULL; + char *errorDescription = NULL; + uint64_t code = 0; + instproxy_status_get_error(status, &errorName, &errorDescription, &code); + + if ([@(statusName) isEqualToString:@"Complete"] || code != 0 || errorName != NULL) + { + void (^completionHandler)(NSError *) = ALTDeviceManager.sharedManager.deletionCompletionHandlers[UUID]; + if (completionHandler != nil) + { + if (code != 0 || errorName != NULL) + { + NSLog(@"Error removing app. %@ (%@). %@", @(code), @(errorName ?: ""), @(errorDescription ?: "")); + + NSError *underlyingError = [NSError errorWithDomain:AltServerInstallationErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(errorDescription ?: "")}]; + NSError *error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorAppDeletionFailed userInfo:@{NSUnderlyingErrorKey: underlyingError}]; + + completionHandler(error); + } + else + { + NSLog(@"Finished removing app!"); + completionHandler(nil); + } + + ALTDeviceManager.sharedManager.deletionCompletionHandlers[UUID] = nil; + } + } +} + void ALTDeviceDidChangeConnectionStatus(const idevice_event_t *event, void *user_data) { ALTDevice * (^deviceForUDID)(NSString *, NSArray *) = ^ALTDevice *(NSString *udid, NSArray *devices) {