diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 3650ec29..00000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-# These are supported funding model platforms
-
-github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
-patreon: rileytestut
-open_collective: # Replace with a single Open Collective username
-ko_fi: # Replace with a single Ko-fi username
-tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
-community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
-liberapay: # Replace with a single Liberapay username
-issuehunt: # Replace with a single IssueHunt username
-otechie: # Replace with a single Otechie username
-custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 289e4d62..00000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: bug
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**To Reproduce**
-Steps to reproduce the behavior:
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
-
-**Desktop (please complete the following information if applicable):**
- - OS: [Mac or Windows]
- - Version: [e.g. Catalina]
-
-**iPhone (please complete the following information):**
- - Device: [e.g. iPhone8]
- - iOS: [e.g. 13.1]
-
-**Additional context and logs**
-Add any error logs or any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 6665601b..00000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: ''
-labels: enhancement
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index 675e77fc..00000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-name: Post Commit to Discord
-
-on:
- push:
- branches:
- - master
- - develop
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- steps:
- - name: Discord notification
- env:
- DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
- DISCORD_USERNAME: AltBot
- uses: Ilshidur/action-discord@c7b60ec
diff --git a/AltBackup/AltBackup.entitlements b/AltBackup/AltBackup.entitlements
new file mode 100644
index 00000000..099f1e39
--- /dev/null
+++ b/AltBackup/AltBackup.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.application-groups
+
+ group.com.rileytestut.AltStore
+
+
+
diff --git a/AltBackup/AppDelegate.swift b/AltBackup/AppDelegate.swift
new file mode 100644
index 00000000..5d8a21a7
--- /dev/null
+++ b/AltBackup/AppDelegate.swift
@@ -0,0 +1,121 @@
+//
+// AppDelegate.swift
+// AltBackup
+//
+// Created by Riley Testut on 5/11/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import UIKit
+
+extension AppDelegate
+{
+ static let startBackupNotification = Notification.Name("io.altstore.StartBackup")
+ static let startRestoreNotification = Notification.Name("io.altstore.StartRestore")
+
+ static let operationDidFinishNotification = Notification.Name("io.altstore.BackupOperationFinished")
+
+ static let operationResultKey = "result"
+}
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+ private var currentBackupReturnURL: URL?
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
+ {
+ // Override point for customization after application launch.
+
+ NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.operationDidFinish(_:)), name: AppDelegate.operationDidFinishNotification, object: nil)
+
+ let viewController = ViewController()
+
+ self.window = UIWindow(frame: UIScreen.main.bounds)
+ self.window?.rootViewController = viewController
+ self.window?.makeKeyAndVisible()
+
+ return true
+ }
+
+ func applicationWillResignActive(_ application: UIApplication) {
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
+ }
+
+ func applicationDidBecomeActive(_ application: UIApplication) {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+ }
+
+ func applicationWillTerminate(_ application: UIApplication) {
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+ }
+
+ func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
+ {
+ return self.open(url)
+ }
+}
+
+private extension AppDelegate
+{
+ func open(_ url: URL) -> Bool
+ {
+ guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false }
+ guard let command = components.host?.lowercased() else { return false }
+
+ switch command
+ {
+ case "backup":
+ guard let returnString = components.queryItems?.first(where: { $0.name == "returnURL" })?.value, let returnURL = URL(string: returnString) else { return false }
+ self.currentBackupReturnURL = returnURL
+ NotificationCenter.default.post(name: AppDelegate.startBackupNotification, object: nil)
+
+ return true
+
+ case "restore":
+ guard let returnString = components.queryItems?.first(where: { $0.name == "returnURL" })?.value, let returnURL = URL(string: returnString) else { return false }
+ self.currentBackupReturnURL = returnURL
+ NotificationCenter.default.post(name: AppDelegate.startRestoreNotification, object: nil)
+
+ return true
+
+ default: return false
+ }
+ }
+
+ @objc func operationDidFinish(_ notification: Notification)
+ {
+ defer { self.currentBackupReturnURL = nil }
+
+ guard
+ let returnURL = self.currentBackupReturnURL,
+ let result = notification.userInfo?[AppDelegate.operationResultKey] as? Result
+ else { return }
+
+ guard var components = URLComponents(url: returnURL, resolvingAgainstBaseURL: false) else { return }
+
+ switch result
+ {
+ case .success:
+ components.path = "/success"
+
+ case .failure(let error as NSError):
+ components.path = "/failure"
+ components.queryItems = ["errorDomain": error.domain,
+ "errorCode": String(error.code),
+ "errorDescription": error.localizedDescription].map { URLQueryItem(name: $0, value: $1) }
+ }
+
+ guard let responseURL = components.url else { return }
+
+ DispatchQueue.main.async {
+ UIApplication.shared.open(responseURL, options: [:]) { (success) in
+ print("Sent response to app with success:", success)
+ }
+ }
+ }
+}
+
diff --git a/AltBackup/Assets.xcassets/AppIcon.appiconset/Contents.json b/AltBackup/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..9221b9bb
--- /dev/null
+++ b/AltBackup/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AltBackup/Assets.xcassets/Background.colorset/Contents.json b/AltBackup/Assets.xcassets/Background.colorset/Contents.json
new file mode 100644
index 00000000..8251d696
--- /dev/null
+++ b/AltBackup/Assets.xcassets/Background.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.518",
+ "green" : "0.502",
+ "red" : "0.004"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.404",
+ "green" : "0.322",
+ "red" : "0.008"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AltBackup/Assets.xcassets/Contents.json b/AltBackup/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/AltBackup/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AltBackup/Assets.xcassets/Text.colorset/Contents.json b/AltBackup/Assets.xcassets/Text.colorset/Contents.json
new file mode 100644
index 00000000..a004a7f8
--- /dev/null
+++ b/AltBackup/Assets.xcassets/Text.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "0.750",
+ "blue" : "0xFF",
+ "green" : "0xFF",
+ "red" : "0xFF"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/AltBackup/BackupController.swift b/AltBackup/BackupController.swift
new file mode 100644
index 00000000..4b2379ac
--- /dev/null
+++ b/AltBackup/BackupController.swift
@@ -0,0 +1,293 @@
+//
+// BackupController.swift
+// AltBackup
+//
+// Created by Riley Testut on 5/12/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+extension ErrorUserInfoKey
+{
+ static let sourceFile: String = "alt_sourceFile"
+ static let sourceFileLine: String = "alt_sourceFileLine"
+}
+
+extension Error
+{
+ var sourceDescription: String? {
+ guard let sourceFile = (self as NSError).userInfo[ErrorUserInfoKey.sourceFile] as? String, let sourceFileLine = (self as NSError).userInfo[ErrorUserInfoKey.sourceFileLine] else {
+ return nil
+ }
+ return "(\((sourceFile as NSString).lastPathComponent), Line \(sourceFileLine))"
+ }
+}
+
+struct BackupError: ALTLocalizedError
+{
+ enum Code
+ {
+ case invalidBundleID
+ case appGroupNotFound(String?)
+ case randomError // Used for debugging.
+ }
+
+ let code: Code
+
+ let sourceFile: String
+ let sourceFileLine: Int
+
+ var errorFailure: String?
+
+ var failureReason: String? {
+ switch self.code
+ {
+ case .invalidBundleID: return NSLocalizedString("The bundle identifier is invalid.", comment: "")
+ case .appGroupNotFound(let appGroup):
+ if let appGroup = appGroup
+ {
+ return String(format: NSLocalizedString("The app group “%@” could not be found.", comment: ""), appGroup)
+ }
+ else
+ {
+ return NSLocalizedString("The AltStore app group could not be found.", comment: "")
+ }
+ case .randomError: return NSLocalizedString("A random error occured.", comment: "")
+ }
+ }
+
+ var errorUserInfo: [String : Any] {
+ let userInfo: [String: Any?] = [NSLocalizedDescriptionKey: self.errorDescription,
+ NSLocalizedFailureReasonErrorKey: self.failureReason,
+ NSLocalizedFailureErrorKey: self.errorFailure,
+ ErrorUserInfoKey.sourceFile: self.sourceFile,
+ ErrorUserInfoKey.sourceFileLine: self.sourceFileLine]
+ return userInfo.compactMapValues { $0 }
+ }
+
+ init(_ code: Code, description: String? = nil, file: String = #file, line: Int = #line)
+ {
+ self.code = code
+ self.errorFailure = description
+ self.sourceFile = file
+ self.sourceFileLine = line
+ }
+}
+
+class BackupController: NSObject
+{
+ private let fileCoordinator = NSFileCoordinator(filePresenter: nil)
+ private let operationQueue = OperationQueue()
+
+ override init()
+ {
+ self.operationQueue.name = "AltBackup-BackupQueue"
+ }
+
+ func performBackup(completionHandler: @escaping (Result) -> Void)
+ {
+ do
+ {
+ guard let bundleIdentifier = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.altBundleID) as? String else {
+ throw BackupError(.invalidBundleID, description: NSLocalizedString("Unable to create backup directory.", comment: ""))
+ }
+
+ guard
+ let altstoreAppGroup = Bundle.main.altstoreAppGroup,
+ let sharedDirectoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: altstoreAppGroup)
+ else { throw BackupError(.appGroupNotFound(nil), description: NSLocalizedString("Unable to create backup directory.", comment: "")) }
+
+ let backupsDirectory = sharedDirectoryURL.appendingPathComponent("Backups")
+
+ // Use temporary directory to prevent messing up successful backup with incomplete one.
+ let temporaryAppBackupDirectory = backupsDirectory.appendingPathComponent("Temp", isDirectory: true).appendingPathComponent(UUID().uuidString)
+ let appBackupDirectory = backupsDirectory.appendingPathComponent(bundleIdentifier)
+
+ let writingIntent = NSFileAccessIntent.writingIntent(with: temporaryAppBackupDirectory, options: [])
+ let replacementIntent = NSFileAccessIntent.writingIntent(with: appBackupDirectory, options: [.forReplacing])
+ self.fileCoordinator.coordinate(with: [writingIntent, replacementIntent], queue: self.operationQueue) { (error) in
+ do
+ {
+ if let error = error
+ {
+ throw error
+ }
+
+ do
+ {
+ let mainGroupBackupDirectory = temporaryAppBackupDirectory.appendingPathComponent("App")
+ try FileManager.default.createDirectory(at: mainGroupBackupDirectory, withIntermediateDirectories: true, attributes: nil)
+
+ let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
+ let backupDocumentsDirectory = mainGroupBackupDirectory.appendingPathComponent(documentsDirectory.lastPathComponent)
+
+ if FileManager.default.fileExists(atPath: backupDocumentsDirectory.path)
+ {
+ try FileManager.default.removeItem(at: backupDocumentsDirectory)
+ }
+
+ if FileManager.default.fileExists(atPath: documentsDirectory.path)
+ {
+ try FileManager.default.copyItem(at: documentsDirectory, to: backupDocumentsDirectory)
+ }
+
+ print("Copied Documents directory from \(documentsDirectory) to \(backupDocumentsDirectory)")
+
+ let libraryDirectory = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
+ let backupLibraryDirectory = mainGroupBackupDirectory.appendingPathComponent(libraryDirectory.lastPathComponent)
+
+ if FileManager.default.fileExists(atPath: backupLibraryDirectory.path)
+ {
+ try FileManager.default.removeItem(at: backupLibraryDirectory)
+ }
+
+ if FileManager.default.fileExists(atPath: libraryDirectory.path)
+ {
+ try FileManager.default.copyItem(at: libraryDirectory, to: backupLibraryDirectory)
+ }
+
+ print("Copied Library directory from \(libraryDirectory) to \(backupLibraryDirectory)")
+ }
+
+ for appGroup in Bundle.main.appGroups where appGroup != altstoreAppGroup
+ {
+ guard let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
+ throw BackupError(.appGroupNotFound(appGroup), description: NSLocalizedString("Unable to create app group backup directory.", comment: ""))
+ }
+
+ let backupAppGroupURL = temporaryAppBackupDirectory.appendingPathComponent(appGroup)
+
+ // There are several system hidden files that we don't have permission to read, so we just skip all hidden files in app group directories.
+ try self.copyDirectoryContents(at: appGroupURL, to: backupAppGroupURL, options: [.skipsHiddenFiles])
+ }
+
+ // Replace previous backup with new backup.
+ _ = try FileManager.default.replaceItemAt(appBackupDirectory, withItemAt: temporaryAppBackupDirectory)
+
+ print("Replaced previous backup with new backup:", temporaryAppBackupDirectory)
+
+ completionHandler(.success(()))
+ }
+ catch
+ {
+ do { try FileManager.default.removeItem(at: temporaryAppBackupDirectory) }
+ catch { print("Failed to remove temporary directory.", error) }
+
+ completionHandler(.failure(error))
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+
+ func restoreBackup(completionHandler: @escaping (Result) -> Void)
+ {
+ do
+ {
+ guard let bundleIdentifier = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.altBundleID) as? String else {
+ throw BackupError(.invalidBundleID, description: NSLocalizedString("Unable to access backup.", comment: ""))
+ }
+
+ guard
+ let altstoreAppGroup = Bundle.main.altstoreAppGroup,
+ let sharedDirectoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: altstoreAppGroup)
+ else { throw BackupError(.appGroupNotFound(nil), description: NSLocalizedString("Unable to access backup.", comment: "")) }
+
+ let backupsDirectory = sharedDirectoryURL.appendingPathComponent("Backups")
+ let appBackupDirectory = backupsDirectory.appendingPathComponent(bundleIdentifier)
+
+ let readingIntent = NSFileAccessIntent.readingIntent(with: appBackupDirectory, options: [])
+ self.fileCoordinator.coordinate(with: [readingIntent], queue: self.operationQueue) { (error) in
+ do
+ {
+ if let error = error
+ {
+ throw error
+ }
+
+ let mainGroupBackupDirectory = appBackupDirectory.appendingPathComponent("App")
+
+ let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
+ let backupDocumentsDirectory = mainGroupBackupDirectory.appendingPathComponent(documentsDirectory.lastPathComponent)
+
+ let libraryDirectory = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
+ let backupLibraryDirectory = mainGroupBackupDirectory.appendingPathComponent(libraryDirectory.lastPathComponent)
+
+ try self.copyDirectoryContents(at: backupDocumentsDirectory, to: documentsDirectory)
+ try self.copyDirectoryContents(at: backupLibraryDirectory, to: libraryDirectory)
+
+ for appGroup in Bundle.main.appGroups where appGroup != altstoreAppGroup
+ {
+ guard let appGroupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
+ throw BackupError(.appGroupNotFound(appGroup), description: NSLocalizedString("Unable to read app group backup.", comment: ""))
+ }
+
+ let backupAppGroupURL = appBackupDirectory.appendingPathComponent(appGroup)
+ try self.copyDirectoryContents(at: backupAppGroupURL, to: appGroupURL)
+ }
+
+ completionHandler(.success(()))
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+}
+
+private extension BackupController
+{
+ func copyDirectoryContents(at sourceDirectoryURL: URL, to destinationDirectoryURL: URL, options: FileManager.DirectoryEnumerationOptions = []) throws
+ {
+ guard FileManager.default.fileExists(atPath: sourceDirectoryURL.path) else { return }
+
+ if !FileManager.default.fileExists(atPath: destinationDirectoryURL.path)
+ {
+ try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)
+ }
+
+ for fileURL in try FileManager.default.contentsOfDirectory(at: sourceDirectoryURL, includingPropertiesForKeys: [.isDirectoryKey], options: options)
+ {
+ let isDirectory = try fileURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false
+ let destinationURL = destinationDirectoryURL.appendingPathComponent(fileURL.lastPathComponent)
+
+ if FileManager.default.fileExists(atPath: destinationURL.path)
+ {
+ do {
+ try FileManager.default.removeItem(at: destinationURL)
+ }
+ catch CocoaError.fileWriteNoPermission where isDirectory {
+ try self.copyDirectoryContents(at: fileURL, to: destinationURL, options: options)
+ continue
+ }
+ catch {
+ print(error)
+ throw error
+ }
+ }
+
+ do {
+ try FileManager.default.copyItem(at: fileURL, to: destinationURL)
+ print("Copied item from \(fileURL) to \(destinationURL)")
+ }
+ catch let error where fileURL.lastPathComponent == "Inbox" && fileURL.deletingLastPathComponent().lastPathComponent == "Documents" {
+ // Ignore errors for /Documents/Inbox
+ print("Failed to copy Inbox directory:", error)
+ }
+ catch {
+ print(error)
+ throw error
+ }
+ }
+ }
+}
diff --git a/AltBackup/Base.lproj/LaunchScreen.storyboard b/AltBackup/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..61e8dd4c
--- /dev/null
+++ b/AltBackup/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AltBackup/Info.plist b/AltBackup/Info.plist
new file mode 100644
index 00000000..63d01c06
--- /dev/null
+++ b/AltBackup/Info.plist
@@ -0,0 +1,66 @@
+
+
+
+
+ ALTAppGroups
+
+ group.com.rileytestut.AltStore
+
+ ALTBundleIdentifier
+ com.rileytestut.AltBackup
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleURLTypes
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLName
+ AltBackup General
+ CFBundleURLSchemes
+
+ altbackup
+
+
+
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UIStatusBarStyle
+ UIStatusBarStyleLightContent
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportsDocumentBrowser
+
+
+
diff --git a/AltBackup/UIColor+AltBackup.swift b/AltBackup/UIColor+AltBackup.swift
new file mode 100644
index 00000000..73c7ceeb
--- /dev/null
+++ b/AltBackup/UIColor+AltBackup.swift
@@ -0,0 +1,15 @@
+//
+// UIColor+AltBackup.swift
+// AltBackup
+//
+// Created by Riley Testut on 5/11/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import UIKit
+
+extension UIColor
+{
+ static let altstoreBackground = UIColor(named: "Background")!
+ static let altstoreText = UIColor(named: "Text")!
+}
diff --git a/AltBackup/ViewController.swift b/AltBackup/ViewController.swift
new file mode 100644
index 00000000..0efb7112
--- /dev/null
+++ b/AltBackup/ViewController.swift
@@ -0,0 +1,206 @@
+//
+// ViewController.swift
+// AltBackup
+//
+// Created by Riley Testut on 5/11/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import UIKit
+
+extension Bundle
+{
+ var appName: String? {
+ let appName =
+ Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
+ Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String
+ return appName
+ }
+}
+
+extension ViewController
+{
+ enum BackupOperation
+ {
+ case backup
+ case restore
+ }
+}
+
+class ViewController: UIViewController
+{
+ private let backupController = BackupController()
+
+ private var currentOperation: BackupOperation? {
+ didSet {
+ DispatchQueue.main.async {
+ self.update()
+ }
+ }
+ }
+
+ private var textLabel: UILabel!
+ private var detailTextLabel: UILabel!
+ private var activityIndicatorView: UIActivityIndicatorView!
+
+ override var preferredStatusBarStyle: UIStatusBarStyle {
+ return .lightContent
+ }
+
+ override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
+ {
+ super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+
+ NotificationCenter.default.addObserver(self, selector: #selector(ViewController.backup), name: AppDelegate.startBackupNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(ViewController.restore), name: AppDelegate.startRestoreNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(ViewController.didEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError()
+ }
+
+ override func viewDidLoad()
+ {
+ super.viewDidLoad()
+
+ self.view.backgroundColor = .altstoreBackground
+
+ self.textLabel = UILabel(frame: .zero)
+ self.textLabel.font = UIFont.preferredFont(forTextStyle: .title2)
+ self.textLabel.textColor = .altstoreText
+ self.textLabel.textAlignment = .center
+ self.textLabel.numberOfLines = 0
+
+ self.detailTextLabel = UILabel(frame: .zero)
+ self.detailTextLabel.font = UIFont.preferredFont(forTextStyle: .body)
+ self.detailTextLabel.textColor = .altstoreText
+ self.detailTextLabel.textAlignment = .center
+ self.detailTextLabel.numberOfLines = 0
+
+ self.activityIndicatorView = UIActivityIndicatorView(style: .whiteLarge)
+ self.activityIndicatorView.color = .altstoreText
+ self.activityIndicatorView.startAnimating()
+
+ #if DEBUG
+ let button1 = UIButton(type: .system)
+ button1.setTitle("Backup", for: .normal)
+ button1.setTitleColor(.white, for: .normal)
+ button1.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+ button1.addTarget(self, action: #selector(ViewController.backup), for: .primaryActionTriggered)
+
+ let button2 = UIButton(type: .system)
+ button2.setTitle("Restore", for: .normal)
+ button2.setTitleColor(.white, for: .normal)
+ button2.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
+ button2.addTarget(self, action: #selector(ViewController.restore), for: .primaryActionTriggered)
+
+ let arrangedSubviews = [self.textLabel!, self.detailTextLabel!, self.activityIndicatorView!, button1, button2]
+ #else
+ let arrangedSubviews = [self.textLabel!, self.detailTextLabel!, self.activityIndicatorView!]
+ #endif
+
+ let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.spacing = 22
+ stackView.axis = .vertical
+ stackView.alignment = .center
+ self.view.addSubview(stackView)
+
+ NSLayoutConstraint.activate([stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
+ stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
+ stackView.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: self.view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1.0),
+ self.view.safeAreaLayoutGuide.trailingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: stackView.trailingAnchor, multiplier: 1.0)])
+
+ self.update()
+ }
+}
+
+private extension ViewController
+{
+ @objc func backup()
+ {
+ self.currentOperation = .backup
+
+ self.backupController.performBackup { (result) in
+ let appName = Bundle.main.appName ?? NSLocalizedString("App", comment: "")
+
+ let title = String(format: NSLocalizedString("%@ could not be backed up.", comment: ""), appName)
+ self.process(result, errorTitle: title)
+ }
+ }
+
+ @objc func restore()
+ {
+ self.currentOperation = .restore
+
+ self.backupController.restoreBackup { (result) in
+ let appName = Bundle.main.appName ?? NSLocalizedString("App", comment: "")
+
+ let title = String(format: NSLocalizedString("%@ could not be restored.", comment: ""), appName)
+ self.process(result, errorTitle: title)
+ }
+ }
+
+ func update()
+ {
+ switch self.currentOperation
+ {
+ case .backup:
+ self.textLabel.text = NSLocalizedString("Backing up app data…", comment: "")
+ self.detailTextLabel.isHidden = true
+ self.activityIndicatorView.startAnimating()
+
+ case .restore:
+ self.textLabel.text = NSLocalizedString("Restoring app data…", comment: "")
+ self.detailTextLabel.isHidden = true
+ self.activityIndicatorView.startAnimating()
+
+ case .none:
+ self.textLabel.text = String(format: NSLocalizedString("%@ is inactive.", comment: ""),
+ Bundle.main.appName ?? NSLocalizedString("App", comment: ""))
+
+ self.detailTextLabel.text = String(format: NSLocalizedString("Refresh %@ in AltStore to continue using it.", comment: ""),
+ Bundle.main.appName ?? NSLocalizedString("this app", comment: ""))
+
+ self.detailTextLabel.isHidden = false
+ self.activityIndicatorView.stopAnimating()
+ }
+ }
+}
+
+private extension ViewController
+{
+ func process(_ result: Result, errorTitle: String)
+ {
+ DispatchQueue.main.async {
+ switch result
+ {
+ case .success: break
+ case .failure(let error as NSError):
+ let message: String
+
+ if let sourceDescription = error.sourceDescription
+ {
+ message = error.localizedDescription + "\n\n" + sourceDescription
+ }
+ else
+ {
+ message = error.localizedDescription
+ }
+
+ let alertController = UIAlertController(title: errorTitle, message: message, preferredStyle: .alert)
+ alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
+ self.present(alertController, animated: true, completion: nil)
+ }
+
+ NotificationCenter.default.post(name: AppDelegate.operationDidFinishNotification, object: nil, userInfo: [AppDelegate.operationResultKey: result])
+ }
+ }
+
+ @objc func didEnterBackground(_ notification: Notification)
+ {
+ // Reset UI once we've left app (but not before).
+ self.currentOperation = nil
+ }
+}
diff --git a/AltDaemon/AltDaemon-Bridging-Header.h b/AltDaemon/AltDaemon-Bridging-Header.h
new file mode 100644
index 00000000..94f57276
--- /dev/null
+++ b/AltDaemon/AltDaemon-Bridging-Header.h
@@ -0,0 +1,59 @@
+//
+// Use this file to import your target's public headers that you would like to expose to Swift.
+//
+
+#import
+
+// Shared
+#import "ALTConstants.h"
+#import "ALTConnection.h"
+#import "NSError+ALTServerError.h"
+#import "CFNotificationName+AltStore.h"
+
+// libproc
+int proc_pidpath(int pid, void * buffer, uint32_t buffersize);
+
+// Security.framework
+CF_ENUM(uint32_t) {
+ kSecCSInternalInformation = 1 << 0,
+ kSecCSSigningInformation = 1 << 1,
+ kSecCSRequirementInformation = 1 << 2,
+ kSecCSDynamicInformation = 1 << 3,
+ kSecCSContentInformation = 1 << 4,
+ kSecCSSkipResourceDirectory = 1 << 5,
+ kSecCSCalculateCMSDigest = 1 << 6,
+};
+
+OSStatus SecStaticCodeCreateWithPath(CFURLRef path, uint32_t flags, void ** __nonnull CF_RETURNS_RETAINED staticCode);
+OSStatus SecCodeCopySigningInformation(void *code, uint32_t flags, CFDictionaryRef * __nonnull CF_RETURNS_RETAINED information);
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface AKDevice : NSObject
+
+@property (class, readonly) AKDevice *currentDevice;
+
+@property (strong, readonly) NSString *serialNumber;
+@property (strong, readonly) NSString *uniqueDeviceIdentifier;
+@property (strong, readonly) NSString *serverFriendlyDescription;
+
+@end
+
+@interface AKAppleIDSession : NSObject
+
+- (instancetype)initWithIdentifier:(NSString *)identifier;
+
+- (NSDictionary *)appleIDHeadersForRequest:(NSURLRequest *)request;
+
+@end
+
+@interface LSApplicationWorkspace : NSObject
+
+@property (class, readonly) LSApplicationWorkspace *defaultWorkspace;
+
+- (BOOL)installApplication:(NSURL *)fileURL withOptions:(nullable NSDictionary *)options error:(NSError *_Nullable *)error;
+- (BOOL)uninstallApplication:(NSString *)bundleIdentifier withOptions:(nullable NSDictionary *)options;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/AltDaemon/AltDaemon.entitlements b/AltDaemon/AltDaemon.entitlements
new file mode 100644
index 00000000..3dfaef0b
--- /dev/null
+++ b/AltDaemon/AltDaemon.entitlements
@@ -0,0 +1,22 @@
+
+
+
+
+ application-identifier
+ 6XVY5G3U44.com.rileytestut.AltDaemon
+ get-task-allow
+
+ platform-application
+
+ com.apple.authkit.client.private
+
+ com.apple.private.mobileinstall.allowedSPI
+
+ Install
+ Uninstall
+ InstallForLaunchServices
+ UninstallForLaunchServices
+ InstallLocalProvisioned
+
+
+
diff --git a/AltDaemon/AnisetteDataManager.swift b/AltDaemon/AnisetteDataManager.swift
new file mode 100644
index 00000000..daff25d6
--- /dev/null
+++ b/AltDaemon/AnisetteDataManager.swift
@@ -0,0 +1,65 @@
+//
+// AnisetteDataManager.swift
+// AltDaemon
+//
+// Created by Riley Testut on 6/1/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+import AltSign
+
+private extension UserDefaults
+{
+ @objc var localUserID: String? {
+ get { return self.string(forKey: #keyPath(UserDefaults.localUserID)) }
+ set { self.set(newValue, forKey: #keyPath(UserDefaults.localUserID)) }
+ }
+}
+
+struct AnisetteDataManager
+{
+ static let shared = AnisetteDataManager()
+
+ private let dateFormatter = ISO8601DateFormatter()
+
+ private init()
+ {
+ dlopen("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit", RTLD_NOW);
+ }
+
+ func requestAnisetteData() throws -> ALTAnisetteData
+ {
+ var request = URLRequest(url: URL(string: "https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA")!)
+ request.httpMethod = "POST"
+
+ let akAppleIDSession = unsafeBitCast(NSClassFromString("AKAppleIDSession")!, to: AKAppleIDSession.Type.self)
+ let akDevice = unsafeBitCast(NSClassFromString("AKDevice")!, to: AKDevice.Type.self)
+
+ let session = akAppleIDSession.init(identifier: "com.apple.gs.xcode.auth")
+ let headers = session.appleIDHeaders(for: request)
+
+ let device = akDevice.current
+ let date = self.dateFormatter.date(from: headers["X-Apple-I-Client-Time"] ?? "") ?? Date()
+
+ var localUserID = UserDefaults.standard.localUserID
+ if localUserID == nil
+ {
+ localUserID = UUID().uuidString
+ UserDefaults.standard.localUserID = localUserID
+ }
+
+ let anisetteData = ALTAnisetteData(machineID: headers["X-Apple-I-MD-M"] ?? "",
+ oneTimePassword: headers["X-Apple-I-MD"] ?? "",
+ localUserID: headers["X-Apple-I-MD-LU"] ?? localUserID ?? "",
+ routingInfo: UInt64(headers["X-Apple-I-MD-RINFO"] ?? "") ?? 0,
+ deviceUniqueIdentifier: device.uniqueDeviceIdentifier,
+ deviceSerialNumber: device.serialNumber,
+ deviceDescription: " ",
+ date: date,
+ locale: .current,
+ timeZone: .current)
+ return anisetteData
+ }
+}
diff --git a/AltDaemon/AppManager.swift b/AltDaemon/AppManager.swift
new file mode 100644
index 00000000..a0408ba2
--- /dev/null
+++ b/AltDaemon/AppManager.swift
@@ -0,0 +1,138 @@
+//
+// AppManager.swift
+// AltDaemon
+//
+// Created by Riley Testut on 6/1/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+import AltSign
+
+private extension URL
+{
+ static let profilesDirectoryURL = URL(fileURLWithPath: "/var/MobileDevice/ProvisioningProfiles", isDirectory: true)
+}
+
+private extension CFNotificationName
+{
+ static let updatedProvisioningProfiles = CFNotificationName("MISProvisioningProfileRemoved" as CFString)
+}
+
+struct AppManager
+{
+ static let shared = AppManager()
+
+ private let appQueue = DispatchQueue(label: "com.rileytestut.AltDaemon.appQueue", qos: .userInitiated)
+ private let profilesQueue = OperationQueue()
+
+ private let fileCoordinator = NSFileCoordinator()
+
+ private init()
+ {
+ self.profilesQueue.name = "com.rileytestut.AltDaemon.profilesQueue"
+ self.profilesQueue.qualityOfService = .userInitiated
+ }
+
+ func installApp(at fileURL: URL, bundleIdentifier: String, activeProfiles: Set?, completionHandler: @escaping (Result) -> Void)
+ {
+ self.appQueue.async {
+ let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
+
+ let options = ["CFBundleIdentifier": bundleIdentifier, "AllowInstallLocalProvisioned": NSNumber(value: true)] as [String : Any]
+ let result = Result { try lsApplicationWorkspace.default.installApplication(fileURL, withOptions: options) }
+
+ completionHandler(result)
+ }
+ }
+
+ func removeApp(forBundleIdentifier bundleIdentifier: String, completionHandler: @escaping (Result) -> Void)
+ {
+ self.appQueue.async {
+ let lsApplicationWorkspace = unsafeBitCast(NSClassFromString("LSApplicationWorkspace")!, to: LSApplicationWorkspace.Type.self)
+ lsApplicationWorkspace.default.uninstallApplication(bundleIdentifier, withOptions: nil)
+
+ completionHandler(.success(()))
+ }
+ }
+
+ func install(_ profiles: Set, activeProfiles: Set?, completionHandler: @escaping (Result) -> Void)
+ {
+ let intent = NSFileAccessIntent.writingIntent(with: .profilesDirectoryURL, options: [])
+ self.fileCoordinator.coordinate(with: [intent], queue: self.profilesQueue) { (error) in
+ do
+ {
+ if let error = error
+ {
+ throw error
+ }
+
+ let installingBundleIDs = Set(profiles.map(\.bundleIdentifier))
+
+ let profileURLs = try FileManager.default.contentsOfDirectory(at: intent.url, includingPropertiesForKeys: nil, options: [])
+
+ // Remove all inactive profiles (if active profiles are provided), and the previous profiles.
+ for fileURL in profileURLs
+ {
+ // Use memory mapping to reduce peak memory usage and stay within limit.
+ guard let profile = try? ALTProvisioningProfile(url: fileURL, options: [.mappedIfSafe]) else { continue }
+
+ if installingBundleIDs.contains(profile.bundleIdentifier) || (activeProfiles?.contains(profile.bundleIdentifier) == false && profile.isFreeProvisioningProfile)
+ {
+ try FileManager.default.removeItem(at: fileURL)
+ }
+ else
+ {
+ print("Ignoring:", profile.bundleIdentifier, profile.uuid)
+ }
+ }
+
+ for profile in profiles
+ {
+ let destinationURL = URL.profilesDirectoryURL.appendingPathComponent(profile.uuid.uuidString.lowercased())
+ try profile.data.write(to: destinationURL, options: .atomic)
+ }
+
+ completionHandler(.success(()))
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+
+ // Notify system to prevent accidentally untrusting developer certificate.
+ CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), .updatedProvisioningProfiles, nil, nil, true)
+ }
+ }
+
+ func removeProvisioningProfiles(forBundleIdentifiers bundleIdentifiers: Set, completionHandler: @escaping (Result) -> Void)
+ {
+ let intent = NSFileAccessIntent.writingIntent(with: .profilesDirectoryURL, options: [])
+ self.fileCoordinator.coordinate(with: [intent], queue: self.profilesQueue) { (error) in
+ do
+ {
+ let profileURLs = try FileManager.default.contentsOfDirectory(at: intent.url, includingPropertiesForKeys: nil, options: [])
+
+ for fileURL in profileURLs
+ {
+ guard let profile = ALTProvisioningProfile(url: fileURL) else { continue }
+
+ if bundleIdentifiers.contains(profile.bundleIdentifier)
+ {
+ try FileManager.default.removeItem(at: fileURL)
+ }
+ }
+
+ completionHandler(.success(()))
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+
+ // Notify system to prevent accidentally untrusting developer certificate.
+ CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), .updatedProvisioningProfiles, nil, nil, true)
+ }
+ }
+}
diff --git a/AltDaemon/DaemonRequestHandler.swift b/AltDaemon/DaemonRequestHandler.swift
new file mode 100644
index 00000000..48b2d7c7
--- /dev/null
+++ b/AltDaemon/DaemonRequestHandler.swift
@@ -0,0 +1,123 @@
+//
+// DaemonRequestHandler.swift
+// AltDaemon
+//
+// Created by Riley Testut on 6/1/20.
+// Copyright © 2019 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+typealias DaemonConnectionManager = ConnectionManager
+
+private let connectionManager = ConnectionManager(requestHandler: DaemonRequestHandler(),
+ connectionHandlers: [XPCConnectionHandler()])
+
+extension DaemonConnectionManager
+{
+ static var shared: ConnectionManager {
+ return connectionManager
+ }
+}
+
+struct DaemonRequestHandler: RequestHandler
+{
+ func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void)
+ {
+ do
+ {
+ let anisetteData = try AnisetteDataManager.shared.requestAnisetteData()
+
+ let response = AnisetteDataResponse(anisetteData: anisetteData)
+ completionHandler(.success(response))
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+
+ func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void)
+ {
+ guard let fileURL = request.fileURL else { return completionHandler(.failure(ALTServerError(.invalidRequest))) }
+
+ print("Awaiting begin installation request...")
+
+ connection.receiveRequest() { (result) in
+ print("Received begin installation request with result:", result)
+
+ do
+ {
+ guard case .beginInstallation(let request) = try result.get() else { throw ALTServerError(.unknownRequest) }
+ guard let bundleIdentifier = request.bundleIdentifier else { throw ALTServerError(.invalidRequest) }
+
+ AppManager.shared.installApp(at: fileURL, bundleIdentifier: bundleIdentifier, activeProfiles: request.activeProfiles) { (result) in
+ let result = result.map { InstallationProgressResponse(progress: 1.0) }
+ print("Installed app with result:", result)
+
+ completionHandler(result)
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+
+ func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
+ completionHandler: @escaping (Result) -> Void)
+ {
+ AppManager.shared.install(request.provisioningProfiles, activeProfiles: request.activeProfiles) { (result) in
+ switch result
+ {
+ case .failure(let error):
+ print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
+ completionHandler(.failure(error))
+
+ case .success:
+ print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
+
+ let response = InstallProvisioningProfilesResponse()
+ completionHandler(.success(response))
+ }
+ }
+ }
+
+ func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
+ completionHandler: @escaping (Result) -> Void)
+ {
+ AppManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers) { (result) in
+ switch result
+ {
+ case .failure(let error):
+ print("Failed to remove profiles \(request.bundleIdentifiers):", error)
+ completionHandler(.failure(error))
+
+ case .success:
+ print("Removed profiles:", request.bundleIdentifiers)
+
+ let response = RemoveProvisioningProfilesResponse()
+ completionHandler(.success(response))
+ }
+ }
+ }
+
+ func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void)
+ {
+ AppManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier) { (result) in
+ switch result
+ {
+ case .failure(let error):
+ print("Failed to remove app \(request.bundleIdentifier):", error)
+ completionHandler(.failure(error))
+
+ case .success:
+ print("Removed app:", request.bundleIdentifier)
+
+ let response = RemoveAppResponse()
+ completionHandler(.success(response))
+ }
+ }
+ }
+}
diff --git a/AltDaemon/XPCConnectionHandler.swift b/AltDaemon/XPCConnectionHandler.swift
new file mode 100644
index 00000000..d6ee4202
--- /dev/null
+++ b/AltDaemon/XPCConnectionHandler.swift
@@ -0,0 +1,93 @@
+//
+// XPCConnectionHandler.swift
+// AltDaemon
+//
+// Created by Riley Testut on 9/14/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import Foundation
+import Security
+
+class XPCConnectionHandler: NSObject, ConnectionHandler
+{
+ var connectionHandler: ((Connection) -> Void)?
+ var disconnectionHandler: ((Connection) -> Void)?
+
+ private let dispatchQueue = DispatchQueue(label: "io.altstore.XPCConnectionListener", qos: .utility)
+ private let listeners = XPCConnection.machServiceNames.map { NSXPCListener.makeListener(machServiceName: $0) }
+
+ deinit
+ {
+ self.stopListening()
+ }
+
+ func startListening()
+ {
+ for listener in self.listeners
+ {
+ listener.delegate = self
+ listener.resume()
+ }
+ }
+
+ func stopListening()
+ {
+ self.listeners.forEach { $0.suspend() }
+ }
+}
+
+private extension XPCConnectionHandler
+{
+ func disconnect(_ connection: Connection)
+ {
+ connection.disconnect()
+
+ self.disconnectionHandler?(connection)
+ }
+}
+
+extension XPCConnectionHandler: NSXPCListenerDelegate
+{
+ func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool
+ {
+ let maximumPathLength = 4 * UInt32(MAXPATHLEN)
+
+ let pathBuffer = UnsafeMutablePointer.allocate(capacity: Int(maximumPathLength))
+ defer { pathBuffer.deallocate() }
+
+ proc_pidpath(newConnection.processIdentifier, pathBuffer, maximumPathLength)
+
+ let path = String(cString: pathBuffer)
+ let fileURL = URL(fileURLWithPath: path)
+
+ var code: UnsafeMutableRawPointer?
+ defer { code.map { Unmanaged.fromOpaque($0).release() } }
+
+ var status = SecStaticCodeCreateWithPath(fileURL as CFURL, 0, &code)
+ guard status == 0 else { return false }
+
+ var signingInfo: CFDictionary?
+ defer { signingInfo.map { Unmanaged.passUnretained($0).release() } }
+
+ status = SecCodeCopySigningInformation(code, kSecCSInternalInformation | kSecCSSigningInformation, &signingInfo)
+ guard status == 0 else { return false }
+
+ // Only accept connections from AltStore.
+ guard
+ let codeSigningInfo = signingInfo as? [String: Any],
+ let bundleIdentifier = codeSigningInfo["identifier"] as? String,
+ bundleIdentifier.contains("com.rileytestut.AltStore")
+ else { return false }
+
+ let connection = XPCConnection(newConnection)
+ newConnection.invalidationHandler = { [weak self, weak connection] in
+ guard let self = self, let connection = connection else { return }
+ self.disconnect(connection)
+ }
+
+ self.connectionHandler?(connection)
+
+ return true
+ }
+}
diff --git a/AltDaemon/main.swift b/AltDaemon/main.swift
new file mode 100644
index 00000000..a4b69654
--- /dev/null
+++ b/AltDaemon/main.swift
@@ -0,0 +1,14 @@
+//
+// main.swift
+// AltDaemon
+//
+// Created by Riley Testut on 6/2/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+autoreleasepool {
+ DaemonConnectionManager.shared.start()
+ RunLoop.current.run()
+}
diff --git a/AltDaemon/package/DEBIAN/control b/AltDaemon/package/DEBIAN/control
new file mode 100644
index 00000000..fa91ef73
--- /dev/null
+++ b/AltDaemon/package/DEBIAN/control
@@ -0,0 +1,10 @@
+Package: com.rileytestut.altdaemon
+Name: AltDaemon
+Depends:
+Version: 1.0
+Architecture: iphoneos-arm
+Description: AltDaemon allows AltStore to install and refresh apps without a computer.
+Maintainer: Riley Testut
+Author: Riley Testut
+Homepage: https://altstore.io
+Section: System
diff --git a/AltDaemon/package/DEBIAN/postinst b/AltDaemon/package/DEBIAN/postinst
new file mode 100755
index 00000000..e5b799be
--- /dev/null
+++ b/AltDaemon/package/DEBIAN/postinst
@@ -0,0 +1,2 @@
+#!/bin/sh
+launchctl load /Library/LaunchDaemons/com.rileytestut.altdaemon.plist
diff --git a/AltDaemon/package/DEBIAN/preinst b/AltDaemon/package/DEBIAN/preinst
new file mode 100755
index 00000000..cf29046c
--- /dev/null
+++ b/AltDaemon/package/DEBIAN/preinst
@@ -0,0 +1,2 @@
+#!/bin/sh
+launchctl unload /Library/LaunchDaemons/com.rileytestut.altdaemon.plist >> /dev/null 2>&1
diff --git a/AltDaemon/package/DEBIAN/prerm b/AltDaemon/package/DEBIAN/prerm
new file mode 100755
index 00000000..e88bf33b
--- /dev/null
+++ b/AltDaemon/package/DEBIAN/prerm
@@ -0,0 +1,2 @@
+#!/bin/sh
+launchctl unload /Library/LaunchDaemons/com.rileytestut.altdaemon.plist
diff --git a/AltDaemon/package/Library/LaunchDaemons/com.rileytestut.altdaemon.plist b/AltDaemon/package/Library/LaunchDaemons/com.rileytestut.altdaemon.plist
new file mode 100644
index 00000000..7808ca12
--- /dev/null
+++ b/AltDaemon/package/Library/LaunchDaemons/com.rileytestut.altdaemon.plist
@@ -0,0 +1,28 @@
+
+
+
+
+ Label
+ com.rileytestut.altdaemon
+ ProgramArguments
+
+ /usr/bin/env
+ _MSSafeMode=1
+ _SafeMode=1
+ /usr/bin/AltDaemon
+
+ UserName
+ mobile
+ KeepAlive
+
+ RunAtLoad
+
+ MachServices
+
+ cy:io.altstore.altdaemon
+
+ lh:io.altstore.altdaemon
+
+
+
+
diff --git a/AltDaemon/package/usr/bin/AltDaemon b/AltDaemon/package/usr/bin/AltDaemon
new file mode 100755
index 00000000..7f733693
Binary files /dev/null and b/AltDaemon/package/usr/bin/AltDaemon differ
diff --git a/AltKit/CodableServerError.swift b/AltKit/CodableServerError.swift
deleted file mode 100644
index 5dff7787..00000000
--- a/AltKit/CodableServerError.swift
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-// CodableServerError.swift
-// AltKit
-//
-// Created by Riley Testut on 3/5/20.
-// Copyright © 2020 Riley Testut. All rights reserved.
-//
-
-import Foundation
-
-// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
-extension ALTServerError.Code: Codable {}
-
-struct CodableServerError: Codable
-{
- var error: ALTServerError {
- return ALTServerError(self.errorCode, userInfo: self.userInfo ?? [:])
- }
-
- private var errorCode: ALTServerError.Code
- private var userInfo: [String: String]?
-
- private enum CodingKeys: String, CodingKey
- {
- case errorCode
- case userInfo
- }
-
- init(error: ALTServerError)
- {
- self.errorCode = error.code
-
- let userInfo = error.userInfo.compactMapValues { $0 as? String }
- if !userInfo.isEmpty
- {
- self.userInfo = userInfo
- }
- }
-
- init(from decoder: Decoder) throws
- {
- let container = try decoder.container(keyedBy: CodingKeys.self)
-
- let errorCode = try container.decode(Int.self, forKey: .errorCode)
- self.errorCode = ALTServerError.Code(rawValue: errorCode) ?? .unknown
-
- let userInfo = try container.decodeIfPresent([String: String].self, forKey: .userInfo)
- self.userInfo = userInfo
- }
-
- func encode(to encoder: Encoder) throws
- {
- var container = encoder.container(keyedBy: CodingKeys.self)
- try container.encode(self.error.code.rawValue, forKey: .errorCode)
- try container.encodeIfPresent(self.userInfo, forKey: .userInfo)
- }
-}
-
diff --git a/AltPlugin/Info.plist b/AltPlugin/Info.plist
index 6188d46b..e24386aa 100644
--- a/AltPlugin/Info.plist
+++ b/AltPlugin/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.0
+ $(MARKETING_VERSION)
CFBundleVersion
1
NSHumanReadableCopyright
@@ -58,5 +58,9 @@
# For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d)
6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053
+ Supported11.0PluginCompatibilityUUIDs
+
+ D985F0E4-3BBC-4B95-BBA1-12056AC4A531
+
diff --git a/AltServer/AltPlugin.zip b/AltServer/AltPlugin.zip
new file mode 100644
index 00000000..d6c5b41c
Binary files /dev/null and b/AltServer/AltPlugin.zip differ
diff --git a/AltServer/AltServer-Bridging-Header.h b/AltServer/AltServer-Bridging-Header.h
index 05029051..db577fd9 100644
--- a/AltServer/AltServer-Bridging-Header.h
+++ b/AltServer/AltServer-Bridging-Header.h
@@ -5,4 +5,9 @@
#import "ALTDeviceManager.h"
#import "ALTWiredConnection.h"
#import "ALTNotificationConnection.h"
-#import "AltKit.h"
+
+// Shared
+#import "ALTConstants.h"
+#import "ALTConnection.h"
+#import "NSError+ALTServerError.h"
+#import "CFNotificationName+AltStore.h"
diff --git a/AltServer/AnisetteDataManager.swift b/AltServer/AnisetteDataManager.swift
index e4338649..f28a589d 100644
--- a/AltServer/AnisetteDataManager.swift
+++ b/AltServer/AnisetteDataManager.swift
@@ -7,7 +7,6 @@
//
import Foundation
-import AltKit
class AnisetteDataManager: NSObject
{
diff --git a/AltServer/AppDelegate.swift b/AltServer/AppDelegate.swift
index 34a3790f..773d6305 100644
--- a/AltServer/AppDelegate.swift
+++ b/AltServer/AppDelegate.swift
@@ -12,32 +12,12 @@ import UserNotifications
import AltSign
import LaunchAtLogin
-import STPrivilegedTask
-
-private let pluginDirectoryURL = URL(fileURLWithPath: "/Library/Mail/Bundles", isDirectory: true)
-private let pluginURL = pluginDirectoryURL.appendingPathComponent("AltPlugin.mailbundle")
-
-enum PluginError: LocalizedError
-{
- case cancelled
- case unknown
- case taskError(String)
- case taskErrorCode(Int)
-
- var errorDescription: String? {
- switch self
- {
- case .cancelled: return NSLocalizedString("Mail plug-in installation was cancelled.", comment: "")
- case .unknown: return NSLocalizedString("Failed to install Mail plug-in.", comment: "")
- case .taskError(let output): return output
- case .taskErrorCode(let errorCode): return String(format: NSLocalizedString("There was an error installing the Mail plug-in. (Error Code: %@)", comment: ""), NSNumber(value: errorCode))
- }
- }
-}
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
+ private let pluginManager = PluginManager()
+
private var statusItem: NSStatusItem?
private var connectedDevices = [ALTDevice]()
@@ -52,29 +32,21 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private weak var authenticationAppleIDTextField: NSTextField?
private weak var authenticationPasswordTextField: NSSecureTextField?
- private var isMailPluginInstalled: Bool {
- let isMailPluginInstalled = FileManager.default.fileExists(atPath: pluginURL.path)
- return isMailPluginInstalled
- }
-
func applicationDidFinishLaunching(_ aNotification: Notification)
{
UserDefaults.standard.registerDefaults()
UNUserNotificationCenter.current().delegate = self
- ConnectionManager.shared.start()
+ ServerConnectionManager.shared.start()
ALTDeviceManager.shared.start()
let item = NSStatusBar.system.statusItem(withLength: -1)
- guard let button = item.button else { return }
-
- button.image = NSImage(named: "MenuBarIcon")
- button.target = self
- button.action = #selector(AppDelegate.presentMenu)
-
+ item.menu = self.appMenu
+ item.button?.image = NSImage(named: "MenuBarIcon")
self.statusItem = item
+ self.appMenu.delegate = self
self.connectedDevicesMenu.delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (success, error) in
@@ -92,6 +64,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
UserDefaults.standard.didPresentInitialNotification = true
}
}
+
+ if self.pluginManager.isUpdateAvailable
+ {
+ self.installMailPlugin()
+ }
}
func applicationWillTerminate(_ aNotification: Notification)
@@ -102,40 +79,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private extension AppDelegate
{
- @objc func presentMenu()
- {
- guard let button = self.statusItem?.button, let superview = button.superview, let window = button.window else { return }
-
- self.connectedDevices = ALTDeviceManager.shared.availableDevices
-
- self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off
- self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
-
- if self.isMailPluginInstalled
- {
- self.installMailPluginMenuItem.title = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
- }
- else
- {
- self.installMailPluginMenuItem.title = NSLocalizedString("Install Mail Plug-in", comment: "")
- }
-
- self.installMailPluginMenuItem.target = self
- self.installMailPluginMenuItem.action = #selector(AppDelegate.handleInstallMailPluginMenuItem(_:))
-
- let x = button.frame.origin.x
- let y = button.frame.origin.y - 5
-
- let location = superview.convert(NSMakePoint(x, y), to: nil)
-
- guard let event = NSEvent.mouseEvent(with: .leftMouseUp, location: location,
- modifierFlags: [], timestamp: 0, windowNumber: window.windowNumber, context: nil,
- eventNumber: 0, clickCount: 1, pressure: 0)
- else { return }
-
- NSMenu.popUpContextMenu(self.appMenu, with: event, for: button)
- }
-
@objc func installAltStore(_ item: NSMenuItem)
{
guard case let index = self.connectedDevicesMenu.index(of: item), index != -1 else { return }
@@ -212,6 +155,10 @@ private extension AppDelegate
{
alert.informativeText = underlyingError.localizedDescription
}
+ else if let recoverySuggestion = error.localizedRecoverySuggestion
+ {
+ alert.informativeText = error.localizedDescription + "\n\n" + recoverySuggestion
+ }
else
{
alert.informativeText = error.localizedDescription
@@ -224,27 +171,13 @@ private extension AppDelegate
}
}
- if !self.isMailPluginInstalled
+ if !self.pluginManager.isMailPluginInstalled || self.pluginManager.isUpdateAvailable
{
self.installMailPlugin { (result) in
- DispatchQueue.main.async {
- switch result
- {
- case .failure(PluginError.cancelled): break
- case .failure(let error):
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("Failed to Install Mail Plug-in", comment: "")
- alert.informativeText = error.localizedDescription
- alert.runModal()
-
- case .success:
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("Mail Plug-in Installed", comment: "")
- alert.informativeText = NSLocalizedString("Please restart Mail and enable AltPlugin in Mail's Preferences. Mail must be running when installing or refreshing apps with AltServer.", comment: "")
- alert.runModal()
-
- install()
- }
+ switch result
+ {
+ case .failure: break
+ case .success: install()
}
}
}
@@ -256,236 +189,109 @@ private extension AppDelegate
@objc func toggleLaunchAtLogin(_ item: NSMenuItem)
{
- if item.state == .on
- {
- item.state = .off
- }
- else
- {
- item.state = .on
- }
-
LaunchAtLogin.isEnabled.toggle()
}
@objc func handleInstallMailPluginMenuItem(_ item: NSMenuItem)
{
- if self.isMailPluginInstalled
+ if !self.pluginManager.isMailPluginInstalled || self.pluginManager.isUpdateAvailable
{
- self.uninstallMailPlugin { (result) in
- DispatchQueue.main.async {
- switch result
- {
- case .failure(PluginError.cancelled): break
- case .failure(let error):
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("Failed to Uninstall Mail Plug-in", comment: "")
- alert.informativeText = error.localizedDescription
- alert.runModal()
-
- case .success:
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("Mail Plug-in Uninstalled", comment: "")
- alert.informativeText = NSLocalizedString("Please restart Mail for changes to take effect. You will not be able to use AltServer until the plug-in is reinstalled.", comment: "")
- alert.runModal()
- }
- }
- }
+ self.installMailPlugin()
}
else
{
- self.installMailPlugin { (result) in
- DispatchQueue.main.async {
- switch result
- {
- case .failure(PluginError.cancelled): break
- case .failure(let error):
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("Failed to Install Mail Plug-in", comment: "")
- alert.informativeText = error.localizedDescription
- alert.runModal()
-
- case .success:
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("Mail Plug-in Installed", comment: "")
- alert.informativeText = NSLocalizedString("Please restart Mail and enable AltPlugin in Mail's Preferences. Mail must be running when installing or refreshing apps with AltServer.", comment: "")
- alert.runModal()
- }
- }
- }
+ self.uninstallMailPlugin()
}
}
- func installMailPlugin(completionHandler: @escaping (Result) -> Void)
+ private func installMailPlugin(completion: ((Result) -> Void)? = nil)
{
- do
- {
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("Install Mail Plug-in", comment: "")
- alert.informativeText = NSLocalizedString("AltServer requires a Mail plug-in in order to retrieve necessary information about your Apple ID. Would you like to install it now?", comment: "")
-
- alert.addButton(withTitle: NSLocalizedString("Install Plug-in", comment: ""))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
-
- NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
-
- let response = alert.runModal()
- guard response == .alertFirstButtonReturn else { throw PluginError.cancelled }
-
- self.downloadPlugin { (result) in
- do
+ self.pluginManager.installMailPlugin { (result) in
+ DispatchQueue.main.async {
+ switch result
{
- let fileURL = try result.get()
- defer { try? FileManager.default.removeItem(at: fileURL) }
+ case .failure(PluginError.cancelled): break
+ case .failure(let error):
+ let alert = NSAlert()
+ alert.messageText = NSLocalizedString("Failed to Install Mail Plug-in", comment: "")
+ alert.informativeText = error.localizedDescription
+ alert.runModal()
- // Ensure plug-in directory exists.
- let authorization = try self.runAndKeepAuthorization("mkdir", arguments: ["-p", pluginDirectoryURL.path])
-
- // Unzip AltPlugin to plug-ins directory.
- try self.runAndKeepAuthorization("unzip", arguments: ["-o", fileURL.path, "-d", pluginDirectoryURL.path], authorization: authorization)
- guard self.isMailPluginInstalled else { throw PluginError.unknown }
-
- // Enable Mail plug-in preferences.
- try self.run("defaults", arguments: ["write", "/Library/Preferences/com.apple.mail", "EnableBundles", "-bool", "YES"], authorization: authorization)
-
- print("Finished installing Mail plug-in!")
-
- completionHandler(.success(()))
- }
- catch
- {
- completionHandler(.failure(error))
- }
- }
- }
- catch
- {
- completionHandler(.failure(PluginError.cancelled))
- }
- }
-
- func downloadPlugin(completionHandler: @escaping (Result) -> Void)
- {
- let pluginURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altserver/altplugin/1_0.zip")!
-
- let downloadTask = URLSession.shared.downloadTask(with: pluginURL) { (fileURL, response, error) in
- if let fileURL = fileURL
- {
- print("Downloaded plugin to URL:", fileURL)
- completionHandler(.success(fileURL))
- }
- else
- {
- completionHandler(.failure(error ?? PluginError.unknown))
- }
- }
-
- downloadTask.resume()
- }
-
- func uninstallMailPlugin(completionHandler: @escaping (Result) -> Void)
- {
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
- alert.informativeText = NSLocalizedString("Are you sure you want to uninstall the AltServer Mail plug-in? You will no longer be able to install or refresh apps with AltStore.", comment: "")
-
- alert.addButton(withTitle: NSLocalizedString("Uninstall Plug-in", comment: ""))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
-
- NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
-
- let response = alert.runModal()
- guard response == .alertFirstButtonReturn else { return completionHandler(.failure(PluginError.cancelled)) }
-
- DispatchQueue.global().async {
- do
- {
- if FileManager.default.fileExists(atPath: pluginURL.path)
- {
- // Delete Mail plug-in from privileged directory.
- try self.run("rm", arguments: ["-rf", pluginURL.path])
+ case .success:
+ let alert = NSAlert()
+ alert.messageText = NSLocalizedString("Mail Plug-in Installed", comment: "")
+ alert.informativeText = NSLocalizedString("Please restart Mail and enable AltPlugin in Mail's Preferences. Mail must be running when installing or refreshing apps with AltServer.", comment: "")
+ alert.runModal()
}
- completionHandler(.success(()))
- }
- catch
- {
- completionHandler(.failure(error))
+ completion?(result)
}
}
}
-}
-
-private extension AppDelegate
-{
- func run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws
- {
- _ = try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: true)
- }
- @discardableResult
- func runAndKeepAuthorization(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws -> AuthorizationRef
+ private func uninstallMailPlugin()
{
- return try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: false)
- }
-
- func _run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil, freeAuthorization: Bool) throws -> AuthorizationRef
- {
- var launchPath = "/usr/bin/" + program
- if !FileManager.default.fileExists(atPath: launchPath)
- {
- launchPath = "/bin/" + program
- }
-
- print("Running program:", launchPath)
-
- let task = STPrivilegedTask()
- task.launchPath = launchPath
- task.arguments = arguments
- task.freeAuthorizationWhenDone = freeAuthorization
-
- let errorCode: OSStatus
-
- if let authorization = authorization
- {
- errorCode = task.launch(withAuthorization: authorization)
- }
- else
- {
- errorCode = task.launch()
- }
-
- guard errorCode == 0 else { throw PluginError.taskErrorCode(Int(errorCode)) }
-
- task.waitUntilExit()
-
- print("Exit code:", task.terminationStatus)
-
- guard task.terminationStatus == 0 else {
- let outputData = task.outputFileHandle.readDataToEndOfFile()
-
- if let outputString = String(data: outputData, encoding: .utf8), !outputString.isEmpty
- {
- throw PluginError.taskError(outputString)
+ self.pluginManager.uninstallMailPlugin { (result) in
+ DispatchQueue.main.async {
+ switch result
+ {
+ case .failure(PluginError.cancelled): break
+ case .failure(let error):
+ let alert = NSAlert()
+ alert.messageText = NSLocalizedString("Failed to Uninstall Mail Plug-in", comment: "")
+ alert.informativeText = error.localizedDescription
+ alert.runModal()
+
+ case .success:
+ let alert = NSAlert()
+ alert.messageText = NSLocalizedString("Mail Plug-in Uninstalled", comment: "")
+ alert.informativeText = NSLocalizedString("Please restart Mail for changes to take effect. You will not be able to use AltServer until the plug-in is reinstalled.", comment: "")
+ alert.runModal()
+ }
}
-
- throw PluginError.taskErrorCode(Int(task.terminationStatus))
}
-
- guard let authorization = task.authorization else { throw PluginError.unknown }
- return authorization
}
}
extension AppDelegate: NSMenuDelegate
{
+ func menuWillOpen(_ menu: NSMenu)
+ {
+ guard menu == self.appMenu else { return }
+
+ self.connectedDevices = ALTDeviceManager.shared.connectedDevices
+
+ self.launchAtLoginMenuItem.target = self
+ self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
+ self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off
+
+ if self.pluginManager.isUpdateAvailable
+ {
+ self.installMailPluginMenuItem.title = NSLocalizedString("Update Mail Plug-in", comment: "")
+ }
+ else if self.pluginManager.isMailPluginInstalled
+ {
+ self.installMailPluginMenuItem.title = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
+ }
+ else
+ {
+ self.installMailPluginMenuItem.title = NSLocalizedString("Install Mail Plug-in", comment: "")
+ }
+ self.installMailPluginMenuItem.target = self
+ self.installMailPluginMenuItem.action = #selector(AppDelegate.handleInstallMailPluginMenuItem(_:))
+ }
+
func numberOfItems(in menu: NSMenu) -> Int
{
+ guard menu == self.connectedDevicesMenu else { return -1 }
+
return self.connectedDevices.isEmpty ? 1 : self.connectedDevices.count
}
func menu(_ menu: NSMenu, update item: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool
{
+ guard menu == self.connectedDevicesMenu else { return false }
+
if self.connectedDevices.isEmpty
{
item.title = NSLocalizedString("No Connected Devices", comment: "")
diff --git a/AltServer/Base.lproj/Main.storyboard b/AltServer/Base.lproj/Main.storyboard
index d2a97fe9..33f11093 100644
--- a/AltServer/Base.lproj/Main.storyboard
+++ b/AltServer/Base.lproj/Main.storyboard
@@ -1,8 +1,8 @@
-
+
-
+
diff --git a/AltServer/Connections/ALTNotificationConnection.h b/AltServer/Connections/ALTNotificationConnection.h
index 50a0f59d..4654ab2e 100644
--- a/AltServer/Connections/ALTNotificationConnection.h
+++ b/AltServer/Connections/ALTNotificationConnection.h
@@ -6,7 +6,7 @@
// Copyright © 2020 Riley Testut. All rights reserved.
//
-#import
+#import "AltSign.h"
NS_ASSUME_NONNULL_BEGIN
diff --git a/AltServer/Connections/ALTNotificationConnection.m b/AltServer/Connections/ALTNotificationConnection.mm
similarity index 98%
rename from AltServer/Connections/ALTNotificationConnection.m
rename to AltServer/Connections/ALTNotificationConnection.mm
index 6a3a895d..4c486d05 100644
--- a/AltServer/Connections/ALTNotificationConnection.m
+++ b/AltServer/Connections/ALTNotificationConnection.mm
@@ -7,7 +7,8 @@
//
#import "ALTNotificationConnection+Private.h"
-#import "AltKit.h"
+
+#import "NSError+ALTServerError.h"
void ALTDeviceReceivedNotification(const char *notification, void *user_data);
diff --git a/AltServer/Connections/ALTWiredConnection+Private.h b/AltServer/Connections/ALTWiredConnection+Private.h
index 24dc2c85..a6c64d14 100644
--- a/AltServer/Connections/ALTWiredConnection+Private.h
+++ b/AltServer/Connections/ALTWiredConnection+Private.h
@@ -14,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface ALTWiredConnection ()
+@property (nonatomic, readwrite, getter=isConnected) BOOL connected;
+
@property (nonatomic, readonly) idevice_connection_t connection;
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection;
diff --git a/AltServer/Connections/ALTWiredConnection.h b/AltServer/Connections/ALTWiredConnection.h
index 05801f98..d6fde691 100644
--- a/AltServer/Connections/ALTWiredConnection.h
+++ b/AltServer/Connections/ALTWiredConnection.h
@@ -6,12 +6,16 @@
// Copyright © 2020 Riley Testut. All rights reserved.
//
-#import
+#import "AltSign.h"
+
+#import "ALTConnection.h"
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(WiredConnection)
-@interface ALTWiredConnection : NSObject
+@interface ALTWiredConnection : NSObject
+
+@property (nonatomic, readonly, getter=isConnected) BOOL connected;
@property (nonatomic, copy, readonly) ALTDevice *device;
diff --git a/AltServer/Connections/ALTWiredConnection.m b/AltServer/Connections/ALTWiredConnection.mm
similarity index 89%
rename from AltServer/Connections/ALTWiredConnection.m
rename to AltServer/Connections/ALTWiredConnection.mm
index 0b941730..aa674063 100644
--- a/AltServer/Connections/ALTWiredConnection.m
+++ b/AltServer/Connections/ALTWiredConnection.mm
@@ -7,7 +7,9 @@
//
#import "ALTWiredConnection+Private.h"
-#import "AltKit.h"
+
+#import "ALTConnection.h"
+#import "NSError+ALTServerError.h"
@implementation ALTWiredConnection
@@ -30,8 +32,15 @@
- (void)disconnect
{
+ if (![self isConnected])
+ {
+ return;
+ }
+
idevice_disconnect(self.connection);
_connection = nil;
+
+ self.connected = NO;
}
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
@@ -85,7 +94,7 @@
uint32_t size = MIN(4096, (uint32_t)expectedSize - (uint32_t)receivedData.length);
uint32_t receivedBytes = 0;
- if (idevice_connection_receive_timeout(self.connection, bytes, size, &receivedBytes, 0) != IDEVICE_E_SUCCESS)
+ if (idevice_connection_receive_timeout(self.connection, bytes, size, &receivedBytes, 10000) != IDEVICE_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
@@ -98,4 +107,11 @@
});
}
+#pragma mark - NSObject -
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"%@ (Wired)", self.device.name];
+}
+
@end
diff --git a/AltServer/Connections/ClientConnection.swift b/AltServer/Connections/ClientConnection.swift
deleted file mode 100644
index 33b5562c..00000000
--- a/AltServer/Connections/ClientConnection.swift
+++ /dev/null
@@ -1,231 +0,0 @@
-//
-// ClientConnection.swift
-// AltServer
-//
-// Created by Riley Testut on 1/9/20.
-// Copyright © 2020 Riley Testut. All rights reserved.
-//
-
-import Foundation
-import Network
-
-import AltKit
-import AltSign
-
-extension ClientConnection
-{
- enum Connection
- {
- case wireless(NWConnection)
- case wired(WiredConnection)
- }
-}
-
-class ClientConnection
-{
- let connection: Connection
-
- init(connection: Connection)
- {
- self.connection = connection
- }
-
- func disconnect()
- {
- switch self.connection
- {
- case .wireless(let connection):
- switch connection.state
- {
- case .cancelled, .failed:
- print("Disconnecting from \(connection.endpoint)...")
-
- default:
- // State update handler might call this method again.
- connection.cancel()
- }
-
- case .wired(let connection):
- connection.disconnect()
- }
- }
-
- func send(_ response: T, shouldDisconnect: Bool = false, completionHandler: @escaping (Result) -> Void)
- {
- func finish(_ result: Result)
- {
- completionHandler(result)
-
- if shouldDisconnect
- {
- // Add short delay to prevent us from dropping connection too quickly.
- DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
- self.disconnect()
- }
- }
- }
-
- do
- {
- let data = try JSONEncoder().encode(response)
- let responseSize = withUnsafeBytes(of: Int32(data.count)) { Data($0) }
-
- self.send(responseSize) { (result) in
- switch result
- {
- case .failure: finish(.failure(.init(.lostConnection)))
- case .success:
-
- self.send(data) { (result) in
- switch result
- {
- case .failure: finish(.failure(.init(.lostConnection)))
- case .success: finish(.success(()))
- }
- }
- }
- }
- }
- catch
- {
- finish(.failure(.init(.invalidResponse)))
- }
- }
-
- func receiveRequest(completionHandler: @escaping (Result) -> Void)
- {
- let size = MemoryLayout.size
-
- print("Receiving request size")
- self.receiveData(expectedBytes: size) { (result) in
- do
- {
- let data = try result.get()
-
- print("Receiving request...")
- let expectedBytes = Int(data.withUnsafeBytes { $0.load(as: Int32.self) })
- self.receiveData(expectedBytes: expectedBytes) { (result) in
- do
- {
- let data = try result.get()
- let request = try JSONDecoder().decode(ServerRequest.self, from: data)
-
- print("Received installation request:", request)
- completionHandler(.success(request))
- }
- catch
- {
- completionHandler(.failure(ALTServerError(error)))
- }
- }
- }
- catch
- {
- completionHandler(.failure(ALTServerError(error)))
- }
- }
- }
-
- func send(_ data: Data, completionHandler: @escaping (Result) -> Void)
- {
- switch self.connection
- {
- case .wireless(let connection):
- connection.send(content: data, completion: .contentProcessed { (error) in
- if let error = error
- {
- completionHandler(.failure(error))
- }
- else
- {
- completionHandler(.success(()))
- }
- })
-
- case .wired(let connection):
- connection.send(data) { (success, error) in
- if !success
- {
- completionHandler(.failure(ALTServerError(.lostConnection)))
- }
- else
- {
- completionHandler(.success(()))
- }
- }
- }
- }
-
- func receiveData(expectedBytes: Int, completionHandler: @escaping (Result) -> Void)
- {
- func finish(data: Data?, error: Error?)
- {
- do
- {
- let data = try self.process(data: data, error: error)
- completionHandler(.success(data))
- }
- catch
- {
- completionHandler(.failure(ALTServerError(error)))
- }
- }
-
- switch self.connection
- {
- case .wireless(let connection):
- connection.receive(minimumIncompleteLength: expectedBytes, maximumLength: expectedBytes) { (data, _, _, error) in
- finish(data: data, error: error)
- }
-
- case .wired(let connection):
- connection.receiveData(withExpectedSize: expectedBytes) { (data, error) in
- finish(data: data, error: error)
- }
- }
- }
-}
-
-extension ClientConnection: CustomStringConvertible
-{
- var description: String {
- switch self.connection
- {
- case .wireless(let connection): return "\(connection.endpoint) (Wireless)"
- case .wired(let connection): return "\(connection.device.name) (Wired)"
- }
- }
-}
-
-private extension ClientConnection
-{
- func process(data: Data?, error: Error?) throws -> Data
- {
- do
- {
- do
- {
- guard let data = data else { throw error ?? ALTServerError(.unknown) }
- return data
- }
- catch let error as NWError
- {
- print("Error receiving data from connection \(connection)", error)
-
- throw ALTServerError(.lostConnection)
- }
- catch
- {
- throw error
- }
- }
- catch let error as ALTServerError
- {
- throw error
- }
- catch
- {
- preconditionFailure("A non-ALTServerError should never be thrown from this method.")
- }
- }
-}
diff --git a/AltServer/Connections/ConnectionManager.swift b/AltServer/Connections/ConnectionManager.swift
deleted file mode 100644
index 6c5203a9..00000000
--- a/AltServer/Connections/ConnectionManager.swift
+++ /dev/null
@@ -1,508 +0,0 @@
-//
-// ConnectionManager.swift
-// AltServer
-//
-// Created by Riley Testut on 5/23/19.
-// Copyright © 2019 Riley Testut. All rights reserved.
-//
-
-import Foundation
-import Network
-import AppKit
-
-import AltKit
-
-extension ALTServerError
-{
- init(_ error: E)
- {
- switch error
- {
- case let error as ALTServerError: self = error
- case is DecodingError: self = ALTServerError(.invalidRequest)
- case is EncodingError: self = ALTServerError(.invalidResponse)
- case let error as NSError:
- self = ALTServerError(.unknown, userInfo: error.userInfo)
- }
- }
-}
-
-extension ConnectionManager
-{
- enum State
- {
- case notRunning
- case connecting
- case running(NWListener.Service)
- case failed(Swift.Error)
- }
-}
-
-class ConnectionManager
-{
- static let shared = ConnectionManager()
-
- var stateUpdateHandler: ((State) -> Void)?
-
- private(set) var state: State = .notRunning {
- didSet {
- self.stateUpdateHandler?(self.state)
- }
- }
-
- private lazy var listener = self.makeListener()
- private let dispatchQueue = DispatchQueue(label: "com.rileytestut.AltServer.connections", qos: .utility)
-
- private var connections = [ClientConnection]()
- private var notificationConnections = [ALTDevice: NotificationConnection]()
-
- private init()
- {
- NotificationCenter.default.addObserver(self, selector: #selector(ConnectionManager.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(ConnectionManager.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil)
- }
-
- func start()
- {
- switch self.state
- {
- case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue)
- default: break
- }
- }
-
- func stop()
- {
- switch self.state
- {
- case .running: self.listener.cancel()
- default: break
- }
- }
-
- func disconnect(_ connection: ClientConnection)
- {
- connection.disconnect()
-
- if let index = self.connections.firstIndex(where: { $0 === connection })
- {
- self.connections.remove(at: index)
- }
- }
-}
-
-private extension ConnectionManager
-{
- func makeListener() -> NWListener
- {
- let listener = try! NWListener(using: .tcp)
-
- let service: NWListener.Service
-
- if let serverID = UserDefaults.standard.serverID?.data(using: .utf8)
- {
- let txtDictionary = ["serverID": serverID]
- let txtData = NetService.data(fromTXTRecord: txtDictionary)
-
- service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData)
- }
- else
- {
- service = NWListener.Service(type: ALTServerServiceType)
- }
-
- listener.service = service
-
- listener.serviceRegistrationUpdateHandler = { (serviceChange) in
- switch serviceChange
- {
- case .add(.service(let name, let type, let domain, _)):
- let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil)
- self.state = .running(service)
-
- default: break
- }
- }
-
- listener.stateUpdateHandler = { (state) in
- switch state
- {
- case .ready: break
- case .waiting, .setup: self.state = .connecting
- case .cancelled: self.state = .notRunning
- case .failed(let error):
- self.state = .failed(error)
- self.start()
-
- @unknown default: break
- }
- }
-
- listener.newConnectionHandler = { [weak self] (connection) in
- self?.prepare(connection)
- }
-
- return listener
- }
-
- func prepare(_ connection: NWConnection)
- {
- let clientConnection = ClientConnection(connection: .wireless(connection))
-
- guard !self.connections.contains(where: { $0 === clientConnection }) else { return }
- self.connections.append(clientConnection)
-
- connection.stateUpdateHandler = { [weak self] (state) in
- switch state
- {
- case .setup, .preparing: break
-
- case .ready:
- print("Connected to client:", connection.endpoint)
- self?.handleRequest(for: clientConnection)
-
- case .waiting:
- print("Waiting for connection...")
-
- case .failed(let error):
- print("Failed to connect to service \(connection.endpoint).", error)
- self?.disconnect(clientConnection)
-
- case .cancelled:
- self?.disconnect(clientConnection)
-
- @unknown default: break
- }
- }
-
- connection.start(queue: self.dispatchQueue)
- }
-}
-
-private extension ConnectionManager
-{
- func startNotificationConnection(to device: ALTDevice)
- {
- ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in
- guard let connection = connection else { return }
-
- let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest]
- connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in
- guard success else { return }
-
- connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in
- guard let self = self, let connection = connection else { return }
- self.handle(notification, for: connection)
- }
-
- self.notificationConnections[device] = connection
- }
- }
- }
-
- func stopNotificationConnection(to device: ALTDevice)
- {
- guard let connection = self.notificationConnections[device] else { return }
- connection.disconnect()
-
- self.notificationConnections[device] = nil
- }
-
- func handle(_ notification: CFNotificationName, for connection: NotificationConnection)
- {
- switch notification
- {
- case .wiredServerConnectionAvailableRequest:
- connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in
- if let error = error, !success
- {
- print("Error sending wired server connection response.", error)
- }
- else
- {
- print("Sent wired server connection available response!")
- }
- }
-
- case .wiredServerConnectionStartRequest:
- ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in
- if let wiredConnection = wiredConnection
- {
- print("Started wired server connection!")
-
- let clientConnection = ClientConnection(connection: .wired(wiredConnection))
- self.handleRequest(for: clientConnection)
- }
- else if let error = error
- {
- print("Error starting wired server connection.", error)
- }
- }
-
- default: break
- }
- }
-}
-
-private extension ConnectionManager
-{
- func handleRequest(for connection: ClientConnection)
- {
- connection.receiveRequest() { (result) in
- print("Received initial request with result:", result)
-
- switch result
- {
- case .failure(let error):
- let response = ErrorResponse(error: ALTServerError(error))
- connection.send(response, shouldDisconnect: true) { (result) in
- print("Sent error response with result:", result)
- }
-
- case .success(.anisetteData(let request)):
- self.handleAnisetteDataRequest(request, for: connection)
-
- case .success(.prepareApp(let request)):
- self.handlePrepareAppRequest(request, for: connection)
-
- case .success(.beginInstallation): break
-
- case .success(.installProvisioningProfiles(let request)):
- self.handleInstallProvisioningProfilesRequest(request, for: connection)
-
- case .success(.removeProvisioningProfiles(let request)):
- self.handleRemoveProvisioningProfilesRequest(request, for: connection)
-
- case .success(.unknown):
- let response = ErrorResponse(error: ALTServerError(.unknownRequest))
- connection.send(response, shouldDisconnect: true) { (result) in
- print("Sent unknown request response with result:", result)
- }
- }
- }
- }
-
- func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: ClientConnection)
- {
- AnisetteDataManager.shared.requestAnisetteData { (result) in
- switch result
- {
- case .failure(let error):
- let errorResponse = ErrorResponse(error: ALTServerError(error))
- connection.send(errorResponse, shouldDisconnect: true) { (result) in
- print("Sent anisette data error response with result:", result)
- }
-
- case .success(let anisetteData):
- let response = AnisetteDataResponse(anisetteData: anisetteData)
- connection.send(response, shouldDisconnect: true) { (result) in
- print("Sent anisette data response with result:", result)
- }
- }
- }
- }
-
- func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: ClientConnection)
- {
- var temporaryURL: URL?
-
- func finish(_ result: Result)
- {
- if let temporaryURL = temporaryURL
- {
- do { try FileManager.default.removeItem(at: temporaryURL) }
- catch { print("Failed to remove .ipa.", error) }
- }
-
- switch result
- {
- case .failure(let error):
- print("Failed to process request from \(connection).", error)
-
- let response = ErrorResponse(error: ALTServerError(error))
- connection.send(response, shouldDisconnect: true) { (result) in
- print("Sent install app error response to \(connection) with result:", result)
- }
-
- case .success:
- print("Processed request from \(connection).")
-
- let response = InstallationProgressResponse(progress: 1.0)
- connection.send(response, shouldDisconnect: true) { (result) in
- print("Sent install app response to \(connection) with result:", result)
- }
- }
- }
-
- self.receiveApp(for: request, from: connection) { (result) in
- print("Received app with result:", result)
-
- switch result
- {
- case .failure(let error): finish(.failure(error))
- case .success(let fileURL):
- temporaryURL = fileURL
-
- print("Awaiting begin installation request...")
-
- connection.receiveRequest() { (result) in
- print("Received begin installation request with result:", result)
-
- switch result
- {
- case .failure(let error): finish(.failure(error))
- case .success(.beginInstallation(let installRequest)):
- print("Installing to device \(request.udid)...")
-
- self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in
- print("Installed to device with result:", result)
- switch result
- {
- case .failure(let error): finish(.failure(error))
- case .success: finish(.success(()))
- }
- }
-
- case .success:
- let response = ErrorResponse(error: ALTServerError(.unknownRequest))
- connection.send(response, shouldDisconnect: true) { (result) in
- print("Sent unknown request error response to \(connection) with result:", result)
- }
- }
- }
- }
- }
- }
-
- func receiveApp(for request: PrepareAppRequest, from connection: ClientConnection, completionHandler: @escaping (Result) -> Void)
- {
- connection.receiveData(expectedBytes: request.contentSize) { (result) in
- do
- {
- print("Received app data!")
-
- let data = try result.get()
-
- guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) }
-
- print("Writing app data...")
-
- let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa")
- try data.write(to: temporaryURL, options: .atomic)
-
- print("Wrote app to URL:", temporaryURL)
-
- completionHandler(.success(temporaryURL))
- }
- catch
- {
- print("Error processing app data:", error)
-
- completionHandler(.failure(ALTServerError(error)))
- }
- }
- }
-
- func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set?, connection: ClientConnection, completionHandler: @escaping (Result) -> Void)
- {
- let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default)
- var isSending = false
-
- var observation: NSKeyValueObservation?
-
- let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in
- print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription)
-
- if let error = error.map({ $0 as? ALTServerError ?? ALTServerError(.unknown) })
- {
- completionHandler(.failure(error))
- }
- else
- {
- completionHandler(.success(()))
- }
-
- observation?.invalidate()
- observation = nil
- }
-
- observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in
- serialQueue.async {
- guard !isSending else { return }
- isSending = true
-
- print("Progress:", progress.fractionCompleted)
- let response = InstallationProgressResponse(progress: progress.fractionCompleted)
-
- connection.send(response) { (result) in
- serialQueue.async {
- isSending = false
- }
- }
- }
- })
- }
-
- func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: ClientConnection)
- {
- ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in
- if let error = error, !success
- {
- print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
-
- let errorResponse = ErrorResponse(error: ALTServerError(error))
- connection.send(errorResponse, shouldDisconnect: true) { (result) in
- print("Sent install profiles error response with result:", result)
- }
- }
- else
- {
- print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
-
- let response = InstallProvisioningProfilesResponse()
- connection.send(response, shouldDisconnect: true) { (result) in
- print("Sent install profiles response to \(connection) with result:", result)
- }
- }
- }
- }
-
- func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: ClientConnection)
- {
- ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in
- if let error = error, !success
- {
- print("Failed to remove profiles \(request.bundleIdentifiers):", error)
-
- let errorResponse = ErrorResponse(error: ALTServerError(error))
- connection.send(errorResponse, shouldDisconnect: true) { (result) in
- print("Sent remove profiles error response with result:", result)
- }
- }
- else
- {
- print("Removed profiles:", request.bundleIdentifiers)
-
- let response = RemoveProvisioningProfilesResponse()
- connection.send(response, shouldDisconnect: true) { (result) in
- print("Sent remove profiles error response to \(connection) with result:", result)
- }
- }
- }
- }
-}
-
-private extension ConnectionManager
-{
- @objc func deviceDidConnect(_ notification: Notification)
- {
- guard let device = notification.object as? ALTDevice else { return }
- self.startNotificationConnection(to: device)
- }
-
- @objc func deviceDidDisconnect(_ notification: Notification)
- {
- guard let device = notification.object as? ALTDevice else { return }
- self.stopNotificationConnection(to: device)
- }
-}
diff --git a/AltServer/Connections/RequestHandler.swift b/AltServer/Connections/RequestHandler.swift
new file mode 100644
index 00000000..02326f54
--- /dev/null
+++ b/AltServer/Connections/RequestHandler.swift
@@ -0,0 +1,218 @@
+//
+// RequestHandler.swift
+// AltServer
+//
+// Created by Riley Testut on 5/23/19.
+// Copyright © 2019 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+typealias ServerConnectionManager = ConnectionManager
+
+private let connectionManager = ConnectionManager(requestHandler: ServerRequestHandler(),
+ connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()])
+
+extension ServerConnectionManager
+{
+ static var shared: ConnectionManager {
+ return connectionManager
+ }
+}
+
+struct ServerRequestHandler: RequestHandler
+{
+ func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void)
+ {
+ AnisetteDataManager.shared.requestAnisetteData { (result) in
+ switch result
+ {
+ case .failure(let error): completionHandler(.failure(error))
+ case .success(let anisetteData):
+ let response = AnisetteDataResponse(anisetteData: anisetteData)
+ completionHandler(.success(response))
+ }
+ }
+ }
+
+ func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void)
+ {
+ var temporaryURL: URL?
+
+ func finish(_ result: Result)
+ {
+ if let temporaryURL = temporaryURL
+ {
+ do { try FileManager.default.removeItem(at: temporaryURL) }
+ catch { print("Failed to remove .ipa.", error) }
+ }
+
+ completionHandler(result)
+ }
+
+ self.receiveApp(for: request, from: connection) { (result) in
+ print("Received app with result:", result)
+
+ switch result
+ {
+ case .failure(let error): finish(.failure(error))
+ case .success(let fileURL):
+ temporaryURL = fileURL
+
+ print("Awaiting begin installation request...")
+
+ connection.receiveRequest() { (result) in
+ print("Received begin installation request with result:", result)
+
+ switch result
+ {
+ case .failure(let error): finish(.failure(error))
+ case .success(.beginInstallation(let installRequest)):
+ print("Installing app to device \(request.udid)...")
+
+ self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in
+ print("Installed app to device with result:", result)
+ switch result
+ {
+ case .failure(let error): finish(.failure(error))
+ case .success:
+ let response = InstallationProgressResponse(progress: 1.0)
+ finish(.success(response))
+ }
+ }
+
+ case .success: finish(.failure(ALTServerError(.unknownRequest)))
+ }
+ }
+ }
+ }
+ }
+
+ func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
+ completionHandler: @escaping (Result) -> Void)
+ {
+ ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in
+ if let error = error, !success
+ {
+ print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
+ completionHandler(.failure(ALTServerError(error)))
+ }
+ else
+ {
+ print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
+
+ let response = InstallProvisioningProfilesResponse()
+ completionHandler(.success(response))
+ }
+ }
+ }
+
+ func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
+ completionHandler: @escaping (Result) -> Void)
+ {
+ ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in
+ if let error = error, !success
+ {
+ print("Failed to remove profiles \(request.bundleIdentifiers):", error)
+ completionHandler(.failure(ALTServerError(error)))
+ }
+ else
+ {
+ print("Removed profiles:", request.bundleIdentifiers)
+
+ let response = RemoveProvisioningProfilesResponse()
+ completionHandler(.success(response))
+ }
+ }
+ }
+
+ func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void)
+ {
+ 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)
+ completionHandler(.failure(ALTServerError(error)))
+ }
+ else
+ {
+ print("Removed app:", request.bundleIdentifier)
+
+ let response = RemoveAppResponse()
+ completionHandler(.success(response))
+ }
+ }
+ }
+}
+
+private extension RequestHandler
+{
+ func receiveApp(for request: PrepareAppRequest, from connection: Connection, completionHandler: @escaping (Result) -> Void)
+ {
+ connection.receiveData(expectedSize: request.contentSize) { (result) in
+ do
+ {
+ print("Received app data!")
+
+ let data = try result.get()
+
+ guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) }
+
+ print("Writing app data...")
+
+ let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa")
+ try data.write(to: temporaryURL, options: .atomic)
+
+ print("Wrote app to URL:", temporaryURL)
+
+ completionHandler(.success(temporaryURL))
+ }
+ catch
+ {
+ print("Error processing app data:", error)
+
+ completionHandler(.failure(ALTServerError(error)))
+ }
+ }
+ }
+
+ func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set?, connection: Connection, completionHandler: @escaping (Result) -> Void)
+ {
+ let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default)
+ var isSending = false
+
+ var observation: NSKeyValueObservation?
+
+ let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in
+ print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription)
+
+ if let error = error.map({ ALTServerError($0) })
+ {
+ completionHandler(.failure(error))
+ }
+ else
+ {
+ completionHandler(.success(()))
+ }
+
+ observation?.invalidate()
+ observation = nil
+ }
+
+ observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in
+ serialQueue.async {
+ guard !isSending else { return }
+ isSending = true
+
+ print("Progress:", progress.fractionCompleted)
+ let response = InstallationProgressResponse(progress: progress.fractionCompleted)
+
+ connection.send(response) { (result) in
+ serialQueue.async {
+ isSending = false
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/AltServer/Connections/WiredConnectionHandler.swift b/AltServer/Connections/WiredConnectionHandler.swift
new file mode 100644
index 00000000..0aede491
--- /dev/null
+++ b/AltServer/Connections/WiredConnectionHandler.swift
@@ -0,0 +1,115 @@
+//
+// WiredConnectionHandler.swift
+// AltServer
+//
+// Created by Riley Testut on 6/1/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import Foundation
+
+class WiredConnectionHandler: ConnectionHandler
+{
+ var connectionHandler: ((Connection) -> Void)?
+ var disconnectionHandler: ((Connection) -> Void)?
+
+ private var notificationConnections = [ALTDevice: NotificationConnection]()
+
+ func startListening()
+ {
+ NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil)
+ }
+
+ func stopListening()
+ {
+ NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidConnect, object: nil)
+ NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidDisconnect, object: nil)
+ }
+}
+
+private extension WiredConnectionHandler
+{
+ func startNotificationConnection(to device: ALTDevice)
+ {
+ ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in
+ guard let connection = connection else { return }
+
+ let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest]
+ connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in
+ guard success else { return }
+
+ connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in
+ guard let self = self, let connection = connection else { return }
+ self.handle(notification, for: connection)
+ }
+
+ self.notificationConnections[device] = connection
+ }
+ }
+ }
+
+ func stopNotificationConnection(to device: ALTDevice)
+ {
+ guard let connection = self.notificationConnections[device] else { return }
+ connection.disconnect()
+
+ self.notificationConnections[device] = nil
+ }
+
+ func handle(_ notification: CFNotificationName, for connection: NotificationConnection)
+ {
+ switch notification
+ {
+ case .wiredServerConnectionAvailableRequest:
+ connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in
+ if let error = error, !success
+ {
+ print("Error sending wired server connection response.", error)
+ }
+ else
+ {
+ print("Sent wired server connection available response!")
+ }
+ }
+
+ case .wiredServerConnectionStartRequest:
+ ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in
+ if let wiredConnection = wiredConnection
+ {
+ print("Started wired server connection!")
+ self.connectionHandler?(wiredConnection)
+
+ var observation: NSKeyValueObservation?
+ observation = wiredConnection.observe(\.isConnected) { [weak self] (connection, change) in
+ guard !connection.isConnected else { return }
+ self?.disconnectionHandler?(connection)
+
+ observation?.invalidate()
+ }
+ }
+ else if let error = error
+ {
+ print("Error starting wired server connection.", error)
+ }
+ }
+
+ default: break
+ }
+ }
+}
+
+private extension WiredConnectionHandler
+{
+ @objc func deviceDidConnect(_ notification: Notification)
+ {
+ guard let device = notification.object as? ALTDevice else { return }
+ self.startNotificationConnection(to: device)
+ }
+
+ @objc func deviceDidDisconnect(_ notification: Notification)
+ {
+ guard let device = notification.object as? ALTDevice else { return }
+ self.stopNotificationConnection(to: device)
+ }
+}
diff --git a/AltServer/Connections/WirelessConnectionHandler.swift b/AltServer/Connections/WirelessConnectionHandler.swift
new file mode 100644
index 00000000..9efb1d23
--- /dev/null
+++ b/AltServer/Connections/WirelessConnectionHandler.swift
@@ -0,0 +1,148 @@
+//
+// WirelessConnectionHandler.swift
+// AltKit
+//
+// Created by Riley Testut on 6/1/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import Foundation
+import Network
+
+extension WirelessConnectionHandler
+{
+ public enum State
+ {
+ case notRunning
+ case connecting
+ case running(NWListener.Service)
+ case failed(Swift.Error)
+ }
+}
+
+public class WirelessConnectionHandler: ConnectionHandler
+{
+ public var connectionHandler: ((Connection) -> Void)?
+ public var disconnectionHandler: ((Connection) -> Void)?
+
+ public var stateUpdateHandler: ((State) -> Void)?
+
+ public private(set) var state: State = .notRunning {
+ didSet {
+ self.stateUpdateHandler?(self.state)
+ }
+ }
+
+ private lazy var listener = self.makeListener()
+ private let dispatchQueue = DispatchQueue(label: "io.altstore.WirelessConnectionListener", qos: .utility)
+
+ public func startListening()
+ {
+ switch self.state
+ {
+ case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue)
+ default: break
+ }
+ }
+
+ public func stopListening()
+ {
+ switch self.state
+ {
+ case .running: self.listener.cancel()
+ default: break
+ }
+ }
+}
+
+private extension WirelessConnectionHandler
+{
+ func makeListener() -> NWListener
+ {
+ let listener = try! NWListener(using: .tcp)
+
+ let service: NWListener.Service
+
+ if let serverID = UserDefaults.standard.serverID?.data(using: .utf8)
+ {
+ let txtDictionary = ["serverID": serverID]
+ let txtData = NetService.data(fromTXTRecord: txtDictionary)
+
+ service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData)
+ }
+ else
+ {
+ service = NWListener.Service(type: ALTServerServiceType)
+ }
+
+ listener.service = service
+
+ listener.serviceRegistrationUpdateHandler = { (serviceChange) in
+ switch serviceChange
+ {
+ case .add(.service(let name, let type, let domain, _)):
+ let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil)
+ self.state = .running(service)
+
+ default: break
+ }
+ }
+
+ listener.stateUpdateHandler = { (state) in
+ switch state
+ {
+ case .ready: break
+ case .waiting, .setup: self.state = .connecting
+ case .cancelled: self.state = .notRunning
+ case .failed(let error): self.state = .failed(error)
+ @unknown default: break
+ }
+ }
+
+ listener.newConnectionHandler = { [weak self] (connection) in
+ self?.prepare(connection)
+ }
+
+ return listener
+ }
+
+ func prepare(_ nwConnection: NWConnection)
+ {
+ print("Preparing:", nwConnection)
+
+ // Use same instance for all callbacks.
+ let connection = NetworkConnection(nwConnection)
+
+ nwConnection.stateUpdateHandler = { [weak self] (state) in
+ switch state
+ {
+ case .setup, .preparing: break
+
+ case .ready:
+ print("Connected to client:", connection)
+ self?.connectionHandler?(connection)
+
+ case .waiting:
+ print("Waiting for connection...")
+
+ case .failed(let error):
+ print("Failed to connect to service \(nwConnection.endpoint).", error)
+ self?.disconnect(connection)
+
+ case .cancelled:
+ self?.disconnect(connection)
+
+ @unknown default: break
+ }
+ }
+
+ nwConnection.start(queue: self.dispatchQueue)
+ }
+
+ func disconnect(_ connection: Connection)
+ {
+ connection.disconnect()
+
+ self.disconnectionHandler?(connection)
+ }
+}
diff --git a/AltServer/Devices/ALTDeviceManager+Installation.swift b/AltServer/Devices/ALTDeviceManager+Installation.swift
index a4dee469..63339ac0 100644
--- a/AltServer/Devices/ALTDeviceManager+Installation.swift
+++ b/AltServer/Devices/ALTDeviceManager+Installation.swift
@@ -16,6 +16,8 @@ private let appURL = URL(string: "https://f000.backblazeb2.com/file/altstore-sta
private let appURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore.ipa")!
#endif
+private let appGroupsLock = NSLock()
+
enum InstallError: LocalizedError
{
case cancelled
@@ -115,40 +117,18 @@ extension ALTDeviceManager
let anisetteData = try result.get()
session.anisetteData = anisetteData
- self.registerAppID(name: "AltStore", identifier: "com.rileytestut.AltStore", team: team, session: session) { (result) in
+ self.prepareAllProvisioningProfiles(for: application, team: team, session: session) { (result) in
do
{
- let appID = try result.get()
+ let profiles = try result.get()
- self.updateFeatures(for: appID, app: application, team: team, session: session) { (result) in
- do
- {
- let appID = try result.get()
-
- self.fetchProvisioningProfile(for: appID, team: team, session: session) { (result) in
- do
- {
- let provisioningProfile = try result.get()
-
- self.install(application, to: device, team: team, appID: appID, certificate: certificate, profile: provisioningProfile) { (result) in
- finish(result.error, title: "Failed to Install AltStore")
- }
- }
- catch
- {
- finish(error, title: "Failed to Fetch Provisioning Profile")
- }
- }
- }
- catch
- {
- finish(error, title: "Failed to Update App ID")
- }
+ self.install(application, to: device, team: team, certificate: certificate, profiles: profiles) { (result) in
+ finish(result.error, title: "Failed to Install AltStore")
}
}
catch
{
- finish(error, title: "Failed to Register App")
+ finish(error, title: "Failed to Fetch Provisioning Profiles")
}
}
}
@@ -427,6 +407,92 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
}
+ func prepareAllProvisioningProfiles(for application: ALTApplication, team: ALTTeam, session: ALTAppleAPISession,
+ completion: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
+ {
+ self.prepareProvisioningProfile(for: application, team: team, session: session) { (result) in
+ do
+ {
+ let profile = try result.get()
+
+ var profiles = [application.bundleIdentifier: profile]
+ var error: Error?
+
+ let dispatchGroup = DispatchGroup()
+
+ for appExtension in application.appExtensions
+ {
+ dispatchGroup.enter()
+
+ self.prepareProvisioningProfile(for: appExtension, team: team, session: session) { (result) in
+ switch result
+ {
+ case .failure(let e): error = e
+ case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
+ }
+
+ dispatchGroup.leave()
+ }
+ }
+
+ dispatchGroup.notify(queue: .global()) {
+ if let error = error
+ {
+ completion(.failure(error))
+ }
+ else
+ {
+ completion(.success(profiles))
+ }
+ }
+ }
+ catch
+ {
+ completion(.failure(error))
+ }
+ }
+ }
+
+ func prepareProvisioningProfile(for application: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void)
+ {
+ self.registerAppID(name: application.name, identifier: application.bundleIdentifier, team: team, session: session) { (result) in
+ do
+ {
+ let appID = try result.get()
+
+ self.updateFeatures(for: appID, app: application, team: team, session: session) { (result) in
+ do
+ {
+ let appID = try result.get()
+
+ self.updateAppGroups(for: appID, app: application, team: team, session: session) { (result) in
+ do
+ {
+ let appID = try result.get()
+
+ self.fetchProvisioningProfile(for: appID, team: team, session: session) { (result) in
+ completionHandler(result)
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+
func registerAppID(name appName: String, identifier: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void)
{
let bundleID = "com.\(team.identifier).\(identifier)"
@@ -468,11 +534,119 @@ To prevent this from happening, feel free to try again with another Apple ID to
features[.appGroups] = true
}
- let appID = appID.copy() as! ALTAppID
- appID.features = features
+ var updateFeatures = false
- ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in
- completionHandler(Result(appID, error))
+ // Determine whether the required features are already enabled for the AppID.
+ for (feature, value) in features
+ {
+ if let appIDValue = appID.features[feature] as AnyObject?, (value as AnyObject).isEqual(appIDValue)
+ {
+ // AppID already has this feature enabled and the values are the same.
+ continue
+ }
+ else
+ {
+ // AppID either doesn't have this feature enabled or the value has changed,
+ // so we need to update it to reflect new values.
+ updateFeatures = true
+ break
+ }
+ }
+
+ if updateFeatures
+ {
+ let appID = appID.copy() as! ALTAppID
+ appID.features = features
+
+ ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in
+ completionHandler(Result(appID, error))
+ }
+ }
+ else
+ {
+ completionHandler(.success(appID))
+ }
+ }
+
+ func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void)
+ {
+ let applicationGroups = app.entitlements[.appGroups] as? [String] ?? []
+ if applicationGroups.isEmpty
+ {
+ guard let isAppGroupsEnabled = appID.features[.appGroups] as? Bool, isAppGroupsEnabled else {
+ // No app groups, and we also haven't enabled the feature, so don't continue.
+ // For apps with no app groups but have had the feature enabled already
+ // we'll continue and assign the app ID to an empty array
+ // in case we need to explicitly remove them.
+ return completionHandler(.success(appID))
+ }
+ }
+
+ // Dispatch onto global queue to prevent appGroupsLock deadlock.
+ DispatchQueue.global().async {
+
+ // Ensure we're not concurrently fetching and updating app groups,
+ // which can lead to race conditions such as adding an app group twice.
+ appGroupsLock.lock()
+
+ func finish(_ result: Result)
+ {
+ appGroupsLock.unlock()
+ completionHandler(result)
+ }
+
+ ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in
+ switch Result(groups, error)
+ {
+ case .failure(let error): finish(.failure(error))
+ case .success(let fetchedGroups):
+ let dispatchGroup = DispatchGroup()
+
+ var groups = [ALTAppGroup]()
+ var errors = [Error]()
+
+ for groupIdentifier in applicationGroups
+ {
+ let adjustedGroupIdentifier = groupIdentifier + "." + team.identifier
+
+ if let group = fetchedGroups.first(where: { $0.groupIdentifier == adjustedGroupIdentifier })
+ {
+ groups.append(group)
+ }
+ else
+ {
+ dispatchGroup.enter()
+
+ // Not all characters are allowed in group names, so we replace periods with spaces (like Apple does).
+ let name = "AltStore " + groupIdentifier.replacingOccurrences(of: ".", with: " ")
+
+ ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in
+ switch Result(group, error)
+ {
+ case .success(let group): groups.append(group)
+ case .failure(let error): errors.append(error)
+ }
+
+ dispatchGroup.leave()
+ }
+ }
+ }
+
+ dispatchGroup.notify(queue: .global()) {
+ if let error = errors.first
+ {
+ finish(.failure(error))
+ }
+ else
+ {
+ ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in
+ let result = Result(success, error)
+ finish(result.map { _ in appID })
+ }
+ }
+ }
+ }
+ }
}
}
@@ -508,35 +682,75 @@ To prevent this from happening, feel free to try again with another Apple ID to
}
}
- func install(_ application: ALTApplication, to device: ALTDevice, team: ALTTeam, appID: ALTAppID, certificate: ALTCertificate, profile: ALTProvisioningProfile, completionHandler: @escaping (Result) -> Void)
+ func install(_ application: ALTApplication, to device: ALTDevice, team: ALTTeam, certificate: ALTCertificate, profiles: [String: ALTProvisioningProfile], completionHandler: @escaping (Result) -> Void)
{
+ func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws
+ {
+ guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) }
+ guard let profile = profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
+ guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
+
+ infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
+ infoDictionary[Bundle.Info.altBundleID] = identifier
+
+ for (key, value) in additionalInfoDictionaryValues
+ {
+ infoDictionary[key] = value
+ }
+
+ if let appGroups = profile.entitlements[.appGroups] as? [String]
+ {
+ infoDictionary[Bundle.Info.appGroups] = appGroups
+ }
+
+ try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
+ }
+
DispatchQueue.global().async {
do
{
- let infoPlistURL = application.fileURL.appendingPathComponent("Info.plist")
+ guard let appBundle = Bundle(url: application.fileURL) else { throw ALTError(.missingAppBundle) }
+ guard let infoDictionary = appBundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
+
+ let openAppURL = URL(string: "altstore-" + application.bundleIdentifier + "://")!
+
+ var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? []
+
+ // Embed open URL so AltBackup can return to AltStore.
+ let altstoreURLScheme = ["CFBundleTypeRole": "Editor",
+ "CFBundleURLName": application.bundleIdentifier,
+ "CFBundleURLSchemes": [openAppURL.scheme!]] as [String : Any]
+ allURLSchemes.append(altstoreURLScheme)
+
+ var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes]
+ additionalValues[Bundle.Info.deviceID] = device.identifier
+ additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID
- guard var infoDictionary = NSDictionary(contentsOf: infoPlistURL) as? [String: Any] else { throw ALTError(.missingInfoPlist) }
- infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
- infoDictionary[Bundle.Info.deviceID] = device.identifier
- infoDictionary[Bundle.Info.serverID] = UserDefaults.standard.serverID
- infoDictionary[Bundle.Info.certificateID] = certificate.serialNumber
- try (infoDictionary as NSDictionary).write(to: infoPlistURL)
-
if
let machineIdentifier = certificate.machineIdentifier,
let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier)
{
+ additionalValues[Bundle.Info.certificateID] = certificate.serialNumber
+
let certificateURL = application.fileURL.appendingPathComponent("ALTCertificate.p12")
try encryptedData.write(to: certificateURL, options: .atomic)
}
+ try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
+
+ for appExtension in application.appExtensions
+ {
+ guard let bundle = Bundle(url: appExtension.fileURL) else { throw ALTError(.missingAppBundle) }
+ try prepare(bundle)
+ }
+
let resigner = ALTSigner(team: team, certificate: certificate)
- resigner.signApp(at: application.fileURL, provisioningProfiles: [profile]) { (success, error) in
+ resigner.signApp(at: application.fileURL, provisioningProfiles: Array(profiles.values)) { (success, error) in
do
{
try Result(success, error).get()
- let activeProfiles: Set? = (team.type == .free) ? [profile.bundleIdentifier] : nil
+ let activeProfiles: Set? = (team.type == .free) ? Set(profiles.values.map(\.bundleIdentifier)) : nil
ALTDeviceManager.shared.installApp(at: application.fileURL, toDeviceWithUDID: device.identifier, activeProvisioningProfiles: activeProfiles) { (success, error) in
completionHandler(Result(success, error))
}
diff --git a/AltServer/Devices/ALTDeviceManager.h b/AltServer/Devices/ALTDeviceManager.h
index 8d27d9f3..03f78c97 100644
--- a/AltServer/Devices/ALTDeviceManager.h
+++ b/AltServer/Devices/ALTDeviceManager.h
@@ -7,7 +7,7 @@
//
#import
-#import
+#import "AltSign.h"
@class ALTWiredConnection;
@class ALTNotificationConnection;
@@ -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..219cbc46 100644
--- a/AltServer/Devices/ALTDeviceManager.mm
+++ b/AltServer/Devices/ALTDeviceManager.mm
@@ -8,10 +8,12 @@
#import "ALTDeviceManager.h"
-#import "AltKit.h"
#import "ALTWiredConnection+Private.h"
#import "ALTNotificationConnection+Private.h"
+#import "ALTConstants.h"
+#import "NSError+ALTServerError.h"
+
#include
#include
#include
@@ -20,6 +22,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 +31,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 +59,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 +504,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
@@ -1032,7 +1119,7 @@ NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification = @"ALT
NSString *name = [NSString stringWithCString:device_name encoding:NSUTF8StringEncoding];
NSString *identifier = [NSString stringWithCString:udid encoding:NSUTF8StringEncoding];
- ALTDevice *altDevice = [[ALTDevice alloc] initWithName:name identifier:identifier];
+ ALTDevice *altDevice = [[ALTDevice alloc] initWithName:name identifier:identifier type:ALTDeviceTypeiPhone];
[connectedDevices addObject:altDevice];
if (device_name != NULL)
@@ -1075,7 +1162,7 @@ void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid)
{
if (code != 0 || name != NULL)
{
- NSLog(@"Error installing app. %@ (%@). %@", @(code), @(name), @(description));
+ NSLog(@"Error installing app. %@ (%@). %@", @(code), @(name ?: ""), @(description ?: ""));
NSError *error = nil;
@@ -1085,14 +1172,14 @@ void ALTDeviceManagerUpdateStatus(plist_t command, plist_t status, void *uuid)
}
else
{
- NSString *errorName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
+ NSString *errorName = [NSString stringWithCString:name ?: "" encoding:NSUTF8StringEncoding];
if ([errorName isEqualToString:@"DeviceOSVersionTooLow"])
{
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorUnsupportediOSVersion userInfo:nil];
}
else
{
- NSError *underlyingError = [NSError errorWithDomain:AltServerInstallationErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(description)}];
+ NSError *underlyingError = [NSError errorWithDomain:AltServerInstallationErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: @(description ?: "")}];
error = [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorInstallationFailed userInfo:@{NSUnderlyingErrorKey: underlyingError}];
}
}
@@ -1117,6 +1204,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) {
diff --git a/AltServer/PluginManager.swift b/AltServer/PluginManager.swift
new file mode 100644
index 00000000..d5b2e3d2
--- /dev/null
+++ b/AltServer/PluginManager.swift
@@ -0,0 +1,310 @@
+//
+// PluginManager.swift
+// AltServer
+//
+// Created by Riley Testut on 9/16/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import Foundation
+import AppKit
+import CryptoKit
+
+import STPrivilegedTask
+
+private let pluginDirectoryURL = URL(fileURLWithPath: "/Library/Mail/Bundles", isDirectory: true)
+private let pluginURL = pluginDirectoryURL.appendingPathComponent("AltPlugin.mailbundle")
+
+enum PluginError: LocalizedError
+{
+ case cancelled
+ case unknown
+ case notFound
+ case mismatchedHash(hash: String, expectedHash: String)
+ case taskError(String)
+ case taskErrorCode(Int)
+
+ var errorDescription: String? {
+ switch self
+ {
+ case .cancelled: return NSLocalizedString("Mail plug-in installation was cancelled.", comment: "")
+ case .unknown: return NSLocalizedString("Failed to install Mail plug-in.", comment: "")
+ case .notFound: return NSLocalizedString("The Mail plug-in does not exist at the requested URL.", comment: "")
+ case .mismatchedHash(let hash, let expectedHash): return String(format: NSLocalizedString("The hash of the downloaded Mail plug-in does not match the expected hash.\n\nHash:\n%@\n\nExpected Hash:\n%@", comment: ""), hash, expectedHash)
+ case .taskError(let output): return output
+ case .taskErrorCode(let errorCode): return String(format: NSLocalizedString("There was an error installing the Mail plug-in. (Error Code: %@)", comment: ""), NSNumber(value: errorCode))
+ }
+ }
+}
+
+struct PluginVersion
+{
+ var url: URL
+ var sha256Hash: String
+ var version: String
+
+ static let v1_0 = PluginVersion(url: URL(string: "https://f000.backblazeb2.com/file/altstore/altserver/altplugin/1_0.zip")!,
+ sha256Hash: "070e9b7e1f74e7a6474d36253ab5a3623ff93892acc9e1043c3581f2ded12200",
+ version: "1.0")
+
+ static let v1_1 = PluginVersion(url: Bundle.main.url(forResource: "AltPlugin", withExtension: "zip")!,
+ sha256Hash: "cd1e8c85cbb1935d2874376566671f3c5823101d4933fc6ee63bab8b2a37f800",
+ version: "1.1")
+}
+
+class PluginManager
+{
+ var isMailPluginInstalled: Bool {
+ let isMailPluginInstalled = FileManager.default.fileExists(atPath: pluginURL.path)
+ return isMailPluginInstalled
+ }
+
+ var isUpdateAvailable: Bool {
+ guard let bundle = Bundle(url: pluginURL) else { return false }
+
+ // Load Info.plist from disk because Bundle.infoDictionary is cached by system.
+ let infoDictionaryURL = bundle.bundleURL.appendingPathComponent("Contents/Info.plist")
+ guard let infoDictionary = NSDictionary(contentsOf: infoDictionaryURL) as? [String: Any],
+ let version = infoDictionary["CFBundleShortVersionString"] as? String
+ else { return false }
+
+ let isUpdateAvailable = (version != self.preferredVersion.version)
+ return isUpdateAvailable
+ }
+
+ private var preferredVersion: PluginVersion {
+ if #available(macOS 11, *)
+ {
+ return .v1_1
+ }
+ else
+ {
+ return .v1_0
+ }
+ }
+}
+
+extension PluginManager
+{
+ func installMailPlugin(completionHandler: @escaping (Result) -> Void)
+ {
+ do
+ {
+ let alert = NSAlert()
+
+ if self.isUpdateAvailable
+ {
+ alert.messageText = NSLocalizedString("Update Mail Plug-in", comment: "")
+ alert.informativeText = NSLocalizedString("An update is available for AltServer's Mail plug-in. Please update the plug-in now in order to keep using AltStore.", comment: "")
+
+ alert.addButton(withTitle: NSLocalizedString("Update Plug-in", comment: ""))
+ alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
+ }
+ else
+ {
+ alert.messageText = NSLocalizedString("Install Mail Plug-in", comment: "")
+ alert.informativeText = NSLocalizedString("AltServer requires a Mail plug-in in order to retrieve necessary information about your Apple ID. Would you like to install it now?", comment: "")
+
+ alert.addButton(withTitle: NSLocalizedString("Install Plug-in", comment: ""))
+ alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
+ }
+
+ NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
+
+ let response = alert.runModal()
+ guard response == .alertFirstButtonReturn else { throw PluginError.cancelled }
+
+ self.downloadPlugin { (result) in
+ do
+ {
+ let fileURL = try result.get()
+
+ // Ensure plug-in directory exists.
+ let authorization = try self.runAndKeepAuthorization("mkdir", arguments: ["-p", pluginDirectoryURL.path])
+
+ // Create temporary directory.
+ let temporaryDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
+ try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
+ defer { try? FileManager.default.removeItem(at: temporaryDirectoryURL) }
+
+ // Unzip AltPlugin to temporary directory.
+ try self.runAndKeepAuthorization("unzip", arguments: ["-o", fileURL.path, "-d", temporaryDirectoryURL.path], authorization: authorization)
+
+ if FileManager.default.fileExists(atPath: pluginURL.path)
+ {
+ // Delete existing Mail plug-in.
+ try self.runAndKeepAuthorization("rm", arguments: ["-rf", pluginURL.path], authorization: authorization)
+ }
+
+ // Copy AltPlugin to Mail plug-ins directory.
+ // Must be separate step than unzip to prevent macOS from considering plug-in corrupted.
+ let unzippedPluginURL = temporaryDirectoryURL.appendingPathComponent(pluginURL.lastPathComponent)
+ try self.runAndKeepAuthorization("cp", arguments: ["-R", unzippedPluginURL.path, pluginDirectoryURL.path], authorization: authorization)
+
+ guard self.isMailPluginInstalled else { throw PluginError.unknown }
+
+ // Enable Mail plug-in preferences.
+ try self.run("defaults", arguments: ["write", "/Library/Preferences/com.apple.mail", "EnableBundles", "-bool", "YES"], authorization: authorization)
+
+ print("Finished installing Mail plug-in!")
+
+ completionHandler(.success(()))
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+ catch
+ {
+ completionHandler(.failure(PluginError.cancelled))
+ }
+ }
+
+ func uninstallMailPlugin(completionHandler: @escaping (Result) -> Void)
+ {
+ let alert = NSAlert()
+ alert.messageText = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
+ alert.informativeText = NSLocalizedString("Are you sure you want to uninstall the AltServer Mail plug-in? You will no longer be able to install or refresh apps with AltStore.", comment: "")
+
+ alert.addButton(withTitle: NSLocalizedString("Uninstall Plug-in", comment: ""))
+ alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
+
+ NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
+
+ let response = alert.runModal()
+ guard response == .alertFirstButtonReturn else { return completionHandler(.failure(PluginError.cancelled)) }
+
+ DispatchQueue.global().async {
+ do
+ {
+ if FileManager.default.fileExists(atPath: pluginURL.path)
+ {
+ // Delete Mail plug-in from privileged directory.
+ try self.run("rm", arguments: ["-rf", pluginURL.path])
+ }
+
+ completionHandler(.success(()))
+ }
+ catch
+ {
+ completionHandler(.failure(error))
+ }
+ }
+ }
+}
+
+private extension PluginManager
+{
+ func downloadPlugin(completion: @escaping (Result) -> Void)
+ {
+ let pluginVersion = self.preferredVersion
+
+ func finish(_ result: Result)
+ {
+ do
+ {
+ let fileURL = try result.get()
+
+ if #available(OSX 10.15, *)
+ {
+ let data = try Data(contentsOf: fileURL)
+ let sha256Hash = SHA256.hash(data: data)
+ let hashString = sha256Hash.compactMap { String(format: "%02x", $0) }.joined()
+
+ print("Comparing Mail plug-in hash (\(hashString)) against expected hash (\(pluginVersion.sha256Hash))...")
+ guard hashString == pluginVersion.sha256Hash else { throw PluginError.mismatchedHash(hash: hashString, expectedHash: pluginVersion.sha256Hash) }
+ }
+
+ completion(.success(fileURL))
+ }
+ catch
+ {
+ completion(.failure(error))
+ }
+ }
+
+ if pluginVersion.url.isFileURL
+ {
+ finish(.success(pluginVersion.url))
+ }
+ else
+ {
+ let downloadTask = URLSession.shared.downloadTask(with: pluginVersion.url) { (fileURL, response, error) in
+ if let response = response as? HTTPURLResponse
+ {
+ guard response.statusCode != 404 else { return finish(.failure(PluginError.notFound)) }
+ }
+
+ let result = Result(fileURL, error)
+ finish(result)
+
+ if let fileURL = fileURL
+ {
+ try? FileManager.default.removeItem(at: fileURL)
+ }
+ }
+
+ downloadTask.resume()
+ }
+ }
+
+ func run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws
+ {
+ _ = try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: true)
+ }
+
+ @discardableResult
+ func runAndKeepAuthorization(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws -> AuthorizationRef
+ {
+ return try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: false)
+ }
+
+ func _run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil, freeAuthorization: Bool) throws -> AuthorizationRef
+ {
+ var launchPath = "/usr/bin/" + program
+ if !FileManager.default.fileExists(atPath: launchPath)
+ {
+ launchPath = "/bin/" + program
+ }
+
+ print("Running program:", launchPath)
+
+ let task = STPrivilegedTask()
+ task.launchPath = launchPath
+ task.arguments = arguments
+ task.freeAuthorizationWhenDone = freeAuthorization
+
+ let errorCode: OSStatus
+
+ if let authorization = authorization
+ {
+ errorCode = task.launch(withAuthorization: authorization)
+ }
+ else
+ {
+ errorCode = task.launch()
+ }
+
+ guard errorCode == 0 else { throw PluginError.taskErrorCode(Int(errorCode)) }
+
+ task.waitUntilExit()
+
+ print("Exit code:", task.terminationStatus)
+
+ guard task.terminationStatus == 0 else {
+ let outputData = task.outputFileHandle.readDataToEndOfFile()
+
+ if let outputString = String(data: outputData, encoding: .utf8), !outputString.isEmpty
+ {
+ throw PluginError.taskError(outputString)
+ }
+
+ throw PluginError.taskErrorCode(Int(task.terminationStatus))
+ }
+
+ guard let authorization = task.authorization else { throw PluginError.unknown }
+ return authorization
+ }
+}
diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj
index ea087e79..4db6af16 100644
--- a/AltStore.xcodeproj/project.pbxproj
+++ b/AltStore.xcodeproj/project.pbxproj
@@ -3,53 +3,54 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 51;
+ objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
- 01100C7036F0EBAC5B30984B /* libPods-AltStore.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DE618FA97EA42C3F468D186 /* libPods-AltStore.a */; };
+ 0E33F94B8D78AB969FD309A3 /* Pods_AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A08F67C18350C7990753F03F /* Pods_AltStoreCore.framework */; };
+ 2A77E3D272F3D92436FAC272 /* Pods_AltStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9EEAA842DA87A88A870053B /* Pods_AltStore.framework */; };
A8BCEBEAC0620CF80A2FD26D /* Pods_AltServer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */; };
- BF0201BA22C2EFA3000B93E4 /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; };
- BF0201BB22C2EFA3000B93E4 /* AltSign.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF5AB3A72285FE6C00DC914B /* AltSign.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF0201BD22C2EFBC000B93E4 /* openssl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4713A422976CFC00784A2F /* openssl.framework */; };
- BF0201BE22C2EFBC000B93E4 /* openssl.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF4713A422976CFC00784A2F /* openssl.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- BF02419422F2156E00129732 /* RefreshAttempt.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02419322F2156E00129732 /* RefreshAttempt.swift */; };
BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */; };
BF0241AA22F29CCD00129732 /* UserDefaults+AltServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0241A922F29CCD00129732 /* UserDefaults+AltServer.swift */; };
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858222DE795100DE9F1E /* MyAppsViewController.swift */; };
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */; };
+ BF088D0F25019ABA008082D9 /* AltSign-Static in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D0E25019ABA008082D9 /* AltSign-Static */; };
+ BF088D332501A4FF008082D9 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; };
+ BF088D342501A4FF008082D9 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ BF088D362501A821008082D9 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = BF088D352501A821008082D9 /* AltSign-Dynamic */; };
+ BF088D372501A821008082D9 /* AltSign-Dynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = BF088D352501A821008082D9 /* AltSign-Dynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+ BF088D382501A833008082D9 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; };
+ BF088D392501A833008082D9 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */; };
BF0DCA662433BDF500E3A595 /* AnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0DCA652433BDF500E3A595 /* AnalyticsManager.swift */; };
- BF0F5FC723F394AD0080DB64 /* AltStore3ToAltStore4.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF0F5FC623F394AD0080DB64 /* AltStore3ToAltStore4.xcmappingmodel */; };
- BF100C50232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF100C4F232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel */; };
- BF100C54232D7DAE006A8926 /* StoreAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF100C53232D7DAE006A8926 /* StoreAppPolicy.swift */; };
+ BF10EB34248730750055E6DB /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF10EB33248730750055E6DB /* main.swift */; };
+ BF1614F1250822F100767AEA /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFD247862284BB3B00981D42 /* Roxas.framework */; };
+ BF1614F2250822F100767AEA /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFD247862284BB3B00981D42 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BF18B0F122E25DF9005C4CF5 /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18B0F022E25DF9005C4CF5 /* ToastView.swift */; };
- BF1E312B229F474900370A3C /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3129229F474900370A3C /* ConnectionManager.swift */; };
- BF1E315722A061F500370A3C /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
- BF1E315822A061F900370A3C /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAC8852295C90300587369 /* Result+Conveniences.swift */; };
- BF1E315922A061FB00370A3C /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
- BF1E315A22A0620000370A3C /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
- BF1E315F22A0635900370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
- BF1E316022A0636400370A3C /* libAltKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1E315022A0616100370A3C /* libAltKit.a */; };
- BF258CE322EBAE2800023032 /* AppProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF258CE222EBAE2800023032 /* AppProtocol.swift */; };
- BF26A0E12370C5D400F53F9F /* ALTSourceUserInfoKey.m in Sources */ = {isa = PBXBuildFile; fileRef = BF26A0E02370C5D400F53F9F /* ALTSourceUserInfoKey.m */; };
+ BF18BFFD2485A1E400DD5981 /* WiredConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFFC2485A1E400DD5981 /* WiredConnectionHandler.swift */; };
+ BF1E312B229F474900370A3C /* RequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3129229F474900370A3C /* RequestHandler.swift */; };
+ BF1FE358251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FE357251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift */; };
+ BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FE357251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift */; };
BF29012F2318F6B100D88A45 /* AppBannerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF29012E2318F6B100D88A45 /* AppBannerView.xib */; };
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2901302318F7A800D88A45 /* AppBannerView.swift */; };
+ BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BF989191250AAE86002ACF50 /* ViewApp.intentdefinition */; };
+ BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3432FA246B894F0052F4A1 /* BackupAppOperation.swift */; };
BF3BEFBF2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3BEFBE2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift */; };
BF3BEFC124086A1E00DE7D55 /* RefreshAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3BEFC024086A1E00DE7D55 /* RefreshAppOperation.swift */; };
- BF3D648822E79A3700E9056B /* AppPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D648722E79A3700E9056B /* AppPermission.swift */; };
- BF3D648D22E79AC800E9056B /* ALTAppPermission.m in Sources */ = {isa = PBXBuildFile; fileRef = BF3D648C22E79AC800E9056B /* ALTAppPermission.m */; };
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */; };
BF3D649F22E7B24C00E9056B /* CollapsingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */; };
- BF3D64A222E8031100E9056B /* MergePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D64A122E8031100E9056B /* MergePolicy.swift */; };
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */; };
BF3F786422CAA41E008FBD20 /* ALTDeviceManager+Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */; };
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF41B805233423AE00C593A3 /* TabBarController.swift */; };
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF41B807233433C100C593A3 /* LoadingState.swift */; };
- BF43002E22A714AF0051E2BC /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002D22A714AF0051E2BC /* Keychain.swift */; };
- BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */; };
+ BF42345A25101C35006D1EB2 /* WidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF42345825101C1D006D1EB2 /* WidgetView.swift */; };
+ BF42345C251024B0006D1EB2 /* AltSign-Static in Frameworks */ = {isa = PBXBuildFile; productRef = BF42345B251024B0006D1EB2 /* AltSign-Static */; };
+ BF42345D25102688006D1EB2 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF088D322501A4FF008082D9 /* OpenSSL.xcframework */; };
BF44CC6C232AEB90004DA9C3 /* LaunchAtLogin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF44CC6A232AEB74004DA9C3 /* LaunchAtLogin.framework */; };
BF44CC6D232AEB90004DA9C3 /* LaunchAtLogin.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF44CC6A232AEB74004DA9C3 /* LaunchAtLogin.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF44EEEF246B08BA002A52F2 /* BackupController.swift */; };
+ BF44EEF3246B3A17002A52F2 /* AltBackup.ipa in Resources */ = {isa = PBXBuildFile; fileRef = BF44EEF2246B3A17002A52F2 /* AltBackup.ipa */; };
+ BF44EEFC246B4550002A52F2 /* RemoveAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */; };
BF458690229872EA00BD7491 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF45868F229872EA00BD7491 /* AppDelegate.swift */; };
BF458694229872EA00BD7491 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF458693229872EA00BD7491 /* Assets.xcassets */; };
BF458697229872EA00BD7491 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF458695229872EA00BD7491 /* Main.storyboard */; };
@@ -117,23 +118,69 @@
BF45884A2298D55000BD7491 /* thread.c in Sources */ = {isa = PBXBuildFile; fileRef = BF4588482298D55000BD7491 /* thread.c */; };
BF45884B2298D55000BD7491 /* thread.h in Headers */ = {isa = PBXBuildFile; fileRef = BF4588492298D55000BD7491 /* thread.h */; };
BF4588882298DD3F00BD7491 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4588872298DD3F00BD7491 /* libxml2.tbd */; };
- BF4C7F2523801F0800B2556E /* AltSign.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9B63C5229DD44D002F0A62 /* AltSign.framework */; };
- BF54E8212315EF0D000AE0D8 /* ALTPatreonBenefitType.m in Sources */ = {isa = PBXBuildFile; fileRef = BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */; };
- BF56D2AA23DF88310006506D /* AppID.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF56D2A923DF88310006506D /* AppID.swift */; };
+ BF4B78FE24B3D1DB008AB4AC /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4B78FD24B3D1DB008AB4AC /* SceneDelegate.swift */; };
BF56D2AC23DF8E170006506D /* FetchAppIDsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF56D2AB23DF8E170006506D /* FetchAppIDsOperation.swift */; };
BF56D2AF23DF9E310006506D /* AppIDsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF56D2AE23DF9E310006506D /* AppIDsViewController.swift */; };
+ BF58047E246A28F7008AE704 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF58047D246A28F7008AE704 /* AppDelegate.swift */; };
+ BF580482246A28F7008AE704 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580481246A28F7008AE704 /* ViewController.swift */; };
+ BF580487246A28F9008AE704 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF580486246A28F9008AE704 /* Assets.xcassets */; };
+ BF58048A246A28F9008AE704 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF580488246A28F9008AE704 /* LaunchScreen.storyboard */; };
+ BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */; };
+ BF580498246A3D19008AE704 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF580497246A3D19008AE704 /* UIKit.framework */; };
+ BF58049B246A432D008AE704 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
BF5C5FCF237DF69100EDD0C6 /* ALTPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = BF5C5FCE237DF69100EDD0C6 /* ALTPluginService.m */; };
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */; };
- BF6C336224197D700034FD24 /* NSError+LocalizedFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+LocalizedFailure.swift */; };
- BF6C33652419AE310034FD24 /* AltStore4ToAltStore5.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF6C33642419AE310034FD24 /* AltStore4ToAltStore5.xcmappingmodel */; };
+ BF66EE822501AE50007EE018 /* AltStoreCore.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE802501AE50007EE018 /* AltStoreCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; };
+ BF66EE862501AE50007EE018 /* AltStoreCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ BF66EE8C2501AEB2007EE018 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE8B2501AEB1007EE018 /* Keychain.swift */; };
+ BF66EE942501AEBC007EE018 /* ALTAppPermission.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE8E2501AEBC007EE018 /* ALTAppPermission.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ BF66EE952501AEBC007EE018 /* ALTSourceUserInfoKey.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE8F2501AEBC007EE018 /* ALTSourceUserInfoKey.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ BF66EE962501AEBC007EE018 /* ALTPatreonBenefitType.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE902501AEBC007EE018 /* ALTPatreonBenefitType.m */; };
+ BF66EE972501AEBC007EE018 /* ALTAppPermission.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE912501AEBC007EE018 /* ALTAppPermission.m */; };
+ BF66EE982501AEBC007EE018 /* ALTPatreonBenefitType.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE922501AEBC007EE018 /* ALTPatreonBenefitType.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ BF66EE992501AEBC007EE018 /* ALTSourceUserInfoKey.m in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE932501AEBC007EE018 /* ALTSourceUserInfoKey.m */; };
+ BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE9B2501AEC1007EE018 /* AppProtocol.swift */; };
+ BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EE9C2501AEC1007EE018 /* Fetchable.swift */; };
+ BF66EEA52501AEC5007EE018 /* Benefit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEA02501AEC5007EE018 /* Benefit.swift */; };
+ BF66EEA62501AEC5007EE018 /* PatreonAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEA12501AEC5007EE018 /* PatreonAPI.swift */; };
+ BF66EEA72501AEC5007EE018 /* Campaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEA22501AEC5007EE018 /* Campaign.swift */; };
+ BF66EEA82501AEC5007EE018 /* Patron.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEA32501AEC5007EE018 /* Patron.swift */; };
+ BF66EEA92501AEC5007EE018 /* Tier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEA42501AEC5007EE018 /* Tier.swift */; };
+ BF66EECC2501AECA007EE018 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEAB2501AECA007EE018 /* Source.swift */; };
+ BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEAE2501AECA007EE018 /* StoreAppPolicy.swift */; };
+ BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEAF2501AECA007EE018 /* InstalledAppPolicy.swift */; };
+ BF66EECF2501AECA007EE018 /* AltStoreToAltStore2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEB12501AECA007EE018 /* AltStoreToAltStore2.xcmappingmodel */; };
+ BF66EED02501AECA007EE018 /* AltStore6ToAltStore7.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEB22501AECA007EE018 /* AltStore6ToAltStore7.xcmappingmodel */; };
+ BF66EED12501AECA007EE018 /* AltStore3ToAltStore4.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEB32501AECA007EE018 /* AltStore3ToAltStore4.xcmappingmodel */; };
+ BF66EED22501AECA007EE018 /* AltStore4ToAltStore5.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEB42501AECA007EE018 /* AltStore4ToAltStore5.xcmappingmodel */; };
+ BF66EED32501AECA007EE018 /* AltStore2ToAltStore3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEB52501AECA007EE018 /* AltStore2ToAltStore3.xcmappingmodel */; };
+ BF66EED42501AECA007EE018 /* AltStore5ToAltStore6.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEB62501AECA007EE018 /* AltStore5ToAltStore6.xcmappingmodel */; };
+ BF66EED52501AECA007EE018 /* AltStore.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEB72501AECA007EE018 /* AltStore.xcdatamodeld */; };
+ BF66EED62501AECA007EE018 /* NewsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEBF2501AECA007EE018 /* NewsItem.swift */; };
+ BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC02501AECA007EE018 /* InstalledApp.swift */; };
+ BF66EED82501AECA007EE018 /* SecureValueTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC12501AECA007EE018 /* SecureValueTransformer.swift */; };
+ BF66EED92501AECA007EE018 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC22501AECA007EE018 /* Team.swift */; };
+ BF66EEDA2501AECA007EE018 /* RefreshAttempt.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC32501AECA007EE018 /* RefreshAttempt.swift */; };
+ BF66EEDB2501AECA007EE018 /* StoreApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC42501AECA007EE018 /* StoreApp.swift */; };
+ BF66EEDC2501AECA007EE018 /* MergePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC52501AECA007EE018 /* MergePolicy.swift */; };
+ BF66EEDD2501AECA007EE018 /* AppPermission.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC62501AECA007EE018 /* AppPermission.swift */; };
+ BF66EEDE2501AECA007EE018 /* AppID.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC72501AECA007EE018 /* AppID.swift */; };
+ BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC82501AECA007EE018 /* PatreonAccount.swift */; };
+ BF66EEE02501AECA007EE018 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEC92501AECA007EE018 /* Account.swift */; };
+ BF66EEE12501AECA007EE018 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EECA2501AECA007EE018 /* DatabaseManager.swift */; };
+ BF66EEE22501AECA007EE018 /* InstalledExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EECB2501AECA007EE018 /* InstalledExtension.swift */; };
+ BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE42501AED0007EE018 /* UserDefaults+AltStore.swift */; };
+ BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE52501AED0007EE018 /* JSONDecoder+Properties.swift */; };
+ BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */; };
+ BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE72501AED0007EE018 /* UIApplication+AppExtension.swift */; };
+ BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAA242935ED00125131 /* NSAttributedString+Markdown.m */; };
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAD2429597900125131 /* BannerCollectionViewCell.swift */; };
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAF2429599900125131 /* TextCollectionReusableView.swift */; };
BF6F439223644C6E00A0B879 /* RefreshAltStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6F439123644C6E00A0B879 /* RefreshAltStoreViewController.swift */; };
- BF718BC923C919E300A89F2D /* CFNotificationName+AltStore.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */; };
- BF718BD123C91BD300A89F2D /* ALTWiredConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD023C91BD300A89F2D /* ALTWiredConnection.m */; };
- BF718BD523C928A300A89F2D /* ALTNotificationConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD423C928A300A89F2D /* ALTNotificationConnection.m */; };
- BF718BD823C93DB700A89F2D /* AltKit.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD723C93DB700A89F2D /* AltKit.m */; };
+ BF718BD123C91BD300A89F2D /* ALTWiredConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD023C91BD300A89F2D /* ALTWiredConnection.mm */; };
+ BF718BD523C928A300A89F2D /* ALTNotificationConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD423C928A300A89F2D /* ALTNotificationConnection.mm */; };
BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF74989A23621C0700CED65F /* ForwardingNavigationController.swift */; };
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */; };
BF770E5422BC044E002A40FE /* OperationContexts.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5322BC044E002A40FE /* OperationContexts.swift */; };
@@ -141,46 +188,69 @@
BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E5722BC3D0F002A40FE /* RefreshGroup.swift */; };
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */; };
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */ = {isa = PBXBuildFile; fileRef = BF770E6822BD57DD002A40FE /* Silence.m4a */; };
- BF7C627223DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF7C627123DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel */; };
- BF7C627423DBB78C00515A2D /* InstalledAppPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF7C627323DBB78C00515A2D /* InstalledAppPolicy.swift */; };
+ BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF88F97124F8727D00BB75DF /* AppManagerErrors.swift */; };
+ BF8B17EB250AC40000F8157F /* FileManager+SharedDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */; };
+ BF8CAE452489E772004D6CCE /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */; };
+ BF8CAE462489E772004D6CCE /* AppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE432489E772004D6CCE /* AppManager.swift */; };
+ BF8CAE472489E772004D6CCE /* DaemonRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE442489E772004D6CCE /* DaemonRequestHandler.swift */; };
+ BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */; };
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C122E659F700049BA1 /* AppContentViewController.swift */; };
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F69C322E662D300049BA1 /* AppViewController.swift */; };
- BF9A03C623C7DD0D000D08DB /* ClientConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9A03C523C7DD0D000D08DB /* ClientConnection.swift */; };
+ BF989171250AABF4002ACF50 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF989170250AABF4002ACF50 /* Assets.xcassets */; };
+ BF989177250AABF4002ACF50 /* AltWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = BF989167250AABF3002ACF50 /* AltWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ BF98917E250AAC4F002ACF50 /* Countdown.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF98917C250AAC4F002ACF50 /* Countdown.swift */; };
+ BF98917F250AAC4F002ACF50 /* AltWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF98917D250AAC4F002ACF50 /* AltWidget.swift */; };
+ BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */; };
+ BF989185250AAD1D002ACF50 /* UIColor+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */; };
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */; };
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */; };
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */; };
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4A22DD137F008935CF /* NavigationBar.swift */; };
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4C22DD16DE008935CF /* PillButton.swift */; };
- BF9ABA4F22DD41A9008935CF /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4E22DD41A9008935CF /* UIColor+Hex.swift */; };
- BF9F8D1A242AA6BC0024E48B /* AltStore5ToAltStore6.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BF9F8D19242AA6BC0024E48B /* AltStore5ToAltStore6.xcmappingmodel */; };
BFA8172923C56042001B5953 /* ServerConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172823C56042001B5953 /* ServerConnection.swift */; };
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */; };
- BFA8172D23C5823E001B5953 /* InstalledExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172C23C5823E001B5953 /* InstalledExtension.swift */; };
- BFB11692229322E400BB457C /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB11691229322E400BB457C /* DatabaseManager.swift */; };
- BFB1169B2293274D00BB457C /* JSONDecoder+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB1169A2293274D00BB457C /* JSONDecoder+Properties.swift */; };
+ BFAECC522501B0A400528F27 /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
+ BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
+ BFAECC542501B0A400528F27 /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
+ BFAECC552501B0A400528F27 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; };
+ BFAECC562501B0A400528F27 /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; };
+ BFAECC572501B0A400528F27 /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; };
+ BFAECC582501B0A400528F27 /* ALTConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD723C93DB700A89F2D /* ALTConstants.m */; };
+ BFAECC592501B0A400528F27 /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAC8852295C90300587369 /* Result+Conveniences.swift */; };
+ BFAECC5A2501B0A400528F27 /* NetworkConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CD2489ABE90097E58C /* NetworkConnection.swift */; };
+ BFAECC5B2501B0A400528F27 /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
+ BFAECC5C2501B0A400528F27 /* CFNotificationName+AltStore.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */; };
+ BFAECC5D2501B0BF00528F27 /* ALTConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = BF18BFFE2485A42800DD5981 /* ALTConnection.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ BFAECC5E2501B0BF00528F27 /* CFNotificationName+AltStore.h in Headers */ = {isa = PBXBuildFile; fileRef = BF718BC723C919CC00A89F2D /* CFNotificationName+AltStore.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ BFAECC5F2501B0BF00528F27 /* ALTConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD52BD222A06EFB000B7ED1 /* ALTConstants.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ BFAECC602501B0BF00528F27 /* NSError+ALTServerError.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1E314822A060F400370A3C /* NSError+ALTServerError.h */; settings = {ATTRIBUTES = (Public, ); }; };
BFB3645A2325985F00CD0EB1 /* FindServerOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB364592325985F00CD0EB1 /* FindServerOperation.swift */; };
+ BFB39B5C252BC10E00D1BE50 /* Managed.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB39B5B252BC10E00D1BE50 /* Managed.swift */; };
BFB4323F22DE852000B7F8BC /* UpdateCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFB4323E22DE852000B7F8BC /* UpdateCollectionViewCell.xib */; };
BFB49AAA23834CF900D542D9 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = BFB49AA823834CF900D542D9 /* ALTAnisetteData.m */; };
- BFB6B21B23186D640022A802 /* NewsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB6B21A23186D640022A802 /* NewsItem.swift */; };
BFB6B21E231870160022A802 /* NewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB6B21D231870160022A802 /* NewsViewController.swift */; };
BFB6B220231870B00022A802 /* NewsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB6B21F231870B00022A802 /* NewsCollectionViewCell.swift */; };
BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFB6B22323187A3D0022A802 /* NewsCollectionViewCell.xib */; };
- BFBBE2DD22931B20002097FA /* AltStore.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2DB22931B20002097FA /* AltStore.xcdatamodeld */; };
- BFBBE2DF22931F73002097FA /* StoreApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2DE22931F73002097FA /* StoreApp.swift */; };
- BFBBE2E122931F81002097FA /* InstalledApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBBE2E022931F81002097FA /* InstalledApp.swift */; };
+ BFBE0004250ACFFB0080826E /* ViewApp.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BF989191250AAE86002ACF50 /* ViewApp.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; };
+ BFBE0007250AD0E70080826E /* ViewAppIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF989190250AAE86002ACF50 /* ViewAppIntentHandler.swift */; };
+ BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = BFBF331A2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel */; };
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC1F38C22AEE3A4003AC21A /* DownloadAppOperation.swift */; };
BFC57A652416C72400EB891E /* DeactivateAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC57A642416C72400EB891E /* DeactivateAppOperation.swift */; };
BFC57A6E2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC57A6D2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift */; };
BFC57A702416FC7600EB891E /* InstalledAppsCollectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFC57A6F2416FC7600EB891E /* InstalledAppsCollectionHeaderView.xib */; };
+ BFC712BB2512B9CF00AB5EBE /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC712BA2512B9CF00AB5EBE /* PluginManager.swift */; };
+ BFC712C32512D5F100AB5EBE /* XPCConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC712C12512D5F100AB5EBE /* XPCConnection.swift */; };
+ BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC712C12512D5F100AB5EBE /* XPCConnection.swift */; };
+ BFC712C52512D5F100AB5EBE /* XPCConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC712C22512D5F100AB5EBE /* XPCConnectionHandler.swift */; };
BFC84A4D2421A19100853474 /* SourcesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC84A4C2421A19100853474 /* SourcesViewController.swift */; };
+ BFCB9207250AB2120057B44E /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFCB9206250AB2120057B44E /* Colors.xcassets */; };
+ BFCCB51A245E3401001853EA /* VerifyAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCCB519245E3401001853EA /* VerifyAppOperation.swift */; };
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2476D2284B9A500981D42 /* AppDelegate.swift */; };
BFD247752284B9A500981D42 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFD247732284B9A500981D42 /* Main.storyboard */; };
BFD247772284B9A700981D42 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFD247762284B9A700981D42 /* Assets.xcassets */; };
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFD247782284B9A700981D42 /* LaunchScreen.storyboard */; };
BFD2478C2284C4C300981D42 /* AppIconImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478B2284C4C300981D42 /* AppIconImageView.swift */; };
BFD2478F2284C8F900981D42 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2478E2284C8F900981D42 /* Button.swift */; };
- BFD2479F2284FBD000981D42 /* UIColor+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */; };
- BFD44606241188C400EAB90A /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
BFD52BD422A0800A000B7ED1 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BD322A0800A000B7ED1 /* ServerManager.swift */; };
BFD52C0122A1A9CB000B7ED1 /* ptrarray.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE522A1A9CA000B7ED1 /* ptrarray.c */; };
BFD52C0222A1A9CB000B7ED1 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52BE622A1A9CA000B7ED1 /* base64.c */; };
@@ -213,33 +283,49 @@
BFD52C2022A1A9EC000B7ED1 /* node.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1D22A1A9EC000B7ED1 /* node.c */; };
BFD52C2122A1A9EC000B7ED1 /* node_list.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1E22A1A9EC000B7ED1 /* node_list.c */; };
BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1F22A1A9EC000B7ED1 /* cnary.c */; };
- BFD5D6E8230CC961007955AB /* PatreonAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6E7230CC961007955AB /* PatreonAPI.swift */; };
- BFD5D6EA230CCAE5007955AB /* PatreonAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6E9230CCAE5007955AB /* PatreonAccount.swift */; };
- BFD5D6EE230D8A86007955AB /* Patron.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6ED230D8A86007955AB /* Patron.swift */; };
- BFD5D6F2230DD974007955AB /* Benefit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F1230DD974007955AB /* Benefit.swift */; };
- BFD5D6F4230DDB0A007955AB /* Campaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F3230DDB0A007955AB /* Campaign.swift */; };
- BFD5D6F6230DDB12007955AB /* Tier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD5D6F5230DDB12007955AB /* Tier.swift */; };
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD6B03222DFF20800B86064 /* MyAppsComponents.swift */; };
- BFDB5B1622EE90D300F74113 /* Date+RelativeDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */; };
BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFDB5B2522EFBBEA00F74113 /* BrowseCollectionViewCell.xib */; };
- BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */; };
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */; };
BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0A22AAEDB7007EA6D6 /* Operation.swift */; };
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0C22AAFC19007EA6D6 /* OperationError.swift */; };
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0E22AB2776007EA6D6 /* SendAppOperation.swift */; };
- BFE338DD22F0E7F3002E24B9 /* Source.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DC22F0E7F3002E24B9 /* Source.swift */; };
+ BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */; };
+ BFE00A202503097F00EB4D0C /* INInteraction+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */; };
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338DE22F0EADB002E24B9 /* FetchSourceOperation.swift */; };
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE338E722F10E56002E24B9 /* LaunchViewController.swift */; };
BFE48975238007CE003239E0 /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE48974238007CE003239E0 /* AnisetteDataManager.swift */; };
+ BFE4FFB6251BF7BA0018CF9B /* AltPlugin.zip in Resources */ = {isa = PBXBuildFile; fileRef = BFE4FFB5251BF7BA0018CF9B /* AltPlugin.zip */; };
BFE60738231ADF49002B0E8E /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE60737231ADF49002B0E8E /* Settings.storyboard */; };
BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60739231ADF82002B0E8E /* SettingsViewController.swift */; };
BFE6073C231AE1E7002B0E8E /* SettingsHeaderFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */; };
BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */; };
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */; };
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; };
- BFE6326622A857C200F30809 /* Team.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326522A857C100F30809 /* Team.swift */; };
- BFE6326822A858F300F30809 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326722A858F300F30809 /* Account.swift */; };
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; };
+ BFECAC7F24FD950B0077C41F /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
+ BFECAC8024FD950B0077C41F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; };
+ BFECAC8124FD950B0077C41F /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; };
+ BFECAC8224FD950B0077C41F /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
+ BFECAC8324FD950B0077C41F /* NetworkConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CD2489ABE90097E58C /* NetworkConnection.swift */; };
+ BFECAC8424FD950B0077C41F /* ALTConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD723C93DB700A89F2D /* ALTConstants.m */; };
+ BFECAC8524FD950B0077C41F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; };
+ BFECAC8624FD950B0077C41F /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAC8852295C90300587369 /* Result+Conveniences.swift */; };
+ BFECAC8724FD950B0077C41F /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
+ BFECAC8824FD950E0077C41F /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
+ BFECAC8924FD950E0077C41F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; };
+ BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; };
+ BFECAC8B24FD950E0077C41F /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
+ BFECAC8D24FD950E0077C41F /* ALTConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BD723C93DB700A89F2D /* ALTConstants.m */; };
+ BFECAC8E24FD950E0077C41F /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; };
+ BFECAC8F24FD950E0077C41F /* Result+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFBAC8852295C90300587369 /* Result+Conveniences.swift */; };
+ BFECAC9024FD950E0077C41F /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
+ BFECAC9324FD98BA0077C41F /* CFNotificationName+AltStore.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */; };
+ BFECAC9424FD98BA0077C41F /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
+ BFECAC9524FD98BB0077C41F /* CFNotificationName+AltStore.m in Sources */ = {isa = PBXBuildFile; fileRef = BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */; };
+ BFECAC9624FD98BB0077C41F /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
+ BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = BFF00D2F2501BD7D00746320 /* Intents.intentdefinition */; };
+ BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */; };
+ BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF00D332501BDCF00746320 /* IntentHandler.swift */; };
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68D23219520007A79E1 /* PatreonViewController.swift */; };
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B68F23219C6D007A79E1 /* PatreonComponents.swift */; };
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFF0B6912321A305007A79E1 /* AboutPatreonHeaderView.xib */; };
@@ -247,23 +333,11 @@
BFF0B696232242D3007A79E1 /* LicensesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B695232242D3007A79E1 /* LicensesViewController.swift */; };
BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6972322CAB8007A79E1 /* InstructionsViewController.swift */; };
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF0B6992322D7D0007A79E1 /* UIScreen+CompactHeight.swift */; };
+ BFF615A82510042B00484D3B /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; };
+ BFF767C82489A74E0097E58C /* WirelessConnectionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767C72489A74E0097E58C /* WirelessConnectionHandler.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
- BF1E315B22A0621900370A3C /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = BFD247622284B9A500981D42 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = BF1E314F22A0616100370A3C;
- remoteInfo = AltKit;
- };
- BF1E315D22A0621F00370A3C /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = BFD247622284B9A500981D42 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = BF1E314F22A0616100370A3C;
- remoteInfo = AltKit;
- };
BF4588442298D48B00BD7491 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFD247622284B9A500981D42 /* Project object */;
@@ -271,6 +345,20 @@
remoteGlobalIDString = BF45872A2298D31600BD7491;
remoteInfo = libimobiledevice;
};
+ BF66EE832501AE50007EE018 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = BFD247622284B9A500981D42 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = BF66EE7D2501AE50007EE018;
+ remoteInfo = AltStoreCore;
+ };
+ BF989175250AABF4002ACF50 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = BFD247622284B9A500981D42 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = BF989166250AABF3002ACF50;
+ remoteInfo = AltWidgetExtension;
+ };
BFBFFB262380C72F00993A4A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BFD247622284B9A500981D42 /* Project object */;
@@ -278,6 +366,13 @@
remoteGlobalIDString = BF5C5FC4237DF5AE00EDD0C6;
remoteInfo = AltPlugin;
};
+ BFF615AA2510042B00484D3B /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = BFD247622284B9A500981D42 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = BF66EE7D2501AE50007EE018;
+ remoteInfo = AltStoreCore;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -287,20 +382,24 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
- BF0201BB22C2EFA3000B93E4 /* AltSign.framework in Embed Frameworks */,
- BF0201BE22C2EFBC000B93E4 /* openssl.framework in Embed Frameworks */,
+ BF088D392501A833008082D9 /* OpenSSL.xcframework in Embed Frameworks */,
BF44CC6D232AEB90004DA9C3 /* LaunchAtLogin.framework in Embed Frameworks */,
+ BF088D372501A821008082D9 /* AltSign-Dynamic in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
- BF1E314E22A0616100370A3C /* CopyFiles */ = {
+ BF088D2B2501A087008082D9 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
- dstPath = "include/$(PRODUCT_NAME)";
- dstSubfolderSpec = 16;
+ dstPath = "";
+ dstSubfolderSpec = 10;
files = (
+ BF1614F2250822F100767AEA /* Roxas.framework in Embed Frameworks */,
+ BF088D342501A4FF008082D9 /* OpenSSL.xcframework in Embed Frameworks */,
+ BF66EE862501AE50007EE018 /* AltStoreCore.framework in Embed Frameworks */,
);
+ name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
BF5C5FE9237E438C00EDD0C6 /* CopyFiles */ = {
@@ -312,53 +411,65 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ BF98917B250AABF4002ACF50 /* Embed App Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ BF989177250AABF4002ACF50 /* AltWidgetExtension.appex in Embed App Extensions */,
+ );
+ name = "Embed App Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 0DE618FA97EA42C3F468D186 /* libPods-AltStore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AltStore.a"; sourceTree = BUILT_PRODUCTS_DIR; };
11611D46F8A7C8B928E8156B /* Pods-AltServer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltServer.debug.xcconfig"; path = "Target Support Files/Pods-AltServer/Pods-AltServer.debug.xcconfig"; sourceTree = ""; };
589BA531D903B28F292063E5 /* Pods-AltServer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltServer.release.xcconfig"; path = "Target Support Files/Pods-AltServer/Pods-AltServer.release.xcconfig"; sourceTree = ""; };
+ A08F67C18350C7990753F03F /* Pods_AltStoreCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltStoreCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A136EE677716B80768E9F0A2 /* Pods-AltStore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.release.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.release.xcconfig"; sourceTree = ""; };
- BF02419322F2156E00129732 /* RefreshAttempt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAttempt.swift; sourceTree = ""; };
+ B39BC452F0753C2C33A2D43B /* Pods-AltStoreCore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStoreCore.debug.xcconfig"; path = "Target Support Files/Pods-AltStoreCore/Pods-AltStoreCore.debug.xcconfig"; sourceTree = ""; };
+ BC373DB2C2B6CB739CCBFB5F /* Pods-AltStoreCore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStoreCore.release.xcconfig"; path = "Target Support Files/Pods-AltStoreCore/Pods-AltStoreCore.release.xcconfig"; sourceTree = ""; };
BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAttemptsViewController.swift; sourceTree = ""; };
BF0241A922F29CCD00129732 /* UserDefaults+AltServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AltServer.swift"; sourceTree = ""; };
BF08858222DE795100DE9F1E /* MyAppsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewController.swift; sourceTree = ""; };
BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCollectionViewCell.swift; sourceTree = ""; };
+ BF088D322501A4FF008082D9 /* OpenSSL.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = Dependencies/AltSign/Dependencies/OpenSSL/Frameworks/OpenSSL.xcframework; sourceTree = ""; };
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = ""; };
BF0DCA652433BDF500E3A595 /* AnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsManager.swift; sourceTree = ""; };
- BF0F5FC623F394AD0080DB64 /* AltStore3ToAltStore4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore3ToAltStore4.xcmappingmodel; sourceTree = ""; };
- BF100C46232D7828006A8926 /* AltStore 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 2.xcdatamodel"; sourceTree = ""; };
- BF100C4F232D7CD1006A8926 /* AltStoreToAltStore2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStoreToAltStore2.xcmappingmodel; sourceTree = ""; };
- BF100C53232D7DAE006A8926 /* StoreAppPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreAppPolicy.swift; sourceTree = ""; };
+ BF10EB33248730750055E6DB /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
BF18B0F022E25DF9005C4CF5 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; };
+ BF18BFE724857D7900DD5981 /* AltDaemon */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = AltDaemon; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF18BFF22485828200DD5981 /* ConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = ""; };
+ BF18BFF624858BDE00DD5981 /* Connection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; };
+ BF18BFFC2485A1E400DD5981 /* WiredConnectionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WiredConnectionHandler.swift; sourceTree = ""; };
+ BF18BFFE2485A42800DD5981 /* ALTConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTConnection.h; sourceTree = ""; };
+ BF18C0032485B4DE00DD5981 /* AltDaemon-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AltDaemon-Bridging-Header.h"; sourceTree = ""; };
BF1E3128229F474900370A3C /* ServerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerProtocol.swift; sourceTree = ""; };
- BF1E3129229F474900370A3C /* ConnectionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionManager.swift; sourceTree = ""; };
+ BF1E3129229F474900370A3C /* RequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestHandler.swift; sourceTree = ""; };
BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+AltStore.swift"; sourceTree = ""; };
BF1E314722A060F300370A3C /* AltStore-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AltStore-Bridging-Header.h"; sourceTree = ""; };
BF1E314822A060F400370A3C /* NSError+ALTServerError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSError+ALTServerError.h"; sourceTree = ""; };
BF1E314922A060F400370A3C /* NSError+ALTServerError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSError+ALTServerError.m"; sourceTree = ""; };
- BF1E315022A0616100370A3C /* libAltKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAltKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF1FE357251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSXPCConnection+MachServices.swift"; sourceTree = ""; };
BF219A7E22CAC431007676A6 /* AltStore.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltStore.entitlements; sourceTree = ""; };
- BF258CE222EBAE2800023032 /* AppProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppProtocol.swift; sourceTree = ""; };
- BF26A0DF2370C5D400F53F9F /* ALTSourceUserInfoKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTSourceUserInfoKey.h; sourceTree = ""; };
- BF26A0E02370C5D400F53F9F /* ALTSourceUserInfoKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTSourceUserInfoKey.m; sourceTree = ""; };
BF29012E2318F6B100D88A45 /* AppBannerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AppBannerView.xib; sourceTree = ""; };
BF2901302318F7A800D88A45 /* AppBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBannerView.swift; sourceTree = ""; };
+ BF3432FA246B894F0052F4A1 /* BackupAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupAppOperation.swift; sourceTree = ""; };
BF3BEFBE2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchProvisioningProfilesOperation.swift; sourceTree = ""; };
BF3BEFC024086A1E00DE7D55 /* RefreshAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAppOperation.swift; sourceTree = ""; };
- BF3D648722E79A3700E9056B /* AppPermission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermission.swift; sourceTree = ""; };
- BF3D648B22E79AC800E9056B /* ALTAppPermission.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTAppPermission.h; sourceTree = ""; };
- BF3D648C22E79AC800E9056B /* ALTAppPermission.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTAppPermission.m; sourceTree = ""; };
BF3D649C22E7AC1B00E9056B /* PermissionPopoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionPopoverViewController.swift; sourceTree = ""; };
BF3D649E22E7B24C00E9056B /* CollapsingTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsingTextView.swift; sourceTree = ""; };
- BF3D64A122E8031100E9056B /* MergePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergePolicy.swift; sourceTree = ""; };
BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewControllerCells.swift; sourceTree = ""; };
BF3F786322CAA41E008FBD20 /* ALTDeviceManager+Installation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTDeviceManager+Installation.swift"; sourceTree = ""; };
BF41B805233423AE00C593A3 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; };
BF41B807233433C100C593A3 /* LoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingState.swift; sourceTree = ""; };
- BF43002D22A714AF0051E2BC /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; };
- BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AltStore.swift"; sourceTree = ""; };
+ BF42345825101C1D006D1EB2 /* WidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetView.swift; sourceTree = ""; };
BF44CC6A232AEB74004DA9C3 /* LaunchAtLogin.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LaunchAtLogin.framework; path = Carthage/Build/Mac/LaunchAtLogin.framework; sourceTree = ""; };
+ BF44EEEF246B08BA002A52F2 /* BackupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupController.swift; sourceTree = ""; };
+ BF44EEF2246B3A17002A52F2 /* AltBackup.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; path = AltBackup.ipa; sourceTree = ""; };
+ BF44EEFB246B4550002A52F2 /* RemoveAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAppOperation.swift; sourceTree = ""; };
BF45868D229872EA00BD7491 /* AltServer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AltServer.app; sourceTree = BUILT_PRODUCTS_DIR; };
BF45868F229872EA00BD7491 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
BF458693229872EA00BD7491 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -430,25 +541,76 @@
BF4588422298D40000BD7491 /* libusbmuxd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = libusbmuxd.c; path = Dependencies/libusbmuxd/src/libusbmuxd.c; sourceTree = SOURCE_ROOT; };
BF4588482298D55000BD7491 /* thread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = thread.c; path = Dependencies/libusbmuxd/common/thread.c; sourceTree = SOURCE_ROOT; };
BF4588492298D55000BD7491 /* thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = thread.h; path = Dependencies/libusbmuxd/common/thread.h; sourceTree = SOURCE_ROOT; };
- BF4588542298DC5400BD7491 /* openssl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = openssl.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BF4588872298DD3F00BD7491 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/usr/lib/libxml2.tbd; sourceTree = DEVELOPER_DIR; };
- BF4588962298DE6E00BD7491 /* libzip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = libzip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- BF4713A422976CFC00784A2F /* openssl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = openssl.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- BF54E81F2315EF0D000AE0D8 /* ALTPatreonBenefitType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTPatreonBenefitType.h; sourceTree = ""; };
- BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTPatreonBenefitType.m; sourceTree = ""; };
- BF56D2A823DF87570006506D /* AltStore 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 4.xcdatamodel"; sourceTree = ""; };
- BF56D2A923DF88310006506D /* AppID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppID.swift; sourceTree = ""; };
+ BF4B78FD24B3D1DB008AB4AC /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
BF56D2AB23DF8E170006506D /* FetchAppIDsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAppIDsOperation.swift; sourceTree = ""; };
BF56D2AE23DF9E310006506D /* AppIDsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIDsViewController.swift; sourceTree = ""; };
- BF5AB3A72285FE6C00DC914B /* AltSign.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF58047B246A28F7008AE704 /* AltBackup.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AltBackup.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF58047D246A28F7008AE704 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ BF580481246A28F7008AE704 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
+ BF580486246A28F9008AE704 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ BF580489246A28F9008AE704 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ BF58048B246A28F9008AE704 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltBackup.swift"; sourceTree = ""; };
+ BF580497246A3D19008AE704 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ BF580499246A4153008AE704 /* AltBackup.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltBackup.entitlements; sourceTree = ""; };
BF5C5FC5237DF5AE00EDD0C6 /* AltPlugin.mailbundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AltPlugin.mailbundle; sourceTree = BUILT_PRODUCTS_DIR; };
BF5C5FC7237DF5AE00EDD0C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
BF5C5FCD237DF69100EDD0C6 /* ALTPluginService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTPluginService.h; sourceTree = ""; };
BF5C5FCE237DF69100EDD0C6 /* ALTPluginService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTPluginService.m; sourceTree = ""; };
BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+DirectorySize.swift"; sourceTree = ""; };
- BF6C336124197D700034FD24 /* NSError+LocalizedFailure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+LocalizedFailure.swift"; sourceTree = ""; };
- BF6C33632419ADEB0034FD24 /* AltStore 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 5.xcdatamodel"; sourceTree = ""; };
- BF6C33642419AE310034FD24 /* AltStore4ToAltStore5.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore4ToAltStore5.xcmappingmodel; sourceTree = ""; };
+ BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AltStoreCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF66EE802501AE50007EE018 /* AltStoreCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AltStoreCore.h; sourceTree = ""; };
+ BF66EE812501AE50007EE018 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ BF66EE8B2501AEB1007EE018 /* Keychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; };
+ BF66EE8E2501AEBC007EE018 /* ALTAppPermission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTAppPermission.h; sourceTree = ""; };
+ BF66EE8F2501AEBC007EE018 /* ALTSourceUserInfoKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTSourceUserInfoKey.h; sourceTree = ""; };
+ BF66EE902501AEBC007EE018 /* ALTPatreonBenefitType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTPatreonBenefitType.m; sourceTree = ""; };
+ BF66EE912501AEBC007EE018 /* ALTAppPermission.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTAppPermission.m; sourceTree = ""; };
+ BF66EE922501AEBC007EE018 /* ALTPatreonBenefitType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ALTPatreonBenefitType.h; sourceTree = ""; };
+ BF66EE932501AEBC007EE018 /* ALTSourceUserInfoKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ALTSourceUserInfoKey.m; sourceTree = ""; };
+ BF66EE9B2501AEC1007EE018 /* AppProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppProtocol.swift; sourceTree = ""; };
+ BF66EE9C2501AEC1007EE018 /* Fetchable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fetchable.swift; sourceTree = ""; };
+ BF66EEA02501AEC5007EE018 /* Benefit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Benefit.swift; sourceTree = ""; };
+ BF66EEA12501AEC5007EE018 /* PatreonAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PatreonAPI.swift; sourceTree = ""; };
+ BF66EEA22501AEC5007EE018 /* Campaign.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Campaign.swift; sourceTree = ""; };
+ BF66EEA32501AEC5007EE018 /* Patron.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Patron.swift; sourceTree = ""; };
+ BF66EEA42501AEC5007EE018 /* Tier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tier.swift; sourceTree = ""; };
+ BF66EEAB2501AECA007EE018 /* Source.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Source.swift; sourceTree = ""; };
+ BF66EEAE2501AECA007EE018 /* StoreAppPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreAppPolicy.swift; sourceTree = ""; };
+ BF66EEAF2501AECA007EE018 /* InstalledAppPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstalledAppPolicy.swift; sourceTree = ""; };
+ BF66EEB12501AECA007EE018 /* AltStoreToAltStore2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStoreToAltStore2.xcmappingmodel; sourceTree = ""; };
+ BF66EEB22501AECA007EE018 /* AltStore6ToAltStore7.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore6ToAltStore7.xcmappingmodel; sourceTree = ""; };
+ BF66EEB32501AECA007EE018 /* AltStore3ToAltStore4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore3ToAltStore4.xcmappingmodel; sourceTree = ""; };
+ BF66EEB42501AECA007EE018 /* AltStore4ToAltStore5.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore4ToAltStore5.xcmappingmodel; sourceTree = ""; };
+ BF66EEB52501AECA007EE018 /* AltStore2ToAltStore3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore2ToAltStore3.xcmappingmodel; sourceTree = ""; };
+ BF66EEB62501AECA007EE018 /* AltStore5ToAltStore6.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore5ToAltStore6.xcmappingmodel; sourceTree = ""; };
+ BF66EEB82501AECA007EE018 /* AltStore 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 3.xcdatamodel"; sourceTree = ""; };
+ BF66EEB92501AECA007EE018 /* AltStore.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AltStore.xcdatamodel; sourceTree = ""; };
+ BF66EEBA2501AECA007EE018 /* AltStore 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 6.xcdatamodel"; sourceTree = ""; };
+ BF66EEBB2501AECA007EE018 /* AltStore 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 5.xcdatamodel"; sourceTree = ""; };
+ BF66EEBC2501AECA007EE018 /* AltStore 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 7.xcdatamodel"; sourceTree = ""; };
+ BF66EEBD2501AECA007EE018 /* AltStore 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 2.xcdatamodel"; sourceTree = ""; };
+ BF66EEBE2501AECA007EE018 /* AltStore 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 4.xcdatamodel"; sourceTree = ""; };
+ BF66EEBF2501AECA007EE018 /* NewsItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsItem.swift; sourceTree = ""; };
+ BF66EEC02501AECA007EE018 /* InstalledApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstalledApp.swift; sourceTree = ""; };
+ BF66EEC12501AECA007EE018 /* SecureValueTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureValueTransformer.swift; sourceTree = ""; };
+ BF66EEC22501AECA007EE018 /* Team.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Team.swift; sourceTree = ""; };
+ BF66EEC32501AECA007EE018 /* RefreshAttempt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshAttempt.swift; sourceTree = ""; };
+ BF66EEC42501AECA007EE018 /* StoreApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreApp.swift; sourceTree = ""; };
+ BF66EEC52501AECA007EE018 /* MergePolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MergePolicy.swift; sourceTree = ""; };
+ BF66EEC62501AECA007EE018 /* AppPermission.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppPermission.swift; sourceTree = ""; };
+ BF66EEC72501AECA007EE018 /* AppID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppID.swift; sourceTree = ""; };
+ BF66EEC82501AECA007EE018 /* PatreonAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PatreonAccount.swift; sourceTree = ""; };
+ BF66EEC92501AECA007EE018 /* Account.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; };
+ BF66EECA2501AECA007EE018 /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; };
+ BF66EECB2501AECA007EE018 /* InstalledExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstalledExtension.swift; sourceTree = ""; };
+ BF66EEE42501AED0007EE018 /* UserDefaults+AltStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AltStore.swift"; sourceTree = ""; };
+ BF66EEE52501AED0007EE018 /* JSONDecoder+Properties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+Properties.swift"; sourceTree = ""; };
+ BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Hex.swift"; sourceTree = ""; };
+ BF66EEE72501AED0007EE018 /* UIApplication+AppExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+AppExtension.swift"; sourceTree = ""; };
+ BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+SharedDirectories.swift"; sourceTree = ""; };
+ BF6C336124197D700034FD24 /* NSError+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+AltStore.swift"; sourceTree = ""; };
BF6C8FAA242935ED00125131 /* NSAttributedString+Markdown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+Markdown.m"; path = "Dependencies/MarkdownAttributedString/NSAttributedString+Markdown.m"; sourceTree = SOURCE_ROOT; };
BF6C8FAB242935ED00125131 /* NSAttributedString+Markdown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSAttributedString+Markdown.h"; path = "Dependencies/MarkdownAttributedString/NSAttributedString+Markdown.h"; sourceTree = SOURCE_ROOT; };
BF6C8FAD2429597900125131 /* BannerCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerCollectionViewCell.swift; sourceTree = ""; };
@@ -457,12 +619,12 @@
BF718BC723C919CC00A89F2D /* CFNotificationName+AltStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CFNotificationName+AltStore.h"; sourceTree = ""; };
BF718BC823C919E300A89F2D /* CFNotificationName+AltStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CFNotificationName+AltStore.m"; sourceTree = ""; };
BF718BCF23C91BD300A89F2D /* ALTWiredConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWiredConnection.h; sourceTree = ""; };
- BF718BD023C91BD300A89F2D /* ALTWiredConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWiredConnection.m; sourceTree = ""; };
+ BF718BD023C91BD300A89F2D /* ALTWiredConnection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ALTWiredConnection.mm; sourceTree = ""; };
BF718BD223C91C7000A89F2D /* ALTWiredConnection+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ALTWiredConnection+Private.h"; sourceTree = ""; };
BF718BD323C928A300A89F2D /* ALTNotificationConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTNotificationConnection.h; sourceTree = ""; };
- BF718BD423C928A300A89F2D /* ALTNotificationConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTNotificationConnection.m; sourceTree = ""; };
+ BF718BD423C928A300A89F2D /* ALTNotificationConnection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ALTNotificationConnection.mm; sourceTree = ""; };
BF718BD623C92B3700A89F2D /* ALTNotificationConnection+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ALTNotificationConnection+Private.h"; sourceTree = ""; };
- BF718BD723C93DB700A89F2D /* AltKit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AltKit.m; sourceTree = ""; };
+ BF718BD723C93DB700A89F2D /* ALTConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTConstants.m; sourceTree = ""; };
BF74989A23621C0700CED65F /* ForwardingNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardingNavigationController.swift; sourceTree = ""; };
BF770E5022BB1CF6002A40FE /* InstallAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallAppOperation.swift; sourceTree = ""; };
BF770E5322BC044E002A40FE /* OperationContexts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationContexts.swift; sourceTree = ""; };
@@ -470,44 +632,50 @@
BF770E5722BC3D0F002A40FE /* RefreshGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshGroup.swift; sourceTree = ""; };
BF770E6622BD57C3002A40FE /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = ""; };
BF770E6822BD57DD002A40FE /* Silence.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = Silence.m4a; sourceTree = ""; };
- BF7C627023DBB33300515A2D /* AltStore 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 3.xcdatamodel"; sourceTree = ""; };
- BF7C627123DBB3B400515A2D /* AltStore2ToAltStore3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore2ToAltStore3.xcmappingmodel; sourceTree = ""; };
- BF7C627323DBB78C00515A2D /* InstalledAppPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstalledAppPolicy.swift; sourceTree = ""; };
+ BF88F97124F8727D00BB75DF /* AppManagerErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppManagerErrors.swift; sourceTree = ""; };
+ BF8B17F0250AC62400F8157F /* AltWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltWidgetExtension.entitlements; sourceTree = ""; };
+ BF8CAE422489E772004D6CCE /* AnisetteDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnisetteDataManager.swift; sourceTree = ""; };
+ BF8CAE432489E772004D6CCE /* AppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppManager.swift; sourceTree = ""; };
+ BF8CAE442489E772004D6CCE /* DaemonRequestHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DaemonRequestHandler.swift; sourceTree = ""; };
+ BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Jailbreak.swift"; sourceTree = ""; };
BF8F69C122E659F700049BA1 /* AppContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentViewController.swift; sourceTree = ""; };
BF8F69C322E662D300049BA1 /* AppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewController.swift; sourceTree = ""; };
- BF9A03C523C7DD0D000D08DB /* ClientConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientConnection.swift; sourceTree = ""; };
+ BF989167250AABF3002ACF50 /* AltWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AltWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+ BF989170250AABF4002ACF50 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ BF989172250AABF4002ACF50 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ BF98917C250AAC4F002ACF50 /* Countdown.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Countdown.swift; sourceTree = ""; };
+ BF98917D250AAC4F002ACF50 /* AltWidget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AltWidget.swift; sourceTree = "