mirror of
https://github.com/StikDebug/StikJIT.git
synced 2026-10-15 12:02:15 +00:00
feat: migrate DDI mounting to cryptex
This commit is contained in:
parent
709898d500
commit
4e08cecab9
6 changed files with 3748 additions and 129 deletions
|
|
@ -209,7 +209,7 @@ let paths = DDIPaths.default(
|
|||
)
|
||||
```
|
||||
|
||||
Use the same `DDIPaths` for preparation, JIT enablement, and cache reset. Do not validate cached DDI versions or contents yourself; StikJIT reuses readable, nonempty files and lets the image mounter decide whether they work.
|
||||
Use the same `DDIPaths` for preparation, JIT enablement, and cache reset. The cache contains the image, trust cache, build manifest, cryptex info, and root hash. Do not validate cached DDI versions or contents yourself; StikJIT reuses readable, nonempty files and lets the device's cryptex service decide whether they work.
|
||||
|
||||
A successful DDI mount persists until the device reboots, so it normally needs to be mounted only once per boot.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
import Foundation
|
||||
@_implementationOnly import idevice
|
||||
|
||||
private func ddiMountProgressCallback(progress: size_t, total: size_t, context: UnsafeMutableRawPointer?) {
|
||||
guard let context else { return }
|
||||
let progressHandler = Unmanaged<DDIProgressBox>.fromOpaque(context).takeUnretainedValue()
|
||||
let fraction = total > 0 ? Double(progress) / Double(total) : 0
|
||||
progressHandler.handler(fraction)
|
||||
}
|
||||
|
||||
private final class DDIProgressBox {
|
||||
let handler: (Double) -> Void
|
||||
init(_ handler: @escaping (Double) -> Void) { self.handler = handler }
|
||||
}
|
||||
|
||||
final class DDISession {
|
||||
|
||||
private struct Tunnel {
|
||||
|
|
@ -36,6 +24,25 @@ final class DDISession {
|
|||
let tunnel = try makeTunnel()
|
||||
defer { tunnel.free() }
|
||||
|
||||
var installed: UnsafeMutablePointer<InstalledCryptexC>?
|
||||
let cryptexError = cryptexd_installed_ddi(tunnel.adapter, tunnel.handshake, &installed)
|
||||
defer { cryptexd_free_installed_cryptex(installed) }
|
||||
if installed != nil {
|
||||
if let cryptexError { idevice_error_free(cryptexError) }
|
||||
return true
|
||||
}
|
||||
|
||||
if (try? isLegacyDDIMounted(over: tunnel)) == true {
|
||||
if let cryptexError { idevice_error_free(cryptexError) }
|
||||
return true
|
||||
}
|
||||
if let cryptexError {
|
||||
try IdeviceFFI.check("failed to query installed DDI cryptex") { cryptexError }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func isLegacyDDIMounted(over tunnel: Tunnel) throws -> Bool {
|
||||
var client: OpaquePointer?
|
||||
try IdeviceFFI.check("failed to connect to image mounter") {
|
||||
image_mounter_connect_rsd(tunnel.adapter, tunnel.handshake, &client)
|
||||
|
|
@ -57,81 +64,24 @@ final class DDISession {
|
|||
return deviceCount > 0
|
||||
}
|
||||
|
||||
func mountDDI(imagePath: String, trustcachePath: String, manifestPath: String, progress: @escaping (Double) -> Void) throws {
|
||||
let imageData = try mappedFileData(atPath: imagePath, description: "developer disk image")
|
||||
let trustcacheData = try mappedFileData(atPath: trustcachePath, description: "developer disk image trust cache")
|
||||
let manifestData = try mappedFileData(atPath: manifestPath, description: "developer disk image manifest")
|
||||
func mountDDI(from directoryPath: String, progress: @escaping (Double) -> Void) throws {
|
||||
progress(0)
|
||||
var assets: OpaquePointer?
|
||||
try IdeviceFFI.check("failed to load DDI cryptex assets") {
|
||||
directoryPath.withCString { cryptex1_assets_load($0, &assets) }
|
||||
}
|
||||
guard let assets else {
|
||||
throw StikJITError.ddiFilesMissing("DDI cryptex assets could not be loaded from \(directoryPath)")
|
||||
}
|
||||
defer { cryptex1_assets_free(assets) }
|
||||
|
||||
let tunnel = try makeTunnel()
|
||||
defer { tunnel.free() }
|
||||
|
||||
let uniqueChipID = try fetchUniqueChipID(over: tunnel)
|
||||
|
||||
var client: OpaquePointer?
|
||||
try IdeviceFFI.check("failed to connect to image mounter") {
|
||||
image_mounter_connect_rsd(tunnel.adapter, tunnel.handshake, &client)
|
||||
try IdeviceFFI.check("failed to install DDI cryptex") {
|
||||
cryptexd_install_ddi(tunnel.adapter, tunnel.handshake, assets, nil)
|
||||
}
|
||||
guard let client else { throw StikJITError.debugProxyUnavailable }
|
||||
defer { image_mounter_free(client) }
|
||||
|
||||
let progressBox = DDIProgressBox(progress)
|
||||
let context = Unmanaged.passUnretained(progressBox).toOpaque()
|
||||
|
||||
try IdeviceFFI.check("failed to mount personalized developer disk image") {
|
||||
imageData.withUnsafeBytes { imageBuffer -> UnsafeMutablePointer<IdeviceFfiError>? in
|
||||
trustcacheData.withUnsafeBytes { trustcacheBuffer -> UnsafeMutablePointer<IdeviceFfiError>? in
|
||||
manifestData.withUnsafeBytes { manifestBuffer -> UnsafeMutablePointer<IdeviceFfiError>? in
|
||||
image_mounter_mount_personalized_with_callback_rsd(
|
||||
client,
|
||||
tunnel.adapter,
|
||||
tunnel.handshake,
|
||||
imageBuffer.bindMemory(to: UInt8.self).baseAddress,
|
||||
imageData.count,
|
||||
trustcacheBuffer.bindMemory(to: UInt8.self).baseAddress,
|
||||
trustcacheData.count,
|
||||
manifestBuffer.bindMemory(to: UInt8.self).baseAddress,
|
||||
manifestData.count,
|
||||
nil,
|
||||
uniqueChipID,
|
||||
ddiMountProgressCallback,
|
||||
context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedFileData(atPath path: String, description: String) throws -> Data {
|
||||
guard FileManager.default.fileExists(atPath: path) else {
|
||||
throw StikJITError.ddiFilesMissing("\(description) not found at \(path)")
|
||||
}
|
||||
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
|
||||
guard !data.isEmpty else {
|
||||
throw StikJITError.ddiFilesMissing("\(description) is empty at \(path)")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private func fetchUniqueChipID(over tunnel: Tunnel) throws -> UInt64 {
|
||||
var client: OpaquePointer?
|
||||
try IdeviceFFI.check("failed to connect to lockdownd") {
|
||||
lockdownd_connect_rsd(tunnel.adapter, tunnel.handshake, &client)
|
||||
}
|
||||
guard let client else { throw StikJITError.debugProxyUnavailable }
|
||||
defer { lockdownd_client_free(client) }
|
||||
|
||||
var uniqueChipIDPlist: plist_t?
|
||||
try IdeviceFFI.check("failed to query UniqueChipID") {
|
||||
"UniqueChipID".withCString { lockdownd_get_value(client, $0, nil, &uniqueChipIDPlist) }
|
||||
}
|
||||
guard let uniqueChipIDPlist else {
|
||||
throw StikJITError.device(code: -1, subCode: 0, message: "UniqueChipID was not returned by lockdownd")
|
||||
}
|
||||
defer { plist_free(uniqueChipIDPlist) }
|
||||
|
||||
var value: UInt64 = 0
|
||||
plist_get_uint_val(uniqueChipIDPlist, &value)
|
||||
return value
|
||||
progress(1)
|
||||
}
|
||||
|
||||
private func openPairingFile() throws -> OpaquePointer {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,20 @@ public struct DDIPaths: Sendable {
|
|||
|
||||
public var manifestPath: String
|
||||
|
||||
public init(imagePath: String, trustcachePath: String, manifestPath: String) {
|
||||
public var cryptexInfoPath: String
|
||||
|
||||
public var rootHashPath: String
|
||||
|
||||
public init(imagePath: String,
|
||||
trustcachePath: String,
|
||||
manifestPath: String,
|
||||
cryptexInfoPath: String? = nil,
|
||||
rootHashPath: String? = nil) {
|
||||
self.imagePath = imagePath
|
||||
self.trustcachePath = trustcachePath
|
||||
self.manifestPath = manifestPath
|
||||
self.cryptexInfoPath = cryptexInfoPath ?? "\(imagePath).cryptex_info"
|
||||
self.rootHashPath = rootHashPath ?? "\(imagePath).root_hash"
|
||||
}
|
||||
|
||||
public static func `default`(in directory: URL) -> DDIPaths {
|
||||
|
|
@ -35,7 +45,7 @@ public struct DDIPaths: Sendable {
|
|||
}
|
||||
|
||||
var allPaths: [String] {
|
||||
[imagePath, trustcachePath, manifestPath]
|
||||
[imagePath, trustcachePath, manifestPath, cryptexInfoPath, rootHashPath]
|
||||
}
|
||||
|
||||
func removeCachedFiles() throws {
|
||||
|
|
@ -53,13 +63,15 @@ struct DDIDownloadItem {
|
|||
}
|
||||
|
||||
enum DDIDownloadCatalog {
|
||||
private static let baseURL = URL(string: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized")!
|
||||
private static let baseURL = URL(string: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex")!
|
||||
|
||||
static func items(for paths: DDIPaths) -> [DDIDownloadItem] {
|
||||
[
|
||||
DDIDownloadItem(name: "BuildManifest.plist", destinationPath: paths.manifestPath, url: baseURL.appendingPathComponent("BuildManifest.plist")),
|
||||
DDIDownloadItem(name: "Image.dmg", destinationPath: paths.imagePath, url: baseURL.appendingPathComponent("Image.dmg")),
|
||||
DDIDownloadItem(name: "Image.dmg.trustcache", destinationPath: paths.trustcachePath, url: baseURL.appendingPathComponent("Image.dmg.trustcache")),
|
||||
DDIDownloadItem(name: "Image.dmg.cryptex_info", destinationPath: paths.cryptexInfoPath, url: baseURL.appendingPathComponent("Image.dmg.cryptex_info")),
|
||||
DDIDownloadItem(name: "Image.dmg.root_hash", destinationPath: paths.rootHashPath, url: baseURL.appendingPathComponent("Image.dmg.root_hash")),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,9 +144,7 @@ public enum StikJIT {
|
|||
progress(.downloadingDDI(fraction: fraction, status: status))
|
||||
}
|
||||
}
|
||||
try session.mountDDI(imagePath: paths.imagePath,
|
||||
trustcachePath: paths.trustcachePath,
|
||||
manifestPath: paths.manifestPath) { fraction in
|
||||
try session.mountDDI(from: URL(fileURLWithPath: paths.manifestPath).deletingLastPathComponent().path) { fraction in
|
||||
progress(.mountingDDI(fraction: fraction))
|
||||
}
|
||||
progress(.verifyingDDI)
|
||||
|
|
@ -183,9 +181,7 @@ public enum StikJIT {
|
|||
paths: DDIPaths,
|
||||
progress: @escaping (Double) -> Void = { _ in }) throws {
|
||||
let session = DDISession(pairingFilePath: pairingFile.path, configuration: configuration)
|
||||
try session.mountDDI(imagePath: paths.imagePath,
|
||||
trustcachePath: paths.trustcachePath,
|
||||
manifestPath: paths.manifestPath,
|
||||
try session.mountDDI(from: URL(fileURLWithPath: paths.manifestPath).deletingLastPathComponent().path,
|
||||
progress: progress)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3733
idevice/idevice.h
3733
idevice/idevice.h
File diff suppressed because it is too large
Load diff
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue