mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-15 12:02:14 +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 {
|
||||
func isCryptexDDIInstalled() throws -> Bool {
|
||||
func isDeveloperDiskImageMounted() throws -> Bool {
|
||||
try IdeviceBridge.withTunnelHandles(for: self) { adapter, handshake in
|
||||
var installed: UnsafeMutablePointer<InstalledCryptexC>?
|
||||
if let ffiError = cryptexd_installed_ddi(adapter, handshake, &installed) {
|
||||
throw IdeviceBridge.consumeFFIError(ffiError, fallback: "Failed to query installed DDI cryptex")
|
||||
let ffiError = cryptexd_installed_ddi(adapter, handshake, &installed)
|
||||
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) }
|
||||
return installed != nil
|
||||
let legacyMounted = (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) }
|
||||
) { 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 var mountCheckInProgress = false
|
||||
private let mountLock = NSLock()
|
||||
private var mountInProgress = false
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
@ -29,66 +31,86 @@ final class MountingProgress: ObservableObject {
|
|||
mountCheckLock.unlock()
|
||||
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
let mounted = isMounted()
|
||||
let status = checkMountStatus()
|
||||
|
||||
self.mountCheckLock.lock()
|
||||
self.mountCheckInProgress = false
|
||||
self.mountCheckLock.unlock()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.coolisMounted = mounted
|
||||
if status != .unreachable {
|
||||
self.coolisMounted = status == .mounted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
thread.qualityOfService = .background
|
||||
thread.name = "mounting"
|
||||
DispatchQueue.main.async {
|
||||
self.mountingThread = thread
|
||||
}
|
||||
thread.start()
|
||||
}
|
||||
|
||||
private func mount() {
|
||||
let currentlyMounted = isMounted()
|
||||
DispatchQueue.main.async {
|
||||
self.coolisMounted = currentlyMounted
|
||||
switch checkMountStatus() {
|
||||
case .mounted:
|
||||
finishMount {
|
||||
self.coolisMounted = true
|
||||
}
|
||||
return
|
||||
case .unreachable:
|
||||
finishMount()
|
||||
return
|
||||
case .notMounted:
|
||||
break
|
||||
}
|
||||
|
||||
guard isPairing(), !currentlyMounted else {
|
||||
guard isPairing() else {
|
||||
finishMount()
|
||||
return
|
||||
}
|
||||
|
||||
if let mountingThread {
|
||||
mountingThread.cancel()
|
||||
self.mountingThread = nil
|
||||
}
|
||||
|
||||
let thread = Thread { [weak self] in
|
||||
guard let self else { return }
|
||||
let mountError = installCryptexDDI(
|
||||
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
|
||||
if shouldTryAgain {
|
||||
self.pubMount()
|
||||
}
|
||||
let mountError = mountDeveloperDiskImage(from: DeveloperDiskImageService.directoryURL.path)
|
||||
let mounted = mountError == nil || checkMountStatus() == .mounted
|
||||
finishMount {
|
||||
if mounted {
|
||||
self.coolisMounted = true
|
||||
self.checkforMounted()
|
||||
} else if let mountError {
|
||||
LogManager.shared.addErrorLog("Failed to install DDI cryptex: \(mountError)")
|
||||
showAlert(title: "DDI Mount Failed", message: mountError, showOk: true, showTryAgain: true) { shouldTryAgain in
|
||||
if shouldTryAgain {
|
||||
self.pubMount()
|
||||
}
|
||||
} else {
|
||||
self.coolisMounted = true
|
||||
self.checkforMounted()
|
||||
}
|
||||
self.mountingThread = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread.qualityOfService = .background
|
||||
thread.name = "mounting"
|
||||
thread.start()
|
||||
mountingThread = thread
|
||||
private func finishMount(_ completion: @escaping () -> Void = {}) {
|
||||
DispatchQueue.main.async {
|
||||
self.mountingThread = nil
|
||||
self.mountLock.lock()
|
||||
self.mountInProgress = false
|
||||
self.mountLock.unlock()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,17 +25,16 @@ func isMounted() -> Bool {
|
|||
|
||||
func checkMountStatus() -> MountCheckResult {
|
||||
do {
|
||||
return try JITEnableContext.shared.isCryptexDDIInstalled() ? .mounted : .notMounted
|
||||
return try JITEnableContext.shared.isDeveloperDiskImageMounted() ? .mounted : .notMounted
|
||||
} catch {
|
||||
return .unreachable
|
||||
}
|
||||
}
|
||||
|
||||
func installCryptexDDI(from directoryPath: String) -> String? {
|
||||
func mountDeveloperDiskImage(from directoryPath: String) -> String? {
|
||||
do {
|
||||
try JITEnableContext.shared.installCryptexDDI(from: directoryPath)
|
||||
} catch {
|
||||
LogManager.shared.addErrorLog("Failed to install DDI cryptex: \(error.localizedDescription)")
|
||||
return error.localizedDescription
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -7,6 +7,13 @@ import Foundation
|
|||
|
||||
final class 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 session: URLSession
|
||||
|
|
@ -17,9 +24,16 @@ 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)
|
||||
}
|
||||
|
||||
for item in Self.downloadItems {
|
||||
let destinationURL = URL.documentsDirectory.appendingPathComponent(item.relativePath)
|
||||
guard !fileManager.fileExists(atPath: destinationURL.path) else {
|
||||
let destinationURL = Self.directoryURL.appendingPathComponent(item.fileName)
|
||||
guard replaceLegacyFiles || !fileManager.fileExists(atPath: destinationURL.path) else {
|
||||
continue
|
||||
}
|
||||
try await downloadFile(from: item.urlString, to: destinationURL)
|
||||
|
|
@ -56,8 +70,12 @@ final class DeveloperDiskImageService {
|
|||
var completedStages = 0.0
|
||||
|
||||
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 {
|
||||
let fileURL = URL.documentsDirectory.appendingPathComponent(item.relativePath)
|
||||
let fileURL = Self.directoryURL.appendingPathComponent(item.fileName)
|
||||
if fileManager.fileExists(atPath: fileURL.path) {
|
||||
try fileManager.removeItem(at: fileURL)
|
||||
}
|
||||
|
|
@ -68,7 +86,7 @@ final class DeveloperDiskImageService {
|
|||
|
||||
for item in Self.downloadItems {
|
||||
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)
|
||||
completedStages += 1.0
|
||||
progressHandler?(completedStages / totalStages, "\(item.name) ready")
|
||||
|
|
@ -80,27 +98,27 @@ final class DeveloperDiskImageService {
|
|||
private static let downloadItems: [DDIDownloadItem] = [
|
||||
.init(
|
||||
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"
|
||||
),
|
||||
.init(
|
||||
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"
|
||||
),
|
||||
.init(
|
||||
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"
|
||||
),
|
||||
.init(
|
||||
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"
|
||||
),
|
||||
.init(
|
||||
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"
|
||||
)
|
||||
]
|
||||
|
|
@ -108,7 +126,7 @@ final class DeveloperDiskImageService {
|
|||
|
||||
private struct DDIDownloadItem {
|
||||
let name: String
|
||||
let relativePath: String
|
||||
let fileName: String
|
||||
let urlString: String
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,12 +70,7 @@ final class TunnelManager: ObservableObject {
|
|||
}
|
||||
|
||||
private func mountDeveloperDiskImageIfNeeded() {
|
||||
let manifestPath = URL.documentsDirectory.appendingPathComponent("DDI_Cryptex/BuildManifest.plist").path
|
||||
guard FileManager.default.fileExists(atPath: manifestPath),
|
||||
!MountingProgress.shared.coolisMounted,
|
||||
MountingProgress.shared.mountingThread == nil else {
|
||||
return
|
||||
}
|
||||
guard DeveloperDiskImageService.filesAreReady else { return }
|
||||
MountingProgress.shared.pubMount()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue