diff --git a/Makefile b/Makefile index 4cf7af0..dfc8ca9 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,7 @@ SparseBox_FILES = \ include/SwiftBridgeCore.swift \ Sources/SparseRestore/MBDB.swift \ Sources/SparseRestore/Backup.swift \ + Sources/SparseRestore/Restore.swift \ Sources/LogView.swift \ Sources/MyApp.swift \ Sources/MobileDevice/MobileDevice.swift \ diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 80ea39e..e23ba45 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -9,8 +9,7 @@ extension UIDocumentPickerViewController { struct ContentView: View { let os = ProcessInfo().operatingSystemVersion - let originalMobileGestalt: URL - let modifiedMobileGestalt: URL + let origMGURL, modMGURL: URL @AppStorage("PairingFile") var pairingFile: String? @State var mobileGestalt: NSMutableDictionary @State var reboot = true @@ -74,13 +73,13 @@ struct ContentView: View { Section { Toggle("Reboot after finish restoring", isOn: $reboot) Button("Apply changes") { - try! mobileGestalt.write(to: modifiedMobileGestalt) + try! mobileGestalt.write(to: modMGURL) applyChanges() } Button("Reset changes") { - try! FileManager.default.removeItem(at: modifiedMobileGestalt) - try! FileManager.default.copyItem(at: originalMobileGestalt, to: modifiedMobileGestalt) - mobileGestalt = try! NSMutableDictionary(contentsOf: modifiedMobileGestalt, error: ()) + try! FileManager.default.removeItem(at: modMGURL) + try! FileManager.default.copyItem(at: origMGURL, to: modMGURL) + mobileGestalt = try! NSMutableDictionary(contentsOf: modMGURL, error: ()) applyChanges() } } footer: { @@ -111,7 +110,8 @@ Thanks to: } .navigationDestination(for: String.self) { view in if view == "ApplyChanges" { - LogView(mgURL: modifiedMobileGestalt, reboot: reboot) + let mbdb = Restore.createBackupFiles(files: generateFilesToRestore()) + LogView(mbdb: mbdb, reboot: reboot) } } .navigationTitle("SparseBox") @@ -127,14 +127,14 @@ Thanks to: init() { let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - originalMobileGestalt = documentsDirectory.appendingPathComponent("OriginalMobileGestalt.plist", conformingTo: .data) - modifiedMobileGestalt = documentsDirectory.appendingPathComponent("ModifiedMobileGestalt.plist", conformingTo: .data) - if !FileManager.default.fileExists(atPath: originalMobileGestalt.path) { + origMGURL = documentsDirectory.appendingPathComponent("OriginalMobileGestalt.plist", conformingTo: .data) + modMGURL = documentsDirectory.appendingPathComponent("ModifiedMobileGestalt.plist", conformingTo: .data) + if !FileManager.default.fileExists(atPath: origMGURL.path) { let url = URL(filePath: "/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist") - try! FileManager.default.copyItem(at: url, to: originalMobileGestalt) - try! FileManager.default.copyItem(at: url, to: modifiedMobileGestalt) + try! FileManager.default.copyItem(at: url, to: origMGURL) + try! FileManager.default.copyItem(at: url, to: modMGURL) } - _mobileGestalt = State(initialValue: try! NSMutableDictionary(contentsOf: modifiedMobileGestalt, error: ())) + _mobileGestalt = State(initialValue: try! NSMutableDictionary(contentsOf: modMGURL, error: ())) // Fix file picker let fixMethod = class_getInstanceMethod(UIDocumentPickerViewController.self, Selector("fix_initForOpeningContentTypes:asCopy:"))! @@ -173,6 +173,12 @@ Thanks to: ) } + func generateFilesToRestore() -> [FileToRestore] { + return [ + FileToRestore(from: modMGURL, to: URL(filePath: "/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/NOT.apple.MobileGestalt.plist"), owner: 501, group: 501) + ] + } + func startMinimuxer() { guard pairingFile != nil else { return diff --git a/Sources/LogView.swift b/Sources/LogView.swift index 4213853..ee963db 100644 --- a/Sources/LogView.swift +++ b/Sources/LogView.swift @@ -5,7 +5,7 @@ let logPipe = Pipe() struct LogView: View { let udid: String let willReboot: Bool - let mobileGestaltURL: URL + let mbdb: Backup @State var log: String = "" @State var ran = false @State var isRebooting = false @@ -44,7 +44,7 @@ struct LogView: View { .navigationTitle(isRebooting ? "Rebooting device" : "Log output") } - init(mgURL: URL, reboot: Bool) { + init(mbdb: Backup, reboot: Bool) { setvbuf(stdout, nil, _IOLBF, 0) // make stdout line-buffered setvbuf(stderr, nil, _IONBF, 0) // make stderr unbuffered @@ -52,8 +52,8 @@ struct LogView: View { dup2(logPipe.fileHandleForWriting.fileDescriptor, fileno(stdout)) dup2(logPipe.fileHandleForWriting.fileDescriptor, fileno(stderr)) - mobileGestaltURL = mgURL - willReboot = reboot + self.mbdb = mbdb + self.willReboot = reboot let deviceList = MobileDevice.deviceList() guard deviceList.count == 1 else { @@ -71,8 +71,6 @@ struct LogView: View { do { try? FileManager.default.removeItem(at: folder) try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: false) - - let mbdb = createBackupFile(from: mobileGestaltURL, to: URL(filePath: "/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist")) try mbdb.writeTo(directory: folder) // Restore now @@ -101,19 +99,5 @@ struct LogView: View { } } - func createBackupFile(from: URL, to: URL) -> Backup { - // open the file and read the contents - let contents = try! Data(contentsOf: from) - - // required on iOS 17.0+ since /var/mobile is on a separate partition - let basePath = to.path(percentEncoded: false).hasPrefix("/var/mobile/") ? "/var/mobile/backup" : "/var/backup" - - // create the backup - return Backup(files: [ - //Directory(path: "", domain: "SysContainerDomain-../../../../../../../..\(basePath)\(to.deletingLastPathComponent().path(percentEncoded: false))", owner: 501, group: 501), - //ConcreteFile(path: "", domain: "SysContainerDomain-../../../../../../../..\(basePath)\(to.path(percentEncoded: false))", contents: contents, owner: 501, group: 501), - ConcreteFile(path: "", domain: "SysContainerDomain-../../../../../../../..\(to.path(percentEncoded: false))", contents: contents, owner: 501, group: 501), - ConcreteFile(path: "", domain: "SysContainerDomain-../../../../../../../../crash_on_purpose", contents: Data()), - ]) - } + } diff --git a/Sources/SparseRestore/Restore.swift b/Sources/SparseRestore/Restore.swift new file mode 100644 index 0000000..5ce3f33 --- /dev/null +++ b/Sources/SparseRestore/Restore.swift @@ -0,0 +1,79 @@ +import Foundation + +class FileToRestore { + var from, to: URL + var owner, group: Int32 + init(from: URL, to: URL, owner: Int32 = 0, group: Int32 = 0) { + self.from = from + self.to = to + self.owner = owner + self.group = group + } +} + +struct Restore { + static func createBackupFiles(files: [FileToRestore]) -> Backup { + // create the files to be backed up + var filesList : [BackupFile] = [ + Directory(path: "", domain: "RootDomain"), + Directory(path: "Library", domain: "RootDomain"), + Directory(path: "Library/Preferences", domain: "RootDomain") + ] + + // create the links + for (index, file) in files.enumerated() { + let contents = try! Data(contentsOf: file.from) + filesList.append(ConcreteFile( + path: "Library/Preferences/temp\(index)", + domain: "RootDomain", + contents: contents, + owner: file.owner, + group: file.group, + inode: UInt64(index))) + } + + // add the file paths + for (index, file) in files.enumerated() { + let restoreFilePath = file.to.path(percentEncoded: false) + var basePath = "/var/backup" + // set it to work in the separate volumes (prevents a bootloop) + if restoreFilePath.hasPrefix("/var/mobile/") { + // required on iOS 17.0+ since /var/mobile is on a separate partition + basePath = "/var/mobile/backup" + } else if restoreFilePath.hasPrefix("/private/var/mobile/") { + basePath = "/private/var/mobile/backup" + } else if restoreFilePath.hasPrefix("/private/var/") { + basePath = "/private/var/backup" + } + filesList.append(Directory( + path: "", + domain: "SysContainerDomain-../../../../../../../..\(basePath)\(file.to.deletingLastPathComponent().path(percentEncoded: false))", + owner: file.owner, + group: file.group + )) + filesList.append(ConcreteFile( + path: "", + domain: "SysContainerDomain-../../../../../../../..\(basePath)\(file.to.path(percentEncoded: false))", + contents: Data(), + owner: file.owner, + group: file.group, + inode: UInt64(index))) + } + + // break the hard links + for (index, _) in files.enumerated() { + filesList.append(ConcreteFile( + path: "", + domain: "SysContainerDomain-../../../../../../../../var/.backup.i/var/root/Library/Preferences/temp\(index)", + contents: Data(), + owner: 501, + group: 501)) + } + + // crash on purpose + filesList.append(ConcreteFile(path: "", domain: "SysContainerDomain-../../../../../../../../crash_on_purpose", contents: Data())) + + // create the backup + return Backup(files: filesList) + } +}