mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-15 20:10:30 +00:00
fix: restore personalized DDI mounting
This commit is contained in:
parent
534b6daa3f
commit
cbaf467b66
4 changed files with 164 additions and 10 deletions
|
|
@ -44,6 +44,35 @@ private enum IdeviceBridge {
|
|||
return makeError(domain: domain, code: code, message: message)
|
||||
}
|
||||
|
||||
static func mappedFileData(atPath path: String, description: String) throws -> Data {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: url, options: .mappedIfSafe)
|
||||
guard !data.isEmpty else {
|
||||
throw makeError(message: "\(description) is empty")
|
||||
}
|
||||
return data
|
||||
} catch let error as NSError {
|
||||
throw makeError(code: error.code, message: "Failed to read \(description): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
static func uint64Value(from plist: plist_t?, fieldName: String) throws -> UInt64 {
|
||||
guard let plist else {
|
||||
throw makeError(message: "\(fieldName) was not returned by lockdownd")
|
||||
}
|
||||
|
||||
var value: UInt64 = 0
|
||||
plist_get_uint_val(plist, &value)
|
||||
|
||||
guard value != 0 else {
|
||||
throw makeError(message: "Failed to decode \(fieldName)")
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
static func withTunnelHandles<T>(
|
||||
for context: JITEnableContext,
|
||||
_ body: (OpaquePointer, OpaquePointer) throws -> T
|
||||
|
|
@ -295,6 +324,59 @@ extension JITEnableContext {
|
|||
}
|
||||
}
|
||||
|
||||
func mountPersonalDDI(withImagePath imagePath: String, trustcachePath: String, manifestPath: String) throws {
|
||||
let imageData = try IdeviceBridge.mappedFileData(atPath: imagePath, description: "developer disk image")
|
||||
let trustcacheData = try IdeviceBridge.mappedFileData(atPath: trustcachePath, description: "developer disk image trust cache")
|
||||
let manifestData = try IdeviceBridge.mappedFileData(atPath: manifestPath, description: "developer disk image manifest")
|
||||
|
||||
try IdeviceBridge.withTunnelHandles(for: self) { adapter, handshake in
|
||||
let uniqueChipID = try IdeviceBridge.withConnectedClient(
|
||||
fallback: "Failed to connect to lockdownd",
|
||||
missingClientMessage: "Lockdownd client was not created",
|
||||
connect: { lockdownd_connect_rsd(adapter, handshake, $0) },
|
||||
cleanup: { lockdownd_client_free($0) }
|
||||
) { lockdownClient in
|
||||
var uniqueChipIDPlist: plist_t?
|
||||
if let ffiError = lockdownd_get_value(lockdownClient, "UniqueChipID", nil, &uniqueChipIDPlist) {
|
||||
throw IdeviceBridge.consumeFFIError(ffiError, fallback: "Failed to query UniqueChipID")
|
||||
}
|
||||
defer { plist_free(uniqueChipIDPlist) }
|
||||
return try IdeviceBridge.uint64Value(from: uniqueChipIDPlist, fieldName: "UniqueChipID")
|
||||
}
|
||||
|
||||
try IdeviceBridge.withConnectedClient(
|
||||
fallback: "Failed to connect to image mounter",
|
||||
missingClientMessage: "Image mounter client was not created",
|
||||
connect: { image_mounter_connect_rsd(adapter, handshake, $0) },
|
||||
cleanup: { image_mounter_free($0) }
|
||||
) { imageMounterClient in
|
||||
let ffiError = imageData.withUnsafeBytes { imageBuffer in
|
||||
trustcacheData.withUnsafeBytes { trustcacheBuffer in
|
||||
manifestData.withUnsafeBytes { manifestBuffer in
|
||||
image_mounter_mount_personalized_rsd(
|
||||
imageMounterClient,
|
||||
adapter,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let ffiError {
|
||||
throw IdeviceBridge.consumeFFIError(ffiError, fallback: "Failed to mount personalized DDI")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAllProfiles() throws -> [Data] {
|
||||
try IdeviceBridge.withTunnelHandles(for: self) { adapter, handshake in
|
||||
try IdeviceBridge.withConnectedClient(
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ final class MountingProgress: ObservableObject {
|
|||
self.coolisMounted = true
|
||||
self.checkforMounted()
|
||||
} else if let mountError {
|
||||
LogManager.shared.addErrorLog("Failed to install DDI cryptex: \(mountError)")
|
||||
LogManager.shared.addErrorLog("Failed to mount DDI: \(mountError)")
|
||||
showAlert(title: "DDI Mount Failed", message: mountError, showOk: true, showTryAgain: true) { shouldTryAgain in
|
||||
if shouldTryAgain {
|
||||
self.pubMount()
|
||||
|
|
|
|||
|
|
@ -33,7 +33,15 @@ func checkMountStatus() -> MountCheckResult {
|
|||
|
||||
func mountDeveloperDiskImage(from directoryPath: String) -> String? {
|
||||
do {
|
||||
try JITEnableContext.shared.installCryptexDDI(from: directoryPath)
|
||||
if DeveloperDiskImageService.usesCryptexDDI {
|
||||
try JITEnableContext.shared.installCryptexDDI(from: directoryPath)
|
||||
} else {
|
||||
try JITEnableContext.shared.mountPersonalDDI(
|
||||
withImagePath: URL(fileURLWithPath: directoryPath).appendingPathComponent("Image.dmg").path,
|
||||
trustcachePath: URL(fileURLWithPath: directoryPath).appendingPathComponent("Image.dmg.trustcache").path,
|
||||
manifestPath: URL(fileURLWithPath: directoryPath).appendingPathComponent("BuildManifest.plist").path
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
return error.localizedDescription
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ final class DeveloperDiskImageService {
|
|||
static var filesAreReady: Bool {
|
||||
downloadItems.allSatisfy {
|
||||
FileManager.default.fileExists(atPath: directoryURL.appendingPathComponent($0.fileName).path)
|
||||
}
|
||||
} && (try? String(contentsOf: mountMethodURL, encoding: .utf8)) == mountMethod.rawValue
|
||||
}
|
||||
|
||||
static var usesCryptexDDI: Bool {
|
||||
mountMethod == .cryptex
|
||||
}
|
||||
|
||||
private let fileManager: FileManager
|
||||
|
|
@ -24,20 +28,21 @@ final class DeveloperDiskImageService {
|
|||
}
|
||||
|
||||
func downloadMissingFiles() async throws {
|
||||
let hasCryptexMarker = ["Image.dmg.cryptex_info", "Image.dmg.root_hash"].contains {
|
||||
fileManager.fileExists(atPath: Self.directoryURL.appendingPathComponent($0).path)
|
||||
}
|
||||
let replaceLegacyFiles = !hasCryptexMarker && Self.downloadItems.contains {
|
||||
fileManager.fileExists(atPath: Self.directoryURL.appendingPathComponent($0.fileName).path)
|
||||
let installedMethod = try? String(contentsOf: Self.mountMethodURL, encoding: .utf8)
|
||||
let replaceFiles = installedMethod != Self.mountMethod.rawValue
|
||||
|
||||
if !Self.usesCryptexDDI {
|
||||
try removeCryptexOnlyFiles()
|
||||
}
|
||||
|
||||
for item in Self.downloadItems {
|
||||
let destinationURL = Self.directoryURL.appendingPathComponent(item.fileName)
|
||||
guard replaceLegacyFiles || !fileManager.fileExists(atPath: destinationURL.path) else {
|
||||
guard replaceFiles || !fileManager.fileExists(atPath: destinationURL.path) else {
|
||||
continue
|
||||
}
|
||||
try await downloadFile(from: item.urlString, to: destinationURL)
|
||||
}
|
||||
try Self.mountMethod.rawValue.write(to: Self.mountMethodURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
func downloadFile(from urlString: String, to destinationURL: URL) async throws {
|
||||
|
|
@ -70,6 +75,12 @@ final class DeveloperDiskImageService {
|
|||
var completedStages = 0.0
|
||||
|
||||
progressHandler?(0.0, "Removing existing DDI files...")
|
||||
if fileManager.fileExists(atPath: Self.mountMethodURL.path) {
|
||||
try fileManager.removeItem(at: Self.mountMethodURL)
|
||||
}
|
||||
if !Self.usesCryptexDDI {
|
||||
try removeCryptexOnlyFiles()
|
||||
}
|
||||
let completionMarker = Self.directoryURL.appendingPathComponent("Image.dmg.root_hash")
|
||||
if fileManager.fileExists(atPath: completionMarker.path) {
|
||||
try fileManager.removeItem(at: completionMarker)
|
||||
|
|
@ -92,10 +103,40 @@ final class DeveloperDiskImageService {
|
|||
progressHandler?(completedStages / totalStages, "\(item.name) ready")
|
||||
}
|
||||
|
||||
try Self.mountMethod.rawValue.write(to: Self.mountMethodURL, atomically: true, encoding: .utf8)
|
||||
|
||||
progressHandler?(1.0, "DDI download complete.")
|
||||
}
|
||||
|
||||
private static let downloadItems: [DDIDownloadItem] = [
|
||||
private func removeCryptexOnlyFiles() throws {
|
||||
for fileName in Self.cryptexOnlyFileNames {
|
||||
let fileURL = Self.directoryURL.appendingPathComponent(fileName)
|
||||
if fileManager.fileExists(atPath: fileURL.path) {
|
||||
try fileManager.removeItem(at: fileURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static var mountMethod: DDIMountMethod {
|
||||
let version = ProcessInfo.processInfo.operatingSystemVersion
|
||||
return version.majorVersion > 26 || (version.majorVersion == 26 && version.minorVersion >= 4)
|
||||
? .cryptex
|
||||
: .personalized
|
||||
}
|
||||
|
||||
private static let mountMethodURL = directoryURL.appendingPathComponent("MountMethod")
|
||||
private static let cryptexOnlyFileNames = ["Image.dmg.cryptex_info", "Image.dmg.root_hash"]
|
||||
|
||||
private static var downloadItems: [DDIDownloadItem] {
|
||||
switch mountMethod {
|
||||
case .cryptex:
|
||||
return cryptexDownloadItems
|
||||
case .personalized:
|
||||
return personalizedDownloadItems
|
||||
}
|
||||
}
|
||||
|
||||
private static let cryptexDownloadItems: [DDIDownloadItem] = [
|
||||
.init(
|
||||
name: "Build Manifest",
|
||||
fileName: "BuildManifest.plist",
|
||||
|
|
@ -122,6 +163,29 @@ final class DeveloperDiskImageService {
|
|||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg.root_hash"
|
||||
)
|
||||
]
|
||||
|
||||
private static let personalizedDownloadItems: [DDIDownloadItem] = [
|
||||
.init(
|
||||
name: "Build Manifest",
|
||||
fileName: "BuildManifest.plist",
|
||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/BuildManifest.plist"
|
||||
),
|
||||
.init(
|
||||
name: "Image",
|
||||
fileName: "Image.dmg",
|
||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg"
|
||||
),
|
||||
.init(
|
||||
name: "TrustCache",
|
||||
fileName: "Image.dmg.trustcache",
|
||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg.trustcache"
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
private enum DDIMountMethod: String {
|
||||
case cryptex
|
||||
case personalized
|
||||
}
|
||||
|
||||
private struct DDIDownloadItem {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue