diff --git a/AltServer/AppDelegate.swift b/AltServer/AppDelegate.swift index 2525f6f6..70c3fb22 100644 --- a/AltServer/AppDelegate.swift +++ b/AltServer/AppDelegate.swift @@ -150,43 +150,55 @@ private extension AppDelegate func enableJIT(for app: InstalledApp, on device: ALTDevice) { - func finish(_ result: Result) - { - DispatchQueue.main.async { - switch result - { - case .failure(let error as NSError): - let localizedTitle = String(format: NSLocalizedString("JIT could not be enabled for %@.", comment: ""), app.name) - self.showErrorAlert(error: error.withLocalizedTitle(localizedTitle)) - - case .success: + Task { + do + { + try await JITManager.shared.enableUnsignedCodeExecution(process: .name(app.executableName), device: device) + + await MainActor.run { let alert = NSAlert() alert.messageText = String(format: NSLocalizedString("Successfully enabled JIT for %@.", comment: ""), app.name) alert.informativeText = String(format: NSLocalizedString("JIT will remain enabled until you quit the app. You can now disconnect %@ from your computer.", comment: ""), device.name) + + NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) + alert.runModal() } } - } - - ALTDeviceManager.shared.prepare(device) { (result) in - switch result + catch let error as JITError where error.code == .dependencyNotFound { - case .failure(let error as NSError): return finish(.failure(error)) - case .success: - ALTDeviceManager.shared.startDebugConnection(to: device) { (connection, error) in - guard let connection = connection else { - return finish(.failure(error! as NSError)) - } + var errorMessage = error.localizedDescription + if let recoverySuggestion = error.recoverySuggestion + { + errorMessage += "\n\n" + recoverySuggestion + } + + await MainActor.run { [errorMessage] in + let alert = NSAlert() + alert.alertStyle = .critical + alert.messageText = NSLocalizedString("Missing AltJIT Dependencies", comment: "") + alert.informativeText = errorMessage - connection.enableUnsignedCodeExecutionForProcess(withName: app.executableName) { (success, error) in - guard success else { - return finish(.failure(error!)) - } - - finish(.success(())) + alert.addButton(withTitle: NSLocalizedString("View Instructions", comment: "")) + alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) + + NSRunningApplication.current.activate(options: .activateIgnoringOtherApps) + + let response = alert.runModal() + if response == .alertFirstButtonReturn + { + let faqURL = URL(string: "https://faq.altstore.io/how-to-use-altstore/altjit")! + NSWorkspace.shared.open(faqURL) } } } + catch let error as NSError + { + await MainActor.run { + let localizedTitle = String(format: NSLocalizedString("JIT could not be enabled for %@.", comment: ""), app.name) + self.showErrorAlert(error: error.withLocalizedTitle(localizedTitle)) + } + } } } diff --git a/AltServer/Connections/RequestHandler.swift b/AltServer/Connections/RequestHandler.swift index 6d879951..2f9491f4 100644 --- a/AltServer/Connections/RequestHandler.swift +++ b/AltServer/Connections/RequestHandler.swift @@ -147,44 +147,36 @@ struct ServerRequestHandler: RequestHandler func handleEnableUnsignedCodeExecutionRequest(_ request: EnableUnsignedCodeExecutionRequest, for connection: Connection, completionHandler: @escaping (Result) -> Void) { guard let device = ALTDeviceManager.shared.availableDevices.first(where: { $0.identifier == request.udid }) else { return completionHandler(.failure(ALTServerError(.deviceNotFound))) } + + let process: AppProcess - ALTDeviceManager.shared.prepare(device) { result in - switch result + if let processID = request.processID + { + process = .pid(processID) + } + else if let processName = request.processName + { + process = .name(processName) + } + else + { + return completionHandler(.failure(ALTServerError(.invalidRequest))) + } + + Task { + do { - case .failure(let error): completionHandler(.failure(error)) - case .success: - ALTDeviceManager.shared.startDebugConnection(to: device) { (connection, error) in - guard let connection = connection else { return completionHandler(.failure(error!)) } - - func finish(success: Bool, error: Error?) - { - if let error = error, !success - { - print("Failed to enable unsigned code execution for process \(request.processID?.description ?? request.processName ?? "nil"):", error) - completionHandler(.failure(ALTServerError(error))) - } - else - { - print("Enabled unsigned code execution for process:", request.processID ?? request.processName ?? "nil") - - let response = EnableUnsignedCodeExecutionResponse() - completionHandler(.success(response)) - } - } - - if let processID = request.processID - { - connection.enableUnsignedCodeExecutionForProcess(withID: processID, completionHandler: finish) - } - else if let processName = request.processName - { - connection.enableUnsignedCodeExecutionForProcess(withName: processName, completionHandler: finish) - } - else - { - finish(success: false, error: ALTServerError(.invalidRequest)) - } - } + try await JITManager.shared.enableUnsignedCodeExecution(process: process, device: device) + + print("Enabled unsigned code execution for process:", request.processID ?? request.processName ?? "nil") + + let response = EnableUnsignedCodeExecutionResponse() + completionHandler(.success(response)) + } + catch + { + print("Failed to enable unsigned code execution for process \(request.processID?.description ?? request.processName ?? "nil"):", error) + completionHandler(.failure(ALTServerError(error))) } } } diff --git a/AltServer/Devices/ALTDeviceManager+Installation.swift b/AltServer/Devices/ALTDeviceManager+Installation.swift index 36e6d413..21e525ab 100644 --- a/AltServer/Devices/ALTDeviceManager+Installation.swift +++ b/AltServer/Devices/ALTDeviceManager+Installation.swift @@ -255,39 +255,19 @@ extension ALTDeviceManager } } -extension ALTDeviceManager +private extension ALTDeviceManager { func prepare(_ device: ALTDevice, completionHandler: @escaping (Result) -> Void) { - ALTDeviceManager.shared.isDeveloperDiskImageMounted(for: device) { (isMounted, error) in - switch (isMounted, error) + Task { + do { - case (_, let error?): return completionHandler(.failure(error)) - case (true, _): return completionHandler(.success(())) - case (false, _): - developerDiskManager.downloadDeveloperDisk(for: device) { (result) in - switch result - { - case .failure(let error): completionHandler(.failure(error)) - case .success((let diskFileURL, let signatureFileURL)): - ALTDeviceManager.shared.installDeveloperDiskImage(at: diskFileURL, signatureURL: signatureFileURL, to: device) { (success, error) in - switch Result(success, error) - { - case .failure(let error as ALTServerError) where error.code == .incompatibleDeveloperDisk: - developerDiskManager.setDeveloperDiskCompatible(false, with: device) - completionHandler(.failure(error)) - - case .failure(let error): - // Don't mark developer disk as incompatible because it probably failed for a different reason. - completionHandler(.failure(error)) - - case .success: - developerDiskManager.setDeveloperDiskCompatible(true, with: device) - completionHandler(.success(())) - } - } - } - } + try await JITManager.shared.prepare(device) + completionHandler(.success(())) + } + catch + { + completionHandler(.failure(error)) } } } diff --git a/AltServer/Extensions/Logger+AltServer.swift b/AltServer/Extensions/Logger+AltServer.swift new file mode 100644 index 00000000..37df6c4e --- /dev/null +++ b/AltServer/Extensions/Logger+AltServer.swift @@ -0,0 +1,16 @@ +// +// Logger+AltServer.swift +// AltStore +// +// Created by Riley Testut on 9/6/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import OSLog + +extension Logger +{ + static let altserverSubsystem = Bundle.main.bundleIdentifier! + + static let main = Logger(subsystem: altserverSubsystem, category: "AltServer") +} diff --git a/AltServer/Extensions/Process+STPrivilegedTask.swift b/AltServer/Extensions/Process+STPrivilegedTask.swift new file mode 100644 index 00000000..04cc573c --- /dev/null +++ b/AltServer/Extensions/Process+STPrivilegedTask.swift @@ -0,0 +1,71 @@ +// +// Process+STPrivilegedTask.swift +// AltServer +// +// Created by Riley Testut on 8/22/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation +import Security +import OSLog + +import STPrivilegedTask + +extension Process +{ + class func runAsAdmin(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws -> AuthorizationRef? + { + var launchPath = "/usr/bin/" + program + if !FileManager.default.fileExists(atPath: launchPath) + { + launchPath = "/bin/" + program + } + + if !FileManager.default.fileExists(atPath: launchPath) + { + launchPath = program + } + + Logger.main.info("Launching admin process: \(launchPath, privacy: .public)") + + let task = STPrivilegedTask() + task.launchPath = launchPath + task.arguments = arguments + task.freeAuthorizationWhenDone = false + + let errorCode: OSStatus + + if let authorization = authorization + { + errorCode = task.launch(withAuthorization: authorization) + } + else + { + errorCode = task.launch() + } + + let executableURL = URL(fileURLWithPath: launchPath) + guard errorCode == 0 else { throw ProcessError.failed(executableURL: executableURL, exitCode: errorCode, output: nil) } + + task.waitUntilExit() + + Logger.main.info("Admin process \(launchPath, privacy: .public) terminated with exit code \(task.terminationStatus, privacy: .public).") + + guard task.terminationStatus == 0 else { + let executableURL = URL(fileURLWithPath: launchPath) + + let outputData = task.outputFileHandle.readDataToEndOfFile() + if let outputString = String(data: outputData, encoding: .utf8), !outputString.isEmpty + { + throw ProcessError.failed(executableURL: executableURL, exitCode: task.terminationStatus, output: outputString) + } + else + { + throw ProcessError.failed(executableURL: executableURL, exitCode: task.terminationStatus, output: nil) + } + } + + return task.authorization + } +} diff --git a/AltServer/JIT/JITManager.swift b/AltServer/JIT/JITManager.swift new file mode 100644 index 00000000..6d9befd7 --- /dev/null +++ b/AltServer/JIT/JITManager.swift @@ -0,0 +1,153 @@ +// +// JITManager.swift +// AltServer +// +// Created by Riley Testut on 8/30/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import RegexBuilder + +import AltSign + +private extension URL +{ + static let python3 = URL(fileURLWithPath: "/usr/bin/python3") + static let altjit = Bundle.main.executableURL!.deletingLastPathComponent().appendingPathComponent("altjit") +} + +class JITManager +{ + static let shared = JITManager() + + private let diskManager = DeveloperDiskManager() + + private var authorization: AuthorizationRef? + + private init() + { + } + + func prepare(_ device: ALTDevice) async throws + { + let isMounted = try await ALTDeviceManager.shared.isDeveloperDiskImageMounted(for: device) + guard !isMounted else { return } + + if #available(macOS 13, *), device.osVersion.majorVersion >= 17 + { + // iOS 17+ + try await self.installPersonalizedDeveloperDisk(onto: device) + } + else + { + try await self.installDeveloperDisk(onto: device) + } + } + + func enableUnsignedCodeExecution(process: AppProcess, device: ALTDevice) async throws + { + try await self.prepare(device) + + if #available(macOS 13, *), device.osVersion.majorVersion >= 17 + { + // iOS 17+ + try await self.enableModernUnsignedCodeExecution(process: process, device: device) + } + else + { + try await self.enableLegacyUnsignedCodeExecution(process: process, device: device) + } + } +} + +private extension JITManager +{ + func installDeveloperDisk(onto device: ALTDevice) async throws + { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + self.diskManager.downloadDeveloperDisk(for: device) { (result) in + switch result + { + case .failure(let error): continuation.resume(throwing: error) + case .success((let diskFileURL, let signatureFileURL)): + ALTDeviceManager.shared.installDeveloperDiskImage(at: diskFileURL, signatureURL: signatureFileURL, to: device) { (success, error) in + switch Result(success, error) + { + case .failure(let error as ALTServerError) where error.code == .incompatibleDeveloperDisk: + self.diskManager.setDeveloperDiskCompatible(false, with: device) + continuation.resume(throwing: error) + + case .failure(let error): + // Don't mark developer disk as incompatible because it probably failed for a different reason. + continuation.resume(throwing: error) + + case .success: + self.diskManager.setDeveloperDiskCompatible(true, with: device) + continuation.resume() + } + } + } + } + } + } + + func enableLegacyUnsignedCodeExecution(process: AppProcess, device: ALTDevice) async throws + { + let connection = try await ALTDeviceManager.shared.startDebugConnection(to: device) + + switch process + { + case .name(let name): try await connection.enableUnsignedCodeExecutionForProcess(withName: name) + case .pid(let pid): try await connection.enableUnsignedCodeExecutionForProcess(withID: pid) + } + } +} + +@available(macOS 13, *) +private extension JITManager +{ + func installPersonalizedDeveloperDisk(onto device: ALTDevice) async throws + { + _ = try await Process.launchAndWait(.altjit, arguments: ["mount", "--udid", device.identifier]) + } + + func enableModernUnsignedCodeExecution(process: AppProcess, device: ALTDevice) async throws + { + do + { + if self.authorization == nil + { + // runAsAdmin() only returns authorization if the process completes successfully, + // so we request authorization for a command that can't fail, then re-use it for the failable command below. + self.authorization = try Process.runAsAdmin("echo", arguments: ["altstore"], authorization: self.authorization) + } + + var arguments = ["enable"] + switch process + { + case .name(let name): arguments.append(name) + case .pid(let pid): arguments.append(String(pid)) + } + arguments += ["--udid", device.identifier] + + self.authorization = try Process.runAsAdmin(URL.altjit.path, arguments: arguments, authorization: self.authorization) + } + catch let error as ProcessError where error.code == .failed + { + let regex = Regex { + "No module named" + + OneOrMore(.whitespace) + + Capture { + OneOrMore(.anyNonNewline) + } + } + + guard let output = error.output, let match = output.firstMatch(of: regex) else { throw error } + + let dependency = String(match.1) + throw JITError.dependencyNotFound(dependency) + } + } +} diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index c67a39e3..724184e5 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 55; objects = { /* Begin PBXBuildFile section */ @@ -362,6 +362,7 @@ D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D533E8B62727841800A9B5DD /* libAppleArchive.tbd */; settings = {ATTRIBUTES = (Weak, ); }; }; D533E8BC2727BBEE00A9B5DD /* libfragmentzip.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D533E8BB2727BBEE00A9B5DD /* libfragmentzip.a */; }; D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D533E8BD2727BBF800A9B5DD /* libcurl.a */; }; + D537C8592AA94D94009A1E08 /* altjit in Embed AltJIT */ = {isa = PBXBuildFile; fileRef = D5FB7A132AA284BE00EF863D /* altjit */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D537C85B2AA9507A009A1E08 /* libcorecrypto.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D537C85A2AA95066009A1E08 /* libcorecrypto.tbd */; platformFilters = (macos, ); }; D53D84022A2158FC00543C3B /* Permissions.plist in Resources */ = {isa = PBXBuildFile; fileRef = D53D84012A2158FC00543C3B /* Permissions.plist */; }; D54058B92A1D6269008CCC58 /* AppPermissionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54058B82A1D6269008CCC58 /* AppPermissionProtocol.swift */; }; @@ -407,7 +408,13 @@ D5A1D2EB2AA513410066CACC /* URL+Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1D2EA2AA513410066CACC /* URL+Tools.swift */; }; D5A1D2EC2AA51D490066CACC /* ProcessError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1B2AA284ED00EF863D /* ProcessError.swift */; }; D5A2193429B14F94002229FC /* DeprecatedAPIs.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2193329B14F94002229FC /* DeprecatedAPIs.swift */; }; + D5A299862AAB9E4E00A3988D /* Process+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B802AA92D1C00F61259 /* Process+Conveniences.swift */; }; + D5A299872AAB9E4E00A3988D /* ProcessError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1B2AA284ED00EF863D /* ProcessError.swift */; }; + D5A299882AAB9E4E00A3988D /* JITError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1D2E32AA50EB60066CACC /* JITError.swift */; }; + D5A299892AAB9E5900A3988D /* AppProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B7D2AA9226C00F61259 /* AppProcess.swift */; }; D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */; }; + D5BA9E9B2A9FE1E8007C0661 /* JITManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5BA9E9A2A9FE1E8007C0661 /* JITManager.swift */; }; + D5C8ACDB2A956B2B00669F92 /* Process+STPrivilegedTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C8ACDA2A956B2B00669F92 /* Process+STPrivilegedTask.swift */; }; D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; }; D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */; }; D5CD805D29CA2C1E00E591B0 /* HeaderContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CD805C29CA2C1E00E591B0 /* HeaderContentViewController.swift */; }; @@ -563,6 +570,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D537C8582AA94D60009A1E08 /* Embed AltJIT */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 6; + files = ( + D537C8592AA94D94009A1E08 /* altjit in Embed AltJIT */, + ); + name = "Embed AltJIT"; + runOnlyForDeploymentPostprocessing = 0; + }; D561B2EC28EF5A4F006752E4 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -985,12 +1003,15 @@ D59A6B7A2AA91B8E00F61259 /* PythonCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PythonCommand.swift; sourceTree = ""; }; D59A6B7D2AA9226C00F61259 /* AppProcess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppProcess.swift; sourceTree = ""; }; D59A6B802AA92D1C00F61259 /* Process+Conveniences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Process+Conveniences.swift"; sourceTree = ""; }; + D59A6B832AA932F700F61259 /* Logger+AltServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+AltServer.swift"; sourceTree = ""; }; D5A0537229B91DB400997551 /* SourceDetailContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceDetailContentViewController.swift; sourceTree = ""; }; D5A1D2E32AA50EB60066CACC /* JITError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JITError.swift; sourceTree = ""; }; D5A1D2E82AA512940066CACC /* RemoteServiceDiscoveryTunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteServiceDiscoveryTunnel.swift; sourceTree = ""; }; D5A1D2EA2AA513410066CACC /* URL+Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Tools.swift"; sourceTree = ""; }; D5A2193329B14F94002229FC /* DeprecatedAPIs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedAPIs.swift; sourceTree = ""; }; D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearAppCacheOperation.swift; sourceTree = ""; }; + D5BA9E9A2A9FE1E8007C0661 /* JITManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JITManager.swift; sourceTree = ""; }; + D5C8ACDA2A956B2B00669F92 /* Process+STPrivilegedTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Process+STPrivilegedTask.swift"; sourceTree = ""; }; D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = ""; }; D5CA0C4C280E242500469595 /* AltStore 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 10.xcdatamodel"; sourceTree = ""; }; D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore9ToAltStore10.xcmappingmodel; sourceTree = ""; }; @@ -1146,6 +1167,8 @@ children = ( BF0241A922F29CCD00129732 /* UserDefaults+AltServer.swift */, BF541C0A25E5A5FA00CD46B2 /* FileManager+URLs.swift */, + D5C8ACDA2A956B2B00669F92 /* Process+STPrivilegedTask.swift */, + D59A6B832AA932F700F61259 /* Logger+AltServer.swift */, ); path = Extensions; sourceTree = ""; @@ -1233,6 +1256,7 @@ BFC15ADB27BC3AD100ED2FB4 /* Plugin */, BF703195229F36FF006E110F /* Devices */, BFD52BDC22A0A659000B7ED1 /* Connections */, + D59A6B792AA919E500F61259 /* JIT */, BF055B4A233B528B0086DEA9 /* Extensions */, BFE972E0260A8B0700D0BDAC /* Categories */, BF703194229F36F6006E110F /* Resources */, @@ -2130,6 +2154,14 @@ path = "Error Log"; sourceTree = ""; }; + D59A6B792AA919E500F61259 /* JIT */ = { + isa = PBXGroup; + children = ( + D5BA9E9A2A9FE1E8007C0661 /* JITManager.swift */, + ); + path = JIT; + sourceTree = ""; + }; D59A6B7C2AA9225C00F61259 /* Types */ = { isa = PBXGroup; children = ( @@ -2280,10 +2312,12 @@ BF7FDA2C23203B6B00B5D3A4 /* Copy Launcher App */, 98BF22D155DBAEA97544E3E6 /* [CP] Embed Pods Frameworks */, BFF7C910257844C900E55F36 /* Embed XPC Services */, + D537C8582AA94D60009A1E08 /* Embed AltJIT */, ); buildRules = ( ); dependencies = ( + D537C8572AA94D4A009A1E08 /* PBXTargetDependency */, BF4588452298D48B00BD7491 /* PBXTargetDependency */, BFF7C90E257844C900E55F36 /* PBXTargetDependency */, ); @@ -2814,12 +2848,16 @@ BFC15ADA27BC352300ED2FB4 /* PluginVersion.swift in Sources */, BFECAC8324FD950B0077C41F /* NetworkConnection.swift in Sources */, BF541C0B25E5A5FA00CD46B2 /* FileManager+URLs.swift in Sources */, + D5A299892AAB9E5900A3988D /* AppProcess.swift in Sources */, + D5A299872AAB9E4E00A3988D /* ProcessError.swift in Sources */, BFECAC8724FD950B0077C41F /* Bundle+AltStore.swift in Sources */, BF3F786422CAA41E008FBD20 /* ALTDeviceManager+Installation.swift in Sources */, + D5A299882AAB9E4E00A3988D /* JITError.swift in Sources */, BF18BFFD2485A1E400DD5981 /* WiredConnectionHandler.swift in Sources */, BFC712BB2512B9CF00AB5EBE /* PluginManager.swift in Sources */, BFECAC8224FD950B0077C41F /* ServerProtocol.swift in Sources */, BFECAC8124FD950B0077C41F /* ALTServerError+Conveniences.swift in Sources */, + D5C8ACDB2A956B2B00669F92 /* Process+STPrivilegedTask.swift in Sources */, BFECAC7F24FD950B0077C41F /* CodableError.swift in Sources */, D5189C012A01BC6800F44625 /* UserInfoValue.swift in Sources */, D51AD28029356B8000967AAA /* ALTWrappedError.m in Sources */, @@ -2835,6 +2873,7 @@ BF458690229872EA00BD7491 /* AppDelegate.swift in Sources */, BFECAC8424FD950B0077C41F /* ALTConstants.m in Sources */, BF4586C52298CDB800BD7491 /* ALTDeviceManager.mm in Sources */, + D59A6B842AA932F700F61259 /* Logger+AltServer.swift in Sources */, BF0241AA22F29CCD00129732 /* UserDefaults+AltServer.swift in Sources */, BFECAC9424FD98BA0077C41F /* NSError+ALTServerError.m in Sources */, BFAD67A325E0854500D4C4D1 /* DeveloperDiskManager.swift in Sources */, @@ -2842,7 +2881,9 @@ BFECAC9324FD98BA0077C41F /* CFNotificationName+AltStore.m in Sources */, BFE48975238007CE003239E0 /* AnisetteDataManager.swift in Sources */, D5DB145B28F9DC5C00A8F606 /* ALTLocalizedError.swift in Sources */, + D5BA9E9B2A9FE1E8007C0661 /* JITManager.swift in Sources */, BFE972E3260A8B2700D0BDAC /* NSError+libimobiledevice.mm in Sources */, + D5A299862AAB9E4E00A3988D /* Process+Conveniences.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3212,6 +3253,11 @@ target = BFF7C903257844C900E55F36 /* AltXPC */; targetProxy = BFF7C90D257844C900E55F36 /* PBXContainerItemProxy */; }; + D537C8572AA94D4A009A1E08 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D5FB7A122AA284BE00EF863D /* AltJIT */; + targetProxy = D537C8562AA94D4A009A1E08 /* PBXContainerItemProxy */; + }; D586D39D28EF58B0000E101F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BFD247692284B9A500981D42 /* AltStore */; @@ -4070,6 +4116,9 @@ "ALTJIT=1", ); GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/../Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SDKROOT)/usr/lib/system", @@ -4106,6 +4155,9 @@ "ALTJIT=1", ); GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/../Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(SDKROOT)/usr/lib/system", diff --git a/Shared/Errors/JITError.swift b/Shared/Errors/JITError.swift index 2b2a0e0f..f22edce6 100644 --- a/Shared/Errors/JITError.swift +++ b/Shared/Errors/JITError.swift @@ -15,11 +15,17 @@ extension JITError typealias Error = JITError case processNotRunning + case dependencyNotFound } static func processNotRunning(_ process: AppProcess, file: StaticString = #file, line: Int = #line) -> JITError { JITError(code: .processNotRunning, process: process, sourceFile: file, sourceLine: UInt(line)) } + + static func dependencyNotFound(_ dependency: String?, file: StaticString = #file, line: Int = #line) -> JITError { + let errorFailure = NSLocalizedString("AltServer requires additional dependencies to enable JIT on iOS 17.", comment: "") + return JITError(code: .dependencyNotFound, errorFailure: errorFailure, dependency: dependency, faq: "https://faq.altstore.io/how-to-use-altstore/altjit", sourceFile: file, sourceLine: UInt(line)) + } } struct JITError: ALTLocalizedError @@ -31,6 +37,9 @@ struct JITError: ALTLocalizedError @UserInfoValue var process: AppProcess? + @UserInfoValue var dependency: String? + @UserInfoValue var faq: String? // Show user FAQ URL in AltStore error log. + var sourceFile: StaticString? var sourceLine: UInt? @@ -40,6 +49,10 @@ struct JITError: ALTLocalizedError case .processNotRunning: let targetName = self.process?.description ?? NSLocalizedString("The target app", comment: "") return String(format: NSLocalizedString("%@ is not running.", comment: ""), targetName) + + case .dependencyNotFound: + let dependencyName = self.dependency.map { "'\($0)'" } ?? NSLocalizedString("A required dependency", comment: "") + return String(format: NSLocalizedString("%@ is not installed.", comment: ""), dependencyName) } } @@ -47,6 +60,7 @@ struct JITError: ALTLocalizedError switch self.code { case .processNotRunning: return NSLocalizedString("Make sure the app is running in the foreground on your device then try again.", comment: "") + case .dependencyNotFound: return NSLocalizedString("Please follow the instructions on the AltStore FAQ to install all required dependencies, then try again.", comment: "") } } }