mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-15 20:10:30 +00:00
feat: smooth cryptex DDI migration
This commit is contained in:
parent
f76672838b
commit
e85e0b3d47
5 changed files with 123 additions and 57 deletions
|
|
@ -233,15 +233,47 @@ private enum IdeviceBridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension JITEnableContext {
|
extension JITEnableContext {
|
||||||
func isCryptexDDIInstalled() throws -> Bool {
|
func isDeveloperDiskImageMounted() throws -> Bool {
|
||||||
try IdeviceBridge.withTunnelHandles(for: self) { adapter, handshake in
|
try IdeviceBridge.withTunnelHandles(for: self) { adapter, handshake in
|
||||||
var installed: UnsafeMutablePointer<InstalledCryptexC>?
|
var installed: UnsafeMutablePointer<InstalledCryptexC>?
|
||||||
if let ffiError = cryptexd_installed_ddi(adapter, handshake, &installed) {
|
let ffiError = cryptexd_installed_ddi(adapter, handshake, &installed)
|
||||||
throw IdeviceBridge.consumeFFIError(ffiError, fallback: "Failed to query installed DDI cryptex")
|
let queryError = ffiError.map {
|
||||||
|
IdeviceBridge.consumeFFIError($0, fallback: "Failed to query installed DDI cryptex")
|
||||||
|
}
|
||||||
|
defer { cryptexd_free_installed_cryptex(installed) }
|
||||||
|
if installed != nil {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
defer { cryptexd_free_installed_cryptex(installed) }
|
let legacyMounted = (try? IdeviceBridge.withConnectedClient(
|
||||||
return installed != nil
|
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) }
|
||||||
|
) { client in
|
||||||
|
var devices: UnsafeMutablePointer<plist_t?>?
|
||||||
|
var deviceCount = 0
|
||||||
|
if let error = image_mounter_copy_devices(client, &devices, &deviceCount) {
|
||||||
|
throw IdeviceBridge.consumeFFIError(error, fallback: "Failed to fetch mounted devices")
|
||||||
|
}
|
||||||
|
if let devices {
|
||||||
|
for index in 0..<deviceCount {
|
||||||
|
plist_free(devices[index])
|
||||||
|
}
|
||||||
|
idevice_data_free(
|
||||||
|
UnsafeMutableRawPointer(devices).assumingMemoryBound(to: UInt8.self),
|
||||||
|
UInt(deviceCount * MemoryLayout<plist_t?>.stride)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return deviceCount > 0
|
||||||
|
}) ?? false
|
||||||
|
if legacyMounted {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if let queryError {
|
||||||
|
throw queryError
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ final class MountingProgress: ObservableObject {
|
||||||
|
|
||||||
private let mountCheckLock = NSLock()
|
private let mountCheckLock = NSLock()
|
||||||
private var mountCheckInProgress = false
|
private var mountCheckInProgress = false
|
||||||
|
private let mountLock = NSLock()
|
||||||
|
private var mountInProgress = false
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
|
|
@ -29,66 +31,86 @@ final class MountingProgress: ObservableObject {
|
||||||
mountCheckLock.unlock()
|
mountCheckLock.unlock()
|
||||||
|
|
||||||
DispatchQueue.global(qos: .utility).async {
|
DispatchQueue.global(qos: .utility).async {
|
||||||
let mounted = isMounted()
|
let status = checkMountStatus()
|
||||||
|
|
||||||
self.mountCheckLock.lock()
|
self.mountCheckLock.lock()
|
||||||
self.mountCheckInProgress = false
|
self.mountCheckInProgress = false
|
||||||
self.mountCheckLock.unlock()
|
self.mountCheckLock.unlock()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.coolisMounted = mounted
|
if status != .unreachable {
|
||||||
|
self.coolisMounted = status == .mounted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pubMount() {
|
func pubMount() {
|
||||||
guard TunnelManager.shared.isConnected else { return }
|
guard TunnelManager.shared.isConnected, DeveloperDiskImageService.filesAreReady else { return }
|
||||||
|
|
||||||
DispatchQueue.global(qos: .utility).async { [weak self] in
|
mountLock.lock()
|
||||||
|
guard !mountInProgress else {
|
||||||
|
mountLock.unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mountInProgress = true
|
||||||
|
mountLock.unlock()
|
||||||
|
|
||||||
|
let thread = Thread { [weak self] in
|
||||||
self?.mount()
|
self?.mount()
|
||||||
}
|
}
|
||||||
|
thread.qualityOfService = .background
|
||||||
|
thread.name = "mounting"
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.mountingThread = thread
|
||||||
|
}
|
||||||
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func mount() {
|
private func mount() {
|
||||||
let currentlyMounted = isMounted()
|
switch checkMountStatus() {
|
||||||
DispatchQueue.main.async {
|
case .mounted:
|
||||||
self.coolisMounted = currentlyMounted
|
finishMount {
|
||||||
|
self.coolisMounted = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case .unreachable:
|
||||||
|
finishMount()
|
||||||
|
return
|
||||||
|
case .notMounted:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
guard isPairing(), !currentlyMounted else {
|
guard isPairing() else {
|
||||||
|
finishMount()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let mountingThread {
|
let mountError = mountDeveloperDiskImage(from: DeveloperDiskImageService.directoryURL.path)
|
||||||
mountingThread.cancel()
|
let mounted = mountError == nil || checkMountStatus() == .mounted
|
||||||
self.mountingThread = nil
|
finishMount {
|
||||||
}
|
if mounted {
|
||||||
|
self.coolisMounted = true
|
||||||
let thread = Thread { [weak self] in
|
self.checkforMounted()
|
||||||
guard let self else { return }
|
} else if let mountError {
|
||||||
let mountError = installCryptexDDI(
|
LogManager.shared.addErrorLog("Failed to install DDI cryptex: \(mountError)")
|
||||||
from: URL.documentsDirectory.appendingPathComponent("DDI_Cryptex").path
|
|
||||||
)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
if let mountError {
|
|
||||||
showAlert(title: "DDI Mount Failed", message: mountError, showOk: true, showTryAgain: true) { shouldTryAgain in
|
showAlert(title: "DDI Mount Failed", message: mountError, showOk: true, showTryAgain: true) { shouldTryAgain in
|
||||||
if shouldTryAgain {
|
if shouldTryAgain {
|
||||||
self.pubMount()
|
self.pubMount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.coolisMounted = true
|
|
||||||
self.checkforMounted()
|
|
||||||
}
|
}
|
||||||
self.mountingThread = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.qualityOfService = .background
|
private func finishMount(_ completion: @escaping () -> Void = {}) {
|
||||||
thread.name = "mounting"
|
DispatchQueue.main.async {
|
||||||
thread.start()
|
self.mountingThread = nil
|
||||||
mountingThread = thread
|
self.mountLock.lock()
|
||||||
|
self.mountInProgress = false
|
||||||
|
self.mountLock.unlock()
|
||||||
|
completion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,16 @@ func isMounted() -> Bool {
|
||||||
|
|
||||||
func checkMountStatus() -> MountCheckResult {
|
func checkMountStatus() -> MountCheckResult {
|
||||||
do {
|
do {
|
||||||
return try JITEnableContext.shared.isCryptexDDIInstalled() ? .mounted : .notMounted
|
return try JITEnableContext.shared.isDeveloperDiskImageMounted() ? .mounted : .notMounted
|
||||||
} catch {
|
} catch {
|
||||||
return .unreachable
|
return .unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func installCryptexDDI(from directoryPath: String) -> String? {
|
func mountDeveloperDiskImage(from directoryPath: String) -> String? {
|
||||||
do {
|
do {
|
||||||
try JITEnableContext.shared.installCryptexDDI(from: directoryPath)
|
try JITEnableContext.shared.installCryptexDDI(from: directoryPath)
|
||||||
} catch {
|
} catch {
|
||||||
LogManager.shared.addErrorLog("Failed to install DDI cryptex: \(error.localizedDescription)")
|
|
||||||
return error.localizedDescription
|
return error.localizedDescription
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ import Foundation
|
||||||
|
|
||||||
final class DeveloperDiskImageService {
|
final class DeveloperDiskImageService {
|
||||||
static let shared = DeveloperDiskImageService()
|
static let shared = DeveloperDiskImageService()
|
||||||
|
static let directoryURL = URL.documentsDirectory.appendingPathComponent("DDI", isDirectory: true)
|
||||||
|
|
||||||
|
static var filesAreReady: Bool {
|
||||||
|
downloadItems.allSatisfy {
|
||||||
|
FileManager.default.fileExists(atPath: directoryURL.appendingPathComponent($0.fileName).path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let fileManager: FileManager
|
private let fileManager: FileManager
|
||||||
private let session: URLSession
|
private let session: URLSession
|
||||||
|
|
@ -17,9 +24,16 @@ final class DeveloperDiskImageService {
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadMissingFiles() async throws {
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
for item in Self.downloadItems {
|
for item in Self.downloadItems {
|
||||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(item.relativePath)
|
let destinationURL = Self.directoryURL.appendingPathComponent(item.fileName)
|
||||||
guard !fileManager.fileExists(atPath: destinationURL.path) else {
|
guard replaceLegacyFiles || !fileManager.fileExists(atPath: destinationURL.path) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
try await downloadFile(from: item.urlString, to: destinationURL)
|
try await downloadFile(from: item.urlString, to: destinationURL)
|
||||||
|
|
@ -56,8 +70,12 @@ final class DeveloperDiskImageService {
|
||||||
var completedStages = 0.0
|
var completedStages = 0.0
|
||||||
|
|
||||||
progressHandler?(0.0, "Removing existing DDI files...")
|
progressHandler?(0.0, "Removing existing DDI files...")
|
||||||
|
let completionMarker = Self.directoryURL.appendingPathComponent("Image.dmg.root_hash")
|
||||||
|
if fileManager.fileExists(atPath: completionMarker.path) {
|
||||||
|
try fileManager.removeItem(at: completionMarker)
|
||||||
|
}
|
||||||
for item in Self.downloadItems {
|
for item in Self.downloadItems {
|
||||||
let fileURL = URL.documentsDirectory.appendingPathComponent(item.relativePath)
|
let fileURL = Self.directoryURL.appendingPathComponent(item.fileName)
|
||||||
if fileManager.fileExists(atPath: fileURL.path) {
|
if fileManager.fileExists(atPath: fileURL.path) {
|
||||||
try fileManager.removeItem(at: fileURL)
|
try fileManager.removeItem(at: fileURL)
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +86,7 @@ final class DeveloperDiskImageService {
|
||||||
|
|
||||||
for item in Self.downloadItems {
|
for item in Self.downloadItems {
|
||||||
progressHandler?(completedStages / totalStages, "Downloading \(item.name)...")
|
progressHandler?(completedStages / totalStages, "Downloading \(item.name)...")
|
||||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(item.relativePath)
|
let destinationURL = Self.directoryURL.appendingPathComponent(item.fileName)
|
||||||
try await downloadFile(from: item.urlString, to: destinationURL)
|
try await downloadFile(from: item.urlString, to: destinationURL)
|
||||||
completedStages += 1.0
|
completedStages += 1.0
|
||||||
progressHandler?(completedStages / totalStages, "\(item.name) ready")
|
progressHandler?(completedStages / totalStages, "\(item.name) ready")
|
||||||
|
|
@ -80,27 +98,27 @@ final class DeveloperDiskImageService {
|
||||||
private static let downloadItems: [DDIDownloadItem] = [
|
private static let downloadItems: [DDIDownloadItem] = [
|
||||||
.init(
|
.init(
|
||||||
name: "Build Manifest",
|
name: "Build Manifest",
|
||||||
relativePath: "DDI_Cryptex/BuildManifest.plist",
|
fileName: "BuildManifest.plist",
|
||||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/BuildManifest.plist"
|
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/BuildManifest.plist"
|
||||||
),
|
),
|
||||||
.init(
|
.init(
|
||||||
name: "Image",
|
name: "Image",
|
||||||
relativePath: "DDI_Cryptex/Image.dmg",
|
fileName: "Image.dmg",
|
||||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg"
|
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg"
|
||||||
),
|
),
|
||||||
.init(
|
.init(
|
||||||
name: "TrustCache",
|
name: "TrustCache",
|
||||||
relativePath: "DDI_Cryptex/Image.dmg.trustcache",
|
fileName: "Image.dmg.trustcache",
|
||||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg.trustcache"
|
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg.trustcache"
|
||||||
),
|
),
|
||||||
.init(
|
.init(
|
||||||
name: "Cryptex Info",
|
name: "Cryptex Info",
|
||||||
relativePath: "DDI_Cryptex/Image.dmg.cryptex_info",
|
fileName: "Image.dmg.cryptex_info",
|
||||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg.cryptex_info"
|
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg.cryptex_info"
|
||||||
),
|
),
|
||||||
.init(
|
.init(
|
||||||
name: "Root Hash",
|
name: "Root Hash",
|
||||||
relativePath: "DDI_Cryptex/Image.dmg.root_hash",
|
fileName: "Image.dmg.root_hash",
|
||||||
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg.root_hash"
|
urlString: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Cryptex/Image.dmg.root_hash"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
@ -108,7 +126,7 @@ final class DeveloperDiskImageService {
|
||||||
|
|
||||||
private struct DDIDownloadItem {
|
private struct DDIDownloadItem {
|
||||||
let name: String
|
let name: String
|
||||||
let relativePath: String
|
let fileName: String
|
||||||
let urlString: String
|
let urlString: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,12 +70,7 @@ final class TunnelManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func mountDeveloperDiskImageIfNeeded() {
|
private func mountDeveloperDiskImageIfNeeded() {
|
||||||
let manifestPath = URL.documentsDirectory.appendingPathComponent("DDI_Cryptex/BuildManifest.plist").path
|
guard DeveloperDiskImageService.filesAreReady else { return }
|
||||||
guard FileManager.default.fileExists(atPath: manifestPath),
|
|
||||||
!MountingProgress.shared.coolisMounted,
|
|
||||||
MountingProgress.shared.mountingThread == nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
MountingProgress.shared.pubMount()
|
MountingProgress.shared.pubMount()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue