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..e835e335
--- /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 var altstoreAppGroup: String? {
+ return Bundle.main.appGroups.first
+ }
+
+ 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.bundleIdentifier else { throw BackupError(.invalidBundleID, description: NSLocalizedString("Unable to create backup directory.", comment: "")) }
+
+ guard
+ let altstoreAppGroup = self.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.bundleIdentifier else { throw BackupError(.invalidBundleID, description: NSLocalizedString("Unable to access backup.", comment: "")) }
+
+ guard
+ let altstoreAppGroup = self.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..25e69f4e
--- /dev/null
+++ b/AltBackup/Info.plist
@@ -0,0 +1,64 @@
+
+
+
+
+ ALTAppGroups
+
+ group.com.rileytestut.AltStore
+
+ 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..8f1adeda
--- /dev/null
+++ b/AltBackup/ViewController.swift
@@ -0,0 +1,131 @@
+//
+// ViewController.swift
+// AltBackup
+//
+// Created by Riley Testut on 5/11/20.
+// Copyright © 2020 Riley Testut. All rights reserved.
+//
+
+import UIKit
+
+class ViewController: UIViewController
+{
+ private let backupController = BackupController()
+
+ 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)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError()
+ }
+
+ override func viewDidLoad()
+ {
+ super.viewDidLoad()
+
+ self.view.backgroundColor = .altstoreBackground
+
+ let textLabel = UILabel(frame: .zero)
+ textLabel.font = UIFont.preferredFont(forTextStyle: .title2)
+ textLabel.textColor = .altstoreText
+ textLabel.text = NSLocalizedString("Backing up app data…", comment: "")
+
+ let activityIndicatorView = UIActivityIndicatorView(style: .whiteLarge)
+ activityIndicatorView.color = .altstoreText
+ 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 = [textLabel, activityIndicatorView, button1, button2]
+ #else
+ let arrangedSubviews = [textLabel, 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)])
+ }
+}
+
+private extension ViewController
+{
+ @objc func backup()
+ {
+ self.backupController.performBackup { (result) in
+ let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
+ Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String ??
+ NSLocalizedString("App", comment: "")
+
+ let title = String(format: NSLocalizedString("%@ could not be backed up.", comment: ""), appName)
+ self.process(result, errorTitle: title)
+ }
+ }
+
+ @objc func restore()
+ {
+ self.backupController.restoreBackup { (result) in
+ let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
+ Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String ??
+ NSLocalizedString("App", comment: "")
+
+ let title = String(format: NSLocalizedString("%@ could not be restored.", comment: ""), appName)
+ self.process(result, errorTitle: title)
+ }
+ }
+}
+
+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])
+ }
+ }
+}
diff --git a/AltKit/Bundle+AltStore.swift b/AltKit/Bundle+AltStore.swift
index 110b20f5..280b547d 100644
--- a/AltKit/Bundle+AltStore.swift
+++ b/AltKit/Bundle+AltStore.swift
@@ -38,4 +38,8 @@ public extension Bundle
let infoPlistURL = self.bundleURL.appendingPathComponent("ALTCertificate.p12")
return infoPlistURL
}
+
+ var appGroups: [String] {
+ return self.infoDictionary?[Bundle.Info.appGroups] as? [String] ?? []
+ }
}
diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj
index 37d53abb..383c2bcb 100644
--- a/AltStore.xcodeproj/project.pbxproj
+++ b/AltStore.xcodeproj/project.pbxproj
@@ -27,7 +27,6 @@
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 */; };
@@ -50,6 +49,7 @@
BF43003022A71C960051E2BC /* UserDefaults+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF43002F22A71C960051E2BC /* UserDefaults+AltStore.swift */; };
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 */; };
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 */; };
@@ -118,10 +118,19 @@
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 */; };
+ BF4E8456246F16D700ECCBD4 /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
BF54E8212315EF0D000AE0D8 /* ALTPatreonBenefitType.m in Sources */ = {isa = PBXBuildFile; fileRef = BF54E8202315EF0D000AE0D8 /* ALTPatreonBenefitType.m */; };
BF56D2AA23DF88310006506D /* AppID.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF56D2A923DF88310006506D /* AppID.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 */; };
+ BF580492246A2C5C008AE704 /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
+ 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+LocalizedFailure.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+LocalizedFailure.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 */; };
@@ -360,6 +369,7 @@
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 = ""; };
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 = ""; };
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 = ""; };
@@ -441,6 +451,15 @@
BF56D2A923DF88310006506D /* AppID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppID.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 = ""; };
+ 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 = ""; };
BF5AB3A72285FE6C00DC914B /* AltSign.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AltSign.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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 = ""; };
@@ -622,6 +641,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ BF580478246A28F7008AE704 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BF580498246A3D19008AE704 /* UIKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
BF5C5FC2237DF5AE00EDD0C6 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -902,6 +929,21 @@
path = "App IDs";
sourceTree = "";
};
+ BF58047C246A28F7008AE704 /* AltBackup */ = {
+ isa = PBXGroup;
+ children = (
+ BF580499246A4153008AE704 /* AltBackup.entitlements */,
+ BF58047D246A28F7008AE704 /* AppDelegate.swift */,
+ BF580481246A28F7008AE704 /* ViewController.swift */,
+ BF44EEEF246B08BA002A52F2 /* BackupController.swift */,
+ BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */,
+ BF580486246A28F9008AE704 /* Assets.xcassets */,
+ BF580488246A28F9008AE704 /* LaunchScreen.storyboard */,
+ BF58048B246A28F9008AE704 /* Info.plist */,
+ );
+ path = AltBackup;
+ sourceTree = "";
+ };
BF5C5FC6237DF5AE00EDD0C6 /* AltPlugin */ = {
isa = PBXGroup;
children = (
@@ -1019,6 +1061,7 @@
BF1E315122A0616100370A3C /* AltKit */,
BF45872C2298D31600BD7491 /* libimobiledevice */,
BF5C5FC6237DF5AE00EDD0C6 /* AltPlugin */,
+ BF58047C246A28F7008AE704 /* AltBackup */,
BFD247852284BB3300981D42 /* Frameworks */,
BFD2476B2284B9A500981D42 /* Products */,
4460E048E3AC1C9708C4FA33 /* Pods */,
@@ -1033,6 +1076,7 @@
BF45872B2298D31600BD7491 /* libimobiledevice.a */,
BF1E315022A0616100370A3C /* libAltKit.a */,
BF5C5FC5237DF5AE00EDD0C6 /* AltPlugin.mailbundle */,
+ BF58047B246A28F7008AE704 /* AltBackup.app */,
);
name = Products;
sourceTree = "";
@@ -1073,6 +1117,7 @@
BFD247852284BB3300981D42 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ BF580497246A3D19008AE704 /* UIKit.framework */,
BF44CC6A232AEB74004DA9C3 /* LaunchAtLogin.framework */,
BF9B63C5229DD44D002F0A62 /* AltSign.framework */,
BF4588962298DE6E00BD7491 /* libzip.framework */,
@@ -1370,6 +1415,23 @@
productReference = BF45872B2298D31600BD7491 /* libimobiledevice.a */;
productType = "com.apple.product-type.library.static";
};
+ BF58047A246A28F7008AE704 /* AltBackup */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = BF58048E246A28F9008AE704 /* Build configuration list for PBXNativeTarget "AltBackup" */;
+ buildPhases = (
+ BF580477246A28F7008AE704 /* Sources */,
+ BF580478246A28F7008AE704 /* Frameworks */,
+ BF580479246A28F7008AE704 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = AltBackup;
+ productName = AltBackup;
+ productReference = BF58047B246A28F7008AE704 /* AltBackup.app */;
+ productType = "com.apple.product-type.application";
+ };
BF5C5FC4237DF5AE00EDD0C6 /* AltPlugin */ = {
isa = PBXNativeTarget;
buildConfigurationList = BF5C5FC8237DF5AE00EDD0C6 /* Build configuration list for PBXNativeTarget "AltPlugin" */;
@@ -1414,7 +1476,7 @@
BFD247622284B9A500981D42 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 1120;
+ LastSwiftUpdateCheck = 1140;
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "Riley Testut";
TargetAttributes = {
@@ -1436,6 +1498,9 @@
BF45872A2298D31600BD7491 = {
CreatedOnToolsVersion = 10.2.1;
};
+ BF58047A246A28F7008AE704 = {
+ CreatedOnToolsVersion = 11.4.1;
+ };
BF5C5FC4237DF5AE00EDD0C6 = {
CreatedOnToolsVersion = 11.2;
LastSwiftMigration = 1120;
@@ -1472,6 +1537,7 @@
BF1E314F22A0616100370A3C /* AltKit */,
BF45872A2298D31600BD7491 /* libimobiledevice */,
BF5C5FC4237DF5AE00EDD0C6 /* AltPlugin */,
+ BF58047A246A28F7008AE704 /* AltBackup */,
);
};
/* End PBXProject section */
@@ -1486,6 +1552,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ BF580479246A28F7008AE704 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BF58048A246A28F9008AE704 /* LaunchScreen.storyboard in Resources */,
+ BF580487246A28F9008AE704 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
BF5C5FC3237DF5AE00EDD0C6 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1639,11 +1714,11 @@
files = (
BF718BD823C93DB700A89F2D /* AltKit.m in Sources */,
BF1E315A22A0620000370A3C /* NSError+ALTServerError.m in Sources */,
- BF1E315922A061FB00370A3C /* Bundle+AltStore.swift in Sources */,
BF1E315822A061F900370A3C /* Result+Conveniences.swift in Sources */,
BF718BC923C919E300A89F2D /* CFNotificationName+AltStore.m in Sources */,
BFD44606241188C400EAB90A /* CodableServerError.swift in Sources */,
BF1E315722A061F500370A3C /* ServerProtocol.swift in Sources */,
+ BF4E8456246F16D700ECCBD4 /* Bundle+AltStore.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1724,6 +1799,19 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ BF580477246A28F7008AE704 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BF580492246A2C5C008AE704 /* Bundle+AltStore.swift in Sources */,
+ BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */,
+ BF580482246A28F7008AE704 /* ViewController.swift in Sources */,
+ BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */,
+ BF58049B246A432D008AE704 /* NSError+LocalizedFailure.swift in Sources */,
+ BF58047E246A28F7008AE704 /* AppDelegate.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
BF5C5FC1237DF5AE00EDD0C6 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1877,6 +1965,14 @@
name = Main.storyboard;
sourceTree = "";
};
+ BF580488246A28F9008AE704 /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ BF580489246A28F9008AE704 /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
BFD247732284B9A500981D42 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
@@ -2109,6 +2205,49 @@
};
name = Release;
};
+ BF58048C246A28F9008AE704 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CODE_SIGN_ENTITLEMENTS = AltBackup/AltBackup.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = 6XVY5G3U44;
+ INFOPLIST_FILE = AltBackup/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltBackup;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ BF58048D246A28F9008AE704 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CODE_SIGN_ENTITLEMENTS = AltBackup/AltBackup.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 6XVY5G3U44;
+ INFOPLIST_FILE = AltBackup/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltBackup;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
BF5C5FC9237DF5AE00EDD0C6 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -2369,6 +2508,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ BF58048E246A28F9008AE704 /* Build configuration list for PBXNativeTarget "AltBackup" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ BF58048C246A28F9008AE704 /* Debug */,
+ BF58048D246A28F9008AE704 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
BF5C5FC8237DF5AE00EDD0C6 /* Build configuration list for PBXNativeTarget "AltPlugin" */ = {
isa = XCConfigurationList;
buildConfigurations = (