mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-14 17:23:25 +01:00
[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:
@@ -8,13 +8,16 @@
|
||||
|
||||
#import "NSError+ALTServerError.h"
|
||||
|
||||
#if TARGET_OS_OSX
|
||||
#import "AltServer-Swift.h"
|
||||
#else
|
||||
#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 <AltStoreCore/AltStoreCore-Swift.h>
|
||||
@import AltSign;
|
||||
#endif
|
||||
|
||||
NSErrorDomain const AltServerErrorDomain = @"AltServer.ServerError";
|
||||
NSErrorDomain const AltServerInstallationErrorDomain = @"Apple.InstallationError";
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
#if !ALTJIT
|
||||
import AltSign
|
||||
#endif
|
||||
|
||||
public let ALTLocalizedTitleErrorKey = "ALTLocalizedTitle"
|
||||
public let ALTLocalizedDescriptionKey = "ALTLocalizedDescription"
|
||||
|
||||
52
Shared/Errors/JITError.swift
Normal file
52
Shared/Errors/JITError.swift
Normal file
@@ -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: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Shared/Errors/ProcessError.swift
Normal file
88
Shared/Errors/ProcessError.swift
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
151
Shared/Extensions/Process+Conveniences.swift
Normal file
151
Shared/Extensions/Process+Conveniences.swift
Normal 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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Shared/Types/AppProcess.swift
Normal file
35
Shared/Types/AppProcess.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user