This commit is contained in:
f1shy-dev
2023-02-11 20:16:13 +00:00
parent 0dc0ff8151
commit 486b3d12bd
25 changed files with 671 additions and 378 deletions

3
.gitignore vendored
View File

@@ -41,5 +41,4 @@ Payload
*.ipa.zip *.ipa.zip
xcodebuild.log xcodebuild.log
Dependencies/em_proxy.xcodeproj/project.pbxproj build.sh
Dependencies/minimuxer.xcodeproj/project.pbxproj

View File

@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>ALTAnisetteURL</key>
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
<key>ALTAppGroups</key> <key>ALTAppGroups</key>
<array> <array>
<string>group.$(APP_GROUP_IDENTIFIER)</string> <string>group.$(APP_GROUP_IDENTIFIER)</string>
@@ -9,12 +11,10 @@
</array> </array>
<key>ALTDeviceID</key> <key>ALTDeviceID</key>
<string>00008101-000129D63698001E</string> <string>00008101-000129D63698001E</string>
<key>ALTServerID</key>
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
<key>ALTPairingFile</key> <key>ALTPairingFile</key>
<string>&lt;insert pairing file here&gt;</string> <string>&lt;insert pairing file here&gt;</string>
<key>ALTAnisetteURL</key> <key>ALTServerID</key>
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string> <string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDocumentTypes</key> <key>CFBundleDocumentTypes</key>
@@ -44,8 +44,6 @@
<string>$(PRODUCT_NAME)</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
@@ -93,6 +91,15 @@
</array> </array>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSAppleMusicUsageDescription</key>
<string>So that we can bypass the 3 app limit and disable revokes using MDC and the tccd exploit.</string>
<key>NSBonjourServices</key> <key>NSBonjourServices</key>
<array> <array>
<string>_altserver._tcp</string> <string>_altserver._tcp</string>
@@ -131,13 +138,10 @@
<string>fetch</string> <string>fetch</string>
<string>remote-notification</string> <string>remote-notification</string>
</array> </array>
<key>UIFileSharingEnabled</key>
<true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
@@ -204,7 +208,5 @@
</dict> </dict>
</dict> </dict>
</array> </array>
<key>UIFileSharingEnabled</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@@ -46,36 +46,6 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true) super.viewDidAppear(true)
#if !targetEnvironment(simulator) #if !targetEnvironment(simulator)
let dialogMessage = UIAlertController(title: "MDC Patch", message: "please confirm you would like to patch MDC/three-app-limit, press patch to patch, dont patch to launch sidestore without patch", preferredStyle: .alert)
// Create OK button with action handler
let patch = UIAlertAction(title: "Patch", style: .default, handler: { _ in
patch3AppLimit { result in
switch result {
case .success:
print("patched sucessfully")
case .failure(let err):
switch err {
case .NoFDA(let msg):
self.displayError("Failed to get full disk access: \(msg)")
return
case .FailedPatchd:
self.displayError("Failed to install patchd.")
return
}
}
}
})
let noPatch = UIAlertAction(title: "Continue without Patch", style: .default, handler: { _ in
print("starting SS without mdc patch")
})
dialogMessage.addAction(patch)
dialogMessage.addAction(noPatch)
self.present(dialogMessage, animated: true, completion: nil)
start_em_proxy(bind_addr: Consts.Proxy.serverURL) start_em_proxy(bind_addr: Consts.Proxy.serverURL)
guard let pf = fetchPairingFile() else { guard let pf = fetchPairingFile() else {
@@ -118,7 +88,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data)) types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data))
types.append(.xml) types.append(.xml)
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types) let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
documentPickerController.shouldShowFileExtensions = true // documentPickerController.shouldShowFileExtensions = true
documentPickerController.delegate = self documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil) self.present(documentPickerController, animated: true, completion: nil)
}) })
@@ -242,6 +212,37 @@ extension LaunchViewController {
self.destinationViewController.view.alpha = 1.0 self.destinationViewController.view.alpha = 1.0
} }
if UserDefaults.standard.enableMacDirtyCowExploit, UserDefaults.standard.isMacDirtyCowSupported {
if let previous_exploit_time = UserDefaults.standard.object(forKey: "mdcRanBootTime") {
let last_rantime = previous_exploit_time as! Date
if last_rantime == bootTime() {
return print("exploit has ran this boot - \(last_rantime)")
}
}
self.runExploit()
}
self.didFinishLaunching = true self.didFinishLaunching = true
} }
func runExploit() {
if UserDefaults.standard.enableMacDirtyCowExploit && UserDefaults.standard.isMacDirtyCowSupported {
patch3AppLimit { result in
switch result {
case .success:
UserDefaults.standard.set(bootTime(), forKey: "mdcRanBootTime")
print("patched sucessfully")
case .failure(let err):
switch err {
case .NoFDA(let msg):
self.displayError("Failed to get full disk access: \(msg)")
return
case .FailedPatchd:
self.displayError("Failed to install patchd.")
return
}
}
}
}
}
} }

View File

@@ -16,16 +16,26 @@ func patch3AppLimit(completion: @escaping (PatchResult) -> ()) {
if let error = error { if let error = error {
completion(.failure(PatchError.NoFDA(msg: "Failed to get full disk access: \(error)"))) completion(.failure(PatchError.NoFDA(msg: "Failed to get full disk access: \(error)")))
} }
DispatchQueue.global(qos: .userInitiated).async { // DispatchQueue.global(qos: .userInitiated).async {
print("This is run on a background queue") print("This is run on a background queue")
if !patch_installd() { if !patch_installd() {
completion(.failure(PatchError.FailedPatchd)) completion(.failure(PatchError.FailedPatchd))
} }
} // }
completion(.success) completion(.success)
} }
} }
func bootTime() -> Date? {
var tv = timeval()
var tvSize = MemoryLayout<timeval>.size
let err = sysctlbyname("kern.boottime", &tv, &tvSize, nil, 0);
guard err == 0, tvSize == MemoryLayout<timeval>.size else {
return nil
}
return Date(timeIntervalSince1970: Double(tv.tv_sec) + Double(tv.tv_usec) / 1_000_000.0)
}
enum WhitelistPatchResult { enum WhitelistPatchResult {
case success, failure case success, failure
} }

View File

@@ -580,7 +580,6 @@ static NSData* make_patch_installd(void* executableMap, size_t executableLength)
} }
bool patch_installd() { bool patch_installd() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
const char* targetPath = "/usr/libexec/installd"; const char* targetPath = "/usr/libexec/installd";
int fd = open(targetPath, O_RDONLY | O_CLOEXEC); int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
off_t targetLength = lseek(fd, 0, SEEK_END); off_t targetLength = lseek(fd, 0, SEEK_END);
@@ -591,14 +590,14 @@ bool patch_installd() {
NSData* sourceData = make_patch_installd(targetMap, targetLength); NSData* sourceData = make_patch_installd(targetMap, targetLength);
if (!sourceData) { if (!sourceData) {
NSLog(@"can't patchfind"); NSLog(@"can't patchfind");
// return ; return false;
} }
if (!overwrite_file(fd, sourceData)) { if (!overwrite_file(fd, sourceData)) {
overwrite_file(fd, originalData); overwrite_file(fd, originalData);
munmap(targetMap, targetLength); munmap(targetMap, targetLength);
NSLog(@"can't overwrite"); NSLog(@"can't overwrite");
// return ; return false;
} }
munmap(targetMap, targetLength); munmap(targetMap, targetLength);
xpc_crasher("com.apple.mobile.installd"); xpc_crasher("com.apple.mobile.installd");
@@ -607,8 +606,5 @@ bool patch_installd() {
// TODO(zhuowei): for now we revert it once installd starts // TODO(zhuowei): for now we revert it once installd starts
// so the change will only last until when this installd exits // so the change will only last until when this installd exits
// overwrite_file(fd, originalData); // overwrite_file(fd, originalData);
NSLog(@"patched");
// return;
});
return true; return true;
} }

View File

@@ -6,13 +6,13 @@
// Copyright © 2019 Riley Testut. All rights reserved. // Copyright © 2019 Riley Testut. All rights reserved.
// //
import UIKit
import MobileCoreServices
import Intents
import Combine import Combine
import Intents
import MobileCoreServices
import UIKit
import AltStoreCore
import AltSign import AltSign
import AltStoreCore
import Roxas import Roxas
import Nuke import Nuke
@@ -151,8 +151,7 @@ final class MyAppsViewController: UICollectionViewController
} }
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue) @IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
{ {}
}
} }
private extension MyAppsViewController private extension MyAppsViewController
@@ -170,7 +169,7 @@ private extension MyAppsViewController
dynamicDataSource.numberOfSectionsHandler = { 1 } dynamicDataSource.numberOfSectionsHandler = { 1 }
dynamicDataSource.numberOfItemsHandler = { _ in self.updatesDataSource.itemCount == 0 ? 1 : 0 } dynamicDataSource.numberOfItemsHandler = { _ in self.updatesDataSource.itemCount == 0 ? 1 : 0 }
dynamicDataSource.cellIdentifierHandler = { _ in "NoUpdatesCell" } dynamicDataSource.cellIdentifierHandler = { _ in "NoUpdatesCell" }
dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in dynamicDataSource.cellConfigurationHandler = { cell, _, _ in
let cell = cell as! NoUpdatesCollectionViewCell let cell = cell as! NoUpdatesCollectionViewCell
cell.layoutMargins.left = self.view.layoutMargins.left cell.layoutMargins.left = self.view.layoutMargins.left
cell.layoutMargins.right = self.view.layoutMargins.right cell.layoutMargins.right = self.view.layoutMargins.right
@@ -193,7 +192,7 @@ private extension MyAppsViewController
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.liveFetchLimit = maximumCollapsedUpdatesCount dataSource.liveFetchLimit = maximumCollapsedUpdatesCount
dataSource.cellIdentifierHandler = { _ in "UpdateCell" } dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in dataSource.cellConfigurationHandler = { [weak self] cell, installedApp, _ in
guard let self = self else { return } guard let self = self else { return }
guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return } guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return }
@@ -245,11 +244,12 @@ private extension MyAppsViewController
cell.setNeedsLayout() cell.setNeedsLayout()
} }
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in dataSource.prefetchHandler = { installedApp, _, completionHandler in
guard let iconURL = installedApp.storeApp?.iconURL else { return nil } guard let iconURL = installedApp.storeApp?.iconURL else { return nil }
return RSTAsyncBlockOperation() { (operation) in return RSTAsyncBlockOperation
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { (response, error) in { operation in
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { response, error in
guard !operation.isCancelled else { return operation.finish() } guard !operation.isCancelled else { return operation.finish() }
if let image = response?.image if let image = response?.image
@@ -263,7 +263,7 @@ private extension MyAppsViewController
}) })
} }
} }
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in dataSource.prefetchCompletionHandler = { cell, image, _, error in
let cell = cell as! UpdateCollectionViewCell let cell = cell as! UpdateCollectionViewCell
cell.bannerView.iconImageView.isIndicatingActivity = false cell.bannerView.iconImageView.isIndicatingActivity = false
cell.bannerView.iconImageView.image = image cell.bannerView.iconImageView.image = image
@@ -288,7 +288,7 @@ private extension MyAppsViewController
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.cellIdentifierHandler = { _ in "AppCell" } dataSource.cellIdentifierHandler = { _ in "AppCell" }
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in dataSource.cellConfigurationHandler = { cell, installedApp, indexPath in
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
let cell = cell as! InstalledAppCollectionViewCell let cell = cell as! InstalledAppCollectionViewCell
@@ -363,10 +363,13 @@ private extension MyAppsViewController
cell.bannerView.button.progress = nil cell.bannerView.button.progress = nil
} }
} }
dataSource.prefetchHandler = { (item, indexPath, completion) in dataSource.prefetchHandler = { item, _, completion in
RSTAsyncBlockOperation { (operation) in RSTAsyncBlockOperation
item.managedObjectContext?.perform { { _ in
item.loadIcon { (result) in item.managedObjectContext?.perform
{
item.loadIcon
{ result in
switch result switch result
{ {
case .failure(let error): completion(nil, error) case .failure(let error): completion(nil, error)
@@ -376,7 +379,7 @@ private extension MyAppsViewController
} }
} }
} }
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in dataSource.prefetchCompletionHandler = { cell, image, _, _ in
let cell = cell as! InstalledAppCollectionViewCell let cell = cell as! InstalledAppCollectionViewCell
cell.bannerView.iconImageView.image = image cell.bannerView.iconImageView.image = image
cell.bannerView.iconImageView.isIndicatingActivity = false cell.bannerView.iconImageView.isIndicatingActivity = false
@@ -397,7 +400,7 @@ private extension MyAppsViewController
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext) let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.cellIdentifierHandler = { _ in "AppCell" } dataSource.cellIdentifierHandler = { _ in "AppCell" }
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in dataSource.cellConfigurationHandler = { cell, installedApp, _ in
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
let cell = cell as! InstalledAppCollectionViewCell let cell = cell as! InstalledAppCollectionViewCell
@@ -437,10 +440,13 @@ private extension MyAppsViewController
cell.bannerView.button.progress = nil cell.bannerView.button.progress = nil
} }
} }
dataSource.prefetchHandler = { (item, indexPath, completion) in dataSource.prefetchHandler = { item, _, completion in
RSTAsyncBlockOperation { (operation) in RSTAsyncBlockOperation
item.managedObjectContext?.perform { { _ in
item.loadIcon { (result) in item.managedObjectContext?.perform
{
item.loadIcon
{ result in
switch result switch result
{ {
case .failure(let error): completion(nil, error) case .failure(let error): completion(nil, error)
@@ -450,7 +456,7 @@ private extension MyAppsViewController
} }
} }
} }
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in dataSource.prefetchCompletionHandler = { cell, image, _, _ in
let cell = cell as! InstalledAppCollectionViewCell let cell = cell as! InstalledAppCollectionViewCell
cell.bannerView.iconImageView.image = image cell.bannerView.iconImageView.image = image
cell.bannerView.iconImageView.isIndicatingActivity = false cell.bannerView.iconImageView.isIndicatingActivity = false
@@ -461,10 +467,7 @@ private extension MyAppsViewController
func updateDataSource() func updateDataSource()
{ {
self.dataSource.predicate = nil self.dataSource.predicate = nil
} }
} }
@@ -485,7 +488,8 @@ private extension MyAppsViewController
if self.isViewLoaded if self.isViewLoaded
{ {
UIView.performWithoutAnimation { UIView.performWithoutAnimation
{
self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue)) self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue))
} }
} }
@@ -493,7 +497,8 @@ private extension MyAppsViewController
func fetchAppIDs() func fetchAppIDs()
{ {
AppManager.shared.fetchAppIDs { (result) in AppManager.shared.fetchAppIDs
{ result in
do do
{ {
let (_, context) = try result.get() let (_, context) = try result.get()
@@ -506,12 +511,14 @@ private extension MyAppsViewController
} }
} }
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String : Result<InstalledApp, Error>]) -> Void) func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String: Result<InstalledApp, Error>]) -> Void)
{ {
let group = AppManager.shared.refresh(installedApps, presentingViewController: self, group: self.refreshGroup) let group = AppManager.shared.refresh(installedApps, presentingViewController: self, group: self.refreshGroup)
group.completionHandler = { (results) in group.completionHandler = { results in
DispatchQueue.main.async { DispatchQueue.main.async
let failures = results.compactMapValues { (result) -> Error? in {
let failures = results.compactMapValues
{ result -> Error? in
switch result switch result
{ {
case .failure(OperationError.cancelled): return nil case .failure(OperationError.cancelled): return nil
@@ -557,7 +564,8 @@ private extension MyAppsViewController
self.refreshGroup = group self.refreshGroup = group
UIView.performWithoutAnimation { UIView.performWithoutAnimation
{
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
} }
} }
@@ -570,7 +578,6 @@ private extension MyAppsViewController
let visibleCells = self.collectionView.visibleCells let visibleCells = self.collectionView.visibleCells
self.collectionView.performBatchUpdates({ self.collectionView.performBatchUpdates({
self.isUpdateSectionCollapsed.toggle() self.isUpdateSectionCollapsed.toggle()
UIView.animate(withDuration: 0.3, animations: { UIView.animate(withDuration: 0.3, animations: {
@@ -644,8 +651,10 @@ private extension MyAppsViewController
let installedApps = InstalledApp.fetchAppsForRefreshingAll(in: DatabaseManager.shared.viewContext) let installedApps = InstalledApp.fetchAppsForRefreshingAll(in: DatabaseManager.shared.viewContext)
self.refresh(installedApps) { (result) in self.refresh(installedApps)
DispatchQueue.main.async { { _ in
DispatchQueue.main.async
{
self.isRefreshingAllApps = false self.isRefreshingAllApps = false
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
} }
@@ -654,7 +663,8 @@ private extension MyAppsViewController
if #available(iOS 14, *) if #available(iOS 14, *)
{ {
let interaction = INInteraction.refreshAllApps() let interaction = INInteraction.refreshAllApps()
interaction.donate { (error) in interaction.donate
{ error in
guard let error = error else { return } guard let error = error else { return }
print("Failed to donate intent \(interaction.intent).", error) print("Failed to donate intent \(interaction.intent).", error)
} }
@@ -669,13 +679,17 @@ private extension MyAppsViewController
let installedApp = self.dataSource.item(at: indexPath) let installedApp = self.dataSource.item(at: indexPath)
let previousProgress = AppManager.shared.installationProgress(for: installedApp) let previousProgress = AppManager.shared.installationProgress(for: installedApp)
guard previousProgress == nil else { guard previousProgress == nil
else
{
previousProgress?.cancel() previousProgress?.cancel()
return return
} }
_ = AppManager.shared.update(installedApp, presentingViewController: self) { (result) in _ = AppManager.shared.update(installedApp, presentingViewController: self)
DispatchQueue.main.async { { result in
DispatchQueue.main.async
{
switch result switch result
{ {
case .failure(OperationError.cancelled): case .failure(OperationError.cancelled):
@@ -727,11 +741,14 @@ private extension MyAppsViewController
{ {
var fileURL: URL? var fileURL: URL?
var application: ALTApplication? var application: ALTApplication?
var installedApp: InstalledApp? { var installedApp: InstalledApp?
didSet { {
didSet
{
self.installedAppContext = self.installedApp?.managedObjectContext self.installedAppContext = self.installedApp?.managedObjectContext
} }
} }
private var installedAppContext: NSManagedObjectContext? private var installedAppContext: NSManagedObjectContext?
var error: Error? var error: Error?
@@ -753,8 +770,10 @@ private extension MyAppsViewController
else else
{ {
let downloadProgress = Progress.discreteProgress(totalUnitCount: 100) let downloadProgress = Progress.discreteProgress(totalUnitCount: 100)
downloadOperation = RSTAsyncBlockOperation { (operation) in downloadOperation = RSTAsyncBlockOperation
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in { operation in
let downloadTask = URLSession.shared.downloadTask(with: url)
{ fileURL, response, error in
do do
{ {
let (fileURL, _) = try Result((fileURL, response), error).get() let (fileURL, _) = try Result((fileURL, response), error).get()
@@ -779,7 +798,8 @@ private extension MyAppsViewController
} }
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1) let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
let unzipAppOperation = BlockOperation { let unzipAppOperation = BlockOperation
{
do do
{ {
if let error = context.error if let error = context.error
@@ -788,7 +808,8 @@ private extension MyAppsViewController
} }
guard let fileURL = context.fileURL else { throw OperationError.invalidParameters } guard let fileURL = context.fileURL else { throw OperationError.invalidParameters }
defer { defer
{
try? FileManager.default.removeItem(at: fileURL) try? FileManager.default.removeItem(at: fileURL)
} }
@@ -813,7 +834,8 @@ private extension MyAppsViewController
} }
let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1) let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1)
let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in let removeAppExtensionsOperation = RSTAsyncBlockOperation
{ [weak self] operation in
do do
{ {
if let error = context.error if let error = context.error
@@ -823,8 +845,10 @@ private extension MyAppsViewController
guard let application = context.application else { throw OperationError.invalidParameters } guard let application = context.application else { throw OperationError.invalidParameters }
DispatchQueue.main.async { DispatchQueue.main.async
self?.removeAppExtensions(from: application) { (result) in {
self?.removeAppExtensions(from: application)
{ result in
switch result switch result
{ {
case .success: removeAppExtensionsProgress.completedUnitCount = 1 case .success: removeAppExtensionsProgress.completedUnitCount = 1
@@ -844,7 +868,8 @@ private extension MyAppsViewController
progress.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5) progress.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5)
let installProgress = Progress.discreteProgress(totalUnitCount: 100) let installProgress = Progress.discreteProgress(totalUnitCount: 100)
let installAppOperation = RSTAsyncBlockOperation { (operation) in let installAppOperation = RSTAsyncBlockOperation
{ operation in
do do
{ {
if let error = context.error if let error = context.error
@@ -854,7 +879,8 @@ private extension MyAppsViewController
guard let application = context.application else { throw OperationError.invalidParameters } guard let application = context.application else { throw OperationError.invalidParameters }
let group = AppManager.shared.install(application, presentingViewController: self) { (result) in let group = AppManager.shared.install(application, presentingViewController: self)
{ result in
switch result switch result
{ {
case .success(let installedApp): context.installedApp = installedApp case .success(let installedApp): context.installedApp = installedApp
@@ -873,7 +899,8 @@ private extension MyAppsViewController
installAppOperation.completionBlock = { installAppOperation.completionBlock = {
try? FileManager.default.removeItem(at: temporaryDirectory) try? FileManager.default.removeItem(at: temporaryDirectory)
DispatchQueue.main.async { DispatchQueue.main.async
{
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
self.sideloadingProgressView.observedProgress = nil self.sideloadingProgressView.observedProgress = nil
self.sideloadingProgressView.setHidden(true, animated: true) self.sideloadingProgressView.setHidden(true, animated: true)
@@ -883,12 +910,13 @@ private extension MyAppsViewController
case .success(let app): case .success(let app):
completion(.success(())) completion(.success(()))
app.managedObjectContext?.perform { app.managedObjectContext?.perform
{
print("Successfully installed app:", app.bundleIdentifier) print("Successfully installed app:", app.bundleIdentifier)
} }
case .failure(OperationError.cancelled): case .failure(OperationError.cancelled):
completion(.failure((OperationError.cancelled))) completion(.failure(OperationError.cancelled))
case .failure(let error): case .failure(let error):
let toastView = ToastView(error: error) let toastView = ToastView(error: error)
@@ -930,11 +958,17 @@ private extension MyAppsViewController
@objc func presentInactiveAppsAlert() @objc func presentInactiveAppsAlert()
{ {
let message: String var message: String
if UserDefaults.standard.activeAppLimitIncludesExtensions if UserDefaults.standard.activeAppLimitIncludesExtensions
{ {
message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "") message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "")
if UserDefaults.standard.enableMacDirtyCowExploit
{
message += "\n\n"
message += NSLocalizedString("If you're using the MacDirtyCow exploit to remove the 3-app limit, you can install up to 10 apps and app extensions per Apple ID instead.", comment: "")
}
} }
else else
{ {
@@ -974,13 +1008,15 @@ private extension MyAppsViewController
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "") let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "")
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert) let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { _ in
completion(.failure(OperationError.cancelled)) completion(.failure(OperationError.cancelled))
})) }))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default)
{ _ in
completion(.success(())) completion(.success(()))
}) })
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive)
{ _ in
do do
{ {
for appExtension in application.appExtensions for appExtension in application.appExtensions
@@ -1004,7 +1040,8 @@ private extension MyAppsViewController
{ {
func open(_ installedApp: InstalledApp) func open(_ installedApp: InstalledApp)
{ {
UIApplication.shared.open(installedApp.openAppURL) { success in UIApplication.shared.open(installedApp.openAppURL)
{ success in
guard !success else { return } guard !success else { return }
let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name)) let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name))
@@ -1015,16 +1052,20 @@ private extension MyAppsViewController
func refresh(_ installedApp: InstalledApp) func refresh(_ installedApp: InstalledApp)
{ {
let previousProgress = AppManager.shared.refreshProgress(for: installedApp) let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
guard previousProgress == nil else { guard previousProgress == nil
else
{
previousProgress?.cancel() previousProgress?.cancel()
return return
} }
self.refresh([installedApp]) { (results) in self.refresh([installedApp])
{ results in
// If an error occured, reload the section so the progress bar is no longer visible. // If an error occured, reload the section so the progress bar is no longer visible.
if results.values.contains(where: { $0.error != nil }) if results.values.contains(where: { $0.error != nil })
{ {
DispatchQueue.main.async { DispatchQueue.main.async
{
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
} }
} }
@@ -1040,7 +1081,8 @@ private extension MyAppsViewController
do do
{ {
let app = try result.get() let app = try result.get()
app.managedObjectContext?.perform { app.managedObjectContext?.perform
{
try? app.managedObjectContext?.save() try? app.managedObjectContext?.save()
} }
} }
@@ -1052,7 +1094,8 @@ private extension MyAppsViewController
{ {
print("Failed to activate app:", error) print("Failed to activate app:", error)
DispatchQueue.main.async { DispatchQueue.main.async
{
installedApp.isActive = false installedApp.isActive = false
let toastView = ToastView(error: error) let toastView = ToastView(error: error)
@@ -1073,11 +1116,13 @@ private extension MyAppsViewController
.filter(\.isActive) .filter(\.isActive)
.map { $0.publisher(for: \.isActive) } .map { $0.publisher(for: \.isActive) }
.collect() .collect()
.flatMap { publishers in .flatMap
{ publishers in
Publishers.MergeMany(publishers) Publishers.MergeMany(publishers)
} }
.first { isActive in !isActive } .first { isActive in !isActive }
.sink { _ in .sink
{ _ in
// A previously active app is now inactive, // A previously active app is now inactive,
// which means there are now enough slots to activate the app, // which means there are now enough slots to activate the app,
// so pre-emptively mark it as active to provide visual feedback sooner. // so pre-emptively mark it as active to provide visual feedback sooner.
@@ -1085,9 +1130,11 @@ private extension MyAppsViewController
cancellable?.cancel() cancellable?.cancel()
} }
AppManager.shared.deactivateApps(for: app, presentingViewController: self) { result in AppManager.shared.deactivateApps(for: app, presentingViewController: self)
{ result in
cancellable?.cancel() cancellable?.cancel()
installedApp.managedObjectContext?.perform { installedApp.managedObjectContext?.perform
{
switch result switch result
{ {
case .failure(let error): case .failure(let error):
@@ -1113,7 +1160,8 @@ private extension MyAppsViewController
guard installedApp.isActive else { return } guard installedApp.isActive else { return }
installedApp.isActive = false installedApp.isActive = false
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in AppManager.shared.deactivate(installedApp, presentingViewController: self)
{ result in
do do
{ {
let app = try result.get() let app = try result.get()
@@ -1125,7 +1173,8 @@ private extension MyAppsViewController
{ {
print("Failed to activate app:", error) print("Failed to activate app:", error)
DispatchQueue.main.async { DispatchQueue.main.async
{
installedApp.isActive = true installedApp.isActive = true
let toastView = ToastView(error: error) let toastView = ToastView(error: error)
@@ -1153,13 +1202,15 @@ private extension MyAppsViewController
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
alertController.addAction(.cancel) alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { (action) in alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { _ in
AppManager.shared.remove(installedApp) { (result) in AppManager.shared.remove(installedApp)
{ result in
switch result switch result
{ {
case .success: break case .success: break
case .failure(let error): case .failure(let error):
DispatchQueue.main.async { DispatchQueue.main.async
{
let toastView = ToastView(error: error) let toastView = ToastView(error: error)
toastView.show(in: self) toastView.show(in: self)
} }
@@ -1179,8 +1230,9 @@ private extension MyAppsViewController
alertController.addAction(.cancel) alertController.addAction(.cancel)
let actionTitle = String(format: NSLocalizedString("Back Up %@", comment: ""), installedApp.name) let actionTitle = String(format: NSLocalizedString("Back Up %@", comment: ""), installedApp.name)
alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { (action) in alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { _ in
AppManager.shared.backup(installedApp, presentingViewController: self) { (result) in AppManager.shared.backup(installedApp, presentingViewController: self)
{ result in
do do
{ {
let app = try result.get() let app = try result.get()
@@ -1192,7 +1244,8 @@ private extension MyAppsViewController
{ {
print("Failed to back up app:", error) print("Failed to back up app:", error)
DispatchQueue.main.async { DispatchQueue.main.async
{
let toastView = ToastView(error: error) let toastView = ToastView(error: error)
toastView.show(in: self) toastView.show(in: self)
@@ -1201,7 +1254,8 @@ private extension MyAppsViewController
} }
} }
DispatchQueue.main.async { DispatchQueue.main.async
{
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue]) self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
} }
})) }))
@@ -1214,8 +1268,9 @@ private extension MyAppsViewController
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name) let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet) let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
alertController.addAction(.cancel) alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { (action) in alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { _ in
AppManager.shared.restore(installedApp, presentingViewController: self) { (result) in AppManager.shared.restore(installedApp, presentingViewController: self)
{ result in
do do
{ {
let app = try result.get() let app = try result.get()
@@ -1227,14 +1282,16 @@ private extension MyAppsViewController
{ {
print("Failed to restore app:", error) print("Failed to restore app:", error)
DispatchQueue.main.async { DispatchQueue.main.async
{
let toastView = ToastView(error: error) let toastView = ToastView(error: error)
toastView.show(in: self) toastView.show(in: self)
} }
} }
} }
DispatchQueue.main.async { DispatchQueue.main.async
{
self.collectionView.reloadSections([Section.activeApps.rawValue]) self.collectionView.reloadSections([Section.activeApps.rawValue])
} }
})) }))
@@ -1267,7 +1324,8 @@ private extension MyAppsViewController
self.activeAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp) self.activeAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
self.inactiveAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp) self.inactiveAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in DatabaseManager.shared.persistentContainer.performBackgroundTask
{ context in
do do
{ {
let tempApp = context.object(with: installedApp.objectID) as! InstalledApp let tempApp = context.object(with: installedApp.objectID) as! InstalledApp
@@ -1291,7 +1349,8 @@ private extension MyAppsViewController
if tempApp.isActive if tempApp.isActive
{ {
DispatchQueue.main.async { DispatchQueue.main.async
{
self.refresh(installedApp) self.refresh(installedApp)
} }
} }
@@ -1300,7 +1359,8 @@ private extension MyAppsViewController
{ {
print("Failed to change app icon.", error) print("Failed to change app icon.", error)
DispatchQueue.main.async { DispatchQueue.main.async
{
let toastView = ToastView(error: error) let toastView = ToastView(error: error)
toastView.show(in: self) toastView.show(in: self)
} }
@@ -1311,8 +1371,10 @@ private extension MyAppsViewController
@available(iOS 14, *) @available(iOS 14, *)
func enableJIT(for installedApp: InstalledApp) func enableJIT(for installedApp: InstalledApp)
{ {
AppManager.shared.enableJIT(for: installedApp) { result in AppManager.shared.enableJIT(for: installedApp)
DispatchQueue.main.async { { result in
DispatchQueue.main.async
{
switch result switch result
{ {
case .success: break case .success: break
@@ -1329,7 +1391,8 @@ private extension MyAppsViewController
{ {
@objc func didFetchSource(_ notification: Notification) @objc func didFetchSource(_ notification: Notification)
{ {
DispatchQueue.main.async { DispatchQueue.main.async
{
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
{ {
do { try self.updatesDataSource.fetchedResultsController.performFetch() } do { try self.updatesDataSource.fetchedResultsController.performFetch() }
@@ -1347,7 +1410,8 @@ private extension MyAppsViewController
guard let url = notification.userInfo?[AppDelegate.importAppDeepLinkURLKey] as? URL else { return } guard let url = notification.userInfo?[AppDelegate.importAppDeepLinkURLKey] as? URL else { return }
self.sideloadApp(at: url) { (result) in self.sideloadApp(at: url)
{ _ in
guard url.isFileURL else { return } guard url.isFileURL else { return }
do do
@@ -1374,7 +1438,8 @@ extension MyAppsViewController
case .updates: case .updates:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView
UIView.performWithoutAnimation { UIView.performWithoutAnimation
{
headerView.button.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15) headerView.button.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15)
headerView.button.setTitle("", for: .normal) headerView.button.setTitle("", for: .normal)
headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28) headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28)
@@ -1400,7 +1465,8 @@ extension MyAppsViewController
case .activeApps where kind == UICollectionView.elementKindSectionHeader: case .activeApps where kind == UICollectionView.elementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ActiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ActiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
UIView.performWithoutAnimation { UIView.performWithoutAnimation
{
headerView.layoutMargins.left = self.view.layoutMargins.left headerView.layoutMargins.left = self.view.layoutMargins.left
headerView.layoutMargins.right = self.view.layoutMargins.right headerView.layoutMargins.right = self.view.layoutMargins.right
@@ -1438,7 +1504,8 @@ extension MyAppsViewController
case .inactiveApps where kind == UICollectionView.elementKindSectionHeader: case .inactiveApps where kind == UICollectionView.elementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "InactiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "InactiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
UIView.performWithoutAnimation { UIView.performWithoutAnimation
{
headerView.layoutMargins.left = self.view.layoutMargins.left headerView.layoutMargins.left = self.view.layoutMargins.left
headerView.layoutMargins.right = self.view.layoutMargins.right headerView.layoutMargins.right = self.view.layoutMargins.right
@@ -1507,50 +1574,61 @@ extension MyAppsViewController
{ {
var actions = [UIMenuElement]() var actions = [UIMenuElement]()
let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app")) { (action) in let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app"))
{ _ in
self.open(installedApp) self.open(installedApp)
} }
let openMenu = UIMenu(title: "", options: .displayInline, children: [openAction]) let openMenu = UIMenu(title: "", options: .displayInline, children: [openAction])
let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise")) { (action) in let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise"))
{ _ in
self.refresh(installedApp) self.refresh(installedApp)
} }
let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle")) { (action) in let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle"))
{ _ in
self.activate(installedApp) self.activate(installedApp)
} }
let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive) { (action) in let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive)
{ _ in
self.deactivate(installedApp) self.deactivate(installedApp)
} }
let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { (action) in let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive)
{ _ in
self.remove(installedApp) self.remove(installedApp)
} }
let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt")) { (action) in let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt"))
{ _ in
guard #available(iOS 14, *) else { return } guard #available(iOS 14, *) else { return }
self.enableJIT(for: installedApp) self.enableJIT(for: installedApp)
} }
let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc")) { (action) in let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc"))
{ _ in
self.backup(installedApp) self.backup(installedApp)
} }
let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc")) { (action) in let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc"))
{ _ in
self.exportBackup(for: installedApp) self.exportBackup(for: installedApp)
} }
let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc")) { (action) in let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc"))
{ _ in
self.restore(installedApp) self.restore(installedApp)
} }
let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo")) { (action) in let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo"))
{ _ in
self.chooseIcon(for: installedApp) self.chooseIcon(for: installedApp)
} }
let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) { (action) in let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive])
{ _ in
self.changeIcon(for: installedApp, to: nil) self.changeIcon(for: installedApp, to: nil)
} }
@@ -1562,7 +1640,9 @@ extension MyAppsViewController
let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions) let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions)
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else { guard installedApp.bundleIdentifier != StoreApp.altstoreAppID
else
{
#if BETA #if BETA
return [refreshAction, changeIconMenu] return [refreshAction, changeIconMenu]
#else #else
@@ -1605,9 +1685,10 @@ extension MyAppsViewController
if let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp) if let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp)
{ {
var backupExists = false var backupExists = false
var outError: NSError? = nil var outError: NSError?
self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError) { (backupDirectoryURL) in self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError)
{ backupDirectoryURL in
#if DEBUG #if DEBUG
backupExists = true backupExists = true
#else #else
@@ -1649,7 +1730,7 @@ extension MyAppsViewController
// Legacy sideloaded app, so can't detect if it's deleted. // Legacy sideloaded app, so can't detect if it's deleted.
actions.append(removeAction) actions.append(removeAction)
} }
else if !UserDefaults.standard.isLegacyDeactivationSupported && !installedApp.isActive else if !UserDefaults.standard.isLegacyDeactivationSupported, !installedApp.isActive
{ {
// Inactive apps are actually deleted, so we need another way // Inactive apps are actually deleted, so we need another way
// for user to remove them from AltStore. // for user to remove them from AltStore.
@@ -1670,7 +1751,8 @@ extension MyAppsViewController
case .activeApps, .inactiveApps: case .activeApps, .inactiveApps:
let installedApp = self.dataSource.item(at: indexPath) let installedApp = self.dataSource.item(at: indexPath)
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { (suggestedActions) -> UIMenu? in return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil)
{ _ -> UIMenu? in
let actions = self.actions(for: installedApp) let actions = self.actions(for: installedApp)
let menu = UIMenu(title: "", children: actions) let menu = UIMenu(title: "", children: actions)
@@ -1868,7 +1950,7 @@ extension MyAppsViewController: UICollectionViewDropDelegate
let inactiveAppsHeaderAttributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: Section.inactiveApps.rawValue)) let inactiveAppsHeaderAttributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: Section.inactiveApps.rawValue))
else { return UICollectionViewDropProposal(operation: .cancel) } else { return UICollectionViewDropProposal(operation: .cancel) }
var dropDestinationIndexPath: IndexPath? = nil var dropDestinationIndexPath: IndexPath?
defer defer
{ {
@@ -1881,7 +1963,8 @@ extension MyAppsViewController: UICollectionViewDropDelegate
let indexPaths = [previousIndexPath, dropDestinationIndexPath].compactMap { $0 } let indexPaths = [previousIndexPath, dropDestinationIndexPath].compactMap { $0 }
let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters()) { let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters())
{
for indexPath in indexPaths for indexPath in indexPaths
{ {
// Access cell directly so we can animate it correctly. // Access cell directly so we can animate it correctly.
@@ -1917,12 +2000,16 @@ extension MyAppsViewController: UICollectionViewDropDelegate
{ {
// Activating // Activating
guard point.y > activeAppsHeaderAttributes.frame.minY else { guard point.y > activeAppsHeaderAttributes.frame.minY
else
{
// Above active apps section. // Above active apps section.
return UICollectionViewDropProposal(operation: .cancel) return UICollectionViewDropProposal(operation: .cancel)
} }
guard point.y < inactiveAppsHeaderAttributes.frame.minY else { guard point.y < inactiveAppsHeaderAttributes.frame.minY
else
{
// Inactive apps section. // Inactive apps section.
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
} }
@@ -1940,13 +2027,17 @@ extension MyAppsViewController: UICollectionViewDropDelegate
// Not enough active app slots, so we need to deactivate an app. // Not enough active app slots, so we need to deactivate an app.
// Provided destinationIndexPath is inaccurate. // Provided destinationIndexPath is inaccurate.
guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue else { guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue
else
{
// Invalid destination index path. // Invalid destination index path.
return UICollectionViewDropProposal(operation: .cancel) return UICollectionViewDropProposal(operation: .cancel)
} }
let installedApp = self.dataSource.item(at: indexPath) let installedApp = self.dataSource.item(at: indexPath)
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else { guard installedApp.bundleIdentifier != StoreApp.altstoreAppID
else
{
// Can't deactivate AltStore. // Can't deactivate AltStore.
return UICollectionViewDropProposal(operation: .forbidden, intent: .insertIntoDestinationIndexPath) return UICollectionViewDropProposal(operation: .forbidden, intent: .insertIntoDestinationIndexPath)
} }
@@ -1978,8 +2069,10 @@ extension MyAppsViewController: UICollectionViewDropDelegate
installedApp.isActive = true installedApp.isActive = true
let previousInstalledApp = self.dataSource.item(at: destinationIndexPath) let previousInstalledApp = self.dataSource.item(at: destinationIndexPath)
self.deactivate(previousInstalledApp) { (result) in self.deactivate(previousInstalledApp)
installedApp.managedObjectContext?.perform { { result in
installedApp.managedObjectContext?.perform
{
switch result switch result
{ {
case .failure: installedApp.isActive = false case .failure: installedApp.isActive = false
@@ -2053,7 +2146,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate
switch controller.documentPickerMode switch controller.documentPickerMode
{ {
case .import, .open: case .import, .open:
self.sideloadApp(at: fileURL) { (result) in self.sideloadApp(at: fileURL)
{ result in
print("Sideloaded app at \(fileURL) with result:", result) print("Sideloaded app at \(fileURL) with result:", result)
} }
@@ -2079,7 +2173,7 @@ extension MyAppsViewController: UIViewControllerPreviewingDelegate
previewingContext.sourceRect = cell.frame previewingContext.sourceRect = cell.frame
let app = self.dataSource.item(at: indexPath) let app = self.dataSource.item(at: indexPath)
guard let storeApp = app.storeApp else { return nil} guard let storeApp = app.storeApp else { return nil }
let appViewController = AppViewController.makeAppViewController(app: storeApp) let appViewController = AppViewController.makeAppViewController(app: storeApp)
return appViewController return appViewController
@@ -2099,9 +2193,10 @@ extension MyAppsViewController: UIViewControllerPreviewingDelegate
extension MyAppsViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate extension MyAppsViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate
{ {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any])
{
defer
{ {
defer {
picker.dismiss(animated: true, completion: nil) picker.dismiss(animated: true, completion: nil)
self._imagePickerInstalledApp = nil self._imagePickerInstalledApp = nil
} }

View File

@@ -7,11 +7,11 @@
// //
import Foundation import Foundation
import Roxas
import Network import Network
import Roxas
import AltStoreCore
import AltSign import AltSign
import AltStoreCore
enum AuthenticationError: LocalizedError enum AuthenticationError: LocalizedError
{ {
@@ -22,8 +22,10 @@ enum AuthenticationError: LocalizedError
case missingPrivateKey case missingPrivateKey
case missingCertificate case missingCertificate
var errorDescription: String? { var errorDescription: String?
switch self { {
switch self
{
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "") case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "") case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "") case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
@@ -82,7 +84,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
} }
// Sign In // Sign In
self.signIn() { (result) in self.signIn
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result switch result
@@ -93,7 +96,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
self.progress.completedUnitCount += 1 self.progress.completedUnitCount += 1
// Fetch Team // Fetch Team
self.fetchTeam(for: account, session: session) { (result) in self.fetchTeam(for: account, session: session)
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result switch result
@@ -104,7 +108,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
self.progress.completedUnitCount += 1 self.progress.completedUnitCount += 1
// Fetch Certificate // Fetch Certificate
self.fetchCertificate(for: team, session: session) { (result) in self.fetchCertificate(for: team, session: session)
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result switch result
@@ -115,7 +120,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
self.progress.completedUnitCount += 1 self.progress.completedUnitCount += 1
// Register Device // Register Device
self.registerCurrentDevice(for: team, session: session) { (result) in self.registerCurrentDevice(for: team, session: session)
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result switch result
@@ -125,7 +131,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
self.progress.completedUnitCount += 1 self.progress.completedUnitCount += 1
// Save account/team to disk. // Save account/team to disk.
self.save(team) { (result) in self.save(team)
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) } guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result switch result
@@ -133,7 +140,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
case .failure(let error): self.finish(.failure(error)) case .failure(let error): self.finish(.failure(error))
case .success: case .success:
// Must cache App IDs _after_ saving account/team to disk. // Must cache App IDs _after_ saving account/team to disk.
self.cacheAppIDs(team: team, session: session) { (result) in self.cacheAppIDs(team: team, session: session)
{ result in
let result = result.map { _ in (team, certificate, session) } let result = result.map { _ in (team, certificate, session) }
self.finish(result) self.finish(result)
} }
@@ -152,7 +160,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
func save(_ altTeam: ALTTeam, completionHandler: @escaping (Result<Void, Error>) -> Void) func save(_ altTeam: ALTTeam, completionHandler: @escaping (Result<Void, Error>) -> Void)
{ {
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext() let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
context.performAndWait { context.performAndWait
{
do do
{ {
let account: Account let account: Account
@@ -204,7 +213,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
print("Finished authenticating with result:", result.error?.localizedDescription ?? "success") print("Finished authenticating with result:", result.error?.localizedDescription ?? "success")
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext() let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
context.perform { context.perform
{
do do
{ {
let (altTeam, altCertificate, session) = try result.get() let (altTeam, altCertificate, session) = try result.get()
@@ -241,7 +251,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1) let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion) if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion)
{ {
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
} }
else else
{ {
@@ -258,14 +268,16 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
Keychain.shared.signingCertificate = altCertificate.p12Data() Keychain.shared.signingCertificate = altCertificate.p12Data()
Keychain.shared.signingCertificatePassword = altCertificate.machineIdentifier Keychain.shared.signingCertificatePassword = altCertificate.machineIdentifier
self.showInstructionsIfNecessary() { (didShowInstructions) in self.showInstructionsIfNecessary
{ _ in
let signer = ALTSigner(team: altTeam, certificate: altCertificate) let signer = ALTSigner(team: altTeam, certificate: altCertificate)
// Refresh screen must go last since a successful refresh will cause the app to quit. // Refresh screen must go last since a successful refresh will cause the app to quit.
self.showRefreshScreenIfNecessary(signer: signer, session: session) { (didShowRefreshAlert) in self.showRefreshScreenIfNecessary(signer: signer, session: session)
{ _ in
super.finish(result) super.finish(result)
DispatchQueue.main.async { DispatchQueue.main.async
{
self.navigationController.dismiss(animated: true, completion: nil) self.navigationController.dismiss(animated: true, completion: nil)
} }
} }
@@ -275,7 +287,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
{ {
super.finish(result) super.finish(result)
DispatchQueue.main.async { DispatchQueue.main.async
{
self.navigationController.dismiss(animated: true, completion: nil) self.navigationController.dismiss(animated: true, completion: nil)
} }
} }
@@ -314,14 +327,16 @@ private extension AuthenticationOperation
{ {
func authenticate() func authenticate()
{ {
DispatchQueue.main.async { DispatchQueue.main.async
{
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in authenticationViewController.authenticationHandler = { appleID, password, completionHandler in
self.authenticate(appleID: appleID, password: password) { (result) in self.authenticate(appleID: appleID, password: password)
{ result in
completionHandler(result) completionHandler(result)
} }
} }
authenticationViewController.completionHandler = { (result) in authenticationViewController.completionHandler = { result in
if let (account, session, password) = result if let (account, session, password) = result
{ {
// We presented the Auth UI and the user signed in. // We presented the Auth UI and the user signed in.
@@ -346,7 +361,8 @@ private extension AuthenticationOperation
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
{ {
self.authenticate(appleID: appleID, password: password) { (result) in self.authenticate(appleID: appleID, password: password)
{ result in
switch result switch result
{ {
case .success((let account, let session)): case .success((let account, let session)):
@@ -372,7 +388,7 @@ private extension AuthenticationOperation
self.appleIDEmailAddress = appleID self.appleIDEmailAddress = appleID
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context) let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
fetchAnisetteDataOperation.resultHandler = { (result) in fetchAnisetteDataOperation.resultHandler = { result in
switch result switch result
{ {
case .failure(let error): completionHandler(.failure(error)) case .failure(let error): completionHandler(.failure(error))
@@ -381,10 +397,12 @@ private extension AuthenticationOperation
if let presentingViewController = self.presentingViewController if let presentingViewController = self.presentingViewController
{ {
verificationHandler = { (completionHandler) in verificationHandler = { completionHandler in
DispatchQueue.main.async { DispatchQueue.main.async
{
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert) let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in alertController.addTextField
{ textField in
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.keyboardType = .numberPad textField.keyboardType = .numberPad
@@ -392,7 +410,8 @@ private extension AuthenticationOperation
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField) NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
} }
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default)
{ _ in
let textField = alertController.textFields?.first let textField = alertController.textFields?.first
let code = textField?.text ?? "" let code = textField?.text ?? ""
@@ -402,7 +421,8 @@ private extension AuthenticationOperation
alertController.addAction(submitAction) alertController.addAction(submitAction)
self.submitCodeAction = submitAction self.submitCodeAction = submitAction
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel)
{ _ in
completionHandler(nil) completionHandler(nil)
}) })
@@ -424,7 +444,8 @@ private extension AuthenticationOperation
} }
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData, ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
verificationHandler: verificationHandler) { (account, session, error) in verificationHandler: verificationHandler)
{ account, session, error in
if let account = account, let session = session if let account = account, let session = session
{ {
completionHandler(.success((account, session))) completionHandler(.success((account, session)))
@@ -444,14 +465,21 @@ private extension AuthenticationOperation
{ {
func selectTeam(from teams: [ALTTeam]) func selectTeam(from teams: [ALTTeam])
{ {
if teams.count <= 1 { if teams.count <= 1
if let team = teams.first { {
if let team = teams.first
{
return completionHandler(.success(team)) return completionHandler(.success(team))
} else { }
else
{
return completionHandler(.failure(AuthenticationError.noTeam)) return completionHandler(.failure(AuthenticationError.noTeam))
} }
} else { }
DispatchQueue.main.async { else
{
DispatchQueue.main.async
{
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
selectTeamViewController.teams = teams selectTeamViewController.teams = teams
@@ -465,12 +493,14 @@ private extension AuthenticationOperation
} }
} }
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in ALTAppleAPI.shared.fetchTeams(for: account, session: session)
{ teams, error in
switch Result(teams, error) switch Result(teams, error)
{ {
case .failure(let error): completionHandler(.failure(error)) case .failure(let error): completionHandler(.failure(error))
case .success(let teams): case .success(let teams):
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in DatabaseManager.shared.persistentContainer.performBackgroundTask
{ context in
if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier }) if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier })
{ {
completionHandler(.success(altTeam)) completionHandler(.success(altTeam))
@@ -489,18 +519,22 @@ private extension AuthenticationOperation
func requestCertificate() func requestCertificate()
{ {
let machineName = "AltStore - " + UIDevice.current.name let machineName = "AltStore - " + UIDevice.current.name
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { (certificate, error) in ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session)
{ certificate, error in
do do
{ {
let certificate = try Result(certificate, error).get() let certificate = try Result(certificate, error).get()
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey } guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in ALTAppleAPI.shared.fetchCertificates(for: team, session: session)
{ certificates, error in
do do
{ {
let certificates = try Result(certificates, error).get() let certificates = try Result(certificates, error).get()
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else { guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber })
else
{
throw AuthenticationError.missingCertificate throw AuthenticationError.missingCertificate
} }
@@ -524,7 +558,8 @@ private extension AuthenticationOperation
{ {
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) } guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in ALTAppleAPI.shared.revoke(certificate, for: team, session: session)
{ success, error in
if let error = error, !success if let error = error, !success
{ {
completionHandler(.failure(error)) completionHandler(.failure(error))
@@ -536,7 +571,8 @@ private extension AuthenticationOperation
} }
} }
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in ALTAppleAPI.shared.fetchCertificates(for: team, session: session)
{ certificates, error in
do do
{ {
let certificates = try Result(certificates, error).get() let certificates = try Result(certificates, error).get()
@@ -593,11 +629,14 @@ private extension AuthenticationOperation
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void) func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
{ {
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
else
{
return completionHandler(.failure(OperationError.unknownUDID)) return completionHandler(.failure(OperationError.unknownUDID))
} }
ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session) { (devices, error) in ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session)
{ devices, error in
do do
{ {
let devices = try Result(devices, error).get() let devices = try Result(devices, error).get()
@@ -608,7 +647,8 @@ private extension AuthenticationOperation
} }
else else
{ {
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session) { (device, error) in ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session)
{ device, error in
completionHandler(Result(device, error)) completionHandler(Result(device, error))
} }
} }
@@ -623,7 +663,7 @@ private extension AuthenticationOperation
func cacheAppIDs(team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<Void, Error>) -> Void) func cacheAppIDs(team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<Void, Error>) -> Void)
{ {
let fetchAppIDsOperation = FetchAppIDsOperation(context: self.context) let fetchAppIDsOperation = FetchAppIDsOperation(context: self.context)
fetchAppIDsOperation.resultHandler = { (result) in fetchAppIDsOperation.resultHandler = { result in
do do
{ {
let (_, context) = try result.get() let (_, context) = try result.get()
@@ -644,7 +684,8 @@ private extension AuthenticationOperation
{ {
guard self.shouldShowInstructions else { return completionHandler(false) } guard self.shouldShowInstructions else { return completionHandler(false) }
DispatchQueue.main.async { DispatchQueue.main.async
{
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
instructionsViewController.showsBottomButton = true instructionsViewController.showsBottomButton = true
instructionsViewController.completionHandler = { instructionsViewController.completionHandler = {
@@ -668,7 +709,8 @@ private extension AuthenticationOperation
#if DEBUG #if DEBUG
completionHandler(false) completionHandler(false)
#else #else
DispatchQueue.main.async { DispatchQueue.main.async
{
let context = AuthenticatedOperationContext(context: self.context) let context = AuthenticatedOperationContext(context: self.context)
context.operations.removeAllObjects() // Prevent deadlock due to endless waiting on previous operations to finish. context.operations.removeAllObjects() // Prevent deadlock due to endless waiting on previous operations to finish.

View File

@@ -8,8 +8,8 @@
import Foundation import Foundation
import Network import Network
import AltStoreCore
import AltSign import AltSign
import AltStoreCore
import Roxas import Roxas
@objc(InstallAppOperation) @objc(InstallAppOperation)
@@ -44,8 +44,8 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
else { return self.finish(.failure(OperationError.invalidParameters)) } else { return self.finish(.failure(OperationError.invalidParameters)) }
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext() let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
backgroundContext.perform { backgroundContext.perform
{
/* App */ /* App */
let installedApp: InstalledApp let installedApp: InstalledApp
@@ -142,7 +142,8 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
} }
} }
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in activeProfiles = Set(activeApps.flatMap
{ installedApp -> [String] in
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier } let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
}) })
@@ -152,11 +153,50 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String) let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
let res = minimuxer_install_ipa(ns_bundle_ptr) let res = minimuxer_install_ipa(ns_bundle_ptr)
if res == 0 { if res == 0
{
installedApp.refreshedDate = Date() installedApp.refreshedDate = Date()
self.finish(.success(installedApp)) self.finish(.success(installedApp))
}
else if res == -15
{
// try again
if UserDefaults.standard.enableMacDirtyCowExploit && UserDefaults.standard.isMacDirtyCowSupported
{
patch3AppLimit
{ result in
switch result
{
case .success:
UserDefaults.standard.set(bootTime(), forKey: "mdcRanBootTime")
print("patched sucessfully")
case .failure(let err):
switch err
{
case .NoFDA:
self.finish(.failure(OperationError.mdcNoFDA))
return
case .FailedPatchd:
self.finish(.failure(OperationError.mdcFailedPatchd))
return
}
}
}
} else { let res_try_again = minimuxer_install_ipa(ns_bundle_ptr)
if res_try_again == 0
{
installedApp.refreshedDate = Date()
self.finish(.success(installedApp))
}
else
{
self.finish(.failure(minimuxer_to_operation(code: res_try_again)))
}
}
}
else
{
self.finish(.failure(minimuxer_to_operation(code: res))) self.finish(.failure(minimuxer_to_operation(code: res)))
} }
} }

View File

@@ -6,11 +6,10 @@
// Copyright © 2019 Riley Testut. All rights reserved. // Copyright © 2019 Riley Testut. All rights reserved.
// //
import Foundation
import AltSign import AltSign
import Foundation
enum OperationError: LocalizedError enum OperationError: LocalizedError {
{
static let domain = OperationError.unknown._domain static let domain = OperationError.unknown._domain
case unknown case unknown
@@ -45,6 +44,8 @@ enum OperationError: LocalizedError
case functionArguments case functionArguments
case profileInstall case profileInstall
case noConnection case noConnection
case mdcNoFDA
case mdcFailedPatchd
var failureReason: String? { var failureReason: String? {
switch self { switch self {
@@ -73,22 +74,21 @@ enum OperationError: LocalizedError
case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "") case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "")
case .profileInstall: return NSLocalizedString("Unable to manage profiles on the device", comment: "") case .profileInstall: return NSLocalizedString("Unable to manage profiles on the device", comment: "")
case .noConnection: return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "") case .noConnection: return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
case .mdcNoFDA: return NSLocalizedString("Unable to get Full Disk Access using MDC.", comment: "")
case .mdcFailedPatchd: return NSLocalizedString("Unable to patch installd using MDC.", comment: "")
} }
} }
var recoverySuggestion: String? { var recoverySuggestion: String? {
switch self switch self {
{
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date): case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "") let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
let message: String let message: String
if requiredAppIDs > 1 if requiredAppIDs > 1 {
{
let availableText: String let availableText: String
switch availableAppIDs switch availableAppIDs {
{
case 0: availableText = NSLocalizedString("none are available", comment: "") case 0: availableText = NSLocalizedString("none are available", comment: "")
case 1: availableText = NSLocalizedString("only 1 is available", comment: "") case 1: availableText = NSLocalizedString("only 1 is available", comment: "")
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs)) default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
@@ -97,8 +97,7 @@ enum OperationError: LocalizedError
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText) let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
message = prefixMessage + " " + baseMessage message = prefixMessage + " " + baseMessage
} }
else else {
{
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date) let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
let dateComponentsFormatter = DateComponentsFormatter() let dateComponentsFormatter = DateComponentsFormatter()

View File

@@ -21,7 +21,7 @@
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2"> <label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
<rect key="frame" x="0.0" y="1082" width="375" height="25"/> <rect key="frame" x="0.0" y="1206" width="375" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -168,7 +168,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
<rect key="frame" x="30" y="15.5" width="106" height="20.5"/> <rect key="frame" x="30" y="15.499999999999998" width="106" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -208,7 +208,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Background Refresh" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbG-HB-IOn"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Background Refresh" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EbG-HB-IOn">
<rect key="frame" x="30" y="15.5" width="166" height="20.5"/> <rect key="frame" x="30" y="15.499999999999998" width="166" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -244,7 +244,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
<rect key="frame" x="30" y="15.5" width="100.5" height="20.5"/> <rect key="frame" x="30" y="15.499999999999998" width="100.5" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -276,7 +276,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="How it works" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2CC-iw-3bd"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="How it works" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2CC-iw-3bd">
<rect key="frame" x="30" y="15.5" width="105" height="20.5"/> <rect key="frame" x="30" y="15.499999999999998" width="105" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -359,22 +359,22 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/> <rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="UI Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oqY-wY-1Vf">
<rect key="frame" x="30" y="15.5" width="89" height="20.5"/> <rect key="frame" x="30" y="15.5" width="89" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X"> <stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="gUq-6Q-t5X">
<rect key="frame" x="198" y="15.5" width="147" height="20.5"/> <rect key="frame" x="198" y="15.5" width="147" height="20.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Fabian (thdev)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylE-VL-7Fq">
<rect key="frame" x="0.0" y="0.0" width="115" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="115" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/> <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="e3L-vR-Jae">
<rect key="frame" x="129" y="0.0" width="18" height="20.5"/> <rect key="frame" x="129" y="0.0" width="18" height="20.5"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -403,22 +403,22 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/> <rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
<rect key="frame" x="30" y="15.5" width="115.5" height="20.5"/> <rect key="frame" x="30" y="15.5" width="115.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY"> <stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
<rect key="frame" x="206" y="15.5" width="139" height="20.5"/> <rect key="frame" x="206" y="15.5" width="139" height="20.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/> <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
<rect key="frame" x="121" y="0.0" width="18" height="20.5"/> <rect key="frame" x="121" y="0.0" width="18" height="20.5"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -447,13 +447,13 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/> <rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
<rect key="frame" x="30" y="15.5" width="67.5" height="20.5"/> <rect key="frame" x="30" y="15.5" width="67.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
<rect key="frame" x="327" y="16.5" width="18" height="18"/> <rect key="frame" x="327" y="16.5" width="18" height="18"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -478,22 +478,62 @@
</tableViewCell> </tableViewCell>
</cells> </cells>
</tableViewSection> </tableViewSection>
<tableViewSection headerTitle="" id="2em-H5-kgS">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="n3X-OX-idC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="n3X-OX-idC" id="IVp-7k-KdM">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Enable MDC Exploit" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IY0-94-5LN">
<rect key="frame" x="30" y="15.5" width="163" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Oie-te-KSQ">
<rect key="frame" x="296" y="10" width="51" height="31"/>
<connections>
<action selector="toggleEnableMDCExploit:" destination="aMk-Xp-UL8" eventType="valueChanged" id="tfb-kk-C17"/>
</connections>
</switch>
</subviews>
<constraints>
<constraint firstItem="IY0-94-5LN" firstAttribute="leading" secondItem="IVp-7k-KdM" secondAttribute="leadingMargin" id="07y-eS-INC"/>
<constraint firstItem="Oie-te-KSQ" firstAttribute="centerY" secondItem="IVp-7k-KdM" secondAttribute="centerY" id="1dS-uM-gb1"/>
<constraint firstItem="IY0-94-5LN" firstAttribute="centerY" secondItem="IVp-7k-KdM" secondAttribute="centerY" id="FyZ-BM-Ss0"/>
<constraint firstAttribute="trailingMargin" secondItem="Oie-te-KSQ" secondAttribute="trailing" id="I1v-Ub-eJJ"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="style">
<integer key="value" value="0"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
</userDefinedRuntimeAttributes>
</tableViewCell>
</cells>
</tableViewSection>
<tableViewSection headerTitle="" id="OMa-EK-hRI"> <tableViewSection headerTitle="" id="OMa-EK-hRI">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="870" width="375" height="51"/> <rect key="frame" x="0.0" y="951.5" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/> <rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
<rect key="frame" x="30" y="15.5" width="125.5" height="20.5"/> <rect key="frame" x="30" y="15.5" width="125.5" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
<rect key="frame" x="327" y="16.5" width="18" height="18"/> <rect key="frame" x="327" y="16.5" width="18" height="18"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -514,19 +554,19 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="921" width="375" height="51"/> <rect key="frame" x="0.0" y="1012" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/> <rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
<rect key="frame" x="30" y="15.5" width="187.5" height="20.5"/> <rect key="frame" x="30" y="15.5" width="187.5" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
<rect key="frame" x="327" y="16.5" width="18" height="18"/> <rect key="frame" x="327" y="16.5" width="18" height="18"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -550,19 +590,19 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="972" width="375" height="51"/> <rect key="frame" x="0.0" y="1063" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/> <rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Error Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-OG-5jx"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="View Error Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-OG-5jx">
<rect key="frame" x="30" y="15.5" width="119" height="20.5"/> <rect key="frame" x="30" y="15.5" width="119" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-c5-5wG"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-c5-5wG">
<rect key="frame" x="327" y="16.5" width="18" height="18"/> <rect key="frame" x="327" y="16.5" width="18" height="18"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -586,19 +626,19 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1023" width="375" height="51"/> <rect key="frame" x="0.0" y="1114" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/> <rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
<rect key="frame" x="30" y="15.5" width="140" height="20.5"/> <rect key="frame" x="30" y="15.5" width="140" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
<rect key="frame" x="327" y="16.5" width="18" height="18"/> <rect key="frame" x="327" y="16.5" width="18" height="18"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -619,19 +659,19 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1074" width="375" height="51"/> <rect key="frame" x="0.0" y="1165" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/> <rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
<rect key="frame" x="30" y="15.5" width="154" height="20.5"/> <rect key="frame" x="30" y="15.5" width="154" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
<rect key="frame" x="327" y="16.5" width="18" height="18"/> <rect key="frame" x="327" y="16.5" width="18" height="18"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -665,6 +705,7 @@
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/> <outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/> <outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/> <outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
<outlet property="enableMDCExploitSwitch" destination="Oie-te-KSQ" id="jKn-t1-gyk"/>
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/> <outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
</connections> </connections>
</tableViewController> </tableViewController>

View File

@@ -6,17 +6,17 @@
// Copyright © 2019 Riley Testut. All rights reserved. // Copyright © 2019 Riley Testut. All rights reserved.
// //
import UIKit
import SafariServices
import MessageUI
import Intents import Intents
import IntentsUI import IntentsUI
import MessageUI
import SafariServices
import UIKit
import AltStoreCore import AltStoreCore
extension SettingsViewController private extension SettingsViewController
{ {
fileprivate enum Section: Int, CaseIterable enum Section: Int, CaseIterable
{ {
case signIn case signIn
case account case account
@@ -24,23 +24,25 @@ extension SettingsViewController
case appRefresh case appRefresh
case instructions case instructions
case credits case credits
case macDirtyCow
case debug case debug
} }
fileprivate enum AppRefreshRow: Int, CaseIterable enum AppRefreshRow: Int, CaseIterable
{ {
case backgroundRefresh case backgroundRefresh
@available(iOS 14, *) @available(iOS 14, *)
case addToSiri case addToSiri
static var allCases: [AppRefreshRow] { static var allCases: [AppRefreshRow]
{
guard #available(iOS 14, *) else { return [.backgroundRefresh] } guard #available(iOS 14, *) else { return [.backgroundRefresh] }
return [.backgroundRefresh, .addToSiri] return [.backgroundRefresh, .addToSiri]
} }
} }
fileprivate enum CreditsRow: Int, CaseIterable enum CreditsRow: Int, CaseIterable
{ {
case developer case developer
case operations case operations
@@ -48,7 +50,7 @@ extension SettingsViewController
case softwareLicenses case softwareLicenses
} }
fileprivate enum DebugRow: Int, CaseIterable enum DebugRow: Int, CaseIterable
{ {
case sendFeedback case sendFeedback
case refreshAttempts case refreshAttempts
@@ -72,10 +74,12 @@ final class SettingsViewController: UITableViewController
@IBOutlet private var accountTypeLabel: UILabel! @IBOutlet private var accountTypeLabel: UILabel!
@IBOutlet private var backgroundRefreshSwitch: UISwitch! @IBOutlet private var backgroundRefreshSwitch: UISwitch!
@IBOutlet private var enableMDCExploitSwitch: UISwitch!
@IBOutlet private var versionLabel: UILabel! @IBOutlet private var versionLabel: UILabel!
override var preferredStatusBarStyle: UIStatusBarStyle { override var preferredStatusBarStyle: UIStatusBarStyle
{
return .lightContent return .lightContent
} }
@@ -147,6 +151,7 @@ private extension SettingsViewController
} }
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
self.enableMDCExploitSwitch.isOn = UserDefaults.standard.enableMacDirtyCowExploit
if self.isViewLoaded if self.isViewLoaded
{ {
@@ -204,6 +209,16 @@ private extension SettingsViewController
case .instructions: case .instructions:
break break
case .macDirtyCow:
if isHeader
{
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("MACDIRTYCOW", comment: "")
}
else
{
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Your device supports the MacDirtyCow exploit. When this setting is on, the exploit is used to enable you to sideload more than 3 apps at a time. (warning: might be unstable)", comment: "")
}
case .credits: case .credits:
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "") settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("CREDITS", comment: "")
@@ -223,14 +238,28 @@ private extension SettingsViewController
let size = settingsHeaderFooterView.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) let size = settingsHeaderFooterView.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
return size.height return size.height
} }
func isSectionHidden(_ section: Section) -> Bool
{
switch section
{
case .macDirtyCow:
let isHidden = !(UserDefaults.standard.isMacDirtyCowSupported)
return isHidden
default: return false
}
}
} }
private extension SettingsViewController private extension SettingsViewController
{ {
func signIn() func signIn()
{ {
AppManager.shared.authenticate(presentingViewController: self) { (result) in AppManager.shared.authenticate(presentingViewController: self)
DispatchQueue.main.async { { result in
DispatchQueue.main.async
{
switch result switch result
{ {
case .failure(OperationError.cancelled): case .failure(OperationError.cancelled):
@@ -253,8 +282,10 @@ private extension SettingsViewController
{ {
func signOut() func signOut()
{ {
DatabaseManager.shared.signOut { (error) in DatabaseManager.shared.signOut
DispatchQueue.main.async { { error in
DispatchQueue.main.async
{
if let error = error if let error = error
{ {
let toastView = ToastView(error: error) let toastView = ToastView(error: error)
@@ -269,7 +300,7 @@ private extension SettingsViewController
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to sign out?", comment: ""), message: NSLocalizedString("You will no longer be able to install or refresh apps once you sign out.", comment: ""), preferredStyle: .actionSheet) let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to sign out?", comment: ""), message: NSLocalizedString("You will no longer be able to install or refresh apps once you sign out.", comment: ""), preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in signOut() }) alertController.addAction(UIAlertAction(title: NSLocalizedString("Sign Out", comment: ""), style: .destructive) { _ in signOut() })
alertController.addAction(.cancel) alertController.addAction(.cancel)
//Fix crash on iPad // Fix crash on iPad
alertController.popoverPresentationController?.barButtonItem = sender alertController.popoverPresentationController?.barButtonItem = sender
self.present(alertController, animated: true, completion: nil) self.present(alertController, animated: true, completion: nil)
} }
@@ -279,6 +310,16 @@ private extension SettingsViewController
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
} }
@IBAction func toggleEnableMDCExploit(_ sender: UISwitch)
{
UserDefaults.standard.enableMacDirtyCowExploit = sender.isOn
if UserDefaults.standard.activeAppsLimit != nil
{
UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
}
}
@available(iOS 14, *) @available(iOS 14, *)
@IBAction func addRefreshAppsShortcut() @IBAction func addRefreshAppsShortcut()
{ {
@@ -304,7 +345,8 @@ private extension SettingsViewController
} }
else else
{ {
self.debugGestureTimer = Timer.scheduledTimer(withTimeInterval: 0.4, repeats: false) { [weak self] (timer) in self.debugGestureTimer = Timer.scheduledTimer(withTimeInterval: 0.4, repeats: false)
{ [weak self] _ in
self?.debugGestureCounter = 0 self?.debugGestureCounter = 0
} }
} }
@@ -313,7 +355,8 @@ private extension SettingsViewController
func openTwitter(username: String) func openTwitter(username: String)
{ {
let twitterAppURL = URL(string: "twitter://user?screen_name=" + username)! let twitterAppURL = URL(string: "twitter://user?screen_name=" + username)!
UIApplication.shared.open(twitterAppURL, options: [:]) { (success) in UIApplication.shared.open(twitterAppURL, options: [:])
{ success in
if success if success
{ {
if let selectedIndexPath = self.tableView.indexPathForSelectedRow if let selectedIndexPath = self.tableView.indexPathForSelectedRow
@@ -339,7 +382,8 @@ private extension SettingsViewController
{ {
guard self.presentedViewController == nil else { return } guard self.presentedViewController == nil else { return }
UIView.performWithoutAnimation { UIView.performWithoutAnimation
{
self.navigationController?.popViewController(animated: false) self.navigationController?.popViewController(animated: false)
self.performSegue(withIdentifier: "showPatreon", sender: nil) self.performSegue(withIdentifier: "showPatreon", sender: nil)
} }
@@ -365,6 +409,7 @@ extension SettingsViewController
let section = Section.allCases[section] let section = Section.allCases[section]
switch section switch section
{ {
case _ where self.isSectionHidden(section): return 0
case .signIn: return (self.activeTeam == nil) ? 1 : 0 case .signIn: return (self.activeTeam == nil) ? 1 : 0
case .account: return (self.activeTeam == nil) ? 0 : 3 case .account: return (self.activeTeam == nil) ? 0 : 3
case .appRefresh: return AppRefreshRow.allCases.count case .appRefresh: return AppRefreshRow.allCases.count
@@ -393,9 +438,10 @@ extension SettingsViewController
let section = Section.allCases[section] let section = Section.allCases[section]
switch section switch section
{ {
case _ where self.isSectionHidden(section): return nil
case .signIn where self.activeTeam != nil: return nil case .signIn where self.activeTeam != nil: return nil
case .account where self.activeTeam == nil: return nil case .account where self.activeTeam == nil: return nil
case .signIn, .account, .patreon, .appRefresh, .credits, .debug: case .signIn, .account, .patreon, .appRefresh, .credits, .macDirtyCow, .debug:
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
self.prepare(headerView, for: section, isHeader: true) self.prepare(headerView, for: section, isHeader: true)
return headerView return headerView
@@ -409,8 +455,9 @@ extension SettingsViewController
let section = Section.allCases[section] let section = Section.allCases[section]
switch section switch section
{ {
case _ where self.isSectionHidden(section): return nil
case .signIn where self.activeTeam != nil: return nil case .signIn where self.activeTeam != nil: return nil
case .signIn, .patreon, .appRefresh: case .signIn, .patreon, .appRefresh, .macDirtyCow:
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
self.prepare(footerView, for: section, isHeader: false) self.prepare(footerView, for: section, isHeader: false)
return footerView return footerView
@@ -424,9 +471,10 @@ extension SettingsViewController
let section = Section.allCases[section] let section = Section.allCases[section]
switch section switch section
{ {
case _ where self.isSectionHidden(section): return 1.0
case .signIn where self.activeTeam != nil: return 1.0 case .signIn where self.activeTeam != nil: return 1.0
case .account where self.activeTeam == nil: return 1.0 case .account where self.activeTeam == nil: return 1.0
case .signIn, .account, .patreon, .appRefresh, .credits, .debug: case .signIn, .account, .patreon, .appRefresh, .credits, .debug, .macDirtyCow:
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true) let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
return height return height
@@ -439,9 +487,10 @@ extension SettingsViewController
let section = Section.allCases[section] let section = Section.allCases[section]
switch section switch section
{ {
case _ where self.isSectionHidden(section): return 1.0
case .signIn where self.activeTeam != nil: return 1.0 case .signIn where self.activeTeam != nil: return 1.0
case .account where self.activeTeam == nil: return 1.0 case .account where self.activeTeam == nil: return 1.0
case .signIn, .patreon, .appRefresh: case .signIn, .patreon, .appRefresh, .macDirtyCow:
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false) let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
return height return height
@@ -515,8 +564,10 @@ extension SettingsViewController
message: NSLocalizedString("You can reset the pairing file when you cannot sideload apps or enable JIT. You need to restart SideStore.", comment: ""), message: NSLocalizedString("You can reset the pairing file when you cannot sideload apps or enable JIT. You need to restart SideStore.", comment: ""),
preferredStyle: UIAlertController.Style.actionSheet) preferredStyle: UIAlertController.Style.actionSheet)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive){ _ in alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive)
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty { { _ in
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty
{
try? fm.removeItem(atPath: documentsPath.path) try? fm.removeItem(atPath: documentsPath.path)
NSLog("Pairing File Reseted") NSLog("Pairing File Reseted")
} }
@@ -525,22 +576,25 @@ extension SettingsViewController
self.present(dialogMessage, animated: true, completion: nil) self.present(dialogMessage, animated: true, completion: nil)
}) })
alertController.addAction(.cancel) alertController.addAction(.cancel)
//Fix crash on iPad // Fix crash on iPad
alertController.popoverPresentationController?.sourceView = self.tableView alertController.popoverPresentationController?.sourceView = self.tableView
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath) alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
self.present(alertController, animated: true) self.present(alertController, animated: true)
self.tableView.deselectRow(at: indexPath, animated: true) self.tableView.deselectRow(at: indexPath, animated: true)
case .advancedSettings: case .advancedSettings:
// Create the URL that deep links to your app's custom settings. // Create the URL that deep links to your app's custom settings.
if let url = URL(string: UIApplication.openSettingsURLString) { if let url = URL(string: UIApplication.openSettingsURLString)
{
// Ask the system to open that URL. // Ask the system to open that URL.
UIApplication.shared.open(url) UIApplication.shared.open(url)
} else { }
else
{
ELOG("UIApplication.openSettingsURLString invalid") ELOG("UIApplication.openSettingsURLString invalid")
} }
case .refreshAttempts, .errorLog: break case .refreshAttempts, .errorLog: break
} }
case .macDirtyCow: break
default: break default: break
} }
} }

View File

@@ -10,8 +10,7 @@ import Foundation
import Roxas import Roxas
public extension UserDefaults public extension UserDefaults {
{
static let shared: UserDefaults = { static let shared: UserDefaults = {
guard let appGroup = Bundle.main.appGroups.first else { return .standard } guard let appGroup = Bundle.main.appGroups.first else { return .standard }
@@ -43,25 +42,27 @@ public extension UserDefaults
@NSManaged var trustedSourceIDs: [String]? @NSManaged var trustedSourceIDs: [String]?
@nonobjc
var activeAppsLimit: Int? { var activeAppsLimit: Int? {
get { get {
return self._activeAppsLimit?.intValue return self._activeAppsLimit?.intValue
} }
set { set {
if let value = newValue if let value = newValue {
{
self._activeAppsLimit = NSNumber(value: value) self._activeAppsLimit = NSNumber(value: value)
} }
else else {
{
self._activeAppsLimit = nil self._activeAppsLimit = nil
} }
} }
} }
@NSManaged @objc(activeAppsLimit) private var _activeAppsLimit: NSNumber? @NSManaged @objc(activeAppsLimit) private var _activeAppsLimit: NSNumber?
class func registerDefaults() @NSManaged var enableMacDirtyCowExploit: Bool
{ @NSManaged var isMacDirtyCowSupported: Bool
class func registerDefaults() {
let ios13_5 = OperatingSystemVersion(majorVersion: 13, minorVersion: 5, patchVersion: 0) let ios13_5 = OperatingSystemVersion(majorVersion: 13, minorVersion: 5, patchVersion: 0)
let isLegacyDeactivationSupported = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5) let isLegacyDeactivationSupported = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
let activeAppLimitIncludesExtensions = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5) let activeAppLimitIncludesExtensions = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios13_5)
@@ -69,15 +70,31 @@ public extension UserDefaults
let ios14 = OperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0) let ios14 = OperatingSystemVersion(majorVersion: 14, minorVersion: 0, patchVersion: 0)
let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14) let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14)
let ios16 = OperatingSystemVersion(majorVersion: 16, minorVersion: 0, patchVersion: 0)
let ios16_2 = OperatingSystemVersion(majorVersion: 16, minorVersion: 2, patchVersion: 0)
let ios15_7_2 = OperatingSystemVersion(majorVersion: 15, minorVersion: 7, patchVersion: 2)
// MacDirtyCow supports iOS 14.0 - 15.7.1 OR 16.0 - 16.1.2
let isMacDirtyCowSupported =
(ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios15_7_2)) ||
(ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16_2))
let defaults = [ let defaults = [
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true, #keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported, #keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions, #keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing, #keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
#keyPath(UserDefaults.requiresAppGroupMigration): true #keyPath(UserDefaults.requiresAppGroupMigration): true,
#keyPath(UserDefaults.enableMacDirtyCowExploit): true,
#keyPath(UserDefaults.isMacDirtyCowSupported): isMacDirtyCowSupported,
] ]
UserDefaults.standard.register(defaults: defaults) UserDefaults.standard.register(defaults: defaults)
UserDefaults.shared.register(defaults: defaults) UserDefaults.shared.register(defaults: defaults)
if !isMacDirtyCowSupported {
// Disable enableMacDirtyCowExploit if running iOS version that doesn't support MacDirtyCow.
UserDefaults.standard.enableMacDirtyCowExploit = false
}
} }
} }

View File

@@ -13,7 +13,21 @@ import AltSign
import SemanticVersion import SemanticVersion
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1. // Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
public let ALTActiveAppsLimit = 99999 public extension InstalledApp
{
static var freeAccountActiveAppsLimit: Int
{
if UserDefaults.standard.enableMacDirtyCowExploit && UserDefaults.standard.isMacDirtyCowSupported
{
return 99999
}
else
{
// Free developer accounts are limited to only 3 active sideloaded apps at a time as of iOS 13.3.1.
return 3
}
}
}
public protocol InstalledAppProtocol: Fetchable public protocol InstalledAppProtocol: Fetchable
{ {

View File

@@ -6,8 +6,8 @@
// Copyright © 2020 Riley Testut. All rights reserved. // Copyright © 2020 Riley Testut. All rights reserved.
// //
import CoreData
import AltSign import AltSign
import CoreData
@objc(InstalledAppToInstalledAppMigrationPolicy) @objc(InstalledAppToInstalledAppMigrationPolicy)
class InstalledAppToInstalledAppMigrationPolicy: NSEntityMigrationPolicy class InstalledAppToInstalledAppMigrationPolicy: NSEntityMigrationPolicy
@@ -50,7 +50,7 @@ class InstalledAppToInstalledAppMigrationPolicy: NSEntityMigrationPolicy
// We can assume there is an active app limit, // We can assume there is an active app limit,
// but will confirm next time user authenticates. // but will confirm next time user authenticates.
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
} }
return NSNumber(value: isActive) return NSNumber(value: isActive)

View File

@@ -1,7 +1,7 @@
// Configuration settings file format documentation can be found at: // Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974 // https://help.apple.com/xcode/#/dev745c5c974
MARKETING_VERSION = 0.3.0-m-f1shy-mdc.12 MARKETING_VERSION = 0.3.0-m-f1shy-mdc.14
CURRENT_PROJECT_VERSION = 3020 CURRENT_PROJECT_VERSION = 3020
// Vars to be overwritten by `CodeSigning.xcconfig` if exists // Vars to be overwritten by `CodeSigning.xcconfig` if exists

View File

@@ -25,7 +25,7 @@
outputFiles = ( outputFiles = (
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)", "$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
); );
script = "# generated with cargo-xcode 1.5.0\n\nset -eu; export PATH=\"$PATH:$HOME/.cargo/bin:/usr/local/bin\";\nif [ \"${IS_MACCATALYST-NO}\" = YES ]; then\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-ios-macabi\"\nelse\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-${CARGO_XCODE_TARGET_OS}\"\nfi\nif [ \"$CARGO_XCODE_TARGET_OS\" != \"darwin\" ]; then\n PATH=\"${PATH/\\/Contents\\/Developer\\/Toolchains\\/XcodeDefault.xctoolchain\\/usr\\/bin:/xcode-provided-ld-cant-link-lSystem-for-the-host-build-script:}\"\nfi\nPATH=\"$PATH:/opt/homebrew/bin\" # Rust projects often depend on extra tools like nasm, which Xcode lacks\nif [ \"$CARGO_XCODE_BUILD_MODE\" == release ]; then\n OTHER_INPUT_FILE_FLAGS=\"${OTHER_INPUT_FILE_FLAGS} --release\"\nfi\nif command -v rustup &> /dev/null; then\n if ! rustup target list --installed | egrep -q \"${CARGO_XCODE_TARGET_TRIPLE}\"; then\n echo \"warning: this build requires rustup toolchain for $CARGO_XCODE_TARGET_TRIPLE, but it isn't installed\"\n rustup target add \"${CARGO_XCODE_TARGET_TRIPLE}\" || echo >&2 \"warning: can't install $CARGO_XCODE_TARGET_TRIPLE\"\n fi\nfi\nif [ \"$ACTION\" = clean ]; then\n# ( set -x; cargo clean --manifest-path=\"$SCRIPT_INPUT_FILE\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\n (set -x; echo \"yay\";);\nelse\n ( set -x; cargo build --manifest-path=\"$SCRIPT_INPUT_FILE\" --features=\"${CARGO_XCODE_FEATURES:-}\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\nfi\n# it's too hard to explain Cargo's actual exe path to Xcode build graph, so hardlink to a known-good path instead\nBUILT_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_FILE_NAME}\"\nln -f -- \"$BUILT_SRC\" \"$SCRIPT_OUTPUT_FILE_0\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\nDEP_FILE_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\nif [ -f \"$DEP_FILE_SRC\" ]; then\n DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\nfi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\nFILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\ntouch \"$FILE_LIST\"\nif ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\nfi\n"; script = "# generated with cargo-xcode 1.5.0\n\nset -eu; export PATH=\"$PATH:$HOME/.cargo/bin:/usr/local/bin\";\nif [ \"${IS_MACCATALYST-NO}\" = YES ]; then\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-ios-macabi\"\nelse\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-${CARGO_XCODE_TARGET_OS}\"\nfi\nif [ \"$CARGO_XCODE_TARGET_OS\" != \"darwin\" ]; then\n PATH=\"${PATH/\\/Contents\\/Developer\\/Toolchains\\/XcodeDefault.xctoolchain\\/usr\\/bin:/xcode-provided-ld-cant-link-lSystem-for-the-host-build-script:}\"\nfi\nPATH=\"$PATH:/opt/homebrew/bin\" # Rust projects often depend on extra tools like nasm, which Xcode lacks\nif [ \"$CARGO_XCODE_BUILD_MODE\" == release ]; then\n OTHER_INPUT_FILE_FLAGS=\"${OTHER_INPUT_FILE_FLAGS} --release\"\nfi\nif command -v rustup &> /dev/null; then\n if ! rustup target list --installed | egrep -q \"${CARGO_XCODE_TARGET_TRIPLE}\"; then\n echo \"warning: this build requires rustup toolchain for $CARGO_XCODE_TARGET_TRIPLE, but it isn't installed\"\n rustup target add \"${CARGO_XCODE_TARGET_TRIPLE}\" || echo >&2 \"warning: can't install $CARGO_XCODE_TARGET_TRIPLE\"\n fi\nfi\nif [ \"$ACTION\" = clean ]; then\n #( set -x; cargo clean --manifest-path=\"$SCRIPT_INPUT_FILE\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\n (set -x; echo \"yay\";);\nelse\nexport XCOUTDIR=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}\"\necho \"pwd is $PWD, xcoutdir is $XCOUTDIR\"\n ( set -x; cargo build --manifest-path=\"$SCRIPT_INPUT_FILE\" --features=\"${CARGO_XCODE_FEATURES:-}\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\" --target-dir=\"/Users/vrishank/Desktop/master-mdc/Dependencies/em_proxy/target\" --out-dir=\"$XCOUTDIR\" -Z unstable-options; );\nfi\n# it's too hard to explain Cargo's actual exe path to Xcode build graph, so hardlink to a known-good path instead\nBUILT_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_FILE_NAME}\"\nln -f -- \"$BUILT_SRC\" \"$SCRIPT_OUTPUT_FILE_0\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n\nDEP_FILE_SRC=\"/Users/vrishank/Desktop/master-mdc/Dependencies/em_proxy/target/aarch64-apple-ios/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\necho \"depfilesrc $DEP_FILE_SRC\"\nif [ -f \"$DEP_FILE_SRC\" ]; then\n DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\nfi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\nFILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\ntouch \"$FILE_LIST\"\nif ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\nfi\n";
}; };
/* End PBXBuildRule section */ /* End PBXBuildRule section */

BIN
Dependencies/libboringtun.dylib vendored Executable file

Binary file not shown.

BIN
Dependencies/libem_proxy.a vendored Normal file

Binary file not shown.

BIN
Dependencies/libminimuxer.a vendored Normal file

Binary file not shown.

BIN
Dependencies/libminimuxer.rlib vendored Normal file

Binary file not shown.

View File

@@ -24,7 +24,7 @@
outputFiles = ( outputFiles = (
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)", "$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
); );
script = "# generated with cargo-xcode 1.5.0\n\nset -eu; export PATH=\"$PATH:$HOME/.cargo/bin:/usr/local/bin\";\nif [ \"${IS_MACCATALYST-NO}\" = YES ]; then\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-ios-macabi\"\nelse\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-${CARGO_XCODE_TARGET_OS}\"\nfi\nif [ \"$CARGO_XCODE_TARGET_OS\" != \"darwin\" ]; then\n PATH=\"${PATH/\\/Contents\\/Developer\\/Toolchains\\/XcodeDefault.xctoolchain\\/usr\\/bin:/xcode-provided-ld-cant-link-lSystem-for-the-host-build-script:}\"\nfi\nPATH=\"$PATH:/opt/homebrew/bin\" # Rust projects often depend on extra tools like nasm, which Xcode lacks\nif [ \"$CARGO_XCODE_BUILD_MODE\" == release ]; then\n OTHER_INPUT_FILE_FLAGS=\"${OTHER_INPUT_FILE_FLAGS} --release\"\nfi\nif command -v rustup &> /dev/null; then\n if ! rustup target list --installed | egrep -q \"${CARGO_XCODE_TARGET_TRIPLE}\"; then\n echo \"warning: this build requires rustup toolchain for $CARGO_XCODE_TARGET_TRIPLE, but it isn't installed\"\n rustup target add \"${CARGO_XCODE_TARGET_TRIPLE}\" || echo >&2 \"warning: can't install $CARGO_XCODE_TARGET_TRIPLE\"\n fi\nfi\nif [ \"$ACTION\" = clean ]; then\n #( set -x; cargo clean --manifest-path=\"$SCRIPT_INPUT_FILE\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\n (set -x; echo \"yay\";);\nelse\n ( set -x; cargo build --manifest-path=\"$SCRIPT_INPUT_FILE\" --features=\"${CARGO_XCODE_FEATURES:-}\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\nfi\n# it's too hard to explain Cargo's actual exe path to Xcode build graph, so hardlink to a known-good path instead\nBUILT_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_FILE_NAME}\"\nln -f -- \"$BUILT_SRC\" \"$SCRIPT_OUTPUT_FILE_0\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\nDEP_FILE_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\nif [ -f \"$DEP_FILE_SRC\" ]; then\n DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\nfi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\nFILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\ntouch \"$FILE_LIST\"\nif ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\nfi\n"; script = "# generated with cargo-xcode 1.5.0\n\nset -eu; export PATH=\"$PATH:$HOME/.cargo/bin:/usr/local/bin\";\nif [ \"${IS_MACCATALYST-NO}\" = YES ]; then\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-ios-macabi\"\nelse\n CARGO_XCODE_TARGET_TRIPLE=\"${CARGO_XCODE_TARGET_ARCH}-apple-${CARGO_XCODE_TARGET_OS}\"\nfi\nif [ \"$CARGO_XCODE_TARGET_OS\" != \"darwin\" ]; then\n PATH=\"${PATH/\\/Contents\\/Developer\\/Toolchains\\/XcodeDefault.xctoolchain\\/usr\\/bin:/xcode-provided-ld-cant-link-lSystem-for-the-host-build-script:}\"\nfi\nPATH=\"$PATH:/opt/homebrew/bin\" # Rust projects often depend on extra tools like nasm, which Xcode lacks\nif [ \"$CARGO_XCODE_BUILD_MODE\" == release ]; then\n OTHER_INPUT_FILE_FLAGS=\"${OTHER_INPUT_FILE_FLAGS} --release\"\nfi\nif command -v rustup &> /dev/null; then\n if ! rustup target list --installed | egrep -q \"${CARGO_XCODE_TARGET_TRIPLE}\"; then\n echo \"warning: this build requires rustup toolchain for $CARGO_XCODE_TARGET_TRIPLE, but it isn't installed\"\n rustup target add \"${CARGO_XCODE_TARGET_TRIPLE}\" || echo >&2 \"warning: can't install $CARGO_XCODE_TARGET_TRIPLE\"\n fi\nfi\nif [ \"$ACTION\" = clean ]; then\n #( set -x; cargo clean --manifest-path=\"$SCRIPT_INPUT_FILE\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\"; );\n (set -x; echo \"yay\";);\nelse\nexport XCOUTDIR=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}\"\necho \"pwd is $PWD, xcoutdir is $XCOUTDIR\"\n ( set -x; cargo build --manifest-path=\"$SCRIPT_INPUT_FILE\" --features=\"${CARGO_XCODE_FEATURES:-}\" ${OTHER_INPUT_FILE_FLAGS} --target=\"${CARGO_XCODE_TARGET_TRIPLE}\" --target-dir=\"/Users/vrishank/Desktop/master-mdc/Dependencies/minimuxer/target\" --out-dir=\"$XCOUTDIR\" -Z unstable-options; );\nfi\n# it's too hard to explain Cargo's actual exe path to Xcode build graph, so hardlink to a known-good path instead\nBUILT_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_FILE_NAME}\"\nln -f -- \"$BUILT_SRC\" \"$SCRIPT_OUTPUT_FILE_0\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"${CARGO_TARGET_DIR}/${CARGO_XCODE_TARGET_TRIPLE}/${CARGO_XCODE_BUILD_MODE}/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n\nDEP_FILE_SRC=\"/Users/vrishank/Desktop/master-mdc/Dependencies/minimuxer/target/aarch64-apple-ios/release/libminimuxer.d\"\necho \"depfilesrc $DEP_FILE_SRC\"\nif [ -f \"$DEP_FILE_SRC\" ]; then\n DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\nfi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\nFILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\ntouch \"$FILE_LIST\"\nif ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\nfi\n";
}; };
/* End PBXBuildRule section */ /* End PBXBuildRule section */

View File

@@ -1,17 +0,0 @@
rm -rf archive.xcarchive Payload *.ipa *.ipa.zip
xcodebuild -project AltStore.xcodeproj \
-scheme AltStore \
-sdk iphoneos \
archive -archivePath ./archive \
CODE_SIGNING_REQUIRED=NO \
AD_HOC_CODE_SIGNING_ALLOWED=YES \
CODE_SIGNING_ALLOWED=NO \
DEVELOPMENT_TEAM=XYZ0123456 \
ORG_IDENTIFIER=com.SideStore | tee xcodebuild.log | xcpretty
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
mkdir Payload
mkdir Payload/SideStore.app
cp -R archive.xcarchive/Products/Applications/SideStore.app/ Payload/SideStore.app/
zip -r SideStore_MDC_12.ipa Payload

BIN
test/libminimuxer.a Normal file

Binary file not shown.

BIN
test/libminimuxer.rlib Normal file

Binary file not shown.

0
ziHpvRmJ Normal file
View File