[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
This commit is contained in:
Riley Testut
2023-09-07 18:00:53 -05:00
committed by Magesh K
parent f83303a6b7
commit a7b28d5027
19 changed files with 1539 additions and 1 deletions

View File

@@ -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<Void, Never>) 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<some Publisher<String, Error>> {
return self.outputPublisher
.buffer(size: 100, prefetch: .byRequest, whenFull: .dropOldest)
.values
}
private var outputPublisher: PassthroughSubject<String, Error> {
if let publisher = objc_getAssociatedObject(self, &Process.publisherKey) as? PassthroughSubject<String, Error>
{
return publisher
}
let publisher = PassthroughSubject<String, Error>()
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)")
}
}
}