Adds "Enable JIT" context menu action for active apps

Allows users to manually enable JIT for apps that don't explicitly support AltKit.
This commit is contained in:
Riley Testut
2021-09-03 13:57:15 -05:00
parent e4b0b153e5
commit 9c72b7ae8f
5 changed files with 239 additions and 0 deletions

View File

@@ -347,6 +347,8 @@
BFF7C90F257844C900E55F36 /* AltXPC.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = BFF7C904257844C900E55F36 /* AltXPC.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; BFF7C90F257844C900E55F36 /* AltXPC.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = BFF7C904257844C900E55F36 /* AltXPC.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
BFF7C920257844FA00E55F36 /* ALTPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = BF5C5FCE237DF69100EDD0C6 /* ALTPluginService.m */; }; BFF7C920257844FA00E55F36 /* ALTPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = BF5C5FCE237DF69100EDD0C6 /* ALTPluginService.m */; };
BFF7C9342578492100E55F36 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = BFB49AA823834CF900D542D9 /* ALTAnisetteData.m */; }; BFF7C9342578492100E55F36 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = BFB49AA823834CF900D542D9 /* ALTAnisetteData.m */; };
D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */; };
D57F2C9426E01BC700B9FA39 /* UIDevice+Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */; };
D58D5F2E26DFE68E00E55E38 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = D58D5F2D26DFE68E00E55E38 /* LaunchAtLogin */; }; D58D5F2E26DFE68E00E55E38 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = D58D5F2D26DFE68E00E55E38 /* LaunchAtLogin */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -795,6 +797,8 @@
BFF7EC4C25081E9300BDE521 /* AltStore 8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 8.xcdatamodel"; sourceTree = "<group>"; }; BFF7EC4C25081E9300BDE521 /* AltStore 8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 8.xcdatamodel"; sourceTree = "<group>"; };
BFFCFA45248835530077BFCE /* AltDaemon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltDaemon.entitlements; sourceTree = "<group>"; }; BFFCFA45248835530077BFCE /* AltDaemon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AltDaemon.entitlements; sourceTree = "<group>"; };
C9EEAA842DA87A88A870053B /* Pods_AltStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C9EEAA842DA87A88A870053B /* Pods_AltStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableJITOperation.swift; sourceTree = "<group>"; };
D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Vibration.swift"; sourceTree = "<group>"; };
EA79A60285C6AF5848AA16E9 /* Pods-AltStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.debug.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.debug.xcconfig"; sourceTree = "<group>"; }; EA79A60285C6AF5848AA16E9 /* Pods-AltStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AltStore.debug.xcconfig"; path = "Target Support Files/Pods-AltStore/Pods-AltStore.debug.xcconfig"; sourceTree = "<group>"; };
FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; FC3822AB1C4CF1D4CDF7445D /* Pods_AltServer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AltServer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -1556,6 +1560,7 @@
BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */, BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */,
BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */, BF8CAE4D248AEABA004D6CCE /* UIDevice+Jailbreak.swift */,
BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */, BFE00A1F2503097F00EB4D0C /* INInteraction+AltStore.swift */,
D57F2C9326E01BC700B9FA39 /* UIDevice+Vibration.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1620,6 +1625,7 @@
BF3432FA246B894F0052F4A1 /* BackupAppOperation.swift */, BF3432FA246B894F0052F4A1 /* BackupAppOperation.swift */,
BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */, BFDBBD7F246CB84F004ED2F3 /* RemoveAppBackupOperation.swift */,
BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */, BFF00D312501BDA100746320 /* BackgroundRefreshAppsOperation.swift */,
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */,
); );
path = Operations; path = Operations;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2482,10 +2488,12 @@
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */, BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */, BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */,
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */, BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
D57F2C9426E01BC700B9FA39 /* UIDevice+Vibration.swift in Sources */,
BFD2478F2284C8F900981D42 /* Button.swift in Sources */, BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
BF56D2AC23DF8E170006506D /* FetchAppIDsOperation.swift in Sources */, BF56D2AC23DF8E170006506D /* FetchAppIDsOperation.swift in Sources */,
BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */, BFC1F38D22AEE3A4003AC21A /* DownloadAppOperation.swift in Sources */,
BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */, BFE6073A231ADF82002B0E8E /* SettingsViewController.swift in Sources */,
D57F2C9126E0070200B9FA39 /* EnableJITOperation.swift in Sources */,
BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */, BF8CAE4E248AEABA004D6CCE /* UIDevice+Jailbreak.swift in Sources */,
BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */, BFE338DF22F0EADB002E24B9 /* FetchSourceOperation.swift in Sources */,
BFB6B21E231870160022A802 /* NewsViewController.swift in Sources */, BFB6B21E231870160022A802 /* NewsViewController.swift in Sources */,

View File

@@ -0,0 +1,46 @@
//
// UIDevice+Vibration.swift
// AltStore
//
// Created by Riley Testut on 9/1/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import AudioToolbox
import CoreHaptics
private extension SystemSoundID
{
static let pop = SystemSoundID(1520)
static let cancelled = SystemSoundID(1521)
static let tryAgain = SystemSoundID(1102)
}
@available(iOS 13, *)
extension UIDevice
{
enum VibrationPattern
{
case success
case error
}
}
@available(iOS 13, *)
extension UIDevice
{
var isVibrationSupported: Bool {
return CHHapticEngine.capabilitiesForHardware().supportsHaptics
}
func vibrate(pattern: VibrationPattern)
{
guard self.isVibrationSupported else { return }
switch pattern
{
case .success: AudioServicesPlaySystemSound(.tryAgain)
case .error: AudioServicesPlaySystemSound(.cancelled)
}
}
}

View File

@@ -23,6 +23,7 @@ extension AppManager
static let didFetchSourceNotification = Notification.Name("com.altstore.AppManager.didFetchSource") static let didFetchSourceNotification = Notification.Name("com.altstore.AppManager.didFetchSource")
static let expirationWarningNotificationID = "altstore-expiration-warning" static let expirationWarningNotificationID = "altstore-expiration-warning"
static let enableJITResultNotificationID = "altstore-enable-jit"
} }
@available(iOS 13, *) @available(iOS 13, *)
@@ -540,6 +541,28 @@ extension AppManager
self.run([removeAppOperation, removeAppBackupOperation], context: authenticationContext) self.run([removeAppOperation, removeAppBackupOperation], context: authenticationContext)
} }
@available(iOS 14, *)
func enableJIT(for installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
class Context: OperationContext, EnableJITContext
{
var installedApp: InstalledApp?
}
let context = Context()
context.installedApp = installedApp
let findServerOperation = self.findServer(context: context) { _ in }
let enableJITOperation = EnableJITOperation(context: context)
enableJITOperation.resultHandler = { (result) in
completionHandler(result)
}
enableJITOperation.addDependency(findServerOperation)
self.run([enableJITOperation], context: context, requiresSerialQueue: true)
}
func installationProgress(for app: AppProtocol) -> Progress? func installationProgress(for app: AppProtocol) -> Progress?
{ {
let progress = self.installationProgress[app.bundleIdentifier] let progress = self.installationProgress[app.bundleIdentifier]

View File

@@ -1349,6 +1349,22 @@ private extension MyAppsViewController
} }
} }
} }
@available(iOS 14, *)
func enableJIT(for installedApp: InstalledApp)
{
AppManager.shared.enableJIT(for: installedApp) { result in
DispatchQueue.main.async {
switch result
{
case .success: break
case .failure(let error):
let toastView = ToastView(error: error)
toastView.show(in: self)
}
}
}
}
} }
private extension MyAppsViewController private extension MyAppsViewController
@@ -1555,6 +1571,11 @@ extension MyAppsViewController
self.remove(installedApp) self.remove(installedApp)
} }
let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt")) { (action) in
guard #available(iOS 14, *) else { return }
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")) { (action) in
self.backup(installedApp) self.backup(installedApp)
} }
@@ -1601,6 +1622,11 @@ extension MyAppsViewController
actions.append(activateAction) actions.append(activateAction)
} }
if installedApp.isActive, #available(iOS 14, *)
{
actions.append(jitAction)
}
#if BETA #if BETA
actions.append(changeIconMenu) actions.append(changeIconMenu)
#endif #endif

View File

@@ -0,0 +1,136 @@
//
// EnableJITOperation.swift
// EnableJITOperation
//
// Created by Riley Testut on 9/1/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import UIKit
import Combine
import AltStoreCore
@available(iOS 14, *)
protocol EnableJITContext
{
var server: Server? { get }
var installedApp: InstalledApp? { get }
var error: Error? { get }
}
@available(iOS 14, *)
class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
{
let context: Context
private var cancellable: AnyCancellable?
init(context: Context)
{
self.context = context
}
override func main()
{
super.main()
if let error = self.context.error
{
self.finish(.failure(error))
return
}
guard let server = self.context.server, let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { return self.finish(.failure(OperationError.unknownUDID)) }
installedApp.managedObjectContext?.perform {
guard let bundle = Bundle(url: installedApp.fileURL),
let processName = bundle.executableURL?.lastPathComponent
else { return self.finish(.failure(OperationError.invalidApp)) }
let appName = installedApp.name
let openAppURL = installedApp.openAppURL
ServerManager.shared.connect(to: server) { result in
switch result
{
case .failure(let error): self.finish(.failure(error))
case .success(let connection):
print("Sending enable JIT request...")
DispatchQueue.main.async {
// Launch app to make sure it is running in foreground.
UIApplication.shared.open(openAppURL) { success in
guard success else { return self.finish(.failure(OperationError.openAppFailed(name: appName))) }
// Combine immediately finishes if an error is thrown, but we want to wait at least until app enters background.
// As a workaround, we set error type to Never and use Result<Void, Error> as the value type instead.
let result = Future<Result<Void, Error>, Never> { promise in
let request = EnableUnsignedCodeExecutionRequest(udid: udid, processName: processName)
connection.send(request) { result in
print("Sent enable JIT request!")
switch result
{
case .failure(let error): promise(.success(.failure(error)))
case .success:
print("Waiting for enable JIT response...")
connection.receiveResponse() { result in
print("Received enable JIT response:", result)
switch result
{
case .failure(let error): promise(.success(.failure(error)))
case .success(.error(let response)): promise(.success(.failure(response.error)))
case .success(.enableUnsignedCodeExecution): promise(.success(.success(())))
case .success: promise(.success(.failure(ALTServerError(.unknownResponse))))
}
}
}
}
}
//TODO: Handle case where app does not enter background (e.g. iPad multitasking).
self.cancellable = result
.combineLatest(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification, object: nil))
.first()
.receive(on: DispatchQueue.main)
.sink { (result, _) in
let content = UNMutableNotificationContent()
switch result
{
case .failure(let error):
content.title = String(format: NSLocalizedString("Could not enable JIT for %@", comment: ""), appName)
content.body = error.localizedDescription
UIDevice.current.vibrate(pattern: .error)
case .success:
content.title = String(format: NSLocalizedString("Enabled JIT for %@", comment: ""), appName)
content.body = String(format: NSLocalizedString("JIT will remain enabled until you quit the app.", comment: ""))
UIDevice.current.vibrate(pattern: .success)
}
if UIApplication.shared.applicationState == .background
{
// For some reason, notification won't show up reliably unless we provide a trigger (as of iOS 15).
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
let request = UNNotificationRequest(identifier: AppManager.enableJITResultNotificationID, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
self.finish(result)
}
}
}
}
}
}
}
}