mirror of
https://github.com/StikDebug/StikJIT.git
synced 2026-10-15 20:10:31 +00:00
187 lines
7.1 KiB
Swift
187 lines
7.1 KiB
Swift
import Foundation
|
|
|
|
public enum StikJIT {
|
|
|
|
public enum Script: Sendable {
|
|
|
|
case universal
|
|
|
|
case legacy
|
|
|
|
case custom(URL)
|
|
|
|
var name: String {
|
|
switch self {
|
|
case .universal: return "universal"
|
|
case .legacy: return "legacy"
|
|
case .custom(let url): return url.lastPathComponent
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct DeviceSecurityState: Sendable {
|
|
|
|
public let isTXMPresent: Bool?
|
|
|
|
public init(isTXMPresent: Bool?) {
|
|
self.isTXMPresent = isTXMPresent
|
|
}
|
|
}
|
|
|
|
public enum PreparationStage: Sendable {
|
|
|
|
case checkingReachability
|
|
|
|
case checkingDDI
|
|
|
|
case downloadingDDI(fraction: Double, status: String)
|
|
|
|
case mountingDDI(fraction: Double)
|
|
|
|
case verifyingDDI
|
|
|
|
case ready
|
|
}
|
|
|
|
public enum JITReadiness: Sendable {
|
|
|
|
case unreachable(reason: String)
|
|
|
|
case preparationFailed(reason: String)
|
|
|
|
case ready(DeviceSecurityState)
|
|
|
|
public var isReady: Bool {
|
|
if case .ready = self { return true }
|
|
return false
|
|
}
|
|
}
|
|
|
|
public struct Configuration: Sendable {
|
|
|
|
public var deviceAddress: String
|
|
|
|
public var rsdPort: UInt16
|
|
|
|
public var connectionTimeout: TimeInterval
|
|
|
|
public init(deviceAddress: String = "10.7.0.1",
|
|
rsdPort: UInt16 = 49152,
|
|
connectionTimeout: TimeInterval = 5) {
|
|
self.deviceAddress = deviceAddress
|
|
self.rsdPort = rsdPort
|
|
self.connectionTimeout = connectionTimeout
|
|
}
|
|
|
|
public static var `default`: Configuration { Configuration() }
|
|
}
|
|
|
|
@available(*, deprecated, message: "Use enableJIT with ddiPaths to prepare the device before enabling JIT.")
|
|
public static func enableJIT(targetPID: Int32,
|
|
pairingFile: URL,
|
|
configuration: Configuration = .default,
|
|
script: Script = .universal,
|
|
forceScript: Bool = false,
|
|
progress: @escaping (String) -> Void = { _ in }) throws {
|
|
let txmPresence = ProcessInfo.processInfo.txmPresence
|
|
let session = JITSession(pairingFilePath: pairingFile.path, configuration: configuration)
|
|
try session.enableJIT(targetPID: targetPID,
|
|
script: script,
|
|
forceScript: forceScript,
|
|
txmPresence: txmPresence,
|
|
progress: progress)
|
|
}
|
|
|
|
public static func enableJIT(targetPID: Int32,
|
|
pairingFile: URL,
|
|
ddiPaths: DDIPaths,
|
|
configuration: Configuration = .default,
|
|
script: Script = .universal,
|
|
forceScript: Bool = false,
|
|
preparationProgress: @escaping (PreparationStage) -> Void = { _ in },
|
|
progress: @escaping (String) -> Void = { _ in }) throws {
|
|
let readiness = prepareDevice(pairingFile: pairingFile,
|
|
paths: ddiPaths,
|
|
configuration: configuration,
|
|
progress: preparationProgress)
|
|
let securityState: DeviceSecurityState
|
|
switch readiness {
|
|
case .ready(let readySecurityState):
|
|
securityState = readySecurityState
|
|
case .unreachable(let reason), .preparationFailed(let reason):
|
|
throw StikJITError.deviceNotReady(reason)
|
|
}
|
|
|
|
let session = JITSession(pairingFilePath: pairingFile.path, configuration: configuration)
|
|
try session.enableJIT(targetPID: targetPID,
|
|
script: script,
|
|
forceScript: forceScript,
|
|
txmPresence: TXMPresence(isPresent: securityState.isTXMPresent),
|
|
progress: progress)
|
|
}
|
|
|
|
public static var isTXMPresent: Bool? {
|
|
ProcessInfo.processInfo.txmPresence.isPresent
|
|
}
|
|
|
|
public static func prepareDevice(pairingFile: URL,
|
|
paths: DDIPaths,
|
|
configuration: Configuration = .default,
|
|
progress: @escaping (PreparationStage) -> Void = { _ in }) -> JITReadiness {
|
|
progress(.checkingReachability)
|
|
if let reason = EndpointProbe.failureReason(address: configuration.deviceAddress,
|
|
port: configuration.rsdPort,
|
|
timeout: configuration.connectionTimeout) {
|
|
return .unreachable(reason: reason)
|
|
}
|
|
|
|
do {
|
|
progress(.checkingDDI)
|
|
let session = DDISession(pairingFilePath: pairingFile.path, configuration: configuration)
|
|
if try !session.isMounted() {
|
|
if !paths.allFilesUsable {
|
|
try SynchronousDDIDownloader.download(to: paths) { fraction, status in
|
|
progress(.downloadingDDI(fraction: fraction, status: status))
|
|
}
|
|
}
|
|
try session.mountDDI(from: URL(fileURLWithPath: paths.manifestPath).deletingLastPathComponent().path) { fraction in
|
|
progress(.mountingDDI(fraction: fraction))
|
|
}
|
|
progress(.verifyingDDI)
|
|
guard try session.isMounted() else {
|
|
throw StikJITError.ddiNotMounted
|
|
}
|
|
}
|
|
|
|
let securityState = DeviceSecurityState(isTXMPresent: isTXMPresent)
|
|
progress(.ready)
|
|
return .ready(securityState)
|
|
} catch {
|
|
return .preparationFailed(reason: error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
public static func resetCachedDDI(at paths: DDIPaths) throws {
|
|
try paths.removeCachedFiles()
|
|
}
|
|
|
|
public static func isDDIMounted(pairingFile: URL,
|
|
configuration: Configuration = .default) throws -> Bool {
|
|
try DDISession(pairingFilePath: pairingFile.path, configuration: configuration).isMounted()
|
|
}
|
|
|
|
@available(iOS 17.4, *)
|
|
public static func downloadDDIIfNeeded(to paths: DDIPaths,
|
|
progress: @escaping (Double, String) -> Void = { _, _ in }) async throws {
|
|
try await DeveloperDiskImageService.shared.downloadIfNeeded(to: paths, progress: progress)
|
|
}
|
|
|
|
public static func mountDDI(pairingFile: URL,
|
|
configuration: Configuration = .default,
|
|
paths: DDIPaths,
|
|
progress: @escaping (Double) -> Void = { _ in }) throws {
|
|
let session = DDISession(pairingFilePath: pairingFile.path, configuration: configuration)
|
|
try session.mountDDI(from: URL(fileURLWithPath: paths.manifestPath).deletingLastPathComponent().path,
|
|
progress: progress)
|
|
}
|
|
}
|