├── .gitignore ├── .gitmodules ├── Bookmark Location in Geranium ├── ActionViewController.swift ├── Base.lproj │ └── MainInterface.storyboard ├── Bookmark Location in Geranium.entitlements ├── Info.plist ├── Media.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── slice of cheese.png │ ├── Contents.json │ └── TouchBarBezel.colorset │ │ └── Contents.json └── entitlements.plist ├── Geranium.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ └── Geranium.xcscheme ├── Geranium ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ └── geranium.png │ └── Contents.json ├── BetaChecker.swift ├── ByeTime │ ├── ByeTimeHelper.swift │ └── ByeTimeView.swift ├── Cleaner │ ├── CleanerView.swift │ ├── CustomPaths.swift │ ├── FileCleaner.swift │ ├── NotifHelp.swift │ ├── Paths.swift │ └── ProgressBar.swift ├── ContentView.swift ├── DaemonMan │ ├── CurrentlyDisabledDaemonView.swift │ ├── DaemonDisabler.swift │ ├── DaemonView.swift │ └── DaemonWelcomeView.swift ├── Geranium-Bringing-Header.h ├── Geranium.entitlements ├── GeraniumApp.swift ├── HomeView.swift ├── Icons │ ├── Beta.png │ ├── Bouquet.png │ └── Flore.png ├── Info.plist ├── Libs │ ├── Addon.swift │ ├── LogHelper.swift │ ├── RootHelperMan.swift │ └── TrollStore │ │ ├── CoreServices.h │ │ ├── TSUtil.h │ │ └── TSUtil.m ├── LocSim │ ├── BookMark │ │ ├── BookMarkHelper.swift │ │ └── BookMarkSlider.swift │ ├── CustomMapView.swift │ ├── LocSimManager.swift │ ├── LocSimPrivateHeaders.h │ └── LocSimView.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SettingsView.swift ├── The Supviser │ └── SuperviseView.swift ├── Translations │ ├── InfoPlist.xcstrings │ └── Localizable.xcstrings └── WelcomeView.swift ├── LICENSE.md ├── README.md ├── entitlements.plist ├── icon.sketch └── ipabuild.sh /.gitignore: -------------------------------------------------------------------------------- 1 | Geranium.xcodeproj/xcuserdata/* 2 | Geranium.xcodeproj/project.xcworkspace/xcuserdata/* 3 | Geranium.xcodeproj/xcuserdata/ 4 | Geranium.xcodeproj/project.xcworkspace/xcuserdata/ 5 | Geranium.xcodeproj/project.xcworkspace/xcuserdata/cclerc.xcuserdatad/UserInterfaceState.xcuserstate 6 | **/.DS_Store 7 | build/* 8 | build/ 9 | *.entitlements 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "RootHelper"] 2 | path = RootHelper 3 | url = https://github.com/c22dev/Geranium-RH 4 | 5 | -------------------------------------------------------------------------------- /Bookmark Location in Geranium/ActionViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import MobileCoreServices 4 | import UniformTypeIdentifiers 5 | 6 | class ActionViewController: UIViewController { 7 | 8 | @IBOutlet weak var imageView: UIImageView! 9 | @IBOutlet weak var textField: UITextField! 10 | var latitudeDouble: Double = 0.0 11 | var longitudeDouble: Double = 0.0 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | } 15 | 16 | // Helper method to extract query parameters from URL 17 | private func getParameter(from url: URL, key: String) -> String? { 18 | guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), 19 | let queryItems = components.queryItems else { 20 | return nil 21 | } 22 | 23 | return queryItems.first { $0.name == key }?.value 24 | } 25 | 26 | @IBAction func saveButtonPressed(_ sender: UIButton) { 27 | if let sharedItems = extensionContext?.inputItems as? [NSExtensionItem], 28 | let firstItem = sharedItems.first, 29 | let attachments = firstItem.attachments { 30 | 31 | for provider in attachments { 32 | if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier as String) { 33 | provider.loadItem(forTypeIdentifier: UTType.url.identifier as String, options: nil, completionHandler: { (url, error) in 34 | if let url = url as? URL { 35 | if let latitude = self.getParameter(from: url, key: "ll")?.components(separatedBy: ",").first, 36 | let longitude = self.getParameter(from: url, key: "ll")?.components(separatedBy: ",").last, 37 | let latitudeDouble = Double(latitude), 38 | let longitudeDouble = Double(longitude) { 39 | DispatchQueue.main.async { 40 | let bookmarkName = self.textField.text 41 | print(self.BookMarkSave(lat: latitudeDouble, long: longitudeDouble, name: bookmarkName ?? "")) 42 | self.done() 43 | } 44 | } 45 | } 46 | }) 47 | } 48 | } 49 | } 50 | dismiss(animated: true) { 51 | } 52 | } 53 | 54 | @IBAction func done() { 55 | self.extensionContext?.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil) 56 | } 57 | 58 | let sharedUserDefaultsSuiteName = "group.live.cclerc.geraniumBookmarks" 59 | 60 | func BookMarkSave(lat: Double, long: Double, name: String) -> Bool { 61 | let bookmark: [String: Any] = ["name": name, "lat": lat, "long": long] 62 | var bookmarks = BookMarkRetrieve() 63 | bookmarks.append(bookmark) 64 | let sharedUserDefaults = UserDefaults(suiteName: sharedUserDefaultsSuiteName) 65 | sharedUserDefaults?.set(bookmarks, forKey: "bookmarks") 66 | successVibrate() 67 | return true 68 | } 69 | 70 | func BookMarkRetrieve() -> [[String: Any]] { 71 | let sharedUserDefaults = UserDefaults(suiteName: sharedUserDefaultsSuiteName) 72 | if let bookmarks = sharedUserDefaults?.array(forKey: "bookmarks") as? [[String: Any]] { 73 | return bookmarks 74 | } else { 75 | return [] 76 | } 77 | } 78 | } 79 | 80 | // shortened vibrate object 81 | func successVibrate() { 82 | let generator = UINotificationFeedbackGenerator() 83 | generator.notificationOccurred(.success) 84 | } 85 | -------------------------------------------------------------------------------- /Bookmark Location in Geranium/Base.lproj/MainInterface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /Bookmark Location in Geranium/Bookmark Location in Geranium.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.live.cclerc.geraniumBookmarks 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Bookmark Location in Geranium/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | NSExtensionActivationRule 10 | TRUEPREDICATE 11 | NSExtensionServiceAllowsFinderPreviewItem 12 | 13 | NSExtensionServiceAllowsTouchBarItem 14 | 15 | NSExtensionServiceFinderPreviewIconName 16 | NSActionTemplate 17 | NSExtensionServiceTouchBarBezelColorName 18 | TouchBarBezel 19 | NSExtensionServiceTouchBarIconName 20 | NSActionTemplate 21 | 22 | NSExtensionMainStoryboard 23 | MainInterface 24 | NSExtensionPointIdentifier 25 | com.apple.ui-services 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Bookmark Location in Geranium/Media.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "slice of cheese.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Bookmark Location in Geranium/Media.xcassets/AppIcon.appiconset/slice of cheese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c22dev/Geranium/348772ab9ca7906a875404e62c93f392dd2ff51a/Bookmark Location in Geranium/Media.xcassets/AppIcon.appiconset/slice of cheese.png -------------------------------------------------------------------------------- /Bookmark Location in Geranium/Media.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Bookmark Location in Geranium/Media.xcassets/TouchBarBezel.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "mac", 9 | "color" : { 10 | "reference" : "systemPurpleColor" 11 | } 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /Bookmark Location in Geranium/entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.live.cclerc.geraniumBookmarks 8 | 9 | 10 | -------------------------------------------------------------------------------- /Geranium.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Geranium.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Geranium.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "3685d775c781caf98179581cad228856352348b6d5c7679825386e193f54f37b", 3 | "pins" : [ 4 | { 5 | "identity" : "alertkit", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/sparrowcode/AlertKit", 8 | "state" : { 9 | "revision" : "3d35de60ad26aab58395ebecdd63b533546862ed", 10 | "version" : "5.1.8" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /Geranium.xcodeproj/xcshareddata/xcschemes/Geranium.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 35 | 41 | 42 | 43 | 46 | 52 | 53 | 54 | 55 | 56 | 66 | 68 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Geranium/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.427", 9 | "green" : "0.348", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Geranium/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "geranium.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Geranium/Assets.xcassets/AppIcon.appiconset/geranium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c22dev/Geranium/348772ab9ca7906a875404e62c93f392dd2ff51a/Geranium/Assets.xcassets/AppIcon.appiconset/geranium.png -------------------------------------------------------------------------------- /Geranium/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Geranium/BetaChecker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BetaChecker.swift 3 | // Geranium 4 | // 5 | // This is used to check if current device is enrolled onto my db. 6 | // Created by cclerc on 24.12.23. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct BetaView: View { 13 | @Environment(\.dismiss) var dismiss 14 | var body: some View { 15 | @State var textPlaceHorder = UIDevice.current.identifierForVendor?.uuidString ?? "unknown" 16 | @State var validation = "" 17 | @State var isEnrolled = false 18 | var timer: Timer? 19 | VStack { 20 | Image(uiImage: Bundle.main.icon!) 21 | .cornerRadius(10) 22 | Text("Welcome to Geranium Beta Testing Program") 23 | .font(.title2) 24 | .bold() 25 | .multilineTextAlignment(.center) 26 | Text("Please copy your UUID and send it to a developer in charge.") 27 | .padding() 28 | .multilineTextAlignment(.center) 29 | Text(textPlaceHorder) 30 | .padding() 31 | .multilineTextAlignment(.center) 32 | .textSelection(.enabled) 33 | Text(validation) 34 | .padding() 35 | .multilineTextAlignment(.center) 36 | if !isEnrolled { 37 | ProgressView() 38 | .progressViewStyle(CircularProgressViewStyle(tint: .accentColor)) 39 | .scaleEffect(1.0, anchor: .center) 40 | Text("You might want to quit and go back in the app.") 41 | .font(.footnote) 42 | .foregroundColor(.secondary) 43 | .padding(.bottom) 44 | .padding(.top) 45 | .multilineTextAlignment(.center) 46 | } 47 | } 48 | .interactiveDismissDisabled() 49 | .onAppear { 50 | if isDeviceEnrolled() { 51 | print("should quit ?") 52 | isEnrolled.toggle() 53 | close() 54 | } 55 | timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in 56 | if isDeviceEnrolled() { 57 | print("should quit ?") 58 | isEnrolled.toggle() 59 | close() 60 | } 61 | } 62 | } 63 | .onDisappear { 64 | timer?.invalidate() 65 | } 66 | } 67 | func close() { 68 | dismiss() 69 | } 70 | } 71 | 72 | func isDeviceEnrolled() -> Bool { 73 | var result = false 74 | let semaphore = DispatchSemaphore(value: 0) 75 | 76 | let url = URL(string: "https://cclerc.ch/db/geranium/enrolement.txt")! 77 | var request = URLRequest(url: url) 78 | request.cachePolicy = .reloadIgnoringLocalCacheData 79 | 80 | let task = URLSession.shared.dataTask(with: request) { data, response, error in 81 | defer { 82 | semaphore.signal() 83 | } 84 | 85 | if let error = error { 86 | print("Error: \(error)") 87 | return 88 | } 89 | 90 | guard let data = data else { 91 | print("No data received") 92 | return 93 | } 94 | 95 | if let fileContent = String(data: data, encoding: .utf8) { 96 | let vendorID = UIDevice.current.identifierForVendor?.uuidString ?? "unknown" 97 | result = fileContent.contains(vendorID) 98 | if result { 99 | print("User is in the beta program") 100 | } 101 | } else { 102 | print("Failed to convert data to string") 103 | } 104 | } 105 | 106 | task.resume() 107 | semaphore.wait() 108 | return result 109 | } 110 | -------------------------------------------------------------------------------- /Geranium/ByeTime/ByeTimeHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ByeTimeHelper.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 29.12.23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | let STPath = URL(fileURLWithPath: "/var/mobile/Library/Preferences/com.apple.ScreenTimeAgent.plist") 12 | let STString = "/var/mobile/Library/Preferences/com.apple.ScreenTimeAgent.plist" 13 | 14 | let STBckPath = "/var/mobile/Library/Preferences/com.apple.ScreenTimeAgent.gerackup" 15 | 16 | func DisableScreenTime(screentimeagentd: Bool, usagetrackingd: Bool, homed: Bool, familycircled: Bool){ 17 | var result = "" 18 | // Backuping ScreenTime preferences if they exists. 19 | if !FileManager.default.fileExists(atPath: STBckPath) { 20 | result = RootHelper.copy(from: STPath, to: URL(fileURLWithPath: STBckPath)) 21 | if result != "0" { 22 | do { 23 | try FileManager.default.copyItem(at: STPath, to: URL(fileURLWithPath: STBckPath)) 24 | } 25 | catch { 26 | print("error") 27 | } 28 | } 29 | } 30 | 31 | // Removing Screen Time preferences 32 | result = RootHelper.removeItem(at: STPath) 33 | if result != "0" { 34 | do { 35 | try FileManager.default.removeItem(at: STPath) 36 | } 37 | catch { 38 | print("error") 39 | } 40 | } 41 | 42 | // Kill daemons 43 | if screentimeagentd { 44 | killall("ScreenTimeAgent") 45 | } 46 | if homed { 47 | killall("homed") 48 | } 49 | if usagetrackingd { 50 | killall("UsageTrackingAgent") 51 | } 52 | if familycircled { 53 | killall("familycircled") 54 | } 55 | 56 | // Remove ScreenTime preferences if STA respawned it (STA= ScreenTimeAgent) 57 | if !FileManager.default.fileExists(atPath: STBckPath) { 58 | result = RootHelper.removeItem(at: STPath) 59 | if result != "0" { 60 | do { 61 | try FileManager.default.removeItem(at: STPath) 62 | } 63 | catch { 64 | print("error") 65 | } 66 | } 67 | } 68 | 69 | 70 | // Then we disable the daemon from launchd 71 | if screentimeagentd { 72 | daemonManagement(key: "com.apple.ScreenTimeAgent", value: true, plistPath: "/var/db/com.apple.xpc.launchd/disabled.plist") 73 | } 74 | if homed { 75 | daemonManagement(key: "com.apple.homed", value: true, plistPath: "/var/db/com.apple.xpc.launchd/disabled.plist") 76 | } 77 | if usagetrackingd { 78 | daemonManagement(key: "com.apple.UsageTrackingAgent", value: true, plistPath: "/var/db/com.apple.xpc.launchd/disabled.plist") 79 | } 80 | if familycircled { 81 | daemonManagement(key: "com.apple.familycircled", value: true, plistPath: "/var/db/com.apple.xpc.launchd/disabled.plist") 82 | } 83 | successVibrate() 84 | UIApplication.shared.alert(title:"Done !", body:"Please manually reboot your device", withButton: false) 85 | } 86 | 87 | 88 | func enableBack() { 89 | var canIProceed = false 90 | if !FileManager.default.fileExists(atPath: STBckPath) { 91 | UIApplication.shared.confirmAlert(title: "Backup file not found.", body: "Do you want to look for other backup files (Cowabunga or TrollBox) ?", onOK: { 92 | try? FileManager.default.removeItem(atPath: STString) 93 | if FileManager.default.fileExists(atPath: "/var/mobile/Library/Preferences/live.cclerc.ScreenTimeAgent.plist") { 94 | try? FileManager.default.copyItem(atPath: "/var/mobile/Library/Preferences/live.cclerc.ScreenTimeAgent.plist", toPath: STString) 95 | doStuff() 96 | } 97 | else if FileManager.default.fileExists(atPath: "/var/mobile/Library/Preferences/.BACKUP_com.apple.ScreenTimeAgent.plist") { 98 | try? FileManager.default.copyItem(atPath: "/var/mobile/Library/Preferences/.BACKUP_com.apple.ScreenTimeAgent.plist", toPath: STString) 99 | doStuff() 100 | } 101 | else { 102 | UIApplication.shared.confirmAlert(title: "Backup file still not found.", body: "No backup file found. Are you sure you want to proceed ? Old preferences might not be restored.", onOK: { 103 | doStuff() 104 | }, noCancel: false, onCancel: { 105 | canIProceed = false 106 | return 107 | }) 108 | } 109 | }, noCancel: false, onCancel: { 110 | canIProceed = false 111 | }) 112 | } 113 | else { 114 | try? FileManager.default.removeItem(atPath: STString) 115 | try? FileManager.default.copyItem(atPath: STBckPath, toPath: STString) 116 | doStuff() 117 | } 118 | } 119 | 120 | func doStuff() { 121 | let entriesToRemove = ["com.apple.familycircled", "com.apple.UsageTrackingAgent", "com.apple.ScreenTimeAgent", "com.apple.homed"] 122 | RootHelper.removeItem(at: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 123 | RootHelper.copy(from: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist"), to: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 124 | RootHelper.setPermission(url: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 125 | if var plist = try? readPlist(atPath: "/var/mobile/Documents/disabled.plist") { 126 | removeEntriesFromPlistArray(plist, entriesToRemove: entriesToRemove, arrayKey: "") 127 | try? writePlist(plist, toPath: "/var/mobile/Documents/disabled.plist") 128 | } else { 129 | sendLog("ST_REENABLE_ERR_PLIST") 130 | } 131 | RootHelper.removeItem(at: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist")) 132 | RootHelper.move(from: URL(fileURLWithPath :"/var/mobile/Documents/disabled.plist"), to: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist")) 133 | RootHelper.removeItem(at: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 134 | RootHelper.setDaemonPermission(url: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist")) 135 | UIApplication.shared.alert(title:"Done !", body:"Please manually reboot your device", withButton: false) 136 | } 137 | 138 | func readPlist(atPath path: String) throws -> NSMutableDictionary { 139 | guard let data = FileManager.default.contents(atPath: path) else { 140 | throw NSError(domain: "com.example.plistreader", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to read plist file at path \(path)"]) 141 | } 142 | 143 | guard let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? NSMutableDictionary else { 144 | throw NSError(domain: "com.example.plistreader", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to parse plist data"]) 145 | } 146 | 147 | return plist 148 | } 149 | 150 | func removeEntriesFromPlistArray(_ plist: NSMutableDictionary, entriesToRemove: [String], arrayKey: String) { 151 | guard var array = plist[arrayKey] as? [String] else { 152 | print("Array not found in plist for key: \(arrayKey)") 153 | return 154 | } 155 | 156 | array = array.filter { !entriesToRemove.contains($0) } 157 | plist[arrayKey] = array 158 | } 159 | 160 | func writePlist(_ plist: NSMutableDictionary, toPath path: String) throws { 161 | let data = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) 162 | 163 | try data.write(to: URL(fileURLWithPath: path), options: .atomic) 164 | } 165 | -------------------------------------------------------------------------------- /Geranium/ByeTime/ByeTimeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ByeTimeView.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 29.12.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ByeTimeView: View { 11 | @Binding var DebugStuff: Bool 12 | @State var ScreenTimeAgent: Bool = true 13 | @State var usagetrackingd: Bool = true 14 | @State var homed: Bool = false 15 | @State var familycircled: Bool = false 16 | var body: some View { 17 | VStack { 18 | List { 19 | Section(header: Label("Screen Time Agent", systemImage: "hourglass"), footer: Text("Screen Time Agent is responsible for managing all of ScreenTime preferences. Disabling this process only might at best disable Screen Time partially.")) { 20 | Toggle(isOn: $ScreenTimeAgent) { 21 | Text("Disable Screen Time Agent") 22 | } 23 | .disabled(!DebugStuff) 24 | } 25 | Section(header: Label("Usage Tracking Agent", systemImage: "magnifyingglass"), footer: Text("Usage Tracking Agent monitors and reports usage budgets set by Health, Parental Controls (= Screen Time) or Device Management. Disabling this might also disable some other features on your iPhone.")) { 26 | Toggle(isOn: $usagetrackingd) { 27 | Text("Disable Usage Tracking Agent") 28 | } 29 | } 30 | Section(header: Label("Homed", systemImage: "homekit"), footer: Text("Homed is in charge of HomeKit accessories and products, for instance HomePods, connected light bulbs and other, that you are managing from the 'Home' app on your phone. Don't disable this if you are using those accessories.")) { 31 | Toggle(isOn: $homed) { 32 | Text("Disable Homed") 33 | } 34 | } 35 | Section(header: Label("Familycircled", systemImage: "figure.and.child.holdinghands"), footer: Text("Familycircled is in charge of iCloud Family system. This includes family subscriptions, so if you disable this you won't be able to use them. However, this can help preventing ScreenTime from working.")) { 36 | Toggle(isOn: $familycircled) { 37 | Text("Disable Familycircled") 38 | } 39 | } 40 | } 41 | Button("Bye ScreenTime !", action : { 42 | UIApplication.shared.confirmAlert(title: "You are about to disable those daemons.", body: "This will delete Screen Time Preferences files and prevent the following daemoms from starting up with iOS. Are you sure you want to continue ?", onOK: { 43 | UIApplication.shared.alert(title: "Removing Screen Time...", body: "Your device will reboot, but I'd recommend you do a manual reboot after the first automatic one.", animated: false, withButton: false) 44 | DisableScreenTime(screentimeagentd: ScreenTimeAgent, usagetrackingd: usagetrackingd, homed: homed, familycircled: familycircled) 45 | }, noCancel: false, yes: true) 46 | }) 47 | .padding(10) 48 | .background(Color.accentColor) 49 | .cornerRadius(8) 50 | .foregroundColor(.black) 51 | Button("Enable ScreenTime", action : { 52 | UIApplication.shared.confirmAlert(title: "You are about to enable back ScreenTime.", body: "This will revert any ScreenTime backup you made and logging will be put back on.", onOK: { 53 | enableBack() 54 | }, noCancel: false, yes: true) 55 | }) 56 | .padding(10) 57 | .background(Color.accentColor) 58 | .cornerRadius(8) 59 | .foregroundColor(.black) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Geranium/Cleaner/CleanerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CleanerView.swift 3 | // Geranium 4 | // Created by Constantin Clerc on 10/12/2023. 5 | 6 | import SwiftUI 7 | 8 | struct CleanerView: View { 9 | @StateObject private var appSettings = AppSettings() 10 | @State private var customPaths: [String] = UserDefaults.standard.stringArray(forKey: "savedPaths") ?? [] 11 | // View Settings 12 | @State var defaultView: Bool = true 13 | @State var progressView: Bool = false 14 | @State var resultView: Bool = false 15 | @State var wannaReboot: Bool = false 16 | @State var customPathSheet: Bool = false 17 | @State var needHelpWithMyNotifs: Bool = false 18 | @State var shouldIGetSizes = AppSettings().getSizes 19 | 20 | // User Selection 21 | @State var safari = false 22 | @State var appCaches = false 23 | @State var otaCaches = false 24 | @State var leftoverCaches = false 25 | @State var custompathselect = false 26 | 27 | // Sizes 28 | @State private var isLowSize: Bool = false 29 | @State private var safariCacheSize: Double = 0 30 | @State private var GlobalCacheSize: Double = 0 31 | @State private var OTACacheSize: Double = 0 32 | @State private var leftOverCacheSize: Double = 0 33 | @State private var customPathsSize: Double = 0 34 | 35 | // Results 36 | @State private var progressAmount:CGFloat = 0 37 | @State var RHResult = "" 38 | @State var errorDetected: Bool = false 39 | @State var successDetected: Bool = false 40 | 41 | var body: some View { 42 | if UIDevice.current.userInterfaceIdiom == .pad { 43 | if #available(iOS 16.0, *) { 44 | NavigationStack { 45 | cleanerViewMain() 46 | } 47 | } 48 | else { 49 | cleanerViewMain() 50 | } 51 | } else { 52 | NavigationView { 53 | cleanerViewMain() 54 | } 55 | } 56 | } 57 | 58 | @ViewBuilder 59 | private func cleanerViewMain() -> some View { 60 | VStack { 61 | // Default View if nothing is being done 62 | if defaultView { 63 | // check if smth is selected 64 | if safari || appCaches || otaCaches || leftoverCaches || custompathselect { 65 | Button("Clean !", action: { 66 | UIApplication.shared.confirmAlert(title: "Selected options", body: "Safari Caches: \(truelyEnabled(safari))\nGeneral Caches: \(truelyEnabled(appCaches))\nOTA Update Caches: \(truelyEnabled(otaCaches))\nApps Leftover Caches: \(truelyEnabled(leftoverCaches))\(customTest(isEnabled: custompathselect))\n Are you sure you want to permanently delete those files ? \(draftWarning(isEnabled: leftoverCaches))", onOK: { 67 | print("") 68 | withAnimation { 69 | var sizetotal = (safariCacheSize + GlobalCacheSize + OTACacheSize + leftOverCacheSize) / (1024 * 1024) 70 | if sizetotal < appSettings.minimSizeC, shouldIGetSizes { 71 | isLowSize = true 72 | } 73 | 74 | defaultView.toggle() 75 | progressView.toggle() 76 | wannaReboot = false 77 | } 78 | }, noCancel: false, yes: true) 79 | }) 80 | .padding(10) 81 | .background(Color.accentColor) 82 | .cornerRadius(8) 83 | .foregroundColor(.black) 84 | .transition(.scale) 85 | } 86 | else { 87 | Button("Clean !", action: { 88 | UIApplication.shared.confirmAlert(title: "Selected options", body: "watefuk", onOK: { 89 | print("nothing selected ?") 90 | }, noCancel: false, yes: true) 91 | }) 92 | .padding(10) 93 | .background(Color.accentColor) 94 | .cornerRadius(8) 95 | .foregroundColor(.black) 96 | .transition(.scale) 97 | .disabled(true) 98 | } 99 | // Normal Toggles 100 | Toggle(isOn: $safari) { 101 | Image(systemName: "safari") 102 | Text("Safari Caches") 103 | if shouldIGetSizes { 104 | Text("> "+String(format: "%.2f MB", safariCacheSize / (1024 * 1024))) 105 | } 106 | } 107 | .toggleStyle(checkboxiOS()) 108 | .padding(2) 109 | 110 | .onAppear { 111 | if ProcessInfo().operatingSystemVersion.majorVersion == 15, appSettings.firstCleanerTime { 112 | appSettings.firstCleanerTime = false 113 | UIApplication.shared.confirmAlert(title: "⚠️ You are on iOS 15 ⚠️", body: "Cleaning on iOS 15 might break notifications, and some app permissions. Do you want to enable measures that will keep your phone safe ? You might not get everything completly cleaned up. Pressing yes on iOS 15 will keep your device safe.", onOK: { 114 | appSettings.tmpClean = false 115 | }, noCancel: false, onCancel: { 116 | appSettings.tmpClean = true 117 | }, yes: true) 118 | } 119 | if shouldIGetSizes { 120 | getSizeForSafariCaches { size in 121 | self.safariCacheSize = size 122 | } 123 | } 124 | } 125 | Toggle(isOn: $appCaches) { 126 | Image(systemName: "app.dashed") 127 | Text("General Caches") 128 | if shouldIGetSizes { 129 | Text("> " + String(format: "%.2f MB", GlobalCacheSize / (1024 * 1024))) 130 | } 131 | } 132 | .toggleStyle(checkboxiOS()) 133 | .padding(2) 134 | .onAppear { 135 | if shouldIGetSizes { 136 | getSizeForGeneralCaches { size in 137 | self.GlobalCacheSize = size 138 | } 139 | } 140 | } 141 | 142 | Toggle(isOn: $otaCaches) { 143 | Image(systemName: "restart.circle") 144 | Text("OTA Update Caches") 145 | if shouldIGetSizes { 146 | Text("> " + String(format: "%.2f MB", OTACacheSize / (1024 * 1024))) 147 | } 148 | } 149 | .toggleStyle(checkboxiOS()) 150 | .padding(2) 151 | .onAppear { 152 | if shouldIGetSizes { 153 | getSizeForOTA { size in 154 | self.OTACacheSize = size 155 | } 156 | } 157 | } 158 | 159 | Toggle(isOn: $leftoverCaches) { 160 | Image(systemName: "app.badge.checkmark") 161 | Text("Apps Leftover Caches") 162 | if shouldIGetSizes { 163 | Text("> " + String(format: "%.2f MB", leftOverCacheSize / (1024 * 1024))) 164 | } 165 | } 166 | .toggleStyle(checkboxiOS()) 167 | .padding(2) 168 | .onAppear { 169 | if shouldIGetSizes { 170 | getSizeForAppLeftover { size in 171 | self.leftOverCacheSize = size 172 | } 173 | } 174 | } 175 | if !customPaths.isEmpty { 176 | Toggle(isOn: $custompathselect) { 177 | Image(systemName: "folder") 178 | Text("Custom Paths") 179 | if shouldIGetSizes { 180 | Text("> " + String(format: "%.2f MB", customPathsSize / (1024 * 1024))) 181 | } 182 | } 183 | .toggleStyle(checkboxiOS()) 184 | .padding(2) 185 | .onAppear { 186 | if shouldIGetSizes { 187 | getSizeForCustom { size in 188 | self.customPathsSize = size 189 | } 190 | } 191 | } 192 | } 193 | } 194 | // View being in progress 195 | else if progressView { 196 | ProgressBar(value: progressAmount) 197 | .padding(.leading, 50) 198 | .padding(.trailing, 50) 199 | .onAppear { 200 | performCleanup() 201 | } 202 | } 203 | // Success ! 204 | if successDetected, resultView{ 205 | Image(systemName: "checkmark") 206 | .foregroundColor(.green) 207 | .onAppear { 208 | successVibrate() 209 | } 210 | Text("Done !") 211 | .foregroundStyle(.green) 212 | Button("Exit", action: { 213 | withAnimation { 214 | progressAmount = 0 215 | if !appSettings.keepCheckBoxesC { 216 | safari = false 217 | appCaches = false 218 | otaCaches = false 219 | leftoverCaches = false 220 | custompathselect = false 221 | } 222 | isLowSize = false 223 | successDetected.toggle() 224 | resultView.toggle() 225 | defaultView.toggle() 226 | wannaReboot.toggle() 227 | } 228 | }) 229 | .padding(10) 230 | .background(.green) 231 | .cornerRadius(8) 232 | .foregroundColor(.black) 233 | .transition(.scale) 234 | } 235 | // Error... 236 | if errorDetected, resultView { 237 | Image(systemName: "x.circle") 238 | .foregroundColor(.red) 239 | .onAppear { 240 | progressAmount = 0.9 241 | errorVibrate() 242 | } 243 | Text("Error !") 244 | .foregroundStyle(.red) 245 | Text("An error occured with the RootHelper.") 246 | .foregroundColor(.secondary) 247 | .font(.footnote) 248 | .multilineTextAlignment(.center) 249 | 250 | Button("Try again", action: { 251 | withAnimation { 252 | progressAmount = 0 253 | if !appSettings.keepCheckBoxesC { 254 | safari = false 255 | appCaches = false 256 | otaCaches = false 257 | leftoverCaches = false 258 | custompathselect = false 259 | } 260 | isLowSize = false 261 | errorDetected.toggle() 262 | resultView.toggle() 263 | defaultView.toggle() 264 | wannaReboot = true 265 | } 266 | }) 267 | .padding(10) 268 | .background(.red) 269 | .cornerRadius(8) 270 | .foregroundColor(.black) 271 | .transition(.scale) 272 | } 273 | } 274 | .toolbar { 275 | ToolbarItem(placement: .navigationBarLeading) { 276 | if defaultView { 277 | Text("Cleaner") 278 | .font(.title2) 279 | .bold() 280 | } 281 | } 282 | ToolbarItem(placement: .navigationBarTrailing) { 283 | Button(action: { 284 | needHelpWithMyNotifs.toggle() 285 | }) { 286 | if defaultView, ProcessInfo().operatingSystemVersion.majorVersion == 15 { 287 | Image(systemName: "bell.badge") 288 | .resizable() 289 | .aspectRatio(contentMode: .fit) 290 | .frame(width: 24, height: 24) 291 | } 292 | } 293 | } 294 | ToolbarItem(placement: .navigationBarTrailing) { 295 | Button(action: { 296 | customPathSheet.toggle() 297 | }) { 298 | if defaultView { 299 | Image(systemName: "folder.badge.plus") 300 | .resizable() 301 | .aspectRatio(contentMode: .fit) 302 | .frame(width: 24, height: 24) 303 | } 304 | } 305 | } 306 | } 307 | .sheet(isPresented: $customPathSheet) { 308 | CustomPaths() 309 | .onDisappear { 310 | UIApplication.shared.confirmAlert(title: "You need to quit the app to apply changes.", body: "You might want to open it back right after to continue.", onOK: { 311 | exitGracefully() 312 | }, noCancel: true) 313 | } 314 | } 315 | .sheet(isPresented: $needHelpWithMyNotifs) { 316 | NotifHelp() 317 | } 318 | } 319 | func performCleanup() { 320 | cleanProcess(lowSize: isLowSize, safari: safari, appCaches: appCaches, otaCaches: otaCaches, leftOverCaches: 321 | leftoverCaches, custompathselect: custompathselect) { progressHandler in 322 | progressAmount = progressHandler 323 | if (progressAmount >= 0.9) { 324 | withAnimation { 325 | progressView.toggle() 326 | successDetected.toggle() 327 | resultView.toggle() 328 | } 329 | } 330 | if (progressAmount < -5) { 331 | withAnimation { 332 | sendLog("Error Cleaning") 333 | progressAmount = 0 334 | progressView.toggle() 335 | errorDetected.toggle() 336 | resultView.toggle() 337 | } 338 | } 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /Geranium/Cleaner/CustomPaths.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPaths.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 15.01.24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CustomPaths: View { 11 | @State private var paths: [String] = UserDefaults.standard.stringArray(forKey: "savedPaths") ?? [] 12 | @State private var newPathName = "" 13 | @State private var isAddingPath = false 14 | 15 | var body: some View { 16 | NavigationView { 17 | List { 18 | ForEach(paths, id: \.self) { path in 19 | withAnimation { 20 | Text(path) 21 | } 22 | } 23 | .onDelete(perform: deletePaths) 24 | } 25 | .toolbar { 26 | ToolbarItem(placement: .navigationBarLeading) { 27 | Text("Custom Paths") 28 | .font(.title2) 29 | .bold() 30 | } 31 | ToolbarItem(placement: .navigationBarTrailing) { 32 | Button(action: { 33 | UIApplication.shared.confirmAlert(title: "Are you sure you want to erase all your custom paths ?", body: "This won't clean them but will erase them from your list.", onOK: { 34 | paths = [] 35 | savePaths() 36 | }, noCancel: false) 37 | }) { 38 | if !paths.isEmpty { 39 | withAnimation { 40 | Text("Clear") 41 | } 42 | } 43 | } 44 | } 45 | ToolbarItem(placement: .navigationBarTrailing) { 46 | Button(action: { 47 | UIApplication.shared.TextFieldAlert( 48 | title: "Enter a path you want to add to your list:", 49 | textFieldPlaceHolder: "/var/mobile/DCIM/" 50 | ) { chemin, _ in 51 | if let chemin = chemin, !chemin.isEmpty { 52 | paths.append(chemin) 53 | savePaths() 54 | isAddingPath.toggle() 55 | } else { 56 | UIApplication.shared.alert(title: "Empty input !!", body: "Please enter a path.") 57 | } 58 | } 59 | }) { 60 | Image(systemName: "plus") 61 | .resizable() 62 | .aspectRatio(contentMode: .fit) 63 | .frame(width: 24, height: 24) 64 | } 65 | } 66 | } 67 | } 68 | .overlay( 69 | Group { 70 | if paths.isEmpty { 71 | VStack { 72 | Text("No paths saved.") 73 | .foregroundColor(.secondary) 74 | .font(.title) 75 | .padding() 76 | .multilineTextAlignment(.center) 77 | Text("To add a path, tap the + button.") 78 | .foregroundColor(.secondary) 79 | .font(.footnote) 80 | .multilineTextAlignment(.center) 81 | } 82 | } 83 | } 84 | , alignment: .center 85 | ) 86 | } 87 | 88 | func deletePaths(at offsets: IndexSet) { 89 | paths.remove(atOffsets: offsets) 90 | savePaths() 91 | } 92 | 93 | func savePaths() { 94 | UserDefaults.standard.set(paths, forKey: "savedPaths") 95 | } 96 | 97 | @ViewBuilder 98 | func addPathView() -> some View { 99 | VStack { 100 | Text("Add a New Path") 101 | .font(.headline) 102 | .padding() 103 | 104 | TextField("Enter path name", text: $newPathName) 105 | .textFieldStyle(RoundedBorderTextFieldStyle()) 106 | .padding() 107 | 108 | HStack { 109 | Spacer() 110 | Button("Cancel") { 111 | isAddingPath.toggle() 112 | } 113 | .padding() 114 | 115 | Button("Save") { 116 | if !newPathName.isEmpty { 117 | paths.append(newPathName) 118 | savePaths() 119 | isAddingPath.toggle() 120 | } 121 | } 122 | .padding() 123 | .disabled(newPathName.isEmpty) 124 | } 125 | } 126 | .padding() 127 | } 128 | } 129 | 130 | struct CustomPaths_Previews: PreviewProvider { 131 | static var previews: some View { 132 | CustomPaths() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Geranium/Cleaner/FileCleaner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileCleaner.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 15.12.23. 6 | // 7 | 8 | import Foundation 9 | 10 | func cleanProcess(lowSize: Bool = false, safari: Bool, appCaches: Bool, otaCaches: Bool, leftOverCaches: Bool, custompathselect: Bool, progressHandler: @escaping (Double) -> Void) { 11 | print("in the func") 12 | var appSettings = AppSettings() 13 | var safariCleanedUp = false 14 | var appCleanedUp = false 15 | var otaCleanedUp = false 16 | var leftCleanedUp = false 17 | var customCleanedUp = false 18 | var RHResult = "" 19 | print(safari, appCaches, otaCaches) 20 | 21 | DispatchQueue.global().async { 22 | func updateProgress(currentStep: Double) { 23 | progressHandler(currentStep) 24 | miniimpactVibrate() 25 | } 26 | if lowSize { 27 | usleep(500000) 28 | } 29 | if safari, !safariCleanedUp { 30 | print("safari") 31 | RHResult = deleteContentsOfDirectory(atPath: removeFilePrefix(safariCachePath)) 32 | if RHResult != "0" { 33 | updateProgress(currentStep: -99) 34 | return 35 | } 36 | updateProgress(currentStep: 0.1) 37 | 38 | RHResult = deleteContentsOfDirectory(atPath: removeFilePrefix(safariImgCachePath)) 39 | if RHResult != "0" { 40 | updateProgress(currentStep: -99) 41 | return 42 | } else { 43 | safariCleanedUp = true 44 | updateProgress(currentStep: 0.1) 45 | } 46 | } else { 47 | updateProgress(currentStep: 0.2) 48 | } 49 | 50 | if appCaches, !appCleanedUp { 51 | print("appcaches") 52 | var paths = [ 53 | logCachesPath, 54 | logmobileCachesPath, 55 | phototmpCachePath, 56 | logsCachesPath, 57 | globalCachesPath, 58 | mailmessdat, 59 | mailattach, 60 | deletedVideos, 61 | deletedPhotos, 62 | photoOther 63 | ] 64 | if appSettings.tmpClean { 65 | paths.append(tmpCachesPath) 66 | } 67 | 68 | for path in paths { 69 | RHResult = deleteContentsOfDirectory(atPath: path) 70 | if RHResult != "0" { 71 | updateProgress(currentStep: -99) 72 | return 73 | } else { 74 | updateProgress(currentStep: 0.3) 75 | } 76 | } 77 | appCleanedUp = true 78 | updateProgress(currentStep: 0.4) 79 | } else { 80 | updateProgress(currentStep: 0.5) 81 | } 82 | 83 | if otaCaches, !otaCleanedUp { 84 | print("otacaches") 85 | RHResult = deleteContentsOfDirectory(atPath: OTAPath) 86 | if RHResult != "0" { 87 | updateProgress(currentStep: -99) 88 | return 89 | } else { 90 | updateProgress(currentStep: 0.6) 91 | } 92 | otaCleanedUp = true 93 | updateProgress(currentStep: 0.6) 94 | } else { 95 | updateProgress(currentStep: 0.6) 96 | } 97 | if custompathselect, !customCleanedUp { 98 | print("custom") 99 | var paths: [String] = UserDefaults.standard.stringArray(forKey: "savedPaths") ?? [] 100 | for path in paths { 101 | RHResult = deleteContentsOfDirectory(atPath: path) 102 | if RHResult != "0" { 103 | updateProgress(currentStep: -99) 104 | return 105 | } else { 106 | updateProgress(currentStep: 0.7) 107 | } 108 | } 109 | customCleanedUp = true 110 | updateProgress(currentStep: 0.7) 111 | } else { 112 | updateProgress(currentStep: 0.8) 113 | } 114 | if leftOverCaches, !leftCleanedUp { 115 | let paths = [leftovYT, leftovTikTok, leftovTwitter, leftovSpotify] 116 | print("leftOv") 117 | for path in paths { 118 | RHResult = deleteContentsOfDirectory(atPath: removeFilePrefix(path)) 119 | if RHResult != "0" { 120 | progressHandler(-99) 121 | return 122 | } else { 123 | updateProgress(currentStep: 0.8) 124 | } 125 | } 126 | leftCleanedUp = true 127 | updateProgress(currentStep: 0.9) 128 | } 129 | else { 130 | updateProgress(currentStep: 0.9) 131 | } 132 | } 133 | } 134 | 135 | 136 | // Adapted from https://stackoverflow.com/a/59425817 137 | func directorySize(at url: URL) -> Int64 { 138 | let fileManager = FileManager.default 139 | var isDirectory: ObjCBool = false 140 | guard fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) else { 141 | return 22 142 | } 143 | 144 | if !isDirectory.boolValue { 145 | return 26 146 | } 147 | 148 | var totalSize: Int64 = 0 149 | 150 | do { 151 | let contents = try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) 152 | for item in contents { 153 | var isDir: ObjCBool = false 154 | guard fileManager.fileExists(atPath: item.path, isDirectory: &isDir) else { 155 | continue 156 | } 157 | 158 | if isDir.boolValue { 159 | RootHelper.setPermission(url: item) 160 | totalSize += directorySize(at: item) 161 | } else { 162 | RootHelper.setPermission(url: item) 163 | 164 | if let fileSize = (try? fileManager.attributesOfItem(atPath: item.path)[.size]) as? Int64 { 165 | totalSize += fileSize 166 | } 167 | } 168 | } 169 | } catch { 170 | print("Error calculating folder size: \(error.localizedDescription)") 171 | } 172 | 173 | return totalSize 174 | } 175 | 176 | 177 | 178 | func deleteContentsOfDirectory(atPath path: String) -> String { 179 | var log = RootHelper.setPermission(url: URL(fileURLWithPath: path)) 180 | print(log) 181 | let log2 = RootHelper.removeItem(at: URL(fileURLWithPath: path)) 182 | log = RootHelper.createDirectory(at: URL(fileURLWithPath: path)) 183 | print(log2) 184 | // set perms again else things won't work properly for mobile registered paths 185 | log = RootHelper.setPermission(url: URL(fileURLWithPath: path)) 186 | print(log) 187 | print("Contents of directory \(path) deleted") 188 | return(log2) 189 | } 190 | -------------------------------------------------------------------------------- /Geranium/Cleaner/NotifHelp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotifHelp.swift 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 22.02.2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NotifHelp: View { 11 | var body: some View { 12 | NavigationView { 13 | List { 14 | Section(header: Text("Issue"), footer: Text("You might lose some unimportant data, such as WiFi passwords and preferences.")) { 15 | Text("Lost your notifications because of the cleaner ? Don't panic, here is the solution !") 16 | } 17 | Section(header: Text("The procedure")) { 18 | Text("First, you need to open Settings. The app can do it for you if you want. Then, go in **General**, scroll down, and go in **Reset**.") 19 | Button("Open Settings") { 20 | if let url = URL(string:UIApplication.openSettingsURLString) { 21 | if UIApplication.shared.canOpenURL(url) { 22 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 23 | } 24 | } 25 | } 26 | } 27 | Section() { 28 | Text("Then, you need to press the **Reset** button (NOT the erase all contents and settings)") 29 | } 30 | Section() { 31 | Text("Press **Reset All Settings**, follow what's on your screen and you are done !") 32 | } 33 | } 34 | .toolbar { 35 | ToolbarItem(placement: .principal) { 36 | Text("Additional Help") 37 | .font(.title2) 38 | .bold() 39 | .padding(.top, 10) 40 | } 41 | } 42 | 43 | } 44 | .environment(\.defaultMinListRowHeight, 50) 45 | } 46 | } 47 | 48 | #Preview { 49 | NotifHelp() 50 | } 51 | -------------------------------------------------------------------------------- /Geranium/Cleaner/Paths.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Paths.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 22.12.23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | // Caches 12 | public var logmobileCachesPath = "/var/mobile/Library/Logs/" 13 | public var logCachesPath = "/var/log/" 14 | public var logsCachesPath = "/var/logs/" 15 | public var tmpCachesPath = "/var/tmp/" 16 | public var globalCachesPath = "/var/mobile/Library/Caches/com.apple.CacheDeleteAppContainerCaches.deathrow" 17 | public var mailmessdat = "/var/mobile/Library/Mail/MessageData" 18 | public var mailattach = "/var/mobile/Library/Mail/AttachmentData" 19 | public var deletedVideos = "/var/mobile/Media/PhotoData/Thumbnails/VideoKeyFrames/DCIM" 20 | public var deletedPhotos = "/var/mobile/Media/PhotoData/Thumbnails/V2/PhotoData/CPLAssets" 21 | public var phototmpCachePath = "/var/mobile/Media/PhotoData/CPL/storage/filecache/" 22 | public var photoOther = "/var/mobile/Media/PhotoData/Thumbnails/V2/DCIM" 23 | // Safari Caches 24 | public var safariCachePath = "\(getBundleDir(bundleID: "com.apple.mobilesafari"))Library/Caches/" 25 | public var safariImgCachePath = "\(getBundleDir(bundleID: "com.apple.mobilesafari"))Library/Image Cache/" 26 | // OTA Update 27 | public var OTAPath = "/var/MobileSoftwareUpdate/MobileAsset/AssetsV2/com_apple_MobileAsset_SoftwareUpdate/" 28 | // Leftovers 29 | public var leftovYT = "\(getBundleDir(bundleID: "com.google.ios.youtube"))Documents/Carida_Files/" 30 | public var leftovTikTok = "\(getBundleDir(bundleID: "com.zhiliaoapp.musically"))/Documents/drafts/" 31 | public var leftovTwitter = "\(getBundleDir(bundleID: "com.atebits.Tweetie2"))/Caches/" 32 | public var leftovSpotify = "\(getBundleDir(bundleID: "com.spotify.client"))/Caches/" 33 | 34 | // credit to bomberfish 35 | public func getBundleDir(bundleID: String) -> URL { 36 | let fm = FileManager.default 37 | var returnedurl = URL(string: "none") 38 | var dirlist = [""] 39 | 40 | do { 41 | dirlist = try fm.contentsOfDirectory(atPath: "/var/mobile/Containers/Data/Application/") 42 | // print(dirlist) 43 | } catch { 44 | return URL(fileURLWithPath: "Could not access/var/mobile/Containers/Data/Application/.\n\(error.localizedDescription)") 45 | } 46 | 47 | for dir in dirlist { 48 | // print(dir) 49 | let mmpath = "/var/mobile/Containers/Data/Application/" + dir + "/.com.apple.mobile_container_manager.metadata.plist" 50 | // print(mmpath) 51 | do { 52 | var mmDict: [String: Any] 53 | if fm.fileExists(atPath: mmpath) { 54 | mmDict = try PropertyListSerialization.propertyList(from: Data(contentsOf: URL(fileURLWithPath: mmpath)), options: [], format: nil) as? [String: Any] ?? [:] 55 | 56 | // print(mmDict as Any) 57 | if mmDict["MCMMetadataIdentifier"] as! String == bundleID { 58 | returnedurl = URL(fileURLWithPath: "/var/mobile/Containers/Data/Application/").appendingPathComponent(dir) 59 | } 60 | } else { 61 | print("WARNING: Directory \(dir) does not have a metadata plist") 62 | } 63 | } catch { 64 | print("Could not get data of \(mmpath): \(error.localizedDescription)") 65 | return URL(fileURLWithPath: "Could not get data of \(mmpath): \(error.localizedDescription)") 66 | } 67 | } 68 | if returnedurl != URL(string: "none") { 69 | return returnedurl! 70 | } else { 71 | return URL(fileURLWithPath: "App \(bundleID) cannot be found, is a system app, or is not installed.") 72 | } 73 | } 74 | 75 | // catgpt 76 | func removeFilePrefix(_ urlString: String) -> String { 77 | if urlString.hasPrefix("file://") { 78 | let index = urlString.index(urlString.startIndex, offsetBy: 7) 79 | return String(urlString[index...]) 80 | } 81 | return urlString 82 | } 83 | func calculateDirectorySizeAsync(url: URL, completion: @escaping (Double) -> Void) { 84 | DispatchQueue.global().async { 85 | let size = Double(directorySize(at: url)) 86 | DispatchQueue.main.async { 87 | completion(size) 88 | } 89 | } 90 | } 91 | 92 | func getSizeForGeneralCaches(completion: @escaping (Double) -> Void) { 93 | var globalCacheSize: Double = 0 94 | 95 | let paths = [ 96 | logCachesPath, 97 | logmobileCachesPath, 98 | tmpCachesPath, 99 | phototmpCachePath, 100 | logsCachesPath, 101 | globalCachesPath, 102 | mailmessdat, 103 | mailattach, 104 | deletedVideos, 105 | deletedPhotos, 106 | photoOther 107 | ] 108 | 109 | var remainingPaths = paths.count 110 | 111 | for path in paths { 112 | calculateDirectorySizeAsync(url: URL(fileURLWithPath: path)) { size in 113 | globalCacheSize += size 114 | remainingPaths -= 1 115 | 116 | if remainingPaths == 0 { 117 | completion(globalCacheSize) 118 | } 119 | } 120 | } 121 | } 122 | 123 | func getSizeForSafariCaches(completion: @escaping (Double) -> Void) { 124 | var safariCacheSize: Double = 0 125 | calculateDirectorySizeAsync(url: URL(fileURLWithPath: removeFilePrefix(safariCachePath))) { size in 126 | safariCacheSize += size 127 | calculateDirectorySizeAsync(url: URL(fileURLWithPath: removeFilePrefix(safariImgCachePath))) { size in 128 | safariCacheSize += size 129 | completion(safariCacheSize) 130 | } 131 | } 132 | } 133 | 134 | func getSizeForOTA(completion: @escaping (Double) -> Void) { 135 | calculateDirectorySizeAsync(url: URL(fileURLWithPath: OTAPath)) { size in 136 | completion(size) 137 | } 138 | } 139 | 140 | func getSizeForAppLeftover(completion: @escaping (Double) -> Void) { 141 | var leftOverCacheSize: Double = 0 142 | 143 | let paths = [ 144 | leftovYT, 145 | leftovTikTok, 146 | leftovTwitter, 147 | leftovSpotify 148 | ] 149 | 150 | var remainingPaths = paths.count 151 | 152 | for path in paths { 153 | calculateDirectorySizeAsync(url: URL(fileURLWithPath: path)) { size in 154 | leftOverCacheSize += size 155 | remainingPaths -= 1 156 | 157 | if remainingPaths == 0 { 158 | completion(leftOverCacheSize) 159 | } 160 | } 161 | } 162 | } 163 | 164 | func getSizeForCustom(completion: @escaping (Double) -> Void) { 165 | var customPathSize: Double = 0 166 | var paths: [String] = UserDefaults.standard.stringArray(forKey: "savedPaths") ?? [] 167 | var remainingPaths = paths.count 168 | 169 | for path in paths { 170 | calculateDirectorySizeAsync(url: URL(fileURLWithPath: path)) { size in 171 | customPathSize += size 172 | remainingPaths -= 1 173 | 174 | if remainingPaths == 0 { 175 | completion(customPathSize) 176 | } 177 | } 178 | } 179 | } 180 | 181 | func draftWarning(isEnabled: Bool = false)-> String { 182 | if isEnabled { 183 | return "\nThe options you selected will clean your Drafts in some apps (e.g. TikTok)" 184 | } 185 | else { 186 | return "" 187 | } 188 | } 189 | func customTest(isEnabled: Bool = false)-> String { 190 | var paths: [String] = UserDefaults.standard.stringArray(forKey: "savedPaths") ?? [] 191 | if !paths.isEmpty{ 192 | return "\nCustom Paths: Enabled" 193 | } 194 | else { 195 | return "" 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Geranium/Cleaner/ProgressBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressBar.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 15.12.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ProgressBar: View { 11 | var value: CGFloat 12 | 13 | var body: some View { 14 | Spacer() 15 | Spacer() 16 | VStack { 17 | GeometryReader { geometry in 18 | VStack() { 19 | Text("Deleting... \(self.getPercentage(self.value))") 20 | .padding() 21 | ZStack(alignment: .leading) { 22 | Rectangle() 23 | .opacity(0.1) 24 | .cornerRadius(25) 25 | Rectangle() 26 | .frame(minWidth: 0, idealWidth:self.getProgressBarWidth(geometry: geometry), 27 | maxWidth: self.getProgressBarWidth(geometry: geometry)) 28 | .opacity(0) 29 | .background(Color.accentColor) 30 | .animation(.default) 31 | .cornerRadius(25) 32 | } 33 | .frame(height: 10) 34 | }.frame(height: 10) 35 | } 36 | } 37 | Spacer() 38 | } 39 | 40 | func getProgressBarWidth(geometry: GeometryProxy) -> CGFloat { 41 | let frame = geometry.frame(in: .global) 42 | if value == 0.9 { 43 | return frame.size.width * 1 44 | } 45 | else { 46 | return frame.size.width * value 47 | } 48 | } 49 | 50 | func getPercentage(_ value: CGFloat) -> String { 51 | let intValue = Int(ceil(value * 100)) 52 | // in case roothelper fucks up! 53 | if intValue == -9900 { 54 | return "99 %" 55 | } 56 | // it's done 57 | if intValue == 90 { 58 | return "100 %" 59 | } 60 | else { 61 | return "\(intValue) %" 62 | } 63 | } 64 | } 65 | 66 | // Source : https://sarunw.com/posts/swiftui-checkbox/#swiftui-checkbox-on-ios 67 | struct checkboxiOS: ToggleStyle { 68 | func makeBody(configuration: Configuration) -> some View { 69 | Button(action: { 70 | configuration.isOn.toggle() 71 | }, label: { 72 | HStack { 73 | Image(systemName: configuration.isOn ? "checkmark.square" : "square") 74 | configuration.label 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Geranium/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 10/12/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @State var defaultTab = AppSettings().defaultTab 12 | var body: some View { 13 | TabView(selection: $defaultTab) { 14 | HomeView() 15 | .tabItem { 16 | Label("Home", systemImage: "house.fill") 17 | } 18 | .tag(1) 19 | DaemonView() 20 | .tabItem { 21 | Label("Daemons", systemImage: "flag.fill") 22 | } 23 | .tag(2) 24 | .onAppear { 25 | RootHelper.removeItem(at: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 26 | } 27 | LocSimView() 28 | .tabItem { 29 | Label("LocSim", systemImage: "mappin") 30 | } 31 | .tag(3) 32 | CleanerView() 33 | .tabItem { 34 | Label("Cleaner", systemImage: "trash.fill") 35 | } 36 | .tag(4) 37 | .animation(.easeInOut) 38 | SuperviseView() 39 | .tabItem { 40 | Label("Superviser", systemImage: "checkmark.seal.fill") 41 | } 42 | .tag(5) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Geranium/DaemonMan/CurrentlyDisabledDaemonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentlyDisabledDaemonView.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 21.12.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CurrentlyDisabledDaemonView: View { 11 | let plistPath = "/var/mobile/Documents/disabled.plist" 12 | @Environment(\.dismiss) var dismiss 13 | @State private var data: [String: Bool] = [:] 14 | @State private var result: String = "" 15 | var body: some View { 16 | NavigationView { 17 | List { 18 | Section(header: Text("Disabled Daemons Manager"), footer: Text("This is a list of all the currently disabled daemons. Swipe to the right to enable back the daemon.")) { 19 | ForEach(data.sorted(by: { $0.key < $1.key }).filter { $0.value == true }, id: \.key) { key, _ in 20 | Text(key) 21 | .swipeActions { 22 | Button(role: .destructive) { 23 | removeDaemon(key: key) 24 | } label: { 25 | Label("Disable", systemImage: "xmark") 26 | } 27 | } 28 | } 29 | } 30 | Section(header: Text("Screwed up ?"), footer: Text("Delete currently disabled daemon list, and start-over. iOS will generate the file back from scratch next reboot.")) { 31 | Button("Reset") { 32 | result = RootHelper.removeItem(at: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 33 | result = RootHelper.copy(from: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist"), to: URL(fileURLWithPath: "/var/mobile/Documents/disabled.gerackup")) 34 | result = RootHelper.removeItem(at: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist")) 35 | result = RootHelper.removeItem(at: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.migrated")) 36 | data = [:] 37 | UIApplication.shared.alert(title:"Done !", body:"Please manually reboot your device", withButton: false) 38 | } 39 | } 40 | } 41 | .interactiveDismissDisabled() 42 | .navigationBarTitle("Manage") 43 | .navigationBarItems(trailing: 44 | Button("Save") { 45 | saveIt() 46 | close() 47 | } 48 | ) 49 | } 50 | .onAppear { 51 | loadIt() 52 | } 53 | } 54 | private func loadIt() { 55 | if let dict = NSDictionary(contentsOfFile: plistPath) as? [String: Bool] { 56 | data = dict 57 | } 58 | } 59 | private func saveIt() { 60 | (data as NSDictionary).write(toFile: plistPath, atomically: true) 61 | print(result) 62 | var result = RootHelper.removeItem(at: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist")) 63 | result = RootHelper.move(from: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist"), to: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist")) 64 | print(result) 65 | } 66 | 67 | private func removeDaemon(key: String) { 68 | data[key] = nil 69 | } 70 | 71 | func close() { 72 | dismiss() 73 | } 74 | } 75 | 76 | #Preview { 77 | CurrentlyDisabledDaemonView() 78 | } 79 | -------------------------------------------------------------------------------- /Geranium/DaemonMan/DaemonDisabler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DaemonDisabler.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 15.12.23. 6 | // 7 | 8 | import Foundation 9 | 10 | func daemonManagement(key: String, value: Bool, plistPath: String) { 11 | var result = "" 12 | // copy plist 13 | var error = RootHelper.copy(from: URL(fileURLWithPath: plistPath), to: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 14 | print(error) 15 | error = RootHelper.setPermission(url: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 16 | guard let plistData = FileManager.default.contents(atPath: "/var/mobile/Documents/disabled.plist"), 17 | var plistDictionary = try? PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? NSMutableDictionary 18 | else { 19 | print("Error") 20 | return 21 | } 22 | plistDictionary[key] = value 23 | print("new data being transmitted") 24 | if let newData = try? PropertyListSerialization.data(fromPropertyList: plistDictionary, format: .xml, options: 0) { 25 | do { 26 | try newData.write(to: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 27 | } catch { 28 | print("Error \(error)") 29 | } 30 | } else { 31 | print("Error data convert") 32 | } 33 | // we need to do shit with roothelper 34 | print("roothelper") 35 | result = RootHelper.removeItem(at: URL(fileURLWithPath: plistPath)) 36 | print(result) 37 | print("roothelper2") 38 | result = RootHelper.move(from: URL(fileURLWithPath :"/var/mobile/Documents/disabled.plist"), to: URL(fileURLWithPath: plistPath)) 39 | if result != "0" { 40 | sendLog("error while deleting daemon. roothelper returned \(result)") 41 | } 42 | RootHelper.setDaemonPermission(url: URL(fileURLWithPath: plistPath)) 43 | RootHelper.removeItem(at: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 44 | print(result) 45 | } 46 | -------------------------------------------------------------------------------- /Geranium/DaemonMan/DaemonView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DaemonView.swift 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 10/12/2023. 6 | // 7 | 8 | import SwiftUI 9 | import AlertKit 10 | 11 | struct DaemonView: View { 12 | @State private var searchText = "" 13 | @State private var daemonFiles: [String] = [] 14 | @State private var labelForDaemon: String? 15 | @State private var toDisable: [String] = [] 16 | @State private var manageSheet: Bool = false 17 | @State private var isAlphabeticalOrder: Bool = false 18 | @State private var isEditing = false 19 | @State private var selectedItems: Set = Set() 20 | @State private var initialcount: Int = 0 21 | @State private var cancelCount: Int = 0 22 | @AppStorage("isDaemonFirstRun") var isDaemonFirstRun: Bool = true 23 | 24 | var body: some View { 25 | if UIDevice.current.userInterfaceIdiom == .pad { 26 | if #available(iOS 16.0, *) { 27 | NavigationStack { 28 | DaemonMainView() 29 | } 30 | } 31 | // This is probably useless. 32 | else { 33 | NavigationView { 34 | DaemonMainView() 35 | } 36 | } 37 | } else { 38 | NavigationView { 39 | DaemonMainView() 40 | } 41 | } 42 | } 43 | @ViewBuilder 44 | private func DaemonMainView() -> some View { 45 | List { 46 | ForEach(daemonFiles.filter { searchText.isEmpty || $0.localizedCaseInsensitiveContains(searchText) }, id: \.self) { fileName in 47 | HStack { 48 | if isEditing { 49 | Image(systemName: selectedItems.contains(getLabel(fileName) ?? fileName) ? "checkmark.circle.fill" : "circle") 50 | .foregroundColor(selectedItems.contains(getLabel(fileName) ?? fileName) ? .accentColor : .gray) 51 | } 52 | Text(fileName) 53 | .lineLimit(1) 54 | .truncationMode(.tail) 55 | .foregroundColor(toDisable.contains(getLabel(fileName) ?? fileName) ? .red : .primary) 56 | Spacer() 57 | } 58 | .contentShape(Rectangle()) 59 | .onTapGesture { 60 | if isEditing { 61 | toggleSelection(for: getLabel(fileName) ?? fileName) 62 | toDisable = Array(selectedItems) 63 | miniimpactVibrate() 64 | } 65 | } 66 | .swipeActions { 67 | if let existingIndex = toDisable.firstIndex(of: getLabel(fileName) ?? fileName) { 68 | Button(role: .destructive) { 69 | toDisable.remove(at: existingIndex) 70 | if isAlphabeticalOrder { 71 | daemonFiles.sort() 72 | } 73 | } label: { 74 | Label("Enable", systemImage: "hand.thumbsup") 75 | } 76 | .tint(.green) 77 | } 78 | else { 79 | Button(role: .destructive) { 80 | toDisable.append(getLabel(fileName) ?? fileName) 81 | if isAlphabeticalOrder { 82 | daemonFiles.sort() 83 | } 84 | } label: { 85 | Label("Disable", systemImage: "hand.thumbsdown") 86 | } 87 | } 88 | } 89 | .animation(.easeInOut) 90 | } 91 | } 92 | .searchable(text: $searchText) 93 | .toolbar{ 94 | ToolbarItem(placement: .navigationBarLeading) { 95 | Text("Daemons") 96 | .font(.title2) 97 | .bold() 98 | } 99 | ToolbarItem(placement: .navigationBarTrailing) { 100 | Button(action: { 101 | if isEditing { 102 | selectedItems.removeAll() 103 | withAnimation { 104 | isEditing = false 105 | } 106 | } 107 | else { 108 | selectedItems = Set(toDisable) 109 | withAnimation { 110 | isEditing.toggle() 111 | } 112 | AlertKitAPI.present( 113 | title: "Selected", 114 | icon: .done, 115 | style: .iOS17AppleMusic, 116 | haptic: .success 117 | ) 118 | } 119 | }) { 120 | if isEditing { 121 | Text("Done") 122 | .bold() 123 | } else { 124 | Image(systemName: "pencil") 125 | } 126 | } 127 | } 128 | ToolbarItem(placement: .navigationBarTrailing) { 129 | if !isEditing { 130 | Toggle(isOn: $isAlphabeticalOrder) { 131 | Label("Alphabetical", systemImage: "textformat") 132 | } 133 | } 134 | } 135 | ToolbarItem(placement: .navigationBarTrailing) { 136 | if !isEditing { 137 | Button(action: { 138 | var error = RootHelper.removeItem(at: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 139 | error = RootHelper.copy(from: URL(fileURLWithPath: "/var/db/com.apple.xpc.launchd/disabled.plist"), to: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 140 | print(error) 141 | error = RootHelper.setPermission(url: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 142 | manageSheet.toggle() 143 | }) { 144 | Image(systemName: "list.bullet") 145 | .resizable() 146 | .aspectRatio(contentMode: .fit) 147 | .frame(width: 24, height: 24) 148 | } 149 | } 150 | } 151 | ToolbarItem(placement: .navigationBarTrailing) { 152 | if !isEditing { 153 | Button(action: { 154 | if toDisable == [] { 155 | AlertKitAPI.present( 156 | title: "No Daemon Selected !", 157 | icon: .error, 158 | style: .iOS17AppleMusic, 159 | haptic: .error 160 | ) 161 | } 162 | else { 163 | initialcount = toDisable.count 164 | cancelCount = 0 165 | processNextDaemon() 166 | } 167 | }) { 168 | Image(systemName: "checkmark") 169 | .resizable() 170 | .aspectRatio(contentMode: .fit) 171 | .frame(width: 24, height: 24) 172 | } 173 | } 174 | } 175 | } 176 | .onAppear { 177 | updateDaemonFiles() 178 | } 179 | .onChange(of: isAlphabeticalOrder) { newValue in 180 | if newValue { 181 | daemonFiles.sort() 182 | } else { 183 | updateDaemonFiles() 184 | } 185 | } 186 | .sheet(isPresented: $isDaemonFirstRun) { 187 | if #available(iOS 16.0, *) { 188 | NavigationStack { 189 | DaemonWelcomeView() 190 | } 191 | } else { 192 | NavigationView { 193 | DaemonWelcomeView() 194 | } 195 | } 196 | } 197 | .sheet(isPresented:$manageSheet) { 198 | CurrentlyDisabledDaemonView() 199 | .onDisappear { 200 | AlertKitAPI.present( 201 | title: "Saved !", 202 | icon: .done, 203 | style: .iOS17AppleMusic, 204 | haptic: .success 205 | ) 206 | } 207 | } 208 | } 209 | 210 | private func updateDaemonFiles() { 211 | let launchDaemonsURL = URL(fileURLWithPath: "/System/Library/LaunchDaemons/") 212 | do { 213 | let fileManager = FileManager.default 214 | let files = try fileManager.contentsOfDirectory(atPath: launchDaemonsURL.path) 215 | daemonFiles = files.map { URL(fileURLWithPath: $0).deletingPathExtension().lastPathComponent } 216 | } catch { 217 | UIApplication.shared.alert(body: "error reading LaunchDaemons directory: \(error)") 218 | } 219 | } 220 | 221 | private func getLabel(_ fileName: String) -> String? { 222 | let plistURL = URL(fileURLWithPath: "/System/Library/LaunchDaemons/\(fileName).plist") 223 | do { 224 | let data = try Data(contentsOf: plistURL) 225 | let plist = try PropertyListSerialization.propertyList(from: data, format: nil) 226 | if let dict = plist as? [String: Any], let label = dict["Label"] as? String { 227 | return label 228 | } 229 | } catch { 230 | UIApplication.shared.alert(body: "Error reading plist: \(error)") 231 | } 232 | return nil 233 | } 234 | 235 | private func toggleSelection(for SfileName: String) { 236 | if selectedItems.contains(SfileName) { 237 | selectedItems.remove(SfileName) 238 | } else { 239 | selectedItems.insert(SfileName) 240 | } 241 | } 242 | 243 | private func processNextDaemon() { 244 | guard let nextDaemon = toDisable.first else { 245 | if cancelCount != initialcount { 246 | RootHelper.removeItem(at: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 247 | UIApplication.shared.alert(title: "Done.", body: "Successfully disabled selected daemons. Please manually reboot your device now.", withButton: false) 248 | } 249 | else { 250 | RootHelper.removeItem(at: URL(fileURLWithPath: "/var/mobile/Documents/disabled.plist")) 251 | } 252 | initialcount = 0 253 | cancelCount = 0 254 | return 255 | } 256 | 257 | UIApplication.shared.confirmAlert(title: "Are you sure you want to disable \(nextDaemon) ?", body: "You can still undo this action after by going to the manager icon.", onOK: { 258 | daemonManagement(key: nextDaemon, value: true, plistPath: "/var/db/com.apple.xpc.launchd/disabled.plist") 259 | toDisable.removeFirst() 260 | processNextDaemon() 261 | }, noCancel: false, onCancel: { 262 | cancelCount += 1 263 | toDisable.removeFirst() 264 | processNextDaemon() 265 | }, yes: true) 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /Geranium/DaemonMan/DaemonWelcomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DaemonWelcomeView.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 21.12.23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DaemonWelcomeView: View { 11 | @Environment(\.dismiss) var dismiss 12 | var body: some View { 13 | List { 14 | 15 | Section(header: Text("Purpose")) { 16 | Text("By adding some rules to iOS internals, this software prevents the daemon from launching when you boot your phone. The app lists every running daemon, and you can swipe to the left to remove one of them.") 17 | } 18 | 19 | Section(header: Text("WARNING")) { 20 | Text("DISABLING SYSTEM DAEMONS IS DANGEROUS. YOU COULD BOOTLOOP YOUR DEVICE. PROCEED WITH CAUTION. I AM NOT RESPONSIBLE FOR ANY PROBLEM ON YOUR DEVICE.") 21 | .foregroundStyle(.red) 22 | } 23 | Section(header: Text("Please note")) { 24 | Text("If you want to revert any of your choice, you should go into the manager (list icon next to the apply icon), where you can toggle disabled daemons.") 25 | .foregroundStyle(.green) 26 | } 27 | Section(header: Text("More info")) { 28 | Button("A list of daemons and what they do") { 29 | UIApplication.shared.open(URL(string: "https://www.reddit.com/r/jailbreak/comments/10v7j59/tutorial_list_of_ios_daemons_and_what_they_do/")!) 30 | } 31 | Button("A list of daemons you could disable") { 32 | UIApplication.shared.open(URL(string: "https://www.reddit.com/r/jailbreak/comments/10v7j59/comment/j8s1lgj/")!) 33 | } 34 | } 35 | } 36 | .navigationTitle("Notice") 37 | .navigationBarItems(trailing: Button("Understood") { 38 | close() 39 | }) 40 | .environment(\.defaultMinListRowHeight, 50) 41 | .interactiveDismissDisabled() 42 | } 43 | 44 | func close() { 45 | dismiss() 46 | } 47 | } 48 | 49 | #Preview { 50 | DaemonWelcomeView() 51 | } 52 | -------------------------------------------------------------------------------- /Geranium/Geranium-Bringing-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Geranium-Bringing-Header.h 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 10/12/2023. 6 | // 7 | 8 | #import "LocSim/LocSimPrivateHeaders.h" 9 | #import "TSUtil.h" 10 | -------------------------------------------------------------------------------- /Geranium/Geranium.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.live.cclerc.geraniumBookmarks 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Geranium/GeraniumApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeraniumApp.swift 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 10/12/2023. 6 | // 7 | 8 | import SwiftUI 9 | @main 10 | struct GeraniumApp: App { 11 | @State var isInBetaProgram = false // MARK: change this value if the build is Beta 12 | @StateObject private var appSettings = AppSettings() 13 | @Environment(\.dismiss) var dismiss 14 | var body: some Scene { 15 | WindowGroup { 16 | ContentView() 17 | .onAppear { 18 | if checkSandbox(), !appSettings.tsBypass, !appSettings.isFirstRun, isInBetaProgram{ 19 | UIApplication.shared.alert(title:"Geranium wasn't installed with TrollStore", body:"Unable to create test file. The app cannot work without the correct entitlements. Please use TrollStore to install it.", withButton:true) 20 | } 21 | 22 | if !appSettings.updBypass, let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, let url = URL(string: "https://api.github.com/repos/c22dev/Geranium/releases/latest") { 23 | let task = URLSession.shared.dataTask(with: url) {(data, response, error) in 24 | guard let data = data else { return } 25 | if let json = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] { 26 | if (json["tag_name"] as? String)?.replacingOccurrences(of: "v", with: "").compare(version, options: .numeric) == .orderedDescending { 27 | UIApplication.shared.confirmAlert(title: "Update available", body: "Geranium \(json["tag_name"] as? String ?? "update") is available, do you want to install it (you might need to enable Magnifier URL Scheme in TrollStore Settings)?", onOK: { 28 | UIApplication.shared.open(URL(string: "apple-magnifier://install?url=https://github.com/c22dev/Geranium/releases/download/\(json["tag_name"] as? String ?? version)/Geranium.tipa")!) 29 | }, noCancel: false) 30 | } 31 | } 32 | } 33 | task.resume() 34 | } 35 | _ = RootHelper.loadMCM() 36 | } 37 | .sheet(isPresented: $appSettings.isFirstRun) { 38 | if #available(iOS 16.0, *) { 39 | NavigationStack { 40 | WelcomeView(loggingAllowed: $appSettings.loggingAllowed, updBypass: $appSettings.updBypass) 41 | } 42 | } else { 43 | NavigationView { 44 | WelcomeView(loggingAllowed: $appSettings.loggingAllowed, updBypass: $appSettings.updBypass) 45 | } 46 | } 47 | } 48 | .sheet(isPresented: $isInBetaProgram) { 49 | BetaView() 50 | } 51 | } 52 | } 53 | } 54 | 55 | class AppSettings: ObservableObject { 56 | @AppStorage("TSBypass") var tsBypass: Bool = false 57 | @AppStorage("UPDBypass") var updBypass: Bool = false 58 | @AppStorage("isLoggingAllowed") var loggingAllowed: Bool = true 59 | @AppStorage("isFirstRun") var isFirstRun: Bool = true 60 | @AppStorage("minimSizeC") var minimSizeC: Double = 50.0 61 | @AppStorage("keepCheckBoxesC") var keepCheckBoxesC: Bool = true 62 | @AppStorage("LocSimAttempts") var locSimAttemptNB: Int = 1 63 | @AppStorage("locSimMultipleAttempts") var locSimMultipleAttempts: Bool = false 64 | @AppStorage("usrUUID") var usrUUID: String = UIDevice.current.identifierForVendor?.uuidString ?? "unknown" 65 | @AppStorage("languageCode") var languageCode: String = "" 66 | @AppStorage("defaultTab") var defaultTab: Int = 1 67 | @AppStorage("firstCleanerTime") var firstCleanerTime: Bool = true 68 | @AppStorage("tmpClean") var tmpClean: Bool = true 69 | @AppStorage("getSizes") var getSizes: Bool = false 70 | } 71 | 72 | var langaugee: String = { 73 | if AppSettings().languageCode.isEmpty { 74 | return "\(Locale.current.languageCode ?? "en-US")" 75 | } 76 | else { 77 | return "\(Locale.current.languageCode ?? "en")-\(AppSettings().languageCode)" 78 | } 79 | }() 80 | -------------------------------------------------------------------------------- /Geranium/HomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeView.swift 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 10/12/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct HomeView: View { 11 | @Environment(\.colorScheme) var colorScheme 12 | @State var isDebugSheetOn = false 13 | var body: some View { 14 | if #available(iOS 16.0, *) { 15 | NavigationStack { 16 | homeMainView() 17 | } 18 | } else { 19 | NavigationView { 20 | homeMainView() 21 | } 22 | } 23 | } 24 | 25 | @ViewBuilder 26 | private func homeMainView() -> some View { 27 | VStack { 28 | VStack { 29 | Text("") 30 | .padding(.bottom, 20) 31 | Image(uiImage: Bundle.main.icon!) 32 | .cornerRadius(10) 33 | Text("Geranium") 34 | .font(.title2) 35 | .bold() 36 | 37 | Text("made by c22dev") 38 | .font(.footnote) 39 | .foregroundColor(.secondary) 40 | .padding(.bottom) 41 | Button("Respring", action: { 42 | respring() 43 | }) 44 | .padding(.bottom, 24) 45 | } 46 | .background(Color(UIColor.systemGroupedBackground)) 47 | .frame(maxWidth: .infinity) 48 | 49 | ZStack { 50 | if colorScheme == .dark { 51 | Color.black 52 | .ignoresSafeArea() 53 | } 54 | else { 55 | Color.white 56 | .ignoresSafeArea() 57 | } 58 | Text("") 59 | List { 60 | Section (header: Text("Credits")) { 61 | if !isMiniDevice() { 62 | LinkCell(imageLink: "https://cclerc.ch/db/geranium/102235607.png", url: "https://github.com/c22dev", title: "c22dev", description: "Main Developer") 63 | } 64 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/470637062870269952/67eb5d0a0501a96ab0a014ae89027e32.webp?size=160", url: "https://github.com/bomberfish", title: "BomberFish", description: "Daemon Listing") 65 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/412187004407775242/1df69ac879b9e5f98396553eeac80cec.webp?size=160", url: "https://github.com/sourcelocation", title: "sourcelocation", description: "Swift UI Functions") 66 | LinkCell(imageLink: "https://cclerc.ch/db/geranium/85764897.png", url: "https://github.com/haxi0", title: "haxi0", description: "Welcome Page source") 67 | } 68 | } 69 | .disableListScroll() 70 | } 71 | } 72 | .background(Color(UIColor.systemGroupedBackground)) 73 | .toolbar{ 74 | ToolbarItem(placement: .navigationBarTrailing) { 75 | Button(action: { 76 | isDebugSheetOn.toggle() 77 | }) { 78 | Image(systemName: "gear") 79 | .resizable() 80 | .aspectRatio(contentMode: .fit) 81 | .frame(width: 24, height: 24) 82 | } 83 | } 84 | } 85 | .sheet(isPresented: $isDebugSheetOn) { 86 | SettingsView() 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Geranium/Icons/Beta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c22dev/Geranium/348772ab9ca7906a875404e62c93f392dd2ff51a/Geranium/Icons/Beta.png -------------------------------------------------------------------------------- /Geranium/Icons/Bouquet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c22dev/Geranium/348772ab9ca7906a875404e62c93f392dd2ff51a/Geranium/Icons/Bouquet.png -------------------------------------------------------------------------------- /Geranium/Icons/Flore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c22dev/Geranium/348772ab9ca7906a875404e62c93f392dd2ff51a/Geranium/Icons/Flore.png -------------------------------------------------------------------------------- /Geranium/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIcons 6 | 7 | CFBundleAlternateIcons 8 | 9 | Beta 10 | 11 | CFBundleIconFiles 12 | 13 | Beta 14 | 15 | 16 | Bouquet 17 | 18 | CFBundleIconFiles 19 | 20 | Bouquet 21 | 22 | 23 | Flore 24 | 25 | CFBundleIconFiles 26 | 27 | Flore 28 | 29 | 30 | 31 | CFBundlePrimaryIcon 32 | 33 | CFBundleIconFiles 34 | 35 | AppIcon 36 | 37 | CFBundleIconName 38 | 39 | UIPrerenderedIcon 40 | 41 | 42 | UINewsstandIcon 43 | 44 | CFBundleIconFiles 45 | 46 | 47 | 48 | UINewsstandBindingEdge 49 | UINewsstandBindingEdgeLeft 50 | UINewsstandBindingType 51 | UINewsstandBindingTypeMagazine 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Geranium/Libs/Addon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Addon.swift 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 17/12/2022. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import Combine 11 | import UIKit 12 | 13 | struct MaterialView: UIViewRepresentable { 14 | let material: UIBlurEffect.Style 15 | 16 | init(_ material: UIBlurEffect.Style) { 17 | self.material = material 18 | } 19 | 20 | func makeUIView(context: Context) -> UIVisualEffectView { 21 | UIVisualEffectView(effect: UIBlurEffect(style: material)) 22 | } 23 | 24 | func updateUIView(_ uiView: UIVisualEffectView, context: Context) { 25 | uiView.effect = UIBlurEffect(style: material) 26 | } 27 | } 28 | 29 | fileprivate var cancellables = [String : AnyCancellable] () 30 | 31 | public extension Published { 32 | init(wrappedValue defaultValue: Value, key: String) { 33 | let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue 34 | self.init(initialValue: value) 35 | cancellables[key] = projectedValue.sink { val in 36 | UserDefaults.standard.set(val, forKey: key) 37 | } 38 | } 39 | } 40 | 41 | extension StringProtocol { 42 | subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] } 43 | subscript(range: Range) -> SubSequence { 44 | let startIndex = index(self.startIndex, offsetBy: range.lowerBound) 45 | return self[startIndex..) -> SubSequence { 48 | let startIndex = index(self.startIndex, offsetBy: range.lowerBound) 49 | return self[startIndex..) -> SubSequence { self[index(startIndex, offsetBy: range.lowerBound)...] } 52 | subscript(range: PartialRangeThrough) -> SubSequence { self[...index(startIndex, offsetBy: range.upperBound)] } 53 | subscript(range: PartialRangeUpTo) -> SubSequence { self[.. some View { 63 | let uiColor = UIColor(color) 64 | 65 | // Set appearance for both normal and large sizes. 66 | UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: uiColor ] 67 | UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: uiColor ] 68 | 69 | return self 70 | } 71 | } 72 | 73 | func respring() { 74 | UIImpactFeedbackGenerator(style: .soft).impactOccurred() 75 | 76 | let animator = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 1) { 77 | let windows: [UIWindow] = UIApplication.shared.connectedScenes 78 | .compactMap { $0 as? UIWindowScene } 79 | .flatMap(\.windows) 80 | 81 | for window in windows { 82 | window.alpha = 0 83 | window.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) 84 | } 85 | } 86 | 87 | animator.addCompletion { _ in 88 | killall("SpringBoard") 89 | killall("FrontBoard") 90 | killall("BackBoard") 91 | // if others failed... 92 | UIApplication.shared.respringDeprecated() 93 | sleep(2) 94 | exit(0) 95 | } 96 | 97 | animator.startAnimation() 98 | } 99 | 100 | func exitGracefully() { 101 | UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil) 102 | Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in 103 | exit(0) 104 | } 105 | } 106 | 107 | 108 | extension UIColor { 109 | var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { 110 | var red: CGFloat = 0 111 | var green: CGFloat = 0 112 | var blue: CGFloat = 0 113 | var alpha: CGFloat = 0 114 | getRed(&red, green: &green, blue: &blue, alpha: &alpha) 115 | 116 | return (red, green, blue, alpha) 117 | } 118 | } 119 | 120 | extension Color { 121 | init(uiColor14: UIColor) { 122 | self.init(red: Double(uiColor14.rgba.red), 123 | green: Double(uiColor14.rgba.green), 124 | blue: Double(uiColor14.rgba.blue), 125 | opacity: Double(uiColor14.rgba.alpha)) 126 | } 127 | } 128 | 129 | var currentUIAlertController: UIAlertController? 130 | 131 | extension UIApplication { 132 | func dismissAlert(animated: Bool) { 133 | DispatchQueue.main.async { 134 | currentUIAlertController?.dismiss(animated: animated) 135 | } 136 | } 137 | func alert(title: String = "Error", body: String, animated: Bool = true, withButton: Bool = true) { 138 | DispatchQueue.main.async { 139 | currentUIAlertController = UIAlertController(title: title, message: body, preferredStyle: .alert) 140 | if withButton { currentUIAlertController?.addAction(.init(title: "OK", style: .cancel)) } 141 | self.present(alert: currentUIAlertController!) 142 | } 143 | } 144 | func confirmAlert(title: String = "Error", body: String, onOK: @escaping () -> (), noCancel: Bool, onCancel: (() -> ())? = nil, yes: Bool = false) { 145 | DispatchQueue.main.async { 146 | currentUIAlertController = UIAlertController(title: title, message: body, preferredStyle: .alert) 147 | if !noCancel { 148 | currentUIAlertController?.addAction(.init(title: "Cancel", style: .cancel, handler: { _ in 149 | onCancel?() 150 | })) 151 | } 152 | 153 | let okActionTitle = yes ? "Yes" : "OK" 154 | currentUIAlertController?.addAction(.init(title: okActionTitle, style: noCancel ? .cancel : .default, handler: { _ in 155 | onOK() 156 | })) 157 | 158 | self.present(alert: currentUIAlertController!) 159 | } 160 | } 161 | 162 | func TextFieldAlert(title: String, message: String? = nil, textFieldPlaceHolder: String, secondTextFieldPlaceHolder: String? = nil, completion: @escaping (String?, String?) -> Void) { 163 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 164 | 165 | alertController.addTextField { (textField) in 166 | textField.placeholder = textFieldPlaceHolder 167 | } 168 | 169 | if let secondPlaceholder = secondTextFieldPlaceHolder { 170 | alertController.addTextField { (textField) in 171 | textField.placeholder = secondPlaceholder 172 | } 173 | } 174 | 175 | let okAction = UIAlertAction(title: "OK", style: .default) { _ in 176 | let firstText = alertController.textFields?.first?.text 177 | let secondText = secondTextFieldPlaceHolder != nil ? alertController.textFields?[1].text : nil 178 | completion(firstText, secondText) 179 | } 180 | 181 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) 182 | alertController.addAction(okAction) 183 | alertController.addAction(cancelAction) 184 | 185 | present(alert: alertController) 186 | } 187 | 188 | func change(title: String = "Error", body: String) { 189 | DispatchQueue.main.async { 190 | currentUIAlertController?.title = title 191 | currentUIAlertController?.message = body 192 | } 193 | } 194 | 195 | func present(alert: UIAlertController) { 196 | if var topController = self.windows.first?.rootViewController { 197 | while let presentedViewController = topController.presentedViewController { 198 | topController = presentedViewController 199 | } 200 | topController.present(alert, animated: true) 201 | } 202 | } 203 | 204 | func respringDeprecated() { 205 | let app = self 206 | // Credit to Amy While for this respring bug 207 | guard let window = app.windows.first else { return } 208 | while true { 209 | window.snapshotView(afterScreenUpdates: false) 210 | } 211 | } 212 | } 213 | 214 | func checkSandbox() -> Bool { 215 | let fileManager = FileManager.default 216 | fileManager.createFile(atPath: "/var/mobile/geraniumtemp", contents: nil) 217 | if fileManager.fileExists(atPath: "/var/mobile/geraniumtemp") { 218 | do { 219 | try fileManager.removeItem(atPath: "/var/mobile/geraniumtemp") 220 | } catch { 221 | print("Failed to remove sandbox check file") 222 | } 223 | return false 224 | } 225 | 226 | return true 227 | } 228 | 229 | func impactVibrate() { 230 | let impact = UIImpactFeedbackGenerator(style: .medium) 231 | impact.impactOccurred() 232 | } 233 | 234 | func miniimpactVibrate() { 235 | let impact = UIImpactFeedbackGenerator(style: .light) 236 | impact.impactOccurred() 237 | } 238 | func successVibrate() { 239 | let generator = UINotificationFeedbackGenerator() 240 | generator.notificationOccurred(.success) 241 | } 242 | func errorVibrate() { 243 | let generator = UINotificationFeedbackGenerator() 244 | generator.notificationOccurred(.error) 245 | } 246 | 247 | extension Bundle { 248 | public var icon: UIImage? { 249 | if let icons = infoDictionary?["CFBundleIcons"] as? [String: Any], 250 | let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any], 251 | let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], 252 | let lastIcon = iconFiles.last { 253 | return UIImage(named: lastIcon) 254 | } 255 | return nil 256 | } 257 | } 258 | 259 | struct LinkCell: View { 260 | var imageLink: String 261 | var url: String 262 | var title: String 263 | var description: String 264 | var systemImage: Bool = false 265 | var circle: Bool = false 266 | 267 | var body: some View { 268 | VStack(alignment: .leading) { 269 | HStack(alignment: .center) { 270 | Group { 271 | if let imageURL = URL(string: imageLink) { 272 | AsyncImageView(url: imageURL) 273 | .frame(width: 30, height: 30) 274 | .cornerRadius(25) 275 | } 276 | } 277 | .aspectRatio(contentMode: .fit) 278 | 279 | Button(action: { 280 | UIApplication.shared.open(URL(string: url)!) 281 | }) { 282 | Text(title) 283 | .font(.headline) 284 | .foregroundColor(Color.accentColor) 285 | } 286 | } 287 | Text(description) 288 | .font(.caption) 289 | .foregroundColor(.gray) 290 | } 291 | } 292 | } 293 | 294 | struct AsyncImageView: View { 295 | @StateObject private var imageLoader = ImageLoader() 296 | 297 | var url: URL 298 | 299 | var body: some View { 300 | Group { 301 | if let uiImage = imageLoader.image { 302 | Image(uiImage: uiImage) 303 | .resizable() 304 | .scaledToFit() 305 | } else { 306 | ProgressView() 307 | } 308 | } 309 | .onAppear { 310 | imageLoader.loadImage(from: url) 311 | } 312 | } 313 | } 314 | 315 | class ImageLoader: ObservableObject { 316 | @Published var image: UIImage? 317 | 318 | func loadImage(from url: URL) { 319 | URLSession.shared.dataTask(with: url) { data, _, error in 320 | if let data = data, let uiImage = UIImage(data: data) { 321 | DispatchQueue.main.async { 322 | self.image = uiImage 323 | } 324 | } 325 | }.resume() 326 | } 327 | } 328 | 329 | func truelyEnabled(_ inputBoolean: Bool) -> String { 330 | if inputBoolean{ 331 | return "Enabled" 332 | } 333 | else { 334 | return "Disabled" 335 | } 336 | } 337 | 338 | // https://stackoverflow.com/a/76762126 339 | extension View { 340 | @ViewBuilder 341 | func disableListScroll() -> some View { 342 | if #available(iOS 16.0, *) { 343 | self 344 | .scrollDisabled(true) 345 | } else { 346 | self 347 | .simultaneousGesture(DragGesture(minimumDistance: 0), including: .all) 348 | } 349 | }} 350 | 351 | func isMiniDevice() -> Bool { 352 | let scale = UIScreen.main.scale 353 | 354 | let ppi = scale * ((UIDevice.current.userInterfaceIdiom == .pad) ? 132 : 163); 355 | let width = UIScreen.main.bounds.size.width * scale 356 | let height = UIScreen.main.bounds.size.height * scale 357 | let horizontal = width / ppi, vertical = height / ppi; 358 | let diagonal = sqrt(pow(horizontal, 2) + pow(vertical, 2)) 359 | 360 | let screenSize = String(format: "%0.1f", diagonal) 361 | let screensize = Float(screenSize) ?? 0.0 362 | 363 | if screensize >= 5.5, UIDevice.current.userInterfaceIdiom != .pad { 364 | return false 365 | } 366 | else { 367 | return true 368 | } 369 | } 370 | 371 | // https://stackoverflow.com/a/56444424 372 | func betterExit() { 373 | UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil) 374 | Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { _ in 375 | exit(0) 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /Geranium/Libs/LogHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LogHelper.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 19.12.23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import UIKit 11 | 12 | func sendLog(_ logMessage: String, isAnalystic: Bool = false) { 13 | @StateObject var appSettings = AppSettings() 14 | @AppStorage("isLoggingAllowed") var loggingAllowed: Bool = true 15 | if loggingAllowed { 16 | let usrUUID = appSettings.usrUUID 17 | // I am too lazy to host this on my personal server, considering there might be a large amount of requests 18 | // Server source code : https://github.com/c22dev/GeraniumServer/tree/python-rewrite 19 | // If you find a way to do something you're not supposed too on this server, pls contact me @c22dev on Discord 20 | let url = URL(string: "https://c22server.pythonanywhere.com/")! 21 | let responseString = "" 22 | var message = "" 23 | var request = URLRequest(url: url) 24 | request.httpMethod = "POST" 25 | if isAnalystic { 26 | message = "--Analytic--\nDevice \(getDeviceCode() ?? "\(UIDevice.current.model)unknown")\nVersion \( UIDevice.current.systemVersion)\nApp Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "unknown")\nTrollStore \(!checkSandbox())\n\nLog\n\(logMessage)" 27 | } 28 | else { 29 | message = "--Log--\nUUID \(usrUUID)\nDevice \(getDeviceCode() ?? "\(UIDevice.current.model)unknown")\nVersion \( UIDevice.current.systemVersion)\nApp Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "unknown")\nTrollStore \(!checkSandbox())\n\nLog\n\(logMessage)" 30 | } 31 | let postData = message.data(using: .utf8) 32 | 33 | request.httpBody = postData 34 | 35 | let task = URLSession.shared.dataTask(with: request) { (data, response, error) in 36 | guard let data = data else { 37 | if let error = error { 38 | print("Error: \(error)") 39 | } 40 | return 41 | } 42 | if let responseString = String(data: data, encoding: .utf8) { 43 | print("Response: \(responseString)") 44 | } 45 | } 46 | task.resume() 47 | } 48 | } 49 | 50 | // https://levelup.gitconnected.com/device-information-in-swift-eef45be38109 51 | func getDeviceCode() -> String? { 52 | var systemInfo = utsname() 53 | uname(&systemInfo) 54 | let modelCode = withUnsafePointer(to: &systemInfo.machine) { 55 | $0.withMemoryRebound(to: CChar.self, capacity: 1) { 56 | ptr in String.init(validatingUTF8: ptr) 57 | } 58 | } 59 | return modelCode 60 | } 61 | -------------------------------------------------------------------------------- /Geranium/Libs/RootHelperMan.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootHelperMan.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 18.12.23. 6 | // 7 | 8 | // thanks sourceloc 9 | import UIKit 10 | 11 | class RootHelper { 12 | static let rootHelperPath = Bundle.main.url(forAuxiliaryExecutable: "GeraniumRootHelper")?.path ?? "/" 13 | 14 | static func whatsthePath() -> String { 15 | return rootHelperPath 16 | } 17 | 18 | static func writeStr(_ str: String, to url: URL) -> String { 19 | let code = spawnRoot(rootHelperPath, ["writedata", str, url.path], nil, nil) 20 | return String(code) 21 | } 22 | static func move(from sourceURL: URL, to destURL: URL) -> String { 23 | let code = spawnRoot(rootHelperPath, ["filemove", sourceURL.path, destURL.path], nil, nil) 24 | return String(code) 25 | } 26 | static func copy(from sourceURL: URL, to destURL: URL) -> String { 27 | let code = spawnRoot(rootHelperPath, ["filecopy", sourceURL.path, destURL.path], nil, nil) 28 | return String(code) 29 | } 30 | static func createDirectory(at url: URL) -> String { 31 | let code = spawnRoot(rootHelperPath, ["makedirectory", url.path, ""], nil, nil) 32 | return String(code) 33 | } 34 | static func removeItem(at url: URL) -> String { 35 | let code = spawnRoot(rootHelperPath, ["removeitem", url.path, ""], nil, nil) 36 | return String(code) 37 | } 38 | static func setPermission(url: URL) -> String { 39 | let code = spawnRoot(rootHelperPath, ["permissionset", url.path, ""], nil, nil) 40 | return String(code) 41 | } 42 | static func setDaemonPermission(url: URL) -> String { 43 | let code = spawnRoot(rootHelperPath, ["daemonperm", url.path, ""], nil, nil) 44 | return String(code) 45 | } 46 | static func rebuildIconCache() -> String { 47 | let code = spawnRoot(rootHelperPath, ["rebuildiconcache", "", ""], nil, nil) 48 | return String(code) 49 | } 50 | static func loadMCM() -> String { 51 | let code = spawnRoot(rootHelperPath, ["", "", ""], nil, nil) 52 | return String(code) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Geranium/Libs/TrollStore/CoreServices.h: -------------------------------------------------------------------------------- 1 | @interface LSBundleProxy 2 | @property (nonatomic,readonly) NSString * bundleIdentifier; 3 | @property (nonatomic) NSURL* dataContainerURL; 4 | -(NSString*)localizedName; 5 | @end 6 | 7 | @interface LSApplicationProxy : LSBundleProxy 8 | + (instancetype)applicationProxyForIdentifier:(NSString*)identifier; 9 | @property NSURL* bundleURL; 10 | @property NSString* bundleType; 11 | @property NSString* canonicalExecutablePath; 12 | @property (nonatomic,readonly) NSDictionary* groupContainerURLs; 13 | @property (nonatomic,readonly) NSArray* plugInKitPlugins; 14 | @property (getter=isInstalled,nonatomic,readonly) BOOL installed; 15 | @property (getter=isPlaceholder,nonatomic,readonly) BOOL placeholder; 16 | @property (getter=isRestricted,nonatomic,readonly) BOOL restricted; 17 | @property (nonatomic,readonly) NSSet * claimedURLSchemes; 18 | @property (nonatomic,readonly) NSString *applicationIdentifier; 19 | @end 20 | 21 | @interface LSApplicationWorkspace : NSObject 22 | + (instancetype)defaultWorkspace; 23 | - (BOOL)registerApplicationDictionary:(NSDictionary*)dict; 24 | - (BOOL)unregisterApplication:(id)arg1; 25 | - (BOOL)_LSPrivateRebuildApplicationDatabasesForSystemApps:(BOOL)arg1 internal:(BOOL)arg2 user:(BOOL)arg3; 26 | - (BOOL)uninstallApplication:(NSString*)arg1 withOptions:(id)arg2; 27 | - (BOOL)openApplicationWithBundleID:(NSString *)arg1 ; 28 | - (void)enumerateApplicationsOfType:(NSUInteger)type block:(void (^)(LSApplicationProxy*))block; 29 | - (NSArray *)allApplications; 30 | @end 31 | 32 | @interface LSEnumerator : NSEnumerator 33 | @property (nonatomic,copy) NSPredicate * predicate; 34 | + (instancetype)enumeratorForApplicationProxiesWithOptions:(NSUInteger)options; 35 | @end 36 | 37 | @interface LSPlugInKitProxy : LSBundleProxy 38 | @property (nonatomic,readonly) NSString* pluginIdentifier; 39 | @property (nonatomic,readonly) NSDictionary * pluginKitDictionary; 40 | + (instancetype)pluginKitProxyForIdentifier:(NSString*)arg1; 41 | @end 42 | 43 | @interface MCMContainer : NSObject 44 | + (id)containerWithIdentifier:(id)arg1 createIfNecessary:(BOOL)arg2 existed:(BOOL*)arg3 error:(id*)arg4; 45 | @property (nonatomic,readonly) NSURL * url; 46 | @end 47 | 48 | @interface MCMDataContainer : MCMContainer 49 | 50 | @end 51 | 52 | @interface MCMAppDataContainer : MCMDataContainer 53 | 54 | @end 55 | 56 | @interface MCMAppContainer : MCMContainer 57 | @end 58 | 59 | @interface MCMPluginKitPluginDataContainer : MCMDataContainer 60 | @end 61 | -------------------------------------------------------------------------------- /Geranium/Libs/TrollStore/TSUtil.h: -------------------------------------------------------------------------------- 1 | @import Foundation; 2 | #import "CoreServices.h" 3 | 4 | extern void chineseWifiFixup(void); 5 | extern void loadMCMFramework(void); 6 | extern NSString* safe_getExecutablePath(); 7 | extern NSString* rootHelperPath(void); 8 | extern NSString* getNSStringFromFile(int fd); 9 | extern void printMultilineNSString(NSString* stringToPrint); 10 | extern int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr); 11 | extern void killall(NSString* processName); 12 | extern void respring(void); 13 | extern void fetchLatestTrollStoreVersion(void (^completionHandler)(NSString* latestVersion)); 14 | 15 | extern NSArray* trollStoreInstalledAppBundlePaths(); 16 | extern NSArray* trollStoreInstalledAppContainerPaths(); 17 | extern NSString* trollStorePath(); 18 | extern NSString* trollStoreAppPath(); 19 | 20 | typedef enum 21 | { 22 | PERSISTENCE_HELPER_TYPE_USER = 1 << 0, 23 | PERSISTENCE_HELPER_TYPE_SYSTEM = 1 << 1, 24 | PERSISTENCE_HELPER_TYPE_ALL = PERSISTENCE_HELPER_TYPE_USER | PERSISTENCE_HELPER_TYPE_SYSTEM 25 | } PERSISTENCE_HELPER_TYPE; 26 | 27 | extern LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes); 28 | -------------------------------------------------------------------------------- /Geranium/Libs/TrollStore/TSUtil.m: -------------------------------------------------------------------------------- 1 | #import "TSUtil.h" 2 | 3 | #import 4 | #import 5 | #import 6 | 7 | @interface PSAppDataUsagePolicyCache : NSObject 8 | + (instancetype)sharedInstance; 9 | - (void)setUsagePoliciesForBundle:(NSString*)bundleId cellular:(BOOL)cellular wifi:(BOOL)wifi; 10 | @end 11 | 12 | #define POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE 1 13 | extern int posix_spawnattr_set_persona_np(const posix_spawnattr_t* __restrict, uid_t, uint32_t); 14 | extern int posix_spawnattr_set_persona_uid_np(const posix_spawnattr_t* __restrict, uid_t); 15 | extern int posix_spawnattr_set_persona_gid_np(const posix_spawnattr_t* __restrict, uid_t); 16 | 17 | void chineseWifiFixup(void) 18 | { 19 | NSBundle *bundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/SettingsCellular.framework"]; 20 | [bundle load]; 21 | [[NSClassFromString(@"PSAppDataUsagePolicyCache") sharedInstance] setUsagePoliciesForBundle:NSBundle.mainBundle.bundleIdentifier cellular:true wifi:true]; 22 | } 23 | 24 | void loadMCMFramework(void) 25 | { 26 | static dispatch_once_t onceToken; 27 | dispatch_once (&onceToken, ^{ 28 | NSBundle* mcmBundle = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/MobileContainerManager.framework"]; 29 | [mcmBundle load]; 30 | }); 31 | } 32 | 33 | extern char*** _NSGetArgv(); 34 | NSString* safe_getExecutablePath() 35 | { 36 | char* executablePathC = **_NSGetArgv(); 37 | return [NSString stringWithUTF8String:executablePathC]; 38 | } 39 | 40 | NSString* getNSStringFromFile(int fd) 41 | { 42 | NSMutableString* ms = [NSMutableString new]; 43 | ssize_t num_read; 44 | char c; 45 | while((num_read = read(fd, &c, sizeof(c)))) 46 | { 47 | [ms appendString:[NSString stringWithFormat:@"%c", c]]; 48 | } 49 | return ms.copy; 50 | } 51 | 52 | void printMultilineNSString(NSString* stringToPrint) 53 | { 54 | NSCharacterSet *separator = [NSCharacterSet newlineCharacterSet]; 55 | NSArray* lines = [stringToPrint componentsSeparatedByCharactersInSet:separator]; 56 | for(NSString* line in lines) 57 | { 58 | NSLog(@"%@", line); 59 | } 60 | } 61 | 62 | int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr) 63 | { 64 | NSMutableArray* argsM = args.mutableCopy ?: [NSMutableArray new]; 65 | [argsM insertObject:path.lastPathComponent atIndex:0]; 66 | 67 | NSUInteger argCount = [argsM count]; 68 | char **argsC = (char **)malloc((argCount + 1) * sizeof(char*)); 69 | 70 | for (NSUInteger i = 0; i < argCount; i++) 71 | { 72 | argsC[i] = strdup([[argsM objectAtIndex:i] UTF8String]); 73 | } 74 | argsC[argCount] = NULL; 75 | 76 | posix_spawnattr_t attr; 77 | posix_spawnattr_init(&attr); 78 | 79 | posix_spawnattr_set_persona_np(&attr, 99, POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE); 80 | posix_spawnattr_set_persona_uid_np(&attr, 0); 81 | posix_spawnattr_set_persona_gid_np(&attr, 0); 82 | 83 | posix_spawn_file_actions_t action; 84 | posix_spawn_file_actions_init(&action); 85 | 86 | int outErr[2]; 87 | if(stdErr) 88 | { 89 | pipe(outErr); 90 | posix_spawn_file_actions_adddup2(&action, outErr[1], STDERR_FILENO); 91 | posix_spawn_file_actions_addclose(&action, outErr[0]); 92 | } 93 | 94 | int out[2]; 95 | if(stdOut) 96 | { 97 | pipe(out); 98 | posix_spawn_file_actions_adddup2(&action, out[1], STDOUT_FILENO); 99 | posix_spawn_file_actions_addclose(&action, out[0]); 100 | } 101 | 102 | pid_t task_pid; 103 | int status = -200; 104 | int spawnError = posix_spawn(&task_pid, [path UTF8String], &action, &attr, (char* const*)argsC, NULL); 105 | posix_spawnattr_destroy(&attr); 106 | for (NSUInteger i = 0; i < argCount; i++) 107 | { 108 | free(argsC[i]); 109 | } 110 | free(argsC); 111 | 112 | if(spawnError != 0) 113 | { 114 | NSLog(@"posix_spawn error %d\n", spawnError); 115 | return spawnError; 116 | } 117 | 118 | do 119 | { 120 | if (waitpid(task_pid, &status, 0) != -1) { 121 | NSLog(@"Child status %d", WEXITSTATUS(status)); 122 | } else 123 | { 124 | perror("waitpid"); 125 | return -222; 126 | } 127 | } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 128 | 129 | if(stdOut) 130 | { 131 | close(out[1]); 132 | NSString* output = getNSStringFromFile(out[0]); 133 | *stdOut = output; 134 | } 135 | 136 | if(stdErr) 137 | { 138 | close(outErr[1]); 139 | NSString* errorOutput = getNSStringFromFile(outErr[0]); 140 | *stdErr = errorOutput; 141 | } 142 | 143 | return WEXITSTATUS(status); 144 | } 145 | 146 | void enumerateProcessesUsingBlock(void (^enumerator)(pid_t pid, NSString* executablePath, BOOL* stop)) 147 | { 148 | static int maxArgumentSize = 0; 149 | if (maxArgumentSize == 0) { 150 | size_t size = sizeof(maxArgumentSize); 151 | if (sysctl((int[]){ CTL_KERN, KERN_ARGMAX }, 2, &maxArgumentSize, &size, NULL, 0) == -1) { 152 | perror("sysctl argument size"); 153 | maxArgumentSize = 4096; // Default 154 | } 155 | } 156 | int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL}; 157 | struct kinfo_proc *info; 158 | size_t length; 159 | int count; 160 | 161 | if (sysctl(mib, 3, NULL, &length, NULL, 0) < 0) 162 | return; 163 | if (!(info = malloc(length))) 164 | return; 165 | if (sysctl(mib, 3, info, &length, NULL, 0) < 0) { 166 | free(info); 167 | return; 168 | } 169 | count = length / sizeof(struct kinfo_proc); 170 | for (int i = 0; i < count; i++) { 171 | @autoreleasepool { 172 | pid_t pid = info[i].kp_proc.p_pid; 173 | if (pid == 0) { 174 | continue; 175 | } 176 | size_t size = maxArgumentSize; 177 | char* buffer = (char *)malloc(length); 178 | if (sysctl((int[]){ CTL_KERN, KERN_PROCARGS2, pid }, 3, buffer, &size, NULL, 0) == 0) { 179 | NSString* executablePath = [NSString stringWithCString:(buffer+sizeof(int)) encoding:NSUTF8StringEncoding]; 180 | 181 | BOOL stop = NO; 182 | enumerator(pid, executablePath, &stop); 183 | if(stop) 184 | { 185 | free(buffer); 186 | break; 187 | } 188 | } 189 | free(buffer); 190 | } 191 | } 192 | free(info); 193 | } 194 | 195 | void killall(NSString* processName) 196 | { 197 | enumerateProcessesUsingBlock(^(pid_t pid, NSString* executablePath, BOOL* stop) 198 | { 199 | if([executablePath.lastPathComponent isEqualToString:processName]) 200 | { 201 | kill(pid, SIGTERM); 202 | } 203 | }); 204 | } 205 | -------------------------------------------------------------------------------- /Geranium/LocSim/BookMark/BookMarkHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookMarkHelper.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 21.12.23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | let sharedUserDefaultsSuiteName = "group.live.cclerc.geraniumBookmarks" 12 | 13 | func BookMarkSave(lat: Double, long: Double, name: String) -> Bool { 14 | let bookmark: [String: Any] = ["name": name, "lat": lat, "long": long] 15 | var bookmarks = BookMarkRetrieve() 16 | bookmarks.append(bookmark) 17 | let sharedUserDefaults = UserDefaults(suiteName: sharedUserDefaultsSuiteName) 18 | sharedUserDefaults?.set(bookmarks, forKey: "bookmarks") 19 | successVibrate() 20 | return true 21 | } 22 | 23 | func BookMarkRetrieve() -> [[String: Any]] { 24 | let sharedUserDefaults = UserDefaults(suiteName: sharedUserDefaultsSuiteName) 25 | if let bookmarks = sharedUserDefaults?.array(forKey: "bookmarks") as? [[String: Any]] { 26 | return bookmarks 27 | } else { 28 | return [] 29 | } 30 | } 31 | 32 | func isThereAnyMika() -> Bool { 33 | let plistPath = "/var/mobile/Library/Preferences/com.mika.LocationSimulation.plist" 34 | guard FileManager.default.fileExists(atPath: plistPath) else { 35 | return false 36 | } 37 | 38 | do { 39 | let data = try Data(contentsOf: URL(fileURLWithPath: plistPath)) 40 | var format: PropertyListSerialization.PropertyListFormat = .xml 41 | let plist = try PropertyListSerialization.propertyList(from: data, options: .mutableContainersAndLeaves, format: &format) 42 | guard let plistDict = plist as? [String: Any] else { 43 | print("Plist is not a dictionary.") 44 | return false 45 | } 46 | if let datasArray = plistDict["datas"] as? [Any] { 47 | return true 48 | } else { 49 | return false 50 | } 51 | 52 | } catch { 53 | return false 54 | } 55 | } 56 | 57 | 58 | func importMika() { 59 | let plistPath = "/var/mobile/Library/Preferences/com.mika.LocationSimulation.plist" 60 | 61 | do { 62 | let data = try Data(contentsOf: URL(fileURLWithPath: plistPath)) 63 | var format: PropertyListSerialization.PropertyListFormat = .xml 64 | let plist = try PropertyListSerialization.propertyList(from: data, options: .mutableContainersAndLeaves, format: &format) 65 | guard let plistDict = plist as? [String: Any] else { 66 | print("not a dictionary") 67 | return 68 | } 69 | if let datasArray = plistDict["datas"] as? [[String: Any]] { 70 | for dataDict in datasArray { 71 | if let la = dataDict["la"] as? Double, 72 | let lo = dataDict["lo"] as? Double, 73 | let remark = dataDict["remark"] as? String { 74 | BookMarkSave(lat: la, long: lo, name: remark) 75 | } 76 | } 77 | } else { 78 | print("datas not there") 79 | } 80 | UIApplication.shared.alert(title:"Done !",body:"Your saved bookmarks/records from Mika's LocSim were imported.") 81 | } catch { 82 | print("cant read file \(error)") 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Geranium/LocSim/BookMark/BookMarkSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookMarkSlider.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 21.12.23. 6 | // 7 | 8 | import SwiftUI 9 | import AlertKit 10 | 11 | struct Bookmark: Identifiable { 12 | var id = UUID() 13 | var name: String 14 | var lat: Double 15 | var long: Double 16 | } 17 | 18 | struct BookMarkSlider: View { 19 | @Environment(\.dismiss) var dismiss 20 | @Binding var lat: Double 21 | @Binding var long: Double 22 | @State private var name = "" 23 | @State private var result: Bool = false 24 | @AppStorage("isMika") var isMika: Bool = false 25 | @State private var bookmarks: [Bookmark] = BookMarkRetrieve().map { 26 | Bookmark(name: $0["name"] as! String, lat: $0["lat"] as! Double, long: $0["long"] as! Double) 27 | } 28 | 29 | var body: some View { 30 | NavigationView { 31 | List { 32 | ForEach(bookmarks) { bookmark in 33 | Button(action: { 34 | close() 35 | LocSimManager.startLocSim(location: .init(latitude: bookmark.lat, longitude: bookmark.long)) 36 | AlertKitAPI.present( 37 | title: "Started !", 38 | icon: .done, 39 | style: .iOS17AppleMusic, 40 | haptic: .success 41 | ) 42 | }) { 43 | VStack(alignment: .leading) { 44 | Text("\(bookmark.name)") 45 | .font(.headline) 46 | .foregroundColor(.primary) 47 | Text("Latitude: \(bookmark.lat) Longitude: \(bookmark.long)") 48 | .font(.subheadline) 49 | .foregroundColor(.gray) 50 | } 51 | } 52 | .contentShape(Rectangle()) 53 | } 54 | .onDelete(perform: deleteBookmark) 55 | } 56 | .toolbar { 57 | ToolbarItem(placement: .navigationBarLeading) { 58 | Text("Bookmarks") 59 | .font(.title2) 60 | .bold() 61 | } 62 | ToolbarItem(placement: .navigationBarTrailing) { 63 | Button(action: { 64 | if lat != 0.00 && long != 0.00 { 65 | UIApplication.shared.TextFieldAlert( 66 | title: "Enter bookmark name", 67 | textFieldPlaceHolder: "Example..." 68 | ) { enteredText, _ in 69 | let bookmarkName = enteredText ?? "Unknown" 70 | result = BookMarkSave(lat: lat, long: long, name: bookmarkName) 71 | bookmarks = BookMarkRetrieve().map { 72 | Bookmark(name: $0["name"] as! String, lat: $0["lat"] as! Double, long: $0["long"] as! Double) 73 | } 74 | } 75 | 76 | } else { 77 | UIApplication.shared.confirmAlert(title: "Your position is set to 0", body: "Are you sure you want to continue? This might be a mistake; try picking somewhere on the map", onOK: { 78 | UIApplication.shared.TextFieldAlert(title: "Enter bookmark name", textFieldPlaceHolder: "Example...", completion: { enteredText, _ in 79 | result = BookMarkSave(lat: lat, long: long, name: enteredText ?? "Unknown") 80 | bookmarks = BookMarkRetrieve().map { 81 | Bookmark(name: $0["name"] as! String, lat: $0["lat"] as! Double, long: $0["long"] as! Double) 82 | } 83 | AlertKitAPI.present( 84 | title: "Added !", 85 | icon: .done, 86 | style: .iOS17AppleMusic, 87 | haptic: .success 88 | ) 89 | }) 90 | }, noCancel: false) 91 | } 92 | }) { 93 | Image(systemName: "plus") 94 | .resizable() 95 | .aspectRatio(contentMode: .fit) 96 | .frame(width: 24, height: 24) 97 | } 98 | } 99 | } 100 | .overlay( 101 | Group { 102 | if bookmarks.isEmpty { 103 | VStack { 104 | Text("No bookmarks saved.") 105 | .foregroundColor(.secondary) 106 | .font(.title) 107 | .padding() 108 | .multilineTextAlignment(.center) 109 | Text("To add a bookmark, select a custom location then tap the + button.") 110 | .foregroundColor(.secondary) 111 | .font(.footnote) 112 | .multilineTextAlignment(.center) 113 | } 114 | } 115 | } 116 | , alignment: .center 117 | ) 118 | .onAppear { 119 | if isThereAnyMika(), !isMika { 120 | UIApplication.shared.confirmAlert(title:"Mika's LocSim bookmarks/records detected.", body: "Do you want to import them ?", onOK: { 121 | importMika() 122 | bookmarks = BookMarkRetrieve().map { 123 | Bookmark(name: $0["name"] as! String, lat: $0["lat"] as! Double, long: $0["long"] as! Double) 124 | } 125 | isMika.toggle() 126 | }, noCancel: false, yes: true) 127 | } 128 | } 129 | } 130 | } 131 | 132 | private func deleteBookmark(at offsets: IndexSet) { 133 | bookmarks.remove(atOffsets: offsets) 134 | updateBookmarks() 135 | AlertKitAPI.present( 136 | title: "Deleted !", 137 | icon: .done, 138 | style: .iOS17AppleMusic, 139 | haptic: .success 140 | ) 141 | } 142 | let sharedUserDefaultsSuiteName = "group.live.cclerc.geraniumBookmarks" 143 | private func updateBookmarks() { 144 | let sharedUserDefaults = UserDefaults(suiteName: sharedUserDefaultsSuiteName) 145 | sharedUserDefaults?.set(bookmarks.map { ["name": $0.name, "lat": $0.lat, "long": $0.long] }, forKey: "bookmarks") 146 | sharedUserDefaults?.synchronize() 147 | } 148 | 149 | func close() { 150 | dismiss() 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Geranium/LocSim/CustomMapView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomMapView.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 21.12.23. 6 | // 7 | 8 | import SwiftUI 9 | import MapKit 10 | 11 | struct CustomMapView: UIViewRepresentable { 12 | @Binding var tappedCoordinate: EquatableCoordinate? 13 | 14 | func makeUIView(context: Context) -> MKMapView { 15 | let mapView = MKMapView() 16 | mapView.delegate = context.coordinator 17 | mapView.showsUserLocation = true 18 | mapView.layer.cornerRadius = 15 19 | mapView.layer.masksToBounds = true 20 | 21 | let tapRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(_:))) 22 | mapView.addGestureRecognizer(tapRecognizer) 23 | return mapView 24 | } 25 | 26 | func updateUIView(_ uiView: MKMapView, context: Context) {} 27 | 28 | func makeCoordinator() -> Coordinator { 29 | Coordinator(self) 30 | } 31 | 32 | class Coordinator: NSObject, MKMapViewDelegate { 33 | var parent: CustomMapView 34 | 35 | init(_ parent: CustomMapView) { 36 | self.parent = parent 37 | } 38 | 39 | @objc func handleTap(_ gesture: UITapGestureRecognizer) { 40 | let mapView = gesture.view as! MKMapView 41 | let touchPoint = gesture.location(in: mapView) 42 | let coordinate = mapView.convert(touchPoint, toCoordinateFrom: mapView) 43 | parent.tappedCoordinate = EquatableCoordinate(coordinate: coordinate) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Geranium/LocSim/LocSimManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocSimManager.swift 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 12.11.2022. 6 | // 7 | 8 | import Foundation 9 | import CoreLocation 10 | 11 | 12 | class LocSimManager { 13 | static let simManager = CLSimulationManager() 14 | 15 | /// Updates timezone 16 | static func post_required_timezone_update(){ 17 | CFNotificationCenterPostNotificationWithOptions(CFNotificationCenterGetDarwinNotifyCenter(), .init("AutomaticTimeZoneUpdateNeeded" as CFString), nil, nil, kCFNotificationDeliverImmediately); 18 | } 19 | 20 | /// Starts a location simulation of specified argument "location" 21 | // TODO: save 22 | static func startLocSim(location: CLLocation) { 23 | simManager.stopLocationSimulation() 24 | simManager.clearSimulatedLocations() 25 | simManager.appendSimulatedLocation(location) 26 | simManager.flush() 27 | simManager.startLocationSimulation() 28 | post_required_timezone_update(); 29 | } 30 | 31 | /// Stops location simulation 32 | static func stopLocSim(){ 33 | simManager.stopLocationSimulation() 34 | simManager.clearSimulatedLocations() 35 | simManager.flush() 36 | post_required_timezone_update(); 37 | } 38 | } 39 | 40 | 41 | struct EquatableCoordinate: Equatable { 42 | var coordinate: CLLocationCoordinate2D 43 | 44 | static func ==(lhs: EquatableCoordinate, rhs: EquatableCoordinate) -> Bool { 45 | lhs.coordinate.latitude == rhs.coordinate.latitude && lhs.coordinate.longitude == rhs.coordinate.longitude 46 | } 47 | } 48 | 49 | 50 | // https://stackoverflow.com/a/75703059 51 | 52 | class LocationModel: NSObject, ObservableObject { 53 | private let locationManager = CLLocationManager() 54 | @Published var authorisationStatus: CLAuthorizationStatus = .notDetermined 55 | 56 | override init() { 57 | super.init() 58 | self.locationManager.delegate = self 59 | } 60 | 61 | public func requestAuthorisation(always: Bool = false) { 62 | if always { 63 | self.locationManager.requestAlwaysAuthorization() 64 | } else { 65 | self.locationManager.requestWhenInUseAuthorization() 66 | } 67 | } 68 | } 69 | 70 | extension LocationModel: CLLocationManagerDelegate { 71 | 72 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 73 | self.authorisationStatus = status 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Geranium/LocSim/LocSimPrivateHeaders.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 udevs 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, version 3. 6 | // 7 | // This program is distributed in the hope that it will be useful, but 8 | // WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | // General Public License for more details. 11 | // 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program. If not, see . 14 | 15 | #import 16 | #import 17 | @interface CLSimulationManager : NSObject 18 | @property (assign,nonatomic) uint8_t locationDeliveryBehavior; 19 | @property (assign,nonatomic) double locationDistance; 20 | @property (assign,nonatomic) double locationInterval; 21 | @property (assign,nonatomic) double locationSpeed; 22 | @property (assign,nonatomic) uint8_t locationRepeatBehavior; 23 | -(void)clearSimulatedLocations; 24 | -(void)startLocationSimulation; 25 | -(void)stopLocationSimulation; 26 | -(void)appendSimulatedLocation:(id)arg1 ; 27 | -(void)flush; 28 | -(void)loadScenarioFromURL:(id)arg1 ; 29 | -(void)setSimulatedWifiPower:(BOOL)arg1 ; 30 | -(void)startWifiSimulation; 31 | -(void)stopWifiSimulation; 32 | -(void)setSimulatedCell:(id)arg1 ; 33 | -(void)startCellSimulation; 34 | -(void)stopCellSimulation; 35 | @end 36 | -------------------------------------------------------------------------------- /Geranium/LocSim/LocSimView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocSimView.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 21.12.23. 6 | // 7 | 8 | import SwiftUI 9 | import CoreLocation 10 | import AlertKit 11 | 12 | struct LocSimView: View { 13 | @StateObject private var appSettings = AppSettings() 14 | 15 | @State private var locationManager = CLLocationManager() 16 | @State private var lat: Double = 0.0 17 | @State private var long: Double = 0.0 18 | @State private var tappedCoordinate: EquatableCoordinate? = nil 19 | @State private var bookmarkSheetTggle: Bool = false 20 | var body: some View { 21 | if #available(iOS 16.0, *) { 22 | NavigationStack { 23 | LocSimMainView() 24 | } 25 | } else { 26 | NavigationView { 27 | LocSimMainView() 28 | } 29 | } 30 | } 31 | @ViewBuilder 32 | private func LocSimMainView() -> some View { 33 | VStack { 34 | CustomMapView(tappedCoordinate: $tappedCoordinate) 35 | .onAppear { 36 | CLLocationManager().requestAlwaysAuthorization() 37 | } 38 | } 39 | .ignoresSafeArea(.keyboard) 40 | .onAppear { 41 | LocationModel().requestAuthorisation() 42 | } 43 | .onChange(of: tappedCoordinate) { newValue in 44 | if let coordinate = newValue { 45 | lat = coordinate.coordinate.latitude 46 | long = coordinate.coordinate.longitude 47 | LocSimManager.startLocSim(location: .init(latitude: lat, longitude: long)) 48 | AlertKitAPI.present( 49 | title: "Started !", 50 | icon: .done, 51 | style: .iOS17AppleMusic, 52 | haptic: .success 53 | ) 54 | } 55 | } 56 | .toolbar{ 57 | ToolbarItem(placement: .navigationBarLeading) { 58 | Text("LocSim") 59 | .font(.title2) 60 | .bold() 61 | } 62 | ToolbarItem(placement: .navigationBarTrailing) { 63 | Button(action: { 64 | UIApplication.shared.TextFieldAlert( 65 | title: "Enter Coordinates", 66 | message: "The location will be simulated on device\nPro tip: Press wherever on the map to move there.", 67 | textFieldPlaceHolder: "Latitude", 68 | secondTextFieldPlaceHolder: "Longitude" 69 | ) { latText, longText in 70 | if let latDouble = Double(latText ?? ""), let longDouble = Double(longText ?? "") { 71 | lat = latDouble 72 | long = longDouble 73 | LocSimManager.startLocSim(location: .init(latitude: latDouble, longitude: longDouble)) 74 | } else { 75 | UIApplication.shared.alert(body: "Those are invalid coordinates mate !") 76 | } 77 | } 78 | }) { 79 | Image(systemName: "mappin") 80 | .resizable() 81 | .aspectRatio(contentMode: .fit) 82 | .frame(width: 24, height: 24) 83 | } 84 | } 85 | ToolbarItem(placement: .navigationBarTrailing) { 86 | Button(action: { 87 | if appSettings.locSimMultipleAttempts { 88 | var countdown = appSettings.locSimAttemptNB 89 | DispatchQueue.global().async { 90 | while countdown > 0 { 91 | LocSimManager.stopLocSim() 92 | countdown -= 1 93 | } 94 | } 95 | } 96 | else { 97 | LocSimManager.stopLocSim() 98 | } 99 | AlertKitAPI.present( 100 | title: "Stopped !", 101 | icon: .done, 102 | style: .iOS17AppleMusic, 103 | haptic: .success 104 | ) 105 | }) { 106 | Image(systemName: "location.slash") 107 | .resizable() 108 | .aspectRatio(contentMode: .fit) 109 | .frame(width: 24, height: 24) 110 | } 111 | } 112 | ToolbarItem(placement: .navigationBarTrailing) { 113 | Button(action: { 114 | bookmarkSheetTggle.toggle() 115 | }) { 116 | Image(systemName: "bookmark") 117 | .resizable() 118 | .aspectRatio(contentMode: .fit) 119 | .frame(width: 24, height: 24) 120 | } 121 | } 122 | } 123 | .sheet(isPresented: $bookmarkSheetTggle) { 124 | BookMarkSlider(lat: $lat, long: $long) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Geranium/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Geranium/SettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsView.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 23.12.23. 6 | // 7 | 8 | import SwiftUI 9 | import AlertKit 10 | 11 | struct SettingsView: View { 12 | @State var defaultTab = AppSettings().defaultTab 13 | @State var DebugStuff: Bool = false 14 | @State var MinimCal: String = "" 15 | @State var LocSimTries: String = "" 16 | @State var localisation: String = { 17 | if langaugee != "" { 18 | return langaugee 19 | } 20 | else if let languages = UserDefaults.standard.array(forKey: "AppleLanguages") as? [String], 21 | let firstLanguage = languages.first { 22 | return "\(Locale.current.languageCode ?? "en-GB")" 23 | } else { 24 | return "en-GB" 25 | } 26 | }() 27 | @StateObject private var appSettings = AppSettings() 28 | 29 | // Custom language 30 | @State var appCodeLanguage = langaugee 31 | let languageMapping: [String: String] = [ 32 | // i made catgpt work for me on this one 33 | "zh-Hans": "Chinese (Simplified)", // 34 | "zh-Hant": "Chinese (Traditional)", // 35 | "Base": "English", // 36 | "en-GB": "English (GB)", 37 | "es": "Spanish", // 38 | "es-419": "Spanish (Latin America)", // 39 | "fr": "French", // 40 | "it": "Italian", // 41 | "ja": "Japanese", // 42 | "ko": "Korean", // 43 | "ru": "Russian", // 44 | "sk": "Slovak", // 45 | "sv": "Swedish", // 46 | "vi": "Vietnamese", // 47 | ] 48 | var sortedLocalisalist: [String] { 49 | languageMapping.keys.sorted() 50 | } 51 | 52 | 53 | // Open Tab 54 | let defaultTabList: [Int: String] = [ 55 | 1: "Home", // 56 | 2: "Daemons", // 57 | 3: "LocSim", // 58 | 4: "Cleaner", 59 | 5: "Superviser", // 60 | ] 61 | 62 | 63 | var body: some View { 64 | NavigationView { 65 | List { 66 | Section(header: Label("ByeTime", systemImage: "hourglass"), footer: Text("ByeTime allows you to completly disable Screen Time, iCloud or not.")) { 67 | NavigationLink(destination: ByeTimeView(DebugStuff: $DebugStuff)) { 68 | HStack { 69 | Text("ByeTime Settings") 70 | } 71 | } 72 | } 73 | 74 | Section(header: Label("App Language", systemImage: "magnifyingglass"), footer: Text("Here you can choose in what language you want the app to be. The app will automatically exit to apply changes ; feel free to launch it again.")) { 75 | Picker("Language", selection: $localisation) { 76 | ForEach(sortedLocalisalist, id: \.self) { abbreviation in 77 | Text(languageMapping[abbreviation] ?? abbreviation) 78 | .tag(abbreviation) 79 | } 80 | } 81 | .pickerStyle(.menu) 82 | .onChange(of: localisation) { newValue in 83 | if localisation.contains("en-GB") || localisation.contains("zh") || localisation.contains("es-419"){ 84 | let parts = localisation.components(separatedBy: "-") 85 | if let appCodeLanguage = parts.last { 86 | print("set custom region: \(appCodeLanguage)") 87 | appSettings.languageCode = appCodeLanguage 88 | UserDefaults.standard.set(["\(localisation)"], forKey: "AppleLanguages") 89 | } 90 | } 91 | else { 92 | print(localisation) 93 | appSettings.languageCode = "" 94 | UserDefaults.standard.set(["\(newValue)"], forKey: "AppleLanguages") 95 | } 96 | UIApplication.shared.confirmAlert(title: "You need to quit the app to apply changes.", body: "You might want to open it back.", onOK: { 97 | exitGracefully() 98 | }, noCancel: true) 99 | } 100 | .onAppear { 101 | print(langaugee) 102 | appSettings.languageCode = "" 103 | } 104 | } 105 | 106 | 107 | Section(header: Label("Debug Stuff", systemImage: "chevron.left.forwardslash.chevron.right"), footer: Text("This setting allows you to see experimental values from some app variables.")) { 108 | Toggle(isOn: $DebugStuff) { 109 | Text("Debug Info") 110 | } 111 | if DebugStuff { 112 | Text("Language set to : \(localisation)") 113 | Button("Set language to Default") { 114 | UserDefaults.standard.set(["Base"], forKey: "AppleLanguages") 115 | UIApplication.shared.confirmAlert(title: "You need to quit the app to apply changes.", body: "You might want to open it back.", onOK: { 116 | exitGracefully() 117 | }, noCancel: true) 118 | } 119 | Text("UUID : \(appSettings.usrUUID)") 120 | Text("RootHelper Path : \(RootHelper.whatsthePath())") 121 | if UIDevice.current.userInterfaceIdiom == .pad { 122 | Text("Is the user running an iPad on iPadOS 16 : yes") 123 | } 124 | else { 125 | Text("Is the user running an iPad on iPadOS 16 : no") 126 | } 127 | Text("Safari Cache Path : \(removeFilePrefix(safariCachePath))") 128 | } 129 | } 130 | 131 | Section(header: Label("Cleaner Settings", systemImage: "trash"), footer: Text("Various settings for the cleaner.")) { 132 | Toggle(isOn: $appSettings.keepCheckBoxesC) { 133 | Text("Keep selection after cleaning") 134 | } 135 | Toggle(isOn: $appSettings.getSizes) { 136 | Text("Calculate Cleaning Size") 137 | } 138 | .onChange(of: appSettings.getSizes) { newValue in 139 | UIApplication.shared.confirmAlert(title: "You need to quit the app to apply changes.", body: "You might want to open it back.", onOK: { 140 | exitGracefully() 141 | }, noCancel: true) 142 | } 143 | Toggle(isOn: Binding( 144 | get: { !appSettings.tmpClean }, 145 | set: { appSettings.tmpClean = !$0 } 146 | )) { 147 | Text("Safe Clean Measures (Enable this if on iOS 15!)") 148 | } 149 | .onChange(of: appSettings.tmpClean) { newValue in 150 | UIApplication.shared.confirmAlert(title: "You need to quit the app to apply changes.", body: "You might want to open it back.", onOK: { 151 | exitGracefully() 152 | }, noCancel: true) 153 | } 154 | 155 | if DebugStuff { 156 | HStack { 157 | Text("Minimum Size:") 158 | Spacer() 159 | TextField("50.0 MB", text: $MinimCal) 160 | .keyboardType(.decimalPad) 161 | .multilineTextAlignment(.trailing) 162 | .onChange(of: MinimCal) { newValue in 163 | MinimCal = newValue.replacingOccurrences(of: ",", with: ".") 164 | } 165 | } 166 | .onAppear { 167 | MinimCal = "\(appSettings.minimSizeC)" 168 | } 169 | .onChange(of: MinimCal) { newValue in 170 | appSettings.minimSizeC = Double(MinimCal) ?? 50.0 171 | } 172 | } 173 | } 174 | Section(header: Label("LocSim Settings", systemImage: "location.fill.viewfinder"), footer: Text("Various settings for LocSim. Sometimes, users can encounter issues with stopping LocSim. Those settings will allow you to attempt to stop LocSim multiple time.")) { 175 | Toggle(isOn: $appSettings.locSimMultipleAttempts) { 176 | Text("Try stopping LocSim multiple times") 177 | } 178 | if appSettings.locSimMultipleAttempts { 179 | HStack { 180 | Text("Minimum Attempts:") 181 | Spacer() 182 | TextField("3", text: $LocSimTries) 183 | .keyboardType(.numberPad) 184 | .multilineTextAlignment(.trailing) 185 | } 186 | .onAppear { 187 | LocSimTries = "\(appSettings.locSimAttemptNB)" 188 | } 189 | .onChange(of: LocSimTries) { newValue in 190 | appSettings.locSimAttemptNB = Int(LocSimTries) ?? 1 191 | } 192 | } 193 | } 194 | Section(header: Label("Startup Settings", systemImage: "play"), footer: Text("This will personalize app start-up settings. Useful for debugging on Simulator or for betas.") 195 | ) { 196 | Picker("Default Tab", selection: $defaultTab) { 197 | ForEach(Array(defaultTabList.keys).sorted(), id: \.self) { key in 198 | Text(defaultTabList[key] ?? "") 199 | .tag(key) 200 | } 201 | } 202 | .pickerStyle(.menu) 203 | .onChange(of: defaultTab) { newValue in 204 | print(defaultTab) 205 | appSettings.defaultTab = defaultTab 206 | print(appSettings.defaultTab) 207 | UIApplication.shared.confirmAlert(title: "You need to quit the app to apply changes.", body: "You might want to open it back.", onOK: { 208 | exitGracefully() 209 | }, noCancel: true) 210 | } 211 | Toggle(isOn: $appSettings.tsBypass) { 212 | Text("Bypass TrollStore Pop Up") 213 | } 214 | .onChange(of: appSettings.tsBypass) { newValue in 215 | AlertKitAPI.present( 216 | title: "Saved !", 217 | icon: .done, 218 | style: .iOS17AppleMusic, 219 | haptic: .success 220 | ) 221 | } 222 | Toggle(isOn: $appSettings.updBypass) { 223 | Text("Bypass App Update Pop Up") 224 | } 225 | .onChange(of: appSettings.updBypass) { newValue in 226 | AlertKitAPI.present( 227 | title: "Saved !", 228 | icon: .done, 229 | style: .iOS17AppleMusic, 230 | haptic: .success 231 | ) 232 | } 233 | .onChange(of: appSettings.tsBypass) { newValue in 234 | AlertKitAPI.present( 235 | title: "Saved !", 236 | icon: .done, 237 | style: .iOS17AppleMusic, 238 | haptic: .success 239 | ) 240 | } 241 | } 242 | Section(header: Label("App Icon", systemImage: "app"), footer: Text("You can choose and define a custom icon proposed by the community.") 243 | ) { 244 | Button(action: { 245 | UIApplication.shared.setAlternateIconName(nil) { error in 246 | if let error = error { 247 | UIApplication.shared.alert(body:"\(error.localizedDescription)") 248 | } 249 | } 250 | }) { 251 | HStack { 252 | Image(uiImage: Bundle.main.icon!) 253 | .cornerRadius(15) 254 | .frame(width: 62.5, height: 62.5) 255 | VStack(alignment: .leading) { 256 | Text("Default") 257 | Text("by c22dev") 258 | .font(.footnote) 259 | .foregroundColor(.secondary) 260 | } 261 | } 262 | } 263 | Button(action: { 264 | UIApplication.shared.setAlternateIconName("Flore") { error in 265 | if let error = error { 266 | UIApplication.shared.alert(body:"\(error.localizedDescription)") 267 | } 268 | } 269 | }) { 270 | HStack { 271 | if let imageURL = URL(string: "https://cclerc.ch/db/geranium/icn/Bouquet.png") { 272 | AsyncImageView(url: imageURL) 273 | .cornerRadius(15) 274 | .frame(width: 62.5, height: 62.5) 275 | } 276 | VStack(alignment: .leading) { 277 | Text("Flore") 278 | Text("by PhucDo") 279 | .font(.footnote) 280 | .foregroundColor(.secondary) 281 | } 282 | } 283 | } 284 | Button(action: { 285 | UIApplication.shared.setAlternateIconName("Beta") { error in 286 | if let error = error { 287 | UIApplication.shared.alert(body:"\(error.localizedDescription)") 288 | } 289 | } 290 | }) { 291 | HStack { 292 | if let imageURL = URL(string: "https://cclerc.ch/db/geranium/icn/Beta-2.png") { 293 | AsyncImageView(url: imageURL) 294 | .cornerRadius(15) 295 | .frame(width: 62.5, height: 62.5) 296 | } 297 | VStack(alignment: .leading) { 298 | Text("Beta") 299 | Text("by c22dev") 300 | .font(.footnote) 301 | .foregroundColor(.secondary) 302 | } 303 | } 304 | } 305 | Button(action: { 306 | UIApplication.shared.setAlternateIconName("Bouquet") { error in 307 | if let error = error { 308 | UIApplication.shared.alert(body:"\(error.localizedDescription)") 309 | } 310 | } 311 | }) { 312 | HStack { 313 | if let imageURL = URL(string: "https://cclerc.ch/db/geranium/icn/Flore.png") { 314 | AsyncImageView(url: imageURL) 315 | .cornerRadius(15) 316 | .frame(width: 62.5, height: 62.5) 317 | } 318 | VStack(alignment: .leading) { 319 | Text("Suika") 320 | Text("by PhucDo") 321 | .font(.footnote) 322 | .foregroundColor(.secondary) 323 | } 324 | } 325 | } 326 | } 327 | Section(header: Label("Logging Settings", systemImage: "cloud"), footer: Text("We collect some logs that are uploaded to our server for fixing bugs and adressing crash logs. The logs never contains any of your personal information, just your device type and the crash log itself. We also collect measurement information to see what was the most used in the app. You can choose if you want to prevent ANY data from being sent to our server.") 328 | ) { 329 | Toggle(isOn: $appSettings.loggingAllowed) { 330 | Text("Enable logging") 331 | } 332 | .onChange(of: appSettings.loggingAllowed) { newValue in 333 | if newValue { 334 | AlertKitAPI.present( 335 | title: "Enabled !", 336 | icon: .done, 337 | style: .iOS17AppleMusic, 338 | haptic: .success 339 | ) 340 | } 341 | else { 342 | AlertKitAPI.present( 343 | title: "Disabled !", 344 | icon: .done, 345 | style: .iOS17AppleMusic, 346 | haptic: .success 347 | ) 348 | } 349 | } 350 | } 351 | Section (header: Label("Translators", systemImage: "pencil"), footer: Text("Thanks to all those amazing translators !")) { 352 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/640759347240108061/79be8e2ce1557085a6cbb6e58b8d6182.webp?size=160", url: "https://twitter.com/CySxL", title: "CySxL", description: "🇹🇼 Chinese (Traditional)") 353 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/620318902983065606/fe19b5c660b9e8535e884e0fe6c15dbe.webp?size=160", url: "https://twitter.com/Defflix19", title: "Defflix", description: "🇨🇿/🇸🇰 Czech & Slovak") 354 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/984192641170276442/a4859069e955ae712a1874acd6c13fb2.webp?size=160", url: "https://twitter.com/dis667_ilya", title: "dis667", description: "🇷🇺 Russian") 355 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/771526460413444096/aa73119afd1f7a84a5e43a4dd7345d1e.webp?size=160", url: "https://twitter.com/w0wbox", title: "w0wbox", description: "🇪🇸 Spanish (Latin America)") 356 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/711515258732150795/622daeaddcbf09887ce40168c7de6a45.webp?size=160", url: "https://twitter.com/LeonardoIzzo_", title: "LeonardoIz", description: "🇪🇸 Spanish / 🇮🇹 Italian / Catalan") 357 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/530721708546588692/e45b6eda6c7127f418d8ec607026bad8.webp?size=160", url: "https://twitter.com/loy64_", title: "Loy64", description: "🇦🇱 Albanian / 🇮🇹 Italian") 358 | LinkCell(imageLink: "https://i.ibb.co/M53ycZw/pasmoi.webp", url: "https://cclerc.ch/pasmoi.html", title: "PasMoi", description: "🇫🇷 French") 359 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/1090613392710062141/dbd47d708e2be6eac591419e085848a9.webp?size=160", url: "https://twitter.com/dobabaophuc", title: "Phuc Do", description: "🇻🇳 Vietnamese") 360 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/662843309391216670/4238ed35692fc2ee1df97033b5c76bcc.webp?size=160", url: "https://twitter.com/SAUCECOMPANY_", title: "saucecompany", description: "🇰🇷 Korean") 361 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/766292544765820958/ca2d1e7a4bd147b67146b51c349f15e0.webp?size=160", url: "https://twitter.com/speedyfriend67", title: "Speedyfriend67", description: "🇰🇷 Korean") 362 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/607904288882294795/76b2725a7e4f0b1fa18bc2fe4938a846.webp?size=160", url: "https://twitter.com/spy_g_", title: "Spy_G", description: "🇸🇪 Swedish") 363 | LinkCell(imageLink: "https://cclerc.ch/db/geranium/dTiW9yol-2.jpg", url: "https://twitter.com/straight_tamago", title: "Straight Tamago", description: "🇯🇵 Japanese") 364 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/1183594247929208874/2bfce82426459ce7f55aeb736fd95a9f.webp?size=160", url: "https://twitter.com/Ting2021", title: "ting0441", description: "🇨🇳 Chinese (Simplified)") 365 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/259867085453131778/685ffaefba4fce61d633f5f5434b7647.webp?size=160", url: "https://twitter.com/Alz971", title: "W$D$B", description: "🇮🇹 Italian") 366 | LinkCell(imageLink: "https://cdn.discordapp.com/avatars/709644128685916185/51c2ef8ff5c774f27662753208fa0f67.webp?size=160", url: "https://twitter.com/yyyyyy_public", title: "yyyywaiwai", description: "🇯🇵 Japanese") 367 | } 368 | } 369 | .navigationTitle("Settings") 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /Geranium/The Supviser/SuperviseView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuperviseView.swift 3 | // Geranium 4 | // 5 | // Created by Constantin Clerc on 10/12/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SuperviseView: View { 11 | @State var organisation_name = "" 12 | @State var plistContent = " AllowPairing CloudConfigurationUIComplete ConfigurationSource 0 IsSupervised PostSetupProfileWasInstalled " 13 | @State var supervisePath = "/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/CloudConfigurationDetails.plist" 14 | var body: some View { 15 | if #available(iOS 16.0, *) { 16 | NavigationStack { 17 | SuperviseMainView() 18 | } 19 | } else { 20 | NavigationView { 21 | SuperviseMainView() 22 | } 23 | } 24 | } 25 | @ViewBuilder 26 | private func SuperviseMainView() -> some View { 27 | VStack { 28 | TextField("Enter organisation name...", text: $organisation_name) 29 | .textFieldStyle(RoundedBorderTextFieldStyle()) 30 | .padding(.trailing, 50) 31 | .padding(.leading, 50) 32 | .padding(.bottom, 10) 33 | Button("Supervise", action : { 34 | print("Attempting to supervise") 35 | print("--supervise--") 36 | if !organisation_name.isEmpty { 37 | print("detected custom orga name") 38 | plistContent = " AllowPairing CloudConfigurationUIComplete ConfigurationSource 0 IsSupervised OrganizationName \(organisation_name) PostSetupProfileWasInstalled " 39 | } 40 | FileManager.default.createFile(atPath: supervisePath, contents: nil) 41 | do { 42 | try plistContent.write(to: URL(fileURLWithPath: "/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/CloudConfigurationDetails.plist"), atomically: true, encoding: .utf8) 43 | 44 | } 45 | catch { 46 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 47 | errorVibrate() 48 | UIApplication.shared.confirmAlert(title: "Error", body: "The app encountered an error while writing to file. Respring anyway ?", onOK: {respring()}, noCancel: false, yes: true) 49 | } 50 | if !organisation_name.isEmpty { 51 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 52 | successVibrate() 53 | UIApplication.shared.confirmAlert(title: "Done !", body: "Your device is now supervised with the custon name \(organisation_name). Please respring now.", onOK: {respring()}, noCancel: true) 54 | } 55 | else { 56 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 57 | successVibrate() 58 | UIApplication.shared.confirmAlert(title: "Done !", body: "Your device is now supervised. Please respring now.", onOK: {respring()}, noCancel: true) 59 | } 60 | }) 61 | .padding(10) 62 | .background(Color.accentColor) 63 | .cornerRadius(8) 64 | .foregroundColor(.black) 65 | Button("Unsupervise", action : { 66 | UIApplication.shared.confirmAlert(title: "Warning", body: "Unsupervising could break some configuration profiles. Are you sure you want to continue ?", onOK: { 67 | print("Attempting to unsupervise") 68 | FileManager.default.createFile(atPath: supervisePath, contents: nil) 69 | plistContent = " AllowPairing CloudConfigurationUIComplete ConfigurationSource 0 IsSupervised PostSetupProfileWasInstalled " 70 | do { 71 | try plistContent.write(to: URL(fileURLWithPath: "/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/CloudConfigurationDetails.plist"), atomically: true, encoding: .utf8) 72 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 73 | UIApplication.shared.confirmAlert(title: "Done !", body: "Please respring your device.", onOK: {respring()}, noCancel: true) 74 | } 75 | catch { 76 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 77 | UIApplication.shared.confirmAlert(title: "Error", body: "The app encountered an error while writing to file. Respring anyway ?", onOK: {respring()}, noCancel: false, yes: true) 78 | } 79 | }, noCancel: false) 80 | }) 81 | .padding(10) 82 | .background(Color.accentColor) 83 | .cornerRadius(8) 84 | .foregroundColor(.black) 85 | } 86 | .toolbar{ 87 | ToolbarItem(placement: .navigationBarLeading) { 88 | Text("Superviser") 89 | .font(.title2) 90 | .bold() 91 | } 92 | } 93 | } 94 | } 95 | 96 | #Preview { 97 | SuperviseView() 98 | } 99 | -------------------------------------------------------------------------------- /Geranium/Translations/InfoPlist.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "CFBundleName" : { 5 | "comment" : "Bundle name", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "en" : { 9 | "stringUnit" : { 10 | "state" : "new", 11 | "value" : "Geranium" 12 | } 13 | } 14 | } 15 | }, 16 | "NSLocationUsageDescription" : { 17 | "comment" : "Privacy - Location Usage Description", 18 | "extractionState" : "extracted_with_value", 19 | "localizations" : { 20 | "en" : { 21 | "stringUnit" : { 22 | "state" : "new", 23 | "value" : "Enabling location for Geranium makes it possible for the app to display your location on the map." 24 | } 25 | }, 26 | "ko" : { 27 | "stringUnit" : { 28 | "state" : "translated", 29 | "value" : "위치 권한을 허용하면 앱 지도에 현재 위치를 표시할 수 있습니다." 30 | } 31 | } 32 | } 33 | }, 34 | "NSLocationWhenInUseUsageDescription" : { 35 | "comment" : "Privacy - Location When In Use Usage Description", 36 | "extractionState" : "extracted_with_value", 37 | "localizations" : { 38 | "en" : { 39 | "stringUnit" : { 40 | "state" : "new", 41 | "value" : "This will allow the app to display your location on the LocSim map." 42 | } 43 | }, 44 | "ko" : { 45 | "stringUnit" : { 46 | "state" : "translated", 47 | "value" : "현재 위치를 LocSim 지도에 표시할 수 있도록 합니다." 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "version" : "1.0" 54 | } -------------------------------------------------------------------------------- /Geranium/WelcomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeView.swift 3 | // Geranium 4 | // 5 | // Created by cclerc on 19.12.23. 6 | // 7 | // credit to haxi0 8 | 9 | import SwiftUI 10 | 11 | struct WelcomeView: View { 12 | @Environment(\.dismiss) var dismiss 13 | @Binding var loggingAllowed: Bool 14 | @Binding var updBypass: Bool 15 | @StateObject private var appSettings = AppSettings() 16 | var body: some View { 17 | List { 18 | Section(header: Text("Setup")) { 19 | Text("Welcome to Geranium, a toolbox for TrollStore that allows you to disable some daemons, simulate your location, clean your phone's storage and other. We need to configure a few things before you can use the app. This will only take a minute. You will still be able to change the settings later.") 20 | } 21 | if getDeviceCode() == "iPhone8,4" { 22 | Section(header: Text("Hi ! SE 1 User")) { 23 | Text("It looks like you are using the app on an iPhone SE 2016 (1st gen). You might encounter serious UI issues. Please excuse me in advance.") 24 | } 25 | } 26 | Section(header: Text("Log Collection")) { 27 | Text("We collect some logs that are uploaded to our server for fixing bugs and adressing crash logs. The logs never contains any of your personal information, just your device type and the crash log itself. We also collect measurement information to see what was the most used in the app. You can choose if you want to prevent ANY data from being sent to our server.") 28 | Toggle(isOn: $loggingAllowed) { 29 | Text("Enable log collection") 30 | } 31 | .onChange(of: loggingAllowed) { newValue in 32 | if !loggingAllowed { 33 | UIApplication.shared.alert(title: "Warning", body: "Disabling logging might make things more difficult for developers if you have an issue.") 34 | } 35 | } 36 | } 37 | 38 | Section(header: Text("Update Warnings")) { 39 | Text("When a new update is published, you will receive a pop-up on app launch asking you if you want to update. You can prevent this from hapenning") 40 | Toggle(isOn: $updBypass) { 41 | Text("Disable update pop-up") 42 | } 43 | } 44 | 45 | Section(header: Text("Cleaner File Sizes")) { 46 | Text("This is an issue that might incorrectly set permissions when calculating file sizes for your device.") 47 | Toggle(isOn: $appSettings.getSizes) { 48 | Text("Calculate File Size") 49 | } 50 | } 51 | if !updBypass { 52 | Section(header: Text("WARNING")) { 53 | Text("If you want to enable auto-update, you need to turn on Magnifier URL Scheme in TrollStore.") 54 | Text("1. Open TrollStore") 55 | Text("2. Go to Settings") 56 | Text("3. Enable URL Scheme") 57 | } 58 | } 59 | } 60 | .navigationTitle("Welcome!") 61 | .navigationBarItems(trailing: Button("Dismiss") { 62 | close() 63 | }) 64 | .environment(\.defaultMinListRowHeight, 50) 65 | .interactiveDismissDisabled() 66 | } 67 | 68 | func close() { 69 | dismiss() 70 | } 71 | } 72 | 73 | public struct CustomButtonStyle: ButtonStyle { 74 | public func makeBody(configuration: Self.Configuration) -> some View { 75 | configuration.label 76 | .font(.body.weight(.medium)) 77 | .padding(.vertical, 12) 78 | .foregroundColor(.white) 79 | .frame(maxWidth: .infinity) 80 | .background(RoundedRectangle(cornerRadius: 14.0, style: .continuous) 81 | .fill(Color.accentColor)) 82 | .opacity(configuration.isPressed ? 0.4 : 1.0) 83 | } 84 | } 85 | 86 | public struct LinkButtonStyle: ButtonStyle { 87 | public func makeBody(configuration: Self.Configuration) -> some View { 88 | configuration.label 89 | .font(.body.weight(.medium)) 90 | .padding(.vertical, 12) 91 | .foregroundColor(.blue) 92 | .frame(maxWidth: .infinity) 93 | .background(RoundedRectangle(cornerRadius: 12.0, style: .continuous) 94 | .fill(Color.blue.opacity(0.1))) 95 | .opacity(configuration.isPressed ? 0.4 : 1.0) 96 | } 97 | } 98 | 99 | public struct DangerButtonStyle: ButtonStyle { 100 | public func makeBody(configuration: Self.Configuration) -> some View { 101 | configuration.label 102 | .font(.headline.weight(.bold)) 103 | .padding(.vertical, 12) 104 | .foregroundColor(.red) 105 | .frame(maxWidth: .infinity) 106 | .background(RoundedRectangle(cornerRadius: 12.0, style: .continuous) 107 | .fill(Color.red.opacity(0.1))) 108 | .opacity(configuration.isPressed ? 0.4 : 1.0) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |

7 | 8 |

9 |

Geranium

10 |

LocSim, Daemon Manager, Cleaner and Superviser for TrollStore

11 | 12 |
13 | 14 |
by c22dev
15 | 16 | ## Installation 17 | To install Geranium, you must have [TrollStore](https://github.com/opa334/TrollStore) 1.3 or later, and a device on iOS 15 or later (if TrollStore supports it). Download the latest release from the release tab, and open it in TrollStore. Then, follow the setup process ! 18 | 19 | ## Features 20 | - **Simulate fake locations + bookmarks** 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | You can also import your old bookmarks from Mika's LocSim (by going into bookmarks tab), and save new bookmarks from Apple Maps ! 30 | 31 | - **Clean your iDevice like never before** 32 | 33 | 34 | 35 | 36 | Tired of the "Other" category taking up all of your space on your iDevice, well, you found the solution ! Some users have cleaned more than 16Gb of storage with this tool ! 37 | Please note that those screenshots aren't accurate because they were took on a simulator that didn't have the cleaned directories. Calculated sizes aren't always accurate. 38 | 39 | 40 | - **Manage daemons** 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Not using HomeKit and other Apple stuff, and want to optimize your phone performances to the best ? Here you can choose what you want to use ! 49 | 50 | - **Disable ScreenTime** 51 | 52 | 53 | Forgot your screentime password ? Here you can say goodbye to Screen Time on your iOS device ! It also works with iCloud ScreenTimes. 54 | Disclaimer : don't use this if your parents manage your Screen Time, if you get caught it would probably make it worse for you. I am not responsible in case you get told off. 55 | 56 | - **Supervise your device** 57 | 58 | 59 | Want to supervise your device with a custom organization name but don't have a computer, here you are ! We also have a great collection of supervised profiles. 60 | ## Build Instructions 61 | Requirements : A Mac having Xcode installed (it can also be a Hackintosh). 62 | 1. Clone this repository (make sure you don't download it as zip but clone it!): 63 | ```git clone https://github.com/c22dev/Geranium/``` 64 | 2. [Install Theos and it's requirements](https://theos.dev/docs/installation-macos) 65 | 4. Ensure you have the [iPhoneOS14.5.sdk SDK](https://github.com/theos/sdks/tree/master/iPhoneOS14.5.sdk) installed 66 | 5. Run ```./ipabuild.sh``` 67 | 6. Done ! Your `tipa` file will be located in `build` directory. 68 | 69 | If you have any issue with building the app, contact me on Discord, or use the commit labeled to latest release. 70 | 71 | ## Contributing 72 | If you are contributing for the app itself, the instructions bellow doesn't apply to you. 73 | ### Misc 74 | Feel free to open a Pull Request on this Github, but please note I won't merge any typo change (I will change it by myself). 75 | ### Translations 76 | If you want to submit a new translation, contact me on Discord. 77 | If it already exists, feel free to edit it ! 78 | ### Icon 79 | You can use the icon.sketch file. Please send an icon in the 1024x1024 resolution, and don't round the borders. 80 | 81 | ## Credits 82 | ### Code-wise 83 | - [BomberFish](https://github.com/bomberfish) & [Fiore](https://github.com/donato-fiore) for their amazing work in daemon listing - And thanks bomberfish for those amazing memes 84 | - [sourcelocation](https://github.com/sourcelocation) for his beautiful SwiftUI extensions and functions, helped me a lot ! - My source of information in icon and video editing ! 85 | - [haxi0](https://github.com/haxi0) for his Welcome Page code, copied the part for the disabling. - Always funny 86 | ### Translators 87 | - [CySxL](https://twitter.com/CySxL) - 🇹🇼 Chinese (Traditional) - Here from the beginning, even if on iOS 14, thanks ! 88 | - [Defflix](https://twitter.com/Defflix19) - 🇨🇿/🇸🇰 Czech & Slovak - But also one of my greatest friend in the jailbreak community :) 89 | - [w0wbox](https://twitter.com/w0wbox) - 🇪🇸 Spanish (Latin America) - Also bug hunter 90 | - [LeonardoIz](https://twitter.com/leonardoizzo_) - 🇪🇸 Spanish / 🇮🇹 Italian / Catalan - Casually speak 3 languages 91 | - [Loy64](https://twitter.com/loy64_) - 🇦🇱 Albanian / 🇮🇹 Italian - Answers faster than light 92 | - [Phuc Do](https://twitter.com/dobabaophuc) - 🇻🇳 Vietnamese - But also an amazing icon designer ! 93 | - [Speedyfriend67](https://twitter.com/speedyfriend67) - 🇰🇷 Korean - Actually speedrunned the translation 94 | - [Spy_G](https://twitter.com/spy_g_) - 🇸🇪 Swedish - Funny nickname 95 | - [Straight Tamago](https://twitter.com/straight_tamago) - 🇯🇵 Japenese - Lovely friend that always helps in code ! 96 | - [ting0441](https://twitter.com/Ting2021) - 🇨🇳 Chinese (Simplified) - UI Bug Reporter 97 | - [W$D$B](https://twitter.com/Alz971) - 🇮🇹 Italian - First to accomplish 100% in translation 98 | - [yyyywaiwai](https://twitter.com/yyyyyy_public) - 🇯🇵 Japenese - Reviewer but also filled the gaps ! 99 | -------------------------------------------------------------------------------- /entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | platform-application 6 | 7 | com.apple.security.iokit-user-client-class 8 | 9 | AGXDeviceUserClient 10 | IOHDIXControllerUserClient 11 | IOSurfaceRootUserClient 12 | 13 | com.apple.security.exception.files.absolute-path.read-write 14 | 15 | / 16 | 17 | com.apple.mobile.deleted.AllowFreeSpace 18 | 19 | com.apple.private.security.container-manager 20 | 21 | com.apple.private.security.no-container 22 | 23 | com.apple.private.security.no-sandbox 24 | 25 | com.apple.private.persona-mgmt 26 | 27 | com.apple.private.WebClips.read-write 28 | 29 | com.apple.locationd.simulation 30 | 31 | com.apple.SystemConfiguration.SCDynamicStore-write-access 32 | 33 | com.apple.private.security.system-application 34 | 35 | com.apple.private.coreservices.canmaplsdatabase 36 | 37 | com.apple.lsapplicationworkspace.rebuildappdatabases 38 | 39 | com.apple.private.MobileContainerManager.allowed 40 | 41 | com.apple.private.MobileInstallationHelperService.InstallDaemonOpsEnabled 42 | 43 | com.apple.private.MobileInstallationHelperService.allowed 44 | 45 | com.apple.private.uninstall.deletion 46 | 47 | com.apple.private.security.storage.MobileDocuments 48 | 49 | com.apple.managedconfiguration.profiled-access 50 | 51 | com.apple.private.security.storage.AppDataContainers 52 | 53 | com.apple.developer.icloud-container-identifiers 54 | 55 | com.apple.security.application-groups 56 | 57 | group.live.cclerc.geraniumBookmarks 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c22dev/Geranium/348772ab9ca7906a875404e62c93f392dd2ff51a/icon.sketch -------------------------------------------------------------------------------- /ipabuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")" 6 | 7 | WORKING_LOCATION="$(pwd)" 8 | APPLICATION_NAME=Geranium 9 | CONFIGURATION=Debug 10 | 11 | rm -rf build 12 | if [ ! -d "build" ]; then 13 | mkdir build 14 | fi 15 | 16 | cd build 17 | 18 | if [ -e "$APPLICATION_NAME.tipa" ]; then 19 | rm $APPLICATION_NAME.tipa 20 | fi 21 | 22 | # Build .app 23 | xcodebuild -project "$WORKING_LOCATION/$APPLICATION_NAME.xcodeproj" \ 24 | -scheme Geranium \ 25 | -configuration Debug \ 26 | -derivedDataPath "$WORKING_LOCATION/build/DerivedData" \ 27 | -destination 'generic/platform=iOS' \ 28 | ONLY_ACTIVE_ARCH="NO" \ 29 | CODE_SIGNING_ALLOWED="NO" \ 30 | 31 | # Build helper 32 | # xcodebuild -project "$WORKING_LOCATION/$APPLICATION_NAME.xcodeproj" \ 33 | # -scheme RootHelper \ 34 | # -configuration Debug \ 35 | # -derivedDataPath "$WORKING_LOCATION/build/DerivedData" \ 36 | # -destination 'generic/platform=iOS' \ 37 | # ONLY_ACTIVE_ARCH="NO" \ 38 | # CODE_SIGNING_ALLOWED="NO" \ 39 | 40 | DD_APP_PATH="$WORKING_LOCATION/build/DerivedData/Build/Products/$CONFIGURATION-iphoneos/$APPLICATION_NAME.app" 41 | TARGET_APP="$WORKING_LOCATION/build/$APPLICATION_NAME.app" 42 | cp -r "$DD_APP_PATH" "$TARGET_APP" 43 | 44 | # Remove signature 45 | codesign --remove "$TARGET_APP" 46 | if [ -e "$TARGET_APP/_CodeSignature" ]; then 47 | rm -rf "$TARGET_APP/_CodeSignature" 48 | fi 49 | if [ -e "$TARGET_APP/embedded.mobileprovision" ]; then 50 | rm -rf "$TARGET_APP/embedded.mobileprovision" 51 | fi 52 | 53 | git submodule update --init --recursive 54 | cd $WORKING_LOCATION/RootHelper 55 | make clean 56 | make 57 | cp $WORKING_LOCATION/RootHelper/.theos/obj/debug/GeraniumRootHelper $WORKING_LOCATION/build/Geranium.app/GeraniumRootHelper 58 | cd - 59 | 60 | #cp $WORKING_LOCATION/build/DerivedData/Build/Products/$CONFIGURATION-iphoneos/RootHelper $WORKING_LOCATION/build/Geranium.app/RootHelper 61 | 62 | # Is ldid installed ? 63 | if command -v ldid &> /dev/null; then 64 | echo "ldid is already installed." 65 | else 66 | # Install ldid using Homebrew 67 | if command -v brew &> /dev/null; then 68 | echo "Installing ldid with Homebrew..." 69 | brew install ldid 70 | echo "ldid has been installed." 71 | else 72 | echo "Homebrew is not installed. Please install Homebrew first." 73 | fi 74 | fi 75 | 76 | # Add entitlements 77 | echo "Adding entitlements" 78 | ldid -S"$WORKING_LOCATION/entitlements.plist" "$TARGET_APP/$APPLICATION_NAME" 79 | # Inject into the Maps thingy 80 | ldid -S"$WORKING_LOCATION/Bookmark Location in Geranium/entitlements.plist" "$TARGET_APP/PlugIns/Bookmark Location in Geranium.appex/Bookmark Location in Geranium" 81 | # idk if this is usefull but uhm 82 | # ldid -S"$WORKING_LOCATION/Bookmark Location in Geranium/entitlements.plist" "$TARGET_APP/PlugIns/Bookmark Location in Geranium.appex/Bookmark Location in Geranium.debug.dylib" 83 | # ldid -S"$WORKING_LOCATION/entitlements.plist" "$TARGET_APP/RootHelper" 84 | 85 | # Package .ipa 86 | rm -rf Payload 87 | mkdir Payload 88 | cp -r $APPLICATION_NAME.app Payload/$APPLICATION_NAME.app 89 | zip -vr $APPLICATION_NAME.tipa Payload 90 | rm -rf $APPLICATION_NAME.app 91 | rm -rf Payload 92 | --------------------------------------------------------------------------------