From a7b28d5027f877e91346011529cc31dcca9a09ba Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 7 Sep 2023 18:00:53 -0500 Subject: [PATCH] [AltJIT] Adds AltJIT CLI tool to enable JIT on devices running iOS 17+ Commands: altjit enable [app/pid] --udid [udid] * Enables JIT for given app/process altjit mount --udid [udid] * Mounts personalized developer disk --- AltJIT/AltJIT-Bridging-Header.h | 9 + AltJIT/AltJIT.swift | 18 + AltJIT/Commands/EnableJIT.swift | 452 ++++++++++++++++++ AltJIT/Commands/MountDisk.swift | 75 +++ AltJIT/Extensions/Logger+AltJIT.swift | 16 + AltJIT/Extensions/Task+Timeout.swift | 57 +++ AltJIT/Extensions/URL+Tools.swift | 15 + AltJIT/Types/ALTErrorKeys.h | 14 + AltJIT/Types/ALTErrorKeys.m | 13 + AltJIT/Types/PythonCommand.swift | 58 +++ .../Types/RemoteServiceDiscoveryTunnel.swift | 41 ++ AltStore.xcodeproj/project.pbxproj | 333 ++++++++++++- .../xcshareddata/xcschemes/AltJIT.xcscheme | 101 ++++ Shared/Categories/NSError+ALTServerError.m | 9 + Shared/Errors/ALTLocalizedError.swift | 3 + Shared/Errors/JITError.swift | 52 ++ Shared/Errors/ProcessError.swift | 88 ++++ Shared/Extensions/Process+Conveniences.swift | 151 ++++++ Shared/Types/AppProcess.swift | 35 ++ 19 files changed, 1539 insertions(+), 1 deletion(-) create mode 100644 AltJIT/AltJIT-Bridging-Header.h create mode 100644 AltJIT/AltJIT.swift create mode 100644 AltJIT/Commands/EnableJIT.swift create mode 100644 AltJIT/Commands/MountDisk.swift create mode 100644 AltJIT/Extensions/Logger+AltJIT.swift create mode 100644 AltJIT/Extensions/Task+Timeout.swift create mode 100644 AltJIT/Extensions/URL+Tools.swift create mode 100644 AltJIT/Types/ALTErrorKeys.h create mode 100644 AltJIT/Types/ALTErrorKeys.m create mode 100644 AltJIT/Types/PythonCommand.swift create mode 100644 AltJIT/Types/RemoteServiceDiscoveryTunnel.swift create mode 100644 AltStore.xcodeproj/xcshareddata/xcschemes/AltJIT.xcscheme create mode 100644 Shared/Errors/JITError.swift create mode 100644 Shared/Errors/ProcessError.swift create mode 100644 Shared/Extensions/Process+Conveniences.swift create mode 100644 Shared/Types/AppProcess.swift diff --git a/AltJIT/AltJIT-Bridging-Header.h b/AltJIT/AltJIT-Bridging-Header.h new file mode 100644 index 00000000..807b3139 --- /dev/null +++ b/AltJIT/AltJIT-Bridging-Header.h @@ -0,0 +1,9 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "ALTErrorKeys.h" + +// Shared +#import "ALTWrappedError.h" +#import "NSError+ALTServerError.h" diff --git a/AltJIT/AltJIT.swift b/AltJIT/AltJIT.swift new file mode 100644 index 00000000..cc73f33b --- /dev/null +++ b/AltJIT/AltJIT.swift @@ -0,0 +1,18 @@ +// +// AltJIT.swift +// AltJIT +// +// Created by Riley Testut on 8/29/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import OSLog +import ArgumentParser + +@main +struct AltJIT: AsyncParsableCommand +{ + static let configuration = CommandConfiguration(commandName: "altjit", + abstract: "Enable JIT for sideloaded apps.", + subcommands: [EnableJIT.self, MountDisk.self]) +} diff --git a/AltJIT/Commands/EnableJIT.swift b/AltJIT/Commands/EnableJIT.swift new file mode 100644 index 00000000..2a50017a --- /dev/null +++ b/AltJIT/Commands/EnableJIT.swift @@ -0,0 +1,452 @@ +// +// EnableJIT.swift +// AltPackage +// +// Created by Riley Testut on 8/29/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation +import OSLog +import RegexBuilder + +import ArgumentParser + +struct EnableJIT: PythonCommand +{ + static let configuration = CommandConfiguration(commandName: "enable", abstract: "Enable JIT for a specific app on your device.") + + @Argument(help: "The name or PID of the app to enable JIT for.", transform: AppProcess.init) + var process: AppProcess + + @Option(help: "Your iOS device's UDID.") + var udid: String + + // PythonCommand + var pythonPath: String? + + mutating func run() async throws + { + // Use local variables to fix "escaping autoclosure captures mutating self parameter" compiler error. + let process = self.process + let udid = self.udid + + do + { + do + { + Logger.main.info("Enabling JIT for \(process, privacy: .private(mask: .hash)) on device \(udid, privacy: .private(mask: .hash))...") + + try await self.prepare() + + let rsdTunnel = try await self.startRSDTunnel() + defer { rsdTunnel.process.terminate() } + print("Connected to device \(self.udid)!", rsdTunnel) + + let port = try await self.startDebugServer(rsdTunnel: rsdTunnel) + print("Started debugserver on port \(port).") + + print("Attaching debugger...") + let lldb = try await self.attachDebugger(ipAddress: rsdTunnel.ipAddress, port: port) + defer { lldb.terminate() } + print("Attached debugger to \(process).") + + try await self.detachDebugger(lldb) + print("Detached debugger from \(process).") + + print("✅ Successfully enabled JIT for \(process) on device \(udid)!") + } + catch let error as ProcessError + { + if let output = error.output + { + print(output) + } + + throw error + } + } + catch + { + print("❌ Unable to enable JIT for \(process) on device \(udid).") + print(error.localizedDescription) + + Logger.main.error("Failed to enable JIT for \(process, privacy: .private(mask: .hash)) on device \(udid, privacy: .private(mask: .hash)). \(error, privacy: .public)") + + throw ExitCode.failure + } + } +} + +private extension EnableJIT +{ + func startRSDTunnel() async throws -> RemoteServiceDiscoveryTunnel + { + do + { + Logger.main.info("Starting RSD tunnel...") + + let process = try Process.launch(.python3, arguments: ["-u", "-m", "pymobiledevice3", "remote", "start-quic-tunnel", "--udid", self.udid], environment: self.processEnvironment) + + do + { + let rsdTunnel = try await withTimeout(seconds: 20) { + let regex = Regex { + "--rsd" + + OneOrMore(.whitespace) + + Capture { + OneOrMore(.anyGraphemeCluster) + } + + OneOrMore(.whitespace) + + TryCapture { + OneOrMore(.digit) + } transform: { match in + Int(match) + } + } + + for try await line in process.outputLines + { + if let match = line.firstMatch(of: regex) + { + let rsdTunnel = RemoteServiceDiscoveryTunnel(ipAddress: String(match.1), port: match.2, process: process) + return rsdTunnel + } + } + + throw ProcessError.unexpectedOutput(executableURL: .python3, output: process.output) + } + + // MUST close standardOutput in order to stream output later. + process.stopOutput() + + return rsdTunnel + } + catch is TimedOutError + { + process.terminate() + + let error = ProcessError.timedOut(executableURL: .python3, output: process.output) + throw error + } + catch + { + process.terminate() + throw error + } + } + catch let error as NSError + { + let localizedFailure = NSLocalizedString("Could not connect to device \(self.udid).", comment: "") + throw error.withLocalizedFailure(localizedFailure) + } + } + + func startDebugServer(rsdTunnel: RemoteServiceDiscoveryTunnel) async throws -> Int + { + do + { + Logger.main.info("Starting debugserver...") + + return try await withTimeout(seconds: 10) { + let arguments = ["-u", "-m", "pymobiledevice3", "developer", "debugserver", "start-server"] + rsdTunnel.commandArguments + + let output = try await Process.launchAndWait(.python3, arguments: arguments, environment: self.processEnvironment) + + let port = Reference(Int.self) + let regex = Regex { + "connect://" + + OneOrMore(.anyGraphemeCluster, .eager) + + ":" + + TryCapture(as: port) { + OneOrMore(.digit) + } transform: { match in + Int(match) + } + } + + if let match = output.firstMatch(of: regex) + { + return match[port] + } + + throw ProcessError.unexpectedOutput(executableURL: .python3, output: output) + } + } + catch let error as NSError + { + let localizedFailure = NSLocalizedString("Could not start debugserver on device \(self.udid).", comment: "") + throw error.withLocalizedFailure(localizedFailure) + } + } + + func attachDebugger(ipAddress: String, port: Int) async throws -> Process + { + do + { + Logger.main.info("Attaching debugger...") + + let processID: Int + + switch self.process + { + case .pid(let pid): processID = pid + case .name(let name): + guard let pid = try await self.getPID(for: name) else { throw JITError.processNotRunning(self.process) } + processID = pid + } + + let process = try Process.launch(.lldb, environment: self.processEnvironment) + + do + { + try await withThrowingTaskGroup(of: Void.self) { taskGroup in + + // // Throw error if program terminates. + // taskGroup.addTask { + // try await withCheckedThrowingContinuation { continuation in + // process.terminationHandler = { process in + // Task { + // // Should NEVER be called unless an error occurs. + // continuation.resume(throwing: ProcessError.terminated(executableURL: .lldb, exitCode: process.terminationStatus, output: process.output)) + // } + // } + // } + // } + + taskGroup.addTask { + do + { + try await self.sendDebuggerCommand("platform select remote-ios", to: process, timeout: 5) { + ChoiceOf { + "SDK Roots:" + "unable to locate SDK" + } + } + + let ipAddress = "[\(ipAddress)]" + let connectCommand = "process connect connect://\(ipAddress):\(port)" + try await self.sendDebuggerCommand(connectCommand, to: process, timeout: 10) + + try await self.sendDebuggerCommand("settings set target.memory-module-load-level minimal", to: process, timeout: 5) + + let attachCommand = "attach -p \(processID)" + let failureMessage = "attach failed" + let output = try await self.sendDebuggerCommand(attachCommand, to: process, timeout: 120) { + + ChoiceOf { + failureMessage + + Regex { + "Process " + OneOrMore(.digit) + " stopped" + } + } + } + + if output.contains(failureMessage) + { + throw ProcessError.failed(executableURL: .lldb, exitCode: -1, output: process.output) + } + } + catch is TimedOutError + { + let error = ProcessError.timedOut(executableURL: .lldb, output: process.output) + throw error + } + } + + // Wait until first child task returns + _ = try await taskGroup.next()! + + // Cancel remaining tasks + taskGroup.cancelAll() + } + + return process + } + catch + { + process.terminate() + throw error + } + } + catch let error as NSError + { + let localizedFailure = String(format: NSLocalizedString("Could not attach debugger to %@.", comment: ""), self.process.description) + throw error.withLocalizedFailure(localizedFailure) + } + } + + func detachDebugger(_ process: Process) async throws + { + do + { + Logger.main.info("Detaching debugger...") + + try await withThrowingTaskGroup(of: Void.self) { taskGroup in + +// // Throw error if program terminates. +// taskGroup.addTask { +// try await withCheckedThrowingContinuation { continuation in +// process.terminationHandler = { process in +// if process.terminationStatus == 0 +// { +// continuation.resume() +// } +// else +// { +// continuation.resume(throwing: ProcessError.terminated(executableURL: .lldb, exitCode: process.terminationStatus, output: process.output)) +// } +// } +// } +// } + + taskGroup.addTask { + do + { + try await self.sendDebuggerCommand("c", to: process, timeout: 10) { + "Process " + OneOrMore(.digit) + " resuming" + } + + try await self.sendDebuggerCommand("detach", to: process, timeout: 10) { + "Process " + OneOrMore(.digit) + " detached" + } + } + catch is TimedOutError + { + let error = ProcessError.timedOut(executableURL: .lldb, output: process.output) + throw error + } + } + + // Wait until first child task returns + _ = try await taskGroup.next()! + + // Cancel remaining tasks + taskGroup.cancelAll() + } + } + catch let error as NSError + { + let localizedFailure = NSLocalizedString("Could not detach debugger from \(self.process).", comment: "") + throw error.withLocalizedFailure(localizedFailure) + } + } +} + +private extension EnableJIT +{ + func getPID(for name: String) async throws -> Int? + { + Logger.main.info("Retrieving PID for \(name, privacy: .private(mask: .hash))...") + + let arguments = ["-m", "pymobiledevice3", "processes", "pgrep", name, "--udid", self.udid] + let output = try await Process.launchAndWait(.python3, arguments: arguments, environment: self.processEnvironment) + + let regex = Regex { + "INFO" + + OneOrMore(.whitespace) + + TryCapture { + OneOrMore(.digit) + } transform: { match in + Int(match) + } + + OneOrMore(.whitespace) + + name + } + + if let match = output.firstMatch(of: regex) + { + Logger.main.info("\(name, privacy: .private(mask: .hash)) PID is \(match.1)") + return match.1 + } + + return nil + } + + @discardableResult + func sendDebuggerCommand(_ command: String, to process: Process, timeout: TimeInterval, + @RegexComponentBuilder regex: @escaping () -> (some RegexComponent)? = { Optional>.none }) async throws -> String + { + guard let inputPipe = process.standardInput as? Pipe else { preconditionFailure("`process` must have a Pipe as its standardInput") } + defer { + inputPipe.fileHandleForWriting.writeabilityHandler = nil + } + + let initialOutput = process.output + + let data = (command + "\n").data(using: .utf8)! // Will always succeed. + Logger.main.info("Sending lldb command: \(command, privacy: .public)") + + let output = try await withTimeout(seconds: timeout) { + // Wait until process is ready to receive input. + try await withCheckedThrowingContinuation { continuation in + inputPipe.fileHandleForWriting.writeabilityHandler = { fileHandle in + inputPipe.fileHandleForWriting.writeabilityHandler = nil + + let result = Result { try fileHandle.write(contentsOf: data) } + continuation.resume(with: result) + } + } + + // Wait until we receive at least one line of output. + for try await _ in process.outputLines + { + break + } + + // Keep waiting until output doesn't change. + // If regex is provided, we keep waiting until a match is found. + var previousOutput = process.output + while true + { + try await Task.sleep(for: .seconds(0.2)) + + let output = process.output + if output == previousOutput + { + guard let regex = regex() else { + // No regex, so break as soon as output stops changing. + break + } + + if output.contains(regex) + { + // Found a match, so exit while loop. + break + } + else + { + // Output hasn't changed, but regex does not match (yet). + continue + } + } + + previousOutput = output + } + + return previousOutput + } + + // Subtract initialOutput from output to get just this command's output. + let commandOutput = output.replacingOccurrences(of: initialOutput, with: "") + return commandOutput + } +} diff --git a/AltJIT/Commands/MountDisk.swift b/AltJIT/Commands/MountDisk.swift new file mode 100644 index 00000000..802feabb --- /dev/null +++ b/AltJIT/Commands/MountDisk.swift @@ -0,0 +1,75 @@ +// +// MountDisk.swift +// AltPackage +// +// Created by Riley Testut on 8/31/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation +import OSLog + +import ArgumentParser + +typealias MountError = MountErrorCode.Error +enum MountErrorCode: Int, ALTErrorEnum +{ + case alreadyMounted + + var errorFailureReason: String { + switch self + { + case .alreadyMounted: return NSLocalizedString("A personalized Developer Disk is already mounted.", comment: "") + } + } +} + +struct MountDisk: PythonCommand +{ + static let configuration = CommandConfiguration(commandName: "mount", abstract: "Mount a personalized developer disk image onto an iOS device.") + + @Option(help: "The iOS device's UDID.") + var udid: String + + // PythonCommand + var pythonPath: String? + + mutating func run() async throws + { + do + { + print("Mounting personalized developer disk...") + + try await self.prepare() + + let output = try await Process.launchAndWait(.python3, arguments: ["-m", "pymobiledevice3", "mounter", "auto-mount", "--udid", self.udid]) + if !output.contains("DeveloperDiskImage") + { + throw ProcessError.unexpectedOutput(executableURL: .python3, output: output) + } + + if output.contains("already mounted") + { + throw MountError(.alreadyMounted) + } + + print("✅ Successfully mounted personalized Developer Disk!") + } + catch let error as MountError where error.code == .alreadyMounted + { + // Prepend ⚠️ since this is not really an error. + let localizedDescription = "⚠️ " + error.localizedDescription + print(localizedDescription) + + throw ExitCode.success + } + catch + { + // Output failure message first before error. + print("❌ Unable to mount personalized Developer Disk.") + print(error.localizedDescription) + + throw ExitCode.failure + } + } +} diff --git a/AltJIT/Extensions/Logger+AltJIT.swift b/AltJIT/Extensions/Logger+AltJIT.swift new file mode 100644 index 00000000..b8453141 --- /dev/null +++ b/AltJIT/Extensions/Logger+AltJIT.swift @@ -0,0 +1,16 @@ +// +// Logger+AltJIT.swift +// AltJIT +// +// Created by Riley Testut on 8/29/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import OSLog + +public extension Logger +{ + static let altjitSubsystem = Bundle.main.bundleIdentifier! + + static let main = Logger(subsystem: altjitSubsystem, category: "AltJIT") +} diff --git a/AltJIT/Extensions/Task+Timeout.swift b/AltJIT/Extensions/Task+Timeout.swift new file mode 100644 index 00000000..33f448f3 --- /dev/null +++ b/AltJIT/Extensions/Task+Timeout.swift @@ -0,0 +1,57 @@ +// +// Task+Timeout.swift +// AltPackage +// +// Created by Riley Testut on 8/31/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// +// Based heavily on https://forums.swift.org/t/running-an-async-task-with-a-timeout/49733/13 +// + +import Foundation + +struct TimedOutError: LocalizedError +{ + var duration: TimeInterval + + public var errorDescription: String? { + //TODO: Change pluralization for 1 second. + let errorDescription = String(format: NSLocalizedString("The task timed out after %@ seconds.", comment: ""), self.duration.formatted()) + return errorDescription + } +} + +/// +/// Execute an operation in the current task subject to a timeout. +/// +/// - Parameters: +/// - seconds: The duration in seconds `operation` is allowed to run before timing out. +/// - operation: The async operation to perform. +/// - Returns: Returns the result of `operation` if it completed in time. +/// - Throws: Throws ``TimedOutError`` if the timeout expires before `operation` completes. +/// If `operation` throws an error before the timeout expires, that error is propagated to the caller. +func withTimeout(seconds: TimeInterval, file: StaticString = #file, line: Int = #line, operation: @escaping @Sendable () async throws -> R) async throws -> R +{ + return try await withThrowingTaskGroup(of: R.self) { group in + let deadline = Date(timeIntervalSinceNow: seconds) + + // Start actual work. + group.addTask { + return try await operation() + } + // Start timeout child task. + group.addTask { + let interval = deadline.timeIntervalSinceNow + if interval > 0 { + try await Task.sleep(for: .seconds(interval)) + } + try Task.checkCancellation() + // We’ve reached the timeout. + throw TimedOutError(duration: seconds) + } + // First finished child task wins, cancel the other task. + let result = try await group.next()! + group.cancelAll() + return result + } +} diff --git a/AltJIT/Extensions/URL+Tools.swift b/AltJIT/Extensions/URL+Tools.swift new file mode 100644 index 00000000..364be414 --- /dev/null +++ b/AltJIT/Extensions/URL+Tools.swift @@ -0,0 +1,15 @@ +// +// URL+Tools.swift +// AltJIT +// +// Created by Riley Testut on 9/3/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +extension URL +{ + static let python3 = URL(fileURLWithPath: "/usr/bin/python3") + static let lldb = URL(fileURLWithPath: "/usr/bin/lldb") +} diff --git a/AltJIT/Types/ALTErrorKeys.h b/AltJIT/Types/ALTErrorKeys.h new file mode 100644 index 00000000..a169eaaf --- /dev/null +++ b/AltJIT/Types/ALTErrorKeys.h @@ -0,0 +1,14 @@ +// +// ALTErrorKeys.h +// AltJIT +// +// Created by Riley Testut on 9/1/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +@import Foundation; + +// Can't import AltSign (for reasons), so re-declare these constants here instead. +extern NSErrorUserInfoKey const ALTSourceFileErrorKey; +extern NSErrorUserInfoKey const ALTSourceLineErrorKey; +extern NSErrorUserInfoKey const ALTAppNameErrorKey; diff --git a/AltJIT/Types/ALTErrorKeys.m b/AltJIT/Types/ALTErrorKeys.m new file mode 100644 index 00000000..48040989 --- /dev/null +++ b/AltJIT/Types/ALTErrorKeys.m @@ -0,0 +1,13 @@ +// +// ALTErrorKeys.m +// AltJIT +// +// Created by Riley Testut on 9/1/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +#import "ALTErrorKeys.h" + +NSErrorUserInfoKey const ALTSourceFileErrorKey = @"ALTSourceFile"; +NSErrorUserInfoKey const ALTSourceLineErrorKey = @"ALTSourceLine"; +NSErrorUserInfoKey const ALTAppNameErrorKey = @"appName"; diff --git a/AltJIT/Types/PythonCommand.swift b/AltJIT/Types/PythonCommand.swift new file mode 100644 index 00000000..0f493e71 --- /dev/null +++ b/AltJIT/Types/PythonCommand.swift @@ -0,0 +1,58 @@ +// +// PythonCommand.swift +// AltJIT +// +// Created by Riley Testut on 9/6/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import ArgumentParser + +protocol PythonCommand: AsyncParsableCommand +{ + var pythonPath: String? { get set } +} + +extension PythonCommand +{ + var processEnvironment: [String: String] { + var environment = ProcessInfo.processInfo.environment + + if let pythonPath + { + environment["PYTHONPATH"] = pythonPath + } + + return environment + } + + mutating func prepare() async throws + { + let pythonPath = try await self.readPythonPath() + self.pythonPath = pythonPath.path(percentEncoded: false) + } +} + +private extension PythonCommand +{ + func readPythonPath() async throws -> URL + { + let processOutput: String + + do + { + processOutput = try await Process.launchAndWait(.python3, arguments: ["-m", "site", "--user-site"]) + } + catch let error as ProcessError where error.exitCode == 2 + { + // Ignore exit code 2. + guard let output = error.output else { throw error } + processOutput = output + } + + let sanitizedOutput = processOutput.trimmingCharacters(in: .whitespacesAndNewlines) + + let pythonURL = URL(filePath: sanitizedOutput) + return pythonURL + } +} diff --git a/AltJIT/Types/RemoteServiceDiscoveryTunnel.swift b/AltJIT/Types/RemoteServiceDiscoveryTunnel.swift new file mode 100644 index 00000000..545b91db --- /dev/null +++ b/AltJIT/Types/RemoteServiceDiscoveryTunnel.swift @@ -0,0 +1,41 @@ +// +// RemoteServiceDiscoveryTunnel.swift +// AltJIT +// +// Created by Riley Testut on 9/3/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +final class RemoteServiceDiscoveryTunnel +{ + let ipAddress: String + let port: Int + + let process: Process + + var commandArguments: [String] { + ["--rsd", self.ipAddress, String(self.port)] + } + + init(ipAddress: String, port: Int, process: Process) + { + self.ipAddress = ipAddress + self.port = port + + self.process = process + } + + deinit + { + self.process.terminate() + } +} + +extension RemoteServiceDiscoveryTunnel: CustomStringConvertible +{ + var description: String { + "\(self.ipAddress) \(self.port)" + } +} diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 797ebe99..1f5e8d40 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -355,9 +355,12 @@ D51AD27F29356B7B00967AAA /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; }; D51AD28029356B8000967AAA /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; }; D52C08EE28AEC37A006C4AE5 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52C08ED28AEC37A006C4AE5 /* AppVersion.swift */; }; + D52DD35E2AAA89A600A7F2B6 /* AltSign-Dynamic in Frameworks */ = {isa = PBXBuildFile; productRef = D52DD35D2AAA89A600A7F2B6 /* AltSign-Dynamic */; }; + D52DD35F2AAA89A700A7F2B6 /* AltSign-Dynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = D52DD35D2AAA89A600A7F2B6 /* AltSign-Dynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D52EF2BE2A0594550096C377 /* AppDetailCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */; }; D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D533E8B62727841800A9B5DD /* libAppleArchive.tbd */; settings = {ATTRIBUTES = (Weak, ); }; }; D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D533E8BD2727BBF800A9B5DD /* libcurl.a */; }; + 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 */; }; D54058BB2A1D8FE3008CCC58 /* UIColor+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54058BA2A1D8FE3008CCC58 /* UIColor+AltStore.swift */; }; @@ -392,7 +395,15 @@ D5935AED29C39DE300C157EF /* SourceDetailsComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5935AEC29C39DE300C157EF /* SourceDetailsComponents.swift */; }; D5935AEF29C3B23600C157EF /* Sources.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D5935AEE29C3B23600C157EF /* Sources.storyboard */; }; D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; }; + D59A6B7B2AA91B8E00F61259 /* PythonCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B7A2AA91B8E00F61259 /* PythonCommand.swift */; }; + D59A6B7F2AA9226C00F61259 /* AppProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B7D2AA9226C00F61259 /* AppProcess.swift */; }; + D59A6B822AA92D1C00F61259 /* Process+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B802AA92D1C00F61259 /* Process+Conveniences.swift */; }; + D59A6B842AA932F700F61259 /* Logger+AltServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59A6B832AA932F700F61259 /* Logger+AltServer.swift */; }; D5A0537329B91DB400997551 /* SourceDetailContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A0537229B91DB400997551 /* SourceDetailContentViewController.swift */; }; + D5A1D2E42AA50EB60066CACC /* JITError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1D2E32AA50EB60066CACC /* JITError.swift */; }; + D5A1D2E92AA512940066CACC /* RemoteServiceDiscoveryTunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1D2E82AA512940066CACC /* RemoteServiceDiscoveryTunnel.swift */; }; + 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 */; }; D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */; }; D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; }; @@ -414,6 +425,18 @@ D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */; }; D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; }; D5FB7A0E2AA25A4E00EF863D /* Previews.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5FB7A0D2AA25A4E00EF863D /* Previews.xcassets */; }; + D5FB7A212AA284ED00EF863D /* EnableJIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1A2AA284ED00EF863D /* EnableJIT.swift */; }; + D5FB7A242AA284ED00EF863D /* Logger+AltJIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1D2AA284ED00EF863D /* Logger+AltJIT.swift */; }; + D5FB7A252AA284ED00EF863D /* AltJIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1E2AA284ED00EF863D /* AltJIT.swift */; }; + D5FB7A262AA284ED00EF863D /* MountDisk.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A1F2AA284ED00EF863D /* MountDisk.swift */; }; + D5FB7A272AA284ED00EF863D /* Task+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A202AA284ED00EF863D /* Task+Timeout.swift */; }; + D5FB7A2A2AA2854100EF863D /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DB145828F9DC1000A8F606 /* ALTLocalizedError.swift */; }; + D5FB7A2B2AA2854400EF863D /* UserInfoValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5189C002A01BC6800F44625 /* UserInfoValue.swift */; }; + D5FB7A2E2AA2859400EF863D /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = D5FB7A2D2AA2859400EF863D /* ArgumentParser */; }; + D5FB7A312AA28A2900EF863D /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; }; + D5FB7A322AA28A4000EF863D /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = D51AD27D29356B7B00967AAA /* ALTWrappedError.m */; }; + D5FB7A392AA28D8300EF863D /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; }; + D5FB7A472AA293D000EF863D /* ALTErrorKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = D5FB7A462AA293D000EF863D /* ALTErrorKeys.m */; }; D5FD4EC52A952EAD0097BEE8 /* AltWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */; }; D5FD4EC92A9530C00097BEE8 /* AppSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FD4EC82A9530C00097BEE8 /* AppSnapshot.swift */; }; D5FD4ECB2A9532960097BEE8 /* DatabaseManager+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FD4ECA2A9532960097BEE8 /* DatabaseManager+Async.swift */; }; @@ -546,6 +569,13 @@ remoteGlobalIDString = BF66EE7D2501AE50007EE018; remoteInfo = AltStoreCore; }; + D537C8562AA94D4A009A1E08 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFD247622284B9A500981D42 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D5FB7A122AA284BE00EF863D; + remoteInfo = AltJIT; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -572,6 +602,28 @@ name = "Embed Foundation Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + BFF7C910257844C900E55F36 /* Embed XPC Services */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; + dstSubfolderSpec = 16; + files = ( + BFF7C90F257844C900E55F36 /* AltXPC.xpc in Embed XPC Services */, + ); + name = "Embed XPC Services"; + runOnlyForDeploymentPostprocessing = 0; + }; + D52DD3602AAA89A700A7F2B6 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D52DD35F2AAA89A700A7F2B6 /* AltSign-Dynamic in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; D561B2EC28EF5A4F006752E4 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -583,6 +635,15 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D5FB7A112AA284BE00EF863D /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -935,6 +996,7 @@ D533E8B82727B61400A9B5DD /* fragmentzip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fragmentzip.h; sourceTree = ""; }; D533E8BB2727BBEE00A9B5DD /* libfragmentzip.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfragmentzip.a; path = Dependencies/fragmentzip/libfragmentzip.a; sourceTree = SOURCE_ROOT; }; D533E8BD2727BBF800A9B5DD /* libcurl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcurl.a; path = Dependencies/libcurl/libcurl.a; sourceTree = SOURCE_ROOT; }; + D537C85A2AA95066009A1E08 /* libcorecrypto.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcorecrypto.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/lib/system/libcorecrypto.tbd; sourceTree = DEVELOPER_DIR; }; D53D84012A2158FC00543C3B /* Permissions.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Permissions.plist; sourceTree = ""; }; D54058B82A1D6269008CCC58 /* AppPermissionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionProtocol.swift; sourceTree = ""; }; D54058BA2A1D8FE3008CCC58 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = ""; }; @@ -970,7 +1032,13 @@ D5935AEC29C39DE300C157EF /* SourceDetailsComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceDetailsComponents.swift; sourceTree = ""; }; D5935AEE29C3B23600C157EF /* Sources.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sources.storyboard; sourceTree = ""; }; D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = ""; }; @@ -991,6 +1059,16 @@ D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = ""; }; D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = ""; }; D5FB7A0D2AA25A4E00EF863D /* Previews.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Previews.xcassets; sourceTree = ""; }; + D5FB7A132AA284BE00EF863D /* altjit */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = altjit; sourceTree = BUILT_PRODUCTS_DIR; }; + D5FB7A1A2AA284ED00EF863D /* EnableJIT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnableJIT.swift; path = AltJIT/Commands/EnableJIT.swift; sourceTree = SOURCE_ROOT; }; + D5FB7A1B2AA284ED00EF863D /* ProcessError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProcessError.swift; path = Shared/Errors/ProcessError.swift; sourceTree = SOURCE_ROOT; }; + D5FB7A1D2AA284ED00EF863D /* Logger+AltJIT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Logger+AltJIT.swift"; path = "AltJIT/Extensions/Logger+AltJIT.swift"; sourceTree = SOURCE_ROOT; }; + D5FB7A1E2AA284ED00EF863D /* AltJIT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AltJIT.swift; path = AltJIT/AltJIT.swift; sourceTree = SOURCE_ROOT; }; + D5FB7A1F2AA284ED00EF863D /* MountDisk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MountDisk.swift; path = AltJIT/Commands/MountDisk.swift; sourceTree = SOURCE_ROOT; }; + D5FB7A202AA284ED00EF863D /* Task+Timeout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Task+Timeout.swift"; path = "AltJIT/Extensions/Task+Timeout.swift"; sourceTree = SOURCE_ROOT; }; + D5FB7A382AA28AC700EF863D /* AltJIT-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "AltJIT-Bridging-Header.h"; path = "AltJIT/AltJIT-Bridging-Header.h"; sourceTree = SOURCE_ROOT; }; + D5FB7A452AA293D000EF863D /* ALTErrorKeys.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTErrorKeys.h; sourceTree = ""; }; + D5FB7A462AA293D000EF863D /* ALTErrorKeys.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTErrorKeys.m; sourceTree = ""; }; D5FD4EC42A952EAD0097BEE8 /* AltWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltWidgetBundle.swift; sourceTree = ""; }; D5FD4EC82A9530C00097BEE8 /* AppSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSnapshot.swift; sourceTree = ""; }; D5FD4ECA2A9532960097BEE8 /* DatabaseManager+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseManager+Async.swift"; sourceTree = ""; }; @@ -1087,6 +1165,16 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D5FB7A102AA284BE00EF863D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D52DD35E2AAA89A600A7F2B6 /* AltSign-Dynamic in Frameworks */, + D537C85B2AA9507A009A1E08 /* libcorecrypto.tbd in Frameworks */, + D5FB7A2E2AA2859400EF863D /* ArgumentParser in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -1249,6 +1337,7 @@ 0EE7FDBF2BE8BBBF00D1E390 /* Errors */, BFF767C32489A6800097E58C /* Extensions */, BFF767C42489A6980097E58C /* Categories */, + D59A6B7C2AA9225C00F61259 /* Types */, ); path = Shared; sourceTree = ""; @@ -1722,6 +1811,9 @@ 19104DB32909C06D00C49C7B /* EmotionalDamage */, 191E5FAC290A5D92001A3B7C /* minimuxer */, B343F886295F7F9B002B1159 /* libfragmentzip.xcodeproj */, + BFF7C905257844C900E55F36 /* AltXPC */, + D5FB7A142AA284BE00EF863D /* AltJIT */, + D586D39928EF58B0000E101F /* AltTests */, BFD247852284BB3300981D42 /* Frameworks */, B3146EC6284F580500BBC3FD /* Roxas.xcodeproj */, BFD2476B2284B9A500981D42 /* Products */, @@ -1740,6 +1832,9 @@ BF989167250AABF3002ACF50 /* AltWidgetExtension.appex */, 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */, 191E5FAB290A5D92001A3B7C /* libminimuxer.a */, + BFF7C904257844C900E55F36 /* AltXPC.xpc */, + D586D39828EF58B0000E101F /* AltTests.xctest */, + D5FB7A132AA284BE00EF863D /* altjit */, ); name = Products; sourceTree = ""; @@ -1782,6 +1877,7 @@ children = ( B343F86C295F759E002B1159 /* libresolv.tbd */, B39575F4284F29E20080B4FF /* Roxas.framework */, + D537C85A2AA95066009A1E08 /* libcorecrypto.tbd */, D533E8B62727841800A9B5DD /* libAppleArchive.tbd */, BF580497246A3D19008AE704 /* UIKit.framework */, BF4588872298DD3F00BD7491 /* libxml2.tbd */, @@ -1940,6 +2036,7 @@ BFF435D7255CBDAB00DD724F /* ALTApplication+AltStoreApp.swift */, BF6C336124197D700034FD24 /* NSError+AltStore.swift */, D5708416292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift */, + D59A6B802AA92D1C00F61259 /* Process+Conveniences.swift */, ); path = Extensions; sourceTree = ""; @@ -2011,6 +2108,17 @@ path = Model; sourceTree = ""; }; + D522F9562AA509E9003E57D1 /* Types */ = { + isa = PBXGroup; + children = ( + D5FB7A452AA293D000EF863D /* ALTErrorKeys.h */, + D5FB7A462AA293D000EF863D /* ALTErrorKeys.m */, + D5A1D2E82AA512940066CACC /* RemoteServiceDiscoveryTunnel.swift */, + D59A6B7A2AA91B8E00F61259 /* PythonCommand.swift */, + ); + path = Types; + sourceTree = ""; + }; D54058B72A1D6251008CCC58 /* Previews */ = { isa = PBXGroup; children = ( @@ -2065,6 +2173,14 @@ path = "Error Log"; sourceTree = ""; }; + D59A6B7C2AA9225C00F61259 /* Types */ = { + isa = PBXGroup; + children = ( + D59A6B7D2AA9226C00F61259 /* AppProcess.swift */, + ); + path = Types; + sourceTree = ""; + }; D5DB145728F9DC0300A8F606 /* Errors */ = { isa = PBXGroup; children = ( @@ -2072,10 +2188,43 @@ D5189C002A01BC6800F44625 /* UserInfoValue.swift */, D51AD27C29356B7B00967AAA /* ALTWrappedError.h */, D51AD27D29356B7B00967AAA /* ALTWrappedError.m */, + D5FB7A1B2AA284ED00EF863D /* ProcessError.swift */, + D5A1D2E32AA50EB60066CACC /* JITError.swift */, ); path = Errors; sourceTree = ""; }; + D5FB7A142AA284BE00EF863D /* AltJIT */ = { + isa = PBXGroup; + children = ( + D5FB7A1E2AA284ED00EF863D /* AltJIT.swift */, + D5FB7A382AA28AC700EF863D /* AltJIT-Bridging-Header.h */, + D5FB7A282AA2851200EF863D /* Commands */, + D522F9562AA509E9003E57D1 /* Types */, + D5FB7A292AA2851C00EF863D /* Extensions */, + ); + path = AltJIT; + sourceTree = ""; + }; + D5FB7A282AA2851200EF863D /* Commands */ = { + isa = PBXGroup; + children = ( + D5FB7A1A2AA284ED00EF863D /* EnableJIT.swift */, + D5FB7A1F2AA284ED00EF863D /* MountDisk.swift */, + ); + path = Commands; + sourceTree = ""; + }; + D5FB7A292AA2851C00EF863D /* Extensions */ = { + isa = PBXGroup; + children = ( + D5FB7A1D2AA284ED00EF863D /* Logger+AltJIT.swift */, + D5A1D2EA2AA513410066CACC /* URL+Tools.swift */, + D5FB7A202AA284ED00EF863D /* Task+Timeout.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2298,6 +2447,28 @@ productReference = BFD2476A2284B9A500981D42 /* SideStore.app */; productType = "com.apple.product-type.application"; }; + D5FB7A122AA284BE00EF863D /* AltJIT */ = { + isa = PBXNativeTarget; + buildConfigurationList = D5FB7A172AA284BE00EF863D /* Build configuration list for PBXNativeTarget "AltJIT" */; + buildPhases = ( + D5FB7A0F2AA284BE00EF863D /* Sources */, + D5FB7A102AA284BE00EF863D /* Frameworks */, + D5FB7A112AA284BE00EF863D /* CopyFiles */, + D52DD3602AAA89A700A7F2B6 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AltJIT; + packageProductDependencies = ( + D5FB7A2D2AA2859400EF863D /* ArgumentParser */, + D52DD35D2AAA89A600A7F2B6 /* AltSign-Dynamic */, + ); + productName = AltJIT; + productReference = D5FB7A132AA284BE00EF863D /* altjit */; + productType = "com.apple.product-type.tool"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -2343,6 +2514,17 @@ }; }; }; + BFF7C903257844C900E55F36 = { + CreatedOnToolsVersion = 12.3; + LastSwiftMigration = 1230; + }; + D586D39728EF58B0000E101F = { + CreatedOnToolsVersion = 14.0.1; + TestTargetID = BFD247692284B9A500981D42; + }; + D5FB7A122AA284BE00EF863D = { + CreatedOnToolsVersion = 15.0; + }; }; }; buildConfigurationList = BFD247652284B9A500981D42 /* Build configuration list for PBXProject "AltStore" */; @@ -2365,6 +2547,7 @@ 4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */, 99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */, 9922FFEA29B501C50020F868 /* XCRemoteSwiftPackageReference "Starscream" */, + D5FB7A2C2AA2859400EF863D /* XCRemoteSwiftPackageReference "swift-argument-parser" */, ); productRefGroup = BFD2476B2284B9A500981D42 /* Products */; projectDirPath = ""; @@ -2396,6 +2579,9 @@ BF989166250AABF3002ACF50 /* AltWidgetExtension */, 19104DB12909C06C00C49C7B /* EmotionalDamage */, 191E5FAA290A5D92001A3B7C /* minimuxer */, + BFF7C903257844C900E55F36 /* AltXPC */, + D5FB7A122AA284BE00EF863D /* AltJIT */, + D586D39728EF58B0000E101F /* AltTests */, ); }; /* End PBXProject section */ @@ -2948,6 +3134,51 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + BFF7C900257844C900E55F36 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BFF7C9342578492100E55F36 /* ALTAnisetteData.m in Sources */, + BFF7C920257844FA00E55F36 /* ALTPluginService.m in Sources */, + BF77A67E25795BBE00BFE477 /* main.swift in Sources */, + BF77A67025795A5600BFE477 /* AltXPC.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D586D39428EF58B0000E101F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D586D39B28EF58B0000E101F /* AltTests.swift in Sources */, + D5F5AF2E28FDD2EC00C938F5 /* TestErrors.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D5FB7A0F2AA284BE00EF863D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D5A1D2EB2AA513410066CACC /* URL+Tools.swift in Sources */, + D5FB7A212AA284ED00EF863D /* EnableJIT.swift in Sources */, + D5FB7A312AA28A2900EF863D /* NSError+AltStore.swift in Sources */, + D59A6B7B2AA91B8E00F61259 /* PythonCommand.swift in Sources */, + D5A1D2EC2AA51D490066CACC /* ProcessError.swift in Sources */, + D5FB7A262AA284ED00EF863D /* MountDisk.swift in Sources */, + D5FB7A392AA28D8300EF863D /* NSError+ALTServerError.m in Sources */, + D5FB7A472AA293D000EF863D /* ALTErrorKeys.m in Sources */, + D59A6B7F2AA9226C00F61259 /* AppProcess.swift in Sources */, + D5FB7A272AA284ED00EF863D /* Task+Timeout.swift in Sources */, + D59A6B822AA92D1C00F61259 /* Process+Conveniences.swift in Sources */, + D5FB7A2A2AA2854100EF863D /* ALTLocalizedError.swift in Sources */, + D5A1D2E92AA512940066CACC /* RemoteServiceDiscoveryTunnel.swift in Sources */, + D5FB7A2B2AA2854400EF863D /* UserInfoValue.swift in Sources */, + D5FB7A242AA284ED00EF863D /* Logger+AltJIT.swift in Sources */, + D5A1D2E42AA50EB60066CACC /* JITError.swift in Sources */, + D5FB7A252AA284ED00EF863D /* AltJIT.swift in Sources */, + D5FB7A322AA28A4000EF863D /* ALTWrappedError.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -3740,6 +3971,80 @@ }; name = Release; }; + D5FB7A182AA284BE00EF863D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CREATE_INFOPLIST_SECTION_IN_BINARY = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 6XVY5G3U44; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "ALTJIT=1", + ); + GENERATE_INFOPLIST_FILE = YES; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)/usr/lib/system", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 13.0; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltJIT; + PRODUCT_NAME = altjit; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = NO; + SWIFT_OBJC_BRIDGING_HEADER = "AltJIT/AltJIT-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + D5FB7A192AA284BE00EF863D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CREATE_INFOPLIST_SECTION_IN_BINARY = YES; + DEVELOPMENT_TEAM = 6XVY5G3U44; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "ALTJIT=1", + ); + GENERATE_INFOPLIST_FILE = YES; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)/usr/lib/system", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 13.0; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = com.rileytestut.AltJIT; + PRODUCT_NAME = altjit; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = macosx; + SKIP_INSTALL = NO; + SWIFT_OBJC_BRIDGING_HEADER = "AltJIT/AltJIT-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -3824,6 +4129,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D5FB7A172AA284BE00EF863D /* Build configuration list for PBXNativeTarget "AltJIT" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D5FB7A182AA284BE00EF863D /* Debug */, + D5FB7A192AA284BE00EF863D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -3907,6 +4221,14 @@ minimumVersion = 4.1.0; }; }; + D5FB7A2C2AA2859400EF863D /* XCRemoteSwiftPackageReference "swift-argument-parser" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-argument-parser.git"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 1.2.3; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3925,7 +4247,11 @@ package = 4879A95D2861046500FC1BBD /* XCRemoteSwiftPackageReference "AltSign" */; productName = AltSign; }; - 4879A9612861049C00FC1BBD /* OpenSSL */ = { + D52DD35D2AAA89A600A7F2B6 /* AltSign-Dynamic */ = { + isa = XCSwiftPackageProductDependency; + productName = "AltSign-Dynamic"; + }; + D561B2EA28EF5A4F006752E4 /* AltSign-Dynamic */ = { isa = XCSwiftPackageProductDependency; package = 4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */; productName = OpenSSL; @@ -3965,6 +4291,11 @@ package = B3C395F5284F362400DA9E2F /* XCRemoteSwiftPackageReference "appcenter-sdk-apple" */; productName = AppCenterCrashes; }; + D5FB7A2D2AA2859400EF863D /* ArgumentParser */ = { + isa = XCSwiftPackageProductDependency; + package = D5FB7A2C2AA2859400EF863D /* XCRemoteSwiftPackageReference "swift-argument-parser" */; + productName = ArgumentParser; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/AltStore.xcodeproj/xcshareddata/xcschemes/AltJIT.xcscheme b/AltStore.xcodeproj/xcshareddata/xcschemes/AltJIT.xcscheme new file mode 100644 index 00000000..440b8636 --- /dev/null +++ b/AltStore.xcodeproj/xcshareddata/xcschemes/AltJIT.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Shared/Categories/NSError+ALTServerError.m b/Shared/Categories/NSError+ALTServerError.m index 911a0973..9d47d6b3 100644 --- a/Shared/Categories/NSError+ALTServerError.m +++ b/Shared/Categories/NSError+ALTServerError.m @@ -14,7 +14,16 @@ #import "AltStoreCore/AltStoreCore-Swift.h" #endif +#if ALTJIT +#import "AltJIT-Swift.h" @import AltSign; +#elif TARGET_OS_OSX +#import "AltServer-Swift.h" +@import AltSign; +#elif !TARGET_OS_OSX +#import +@import AltSign; +#endif NSErrorDomain const AltServerErrorDomain = @"AltServer.ServerError"; NSErrorDomain const AltServerInstallationErrorDomain = @"AltServer.InstallationError"; diff --git a/Shared/Errors/ALTLocalizedError.swift b/Shared/Errors/ALTLocalizedError.swift index 4dc54466..644936f2 100644 --- a/Shared/Errors/ALTLocalizedError.swift +++ b/Shared/Errors/ALTLocalizedError.swift @@ -7,7 +7,10 @@ // import Foundation + +#if !ALTJIT import AltSign +#endif public let ALTLocalizedTitleErrorKey = "ALTLocalizedTitle" public let ALTLocalizedDescriptionKey = "ALTLocalizedDescription" diff --git a/Shared/Errors/JITError.swift b/Shared/Errors/JITError.swift new file mode 100644 index 00000000..2b2a0e0f --- /dev/null +++ b/Shared/Errors/JITError.swift @@ -0,0 +1,52 @@ +// +// JITError.swift +// AltJIT +// +// Created by Riley Testut on 9/3/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +extension JITError +{ + enum Code: Int, ALTErrorCode + { + typealias Error = JITError + + case processNotRunning + } + + static func processNotRunning(_ process: AppProcess, file: StaticString = #file, line: Int = #line) -> JITError { + JITError(code: .processNotRunning, process: process, sourceFile: file, sourceLine: UInt(line)) + } +} + +struct JITError: ALTLocalizedError +{ + let code: Code + + var errorTitle: String? + var errorFailure: String? + + @UserInfoValue var process: AppProcess? + + var sourceFile: StaticString? + var sourceLine: UInt? + + var errorFailureReason: String { + switch self.code + { + case .processNotRunning: + let targetName = self.process?.description ?? NSLocalizedString("The target app", comment: "") + return String(format: NSLocalizedString("%@ is not running.", comment: ""), targetName) + } + } + + var recoverySuggestion: String? { + switch self.code + { + case .processNotRunning: return NSLocalizedString("Make sure the app is running in the foreground on your device then try again.", comment: "") + } + } +} diff --git a/Shared/Errors/ProcessError.swift b/Shared/Errors/ProcessError.swift new file mode 100644 index 00000000..f5e05f4c --- /dev/null +++ b/Shared/Errors/ProcessError.swift @@ -0,0 +1,88 @@ +// +// ProcessError.swift +// AltPackage +// +// Created by Riley Testut on 9/1/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +extension ProcessError +{ + enum Code: Int, ALTErrorCode + { + typealias Error = ProcessError + + case failed + case timedOut + case unexpectedOutput + case terminated + } + + static func failed(executableURL: URL, exitCode: Int32, output: String?, file: StaticString = #file, line: Int = #line) -> ProcessError { + ProcessError(code: .failed, executableURL: executableURL, exitCode: exitCode, output: output, sourceFile: file, sourceLine: UInt(line)) + } + + static func timedOut(executableURL: URL, exitCode: Int32? = nil, output: String? = nil, file: StaticString = #file, line: Int = #line) -> ProcessError { + ProcessError(code: .timedOut, executableURL: executableURL, exitCode: exitCode, output: output, sourceFile: file, sourceLine: UInt(line)) + } + + static func unexpectedOutput(executableURL: URL, output: String, exitCode: Int32? = nil, file: StaticString = #file, line: Int = #line) -> ProcessError { + ProcessError(code: .unexpectedOutput, executableURL: executableURL, exitCode: exitCode, output: output, sourceFile: file, sourceLine: UInt(line)) + } + + static func terminated(executableURL: URL, exitCode: Int32, output: String, file: StaticString = #file, line: Int = #line) -> ProcessError { + ProcessError(code: .terminated, executableURL: executableURL, exitCode: exitCode, output: output, sourceFile: file, sourceLine: UInt(line)) + } +} + +struct ProcessError: ALTLocalizedError +{ + let code: Code + + var errorTitle: String? + var errorFailure: String? + + @UserInfoValue var executableURL: URL? + @UserInfoValue var exitCode: Int32? + @UserInfoValue var output: String? + + var sourceFile: StaticString? + var sourceLine: UInt? + + var errorFailureReason: String { + switch self.code + { + case .failed: + guard let exitCode else { return String(format: NSLocalizedString("%@ failed.", comment: ""), self.processName) } + + let baseMessage = String(format: NSLocalizedString("%@ failed with code %@.", comment: ""), self.processName, NSNumber(value: exitCode)) + guard let lastLine = self.lastOutputLine else { return baseMessage } + + let failureReason = baseMessage + " " + lastLine + return failureReason + + case .timedOut: return String(format: NSLocalizedString("%@ timed out.", comment: ""), self.processName) + case .terminated: return String(format: NSLocalizedString("%@ unexpectedly quit.", comment: ""), self.processName) + case .unexpectedOutput: + let baseMessage = String(format: NSLocalizedString("%@ returned unexpected output.", comment: ""), self.processName) + guard let lastLine = self.lastOutputLine else { return baseMessage } + + let failureReason = baseMessage + " " + lastLine + return failureReason + } + } + + private var processName: String { + guard let executableName = self.executableURL?.lastPathComponent else { return NSLocalizedString("The process", comment: "") } + return String(format: NSLocalizedString("The process '%@'", comment: ""), executableName) + } + + private var lastOutputLine: String? { + guard let output else { return nil } + + let lastLine = output.components(separatedBy: .newlines).last(where: { !$0.isEmpty }) + return lastLine + } +} diff --git a/Shared/Extensions/Process+Conveniences.swift b/Shared/Extensions/Process+Conveniences.swift new file mode 100644 index 00000000..c116340f --- /dev/null +++ b/Shared/Extensions/Process+Conveniences.swift @@ -0,0 +1,151 @@ +// +// Process+Conveniences.swift +// AltStore +// +// Created by Riley Testut on 9/6/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation +import OSLog +import Combine + +@available(macOS 12, *) +extension Process +{ + // Based loosely off of https://developer.apple.com/forums/thread/690310 + class func launch(_ toolURL: URL, arguments: [String] = [], environment: [String: String] = ProcessInfo.processInfo.environment) throws -> Process + { + let inputPipe = Pipe() + let outputPipe = Pipe() + + let process = Process() + process.executableURL = toolURL + process.arguments = arguments + process.environment = environment + process.standardInput = inputPipe + process.standardOutput = outputPipe + process.standardError = outputPipe + + func posixErr(_ error: Int32) -> Error { NSError(domain: NSPOSIXErrorDomain, code: Int(error), userInfo: nil) } + + // If you write to a pipe whose remote end has closed, the OS raises a + // `SIGPIPE` signal whose default disposition is to terminate your + // process. Helpful! `F_SETNOSIGPIPE` disables that feature, causing + // the write to fail with `EPIPE` instead. + + let fcntlResult = fcntl(inputPipe.fileHandleForWriting.fileDescriptor, F_SETNOSIGPIPE, 1) + guard fcntlResult >= 0 else { throw posixErr(errno) } + + // Actually run the process. + try process.run() + + let outputTask = Task { + do + { + let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: toolURL.lastPathComponent) + + // Automatically cancels when fileHandle closes. + for try await line in outputPipe.fileHandleForReading.bytes.lines + { + process.output += line + "\n" + process.outputPublisher.send(line) + + logger.notice("\(line, privacy: .public)") + } + + try Task.checkCancellation() + process.outputPublisher.send(completion: .finished) + } + catch let error as CancellationError + { + process.outputPublisher.send(completion: .failure(error)) + } + catch + { + Logger.main.error("Failed to read process output. \(error.localizedDescription, privacy: .public)") + + try Task.checkCancellation() + process.outputPublisher.send(completion: .failure(error)) + } + } + + process.terminationHandler = { process in + Logger.main.notice("Process \(toolURL, privacy: .public) terminated with exit code \(process.terminationStatus).") + + outputTask.cancel() + process.outputPublisher.send(completion: .finished) + } + + return process + } + + class func launchAndWait(_ toolURL: URL, arguments: [String] = [], environment: [String: String] = ProcessInfo.processInfo.environment) async throws -> String + { + let process = try self.launch(toolURL, arguments: arguments, environment: environment) + + await withCheckedContinuation { (continuation: CheckedContinuation) in + let previousHandler = process.terminationHandler + process.terminationHandler = { process in + previousHandler?(process) + continuation.resume() + } + } + + guard process.terminationStatus == 0 else { + throw ProcessError.failed(executableURL: toolURL, exitCode: process.terminationStatus, output: process.output) + } + + return process.output + } +} + +@available(macOS 12, *) +extension Process +{ + private static var outputKey: Int = 0 + private static var publisherKey: Int = 0 + + fileprivate(set) var output: String { + get { + let output = objc_getAssociatedObject(self, &Process.outputKey) as? String ?? "" + return output + } + set { + objc_setAssociatedObject(self, &Process.outputKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) + } + } + + // Should be type-erased, but oh well. + var outputLines: AsyncThrowingPublisher> { + return self.outputPublisher + .buffer(size: 100, prefetch: .byRequest, whenFull: .dropOldest) + .values + } + + private var outputPublisher: PassthroughSubject { + if let publisher = objc_getAssociatedObject(self, &Process.publisherKey) as? PassthroughSubject + { + return publisher + } + + let publisher = PassthroughSubject() + objc_setAssociatedObject(self, &Process.publisherKey, publisher, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return publisher + } + + // We must manually close outputPipe in order for us to read a second Process' standardOutput via async-await 🤷‍♂️ + func stopOutput() + { + guard let outputPipe = self.standardOutput as? Pipe else { return } + + do + { + try outputPipe.fileHandleForReading.close() + } + catch + { + Logger.main.error("Failed to close \(self.executableURL?.lastPathComponent ?? "process", privacy: .public)'s standardOutput. \(error.localizedDescription, privacy: .public)") + } + } +} diff --git a/Shared/Types/AppProcess.swift b/Shared/Types/AppProcess.swift new file mode 100644 index 00000000..13daeffb --- /dev/null +++ b/Shared/Types/AppProcess.swift @@ -0,0 +1,35 @@ +// +// AppProcess.swift +// AltStore +// +// Created by Riley Testut on 9/6/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +enum AppProcess: CustomStringConvertible +{ + case name(String) + case pid(Int) + + var description: String { + switch self + { + case .name(let name): return name + case .pid(let pid): return "Process \(pid)" + } + } + + init(_ value: String) + { + if let pid = Int(value) + { + self = .pid(pid) + } + else + { + self = .name(value) + } + } +}